Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/build-output-source-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": minor
---

Include source maps by default in `wrangler build --experimental-cf-build-output`

Build Output now matches `wrangler deploy` and `wrangler versions upload`, which upload source maps by default, so Workers built for tools like `cf` get stack traces that point at the original source. Source maps that can't be resolved are skipped rather than failing the build. Set `upload_source_maps = false` to opt out.
7 changes: 7 additions & 0 deletions .changeset/vite-worker-source-maps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/vite-plugin": minor
---

Generate source maps for Worker environments by default

Worker builds now emit source maps unless Vite's `build.sourcemap` or the Worker's `upload_source_maps` is set, so deployed Workers get stack traces that point at the original source. The source maps are written next to the Worker bundle and included in Build Output; the client environment is unchanged, so no source maps are added to public assets. Set `build.sourcemap: false` for the Worker environment, or `upload_source_maps: false` in the Worker config, to opt out.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ import { defineConfig } from "vite";
import { satisfiesMinimumViteVersion } from "../__test-utils__/vite-version";

const config = {
environments: {
build_output_worker: {
build: {
sourcemap: true,
},
},
},
plugins: [
cloudflare({
inspectorPort: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { test } from "vitest";
import { isBuild, rootDir } from "../../../__test-utils__";

test.runIf(isBuild)(
"sets `upload_source_maps` to `true` when `build.sourcemap` is enabled",
"does not set `upload_source_maps` when `build.sourcemap` is disabled",
({ expect }) => {
const wranglerConfig = JSON.parse(
fs.readFileSync(
path.join(rootDir, "dist", "worker", "wrangler.json"),
"utf-8"
)
);
expect(wranglerConfig.upload_source_maps).toBe(true);
expect(wranglerConfig.upload_source_maps).toBeUndefined();
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,15 @@ test.runIf(!isBuild)(
);

test.runIf(isBuild)(
"does not set `upload_source_maps` when `build.sourcemap` is disabled",
"emits source maps and sets `upload_source_maps` to `true` by default",
({ expect }) => {
const outputDir = path.join(rootDir, "dist", "worker");
const wranglerConfig = JSON.parse(
fs.readFileSync(
path.join(rootDir, "dist", "worker", "wrangler.json"),
"utf-8"
)
fs.readFileSync(path.join(outputDir, "wrangler.json"), "utf-8")
);
expect(wranglerConfig.upload_source_maps).toBeUndefined();
expect(wranglerConfig.upload_source_maps).toBe(true);
expect(
fs.existsSync(path.join(outputDir, `${wranglerConfig.main}.map`))
).toBe(true);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default defineConfig({
environments: {
worker: {
build: {
sourcemap: true,
sourcemap: false,
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { CoreHeaders, Miniflare, Response } from "miniflare";
import { resolveConfig } from "vite";
import { onTestFinished, test, vi } from "vitest";
import { CloudflareDevEnvironment } from "../cloudflare-environment";
import {
CloudflareDevEnvironment,
createCloudflareEnvironmentOptions,
} from "../cloudflare-environment";
import { GET_EXPORT_TYPES_PATH, UNKNOWN_HOST } from "../shared";
import type { ResolvedWorkerConfig } from "../plugin-config";
import type { UserConfig } from "vite";

test("surfaces the response body when fetching Worker export types fails", async ({
expect,
Expand Down Expand Up @@ -63,3 +67,56 @@ test("surfaces the response body when fetching Worker export types fails", async
{ headers: { [CoreHeaders.ROUTE_OVERRIDE]: "auxiliary-worker" } }
);
});

function getWorkerSourcemap(
userConfig: UserConfig,
workerConfig: Partial<ResolvedWorkerConfig> = {}
) {
const options = createCloudflareEnvironmentOptions({
workerConfig: {
main: "./src/index.ts",
...workerConfig,
} as ResolvedWorkerConfig,
userConfig,
mode: "production",
environmentName: "worker",
isEntryWorker: true,
isParentEnvironment: true,
hasNodeJsCompat: false,
});

return options.build?.sourcemap;
}

test.for([
{ label: "by default", userConfig: {}, workerConfig: {}, expected: true },
{
label: "from the environment's `build.sourcemap`",
userConfig: { environments: { worker: { build: { sourcemap: false } } } },
workerConfig: {},
expected: false,
},
{
label: "from the top-level `build.sourcemap`",
userConfig: { build: { sourcemap: "hidden" as const } },
workerConfig: {},
expected: "hidden",
},
{
label: "from `upload_source_maps`",
userConfig: {},
workerConfig: { upload_source_maps: false },
expected: false,
},
{
label: "preferring `build.sourcemap` over `upload_source_maps`",
userConfig: { build: { sourcemap: true } },
workerConfig: { upload_source_maps: false },
expected: true,
},
])(
"resolves the Worker environment's `build.sourcemap` $label",
({ userConfig, workerConfig, expected }, { expect }) => {
expect(getWorkerSourcemap(userConfig, workerConfig)).toBe(expected);
}
);
19 changes: 19 additions & 0 deletions packages/vite-plugin-cloudflare/src/cloudflare-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,24 @@ const resolveExtensions = [
".ctx",
];

/**
* Worker source maps are written to the Worker's build output rather than its
* public assets, so emit them by default for readable stack traces. Users can
* opt out with Vite's `build.sourcemap` or the Worker's `upload_source_maps`.
*/
function getWorkerSourcemap(
userConfig: vite.UserConfig,
environmentName: string,
workerConfig: ResolvedWorkerConfig
): vite.BuildEnvironmentOptions["sourcemap"] {
return (
userConfig.environments?.[environmentName]?.build?.sourcemap ??
userConfig.build?.sourcemap ??
workerConfig.upload_source_maps ??
true
);
}

export function createCloudflareEnvironmentOptions({
workerConfig,
userConfig,
Expand Down Expand Up @@ -291,6 +309,7 @@ export function createCloudflareEnvironmentOptions({
emitAssets: true,
manifest: isEntryWorker,
outDir: getOutputDirectory(userConfig, environmentName),
sourcemap: getWorkerSourcemap(userConfig, environmentName, workerConfig),
copyPublicDir: false,
ssr: true,
...(isRolldown
Expand Down
8 changes: 4 additions & 4 deletions packages/workers-utils/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -830,10 +830,10 @@ interface EnvironmentInheritable {
/**
* Include source maps when uploading this worker.
*
* Defaults to `true` for `wrangler deploy`, `wrangler versions upload` and
* `wrangler preview`, where source maps that can't be resolved are skipped
* rather than failing the upload. Defaults to `false` for
* `wrangler build --experimental-cf-build-output`.
* Defaults to `true` for `wrangler deploy`, `wrangler versions upload`,
* `wrangler preview` and `wrangler build --experimental-cf-build-output`,
* where source maps that can't be resolved are skipped rather than failing
* the upload.
*
* For reference, see https://developers.cloudflare.com/workers/wrangler/configuration/#source-maps
*
Expand Down
41 changes: 36 additions & 5 deletions packages/wrangler/src/__tests__/build-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ describe("wrangler build --experimental-cf-build-output", () => {
expect(manifest.type).toBe("complete");
expect(manifest.mainModule).toBe("index.js");
expect(manifest.modules["index.js"]).toEqual({ type: "esm" });
expect(manifest.modules["index.js.map"]).toEqual({ type: "sourcemap" });

expect(fs.existsSync(bundlePath("index.js"))).toBe(true);
expect(fs.existsSync(bundlePath("index.js.map"))).toBe(true);
const generatedTypes = fs.readFileSync(
path.resolve(".cloudflare/types/index.d.ts"),
"utf8"
Expand Down Expand Up @@ -269,7 +271,7 @@ describe("wrangler build --experimental-cf-build-output", () => {
);
});

it("writes source maps when `uploadSourceMaps` is enabled", async ({
it("skips unresolvable source maps by default rather than failing", async ({
expect,
}) => {
await seed({
Expand All @@ -279,10 +281,39 @@ describe("wrangler build --experimental-cf-build-output", () => {
entrypoint: "./src/index.js",
} };`,
"wrangler.config.ts": `export default {
uploadSourceMaps: true,
noBundle: true,
};`,
"src/index.js": `export default {
async fetch() { return new Response("mapped"); }
async fetch() { return new Response("unmapped"); }
};
//# sourceMappingURL=missing.js.map`,
});

await runWrangler(
"build --experimental-new-config --experimental-cf-build-output"
);

const manifest = readOutputConfig().manifest as {
modules: Record<string, { type: string }>;
};
expect(manifest.modules["index.js"]).toEqual({ type: "esm" });
expect(manifest.modules["index.js.map"]).toBeUndefined();
});

it("does not write source maps when `uploadSourceMaps` is disabled", async ({
expect,
}) => {
await seed({
"cloudflare.config.ts": `export default { worker: {
name: "${WORKER_NAME}",
compatibilityDate: "2026-05-18",
entrypoint: "./src/index.js",
} };`,
"wrangler.config.ts": `export default {
uploadSourceMaps: false,
};`,
"src/index.js": `export default {
async fetch() { return new Response("unmapped"); }
};`,
});

Expand All @@ -293,8 +324,8 @@ describe("wrangler build --experimental-cf-build-output", () => {
const manifest = readOutputConfig().manifest as {
modules: Record<string, { type: string }>;
};
expect(manifest.modules["index.js.map"]).toEqual({ type: "sourcemap" });
expect(fs.existsSync(bundlePath("index.js.map"))).toBe(true);
expect(manifest.modules["index.js.map"]).toBeUndefined();
expect(fs.existsSync(bundlePath("index.js.map"))).toBe(false);
});

it("errors when --experimental-cf-build-output is used without --experimental-new-config", async ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export async function mergeBuildOutputProps(config: Config): Promise<{
name: config.name,
compatibilityDate: config.compatibility_date,
compatibilityFlags: config.compatibility_flags,
uploadSourceMaps: config.upload_source_maps ?? false,
uploadSourceMaps: config.upload_source_maps,
jsxFactory: config.jsx_factory,
jsxFragment: config.jsx_fragment,
tsconfig: config.tsconfig,
Expand Down
Loading