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/fix-no-bundle-find-additional-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Respect `find_additional_modules = false` when `no_bundle` is set

When using `no_bundle = true`, wrangler was always scanning for and attaching additional modules even if `find_additional_modules` was explicitly set to `false` in the config. Additional modules are now only collected when `find_additional_modules` is not `false`, consistent with the bundled code path.
7 changes: 7 additions & 0 deletions .changeset/wrangler-experimental-cf-build-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": minor
---

Add experimental `--experimental-cf-build-output` flag to `wrangler build`

When used alongside `--experimental-new-config`, `wrangler build` now emits a self-contained Build Output API directory under `.cloudflare/output/v0/` instead of delegating to `wrangler deploy --dry-run`.
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import * as fs from "node:fs";
import * as fsp from "node:fs/promises";
import * as path from "node:path";
import { removeDirSync } from "@cloudflare/workers-utils";
import { removeDir } from "@cloudflare/workers-utils";
import type {
ModuleType,
ParsedInputWorkerConfig,
ParsedOutputWorkerConfig,
} from "@cloudflare/config";
} from "./schema";

/**
* Initial draft version of the Build Output API.
*
* Will move to `v1` when the spec stabilises.
*/
const BUILD_OUTPUT_VERSION = "v0";
export const BUILD_OUTPUT_VERSION = "v0";

/**
* Project-relative root.
*/
const BUILD_OUTPUT_ROOT = ".cloudflare/output";
export const BUILD_OUTPUT_ROOT = ".cloudflare/output";

/**
* Filename of the per-Worker config.
Expand All @@ -32,23 +31,21 @@ function getBuildOutputDir(root: string): string {
}

/**
* Clean the build output directory
*
* Called once at the start of each build
* Clean the build output directory.
*/
export function cleanBuildOutputDir(root: string): void {
removeDirSync(getBuildOutputDir(root));
export async function cleanBuildOutputDir(root: string): Promise<void> {
await removeDir(getBuildOutputDir(root));
}

/**
* Absolute path to the workers output directory
* Absolute path to the Workers output directory.
*/
export function getWorkersDir(root: string): string {
return path.join(getBuildOutputDir(root), BUILD_OUTPUT_VERSION, "workers");
}

/**
* Absolute path to `worker.config.json` for a given Worker.
* Absolute path to the config file for a given Worker.
*/
export function getWorkerConfigPath(root: string, workerName: string): string {
return path.join(getWorkersDir(root), workerName, WORKER_CONFIG_FILENAME);
Expand All @@ -68,49 +65,22 @@ export function getWorkerAssetsDir(root: string, workerName: string): string {
return path.join(getWorkersDir(root), workerName, "assets");
}

/**
* Map a bundle filename to its declared module type.
*/
export function detectModuleType(filename: string): ModuleType {
const ext = path.extname(filename).toLowerCase();

switch (ext) {
case ".js":
case ".mjs":
return "esm";
case ".wasm":
return "wasm";
case ".bin":
return "data";
case ".txt":
case ".html":
case ".sql":
return "text";
case ".json":
return "json";
case ".map":
return "sourcemap";
default:
return "data";
}
}

/**
* Write the output `worker.config.json` for a given Worker to the Build
* Output API tree.
*
* - Workers mode: `manifest` is provided (bundle/ present on disk).
* - Assets-only mode: `manifest` is omitted (no bundle/ directory).
*/
export function writeOutputWorkerConfig(
export async function writeOutputWorkerConfig(
root: string,
parsedConfig: ParsedInputWorkerConfig,
manifest?: ParsedOutputWorkerConfig["manifest"]
): void {
): Promise<void> {
const { entrypoint: _entrypoint, ...rest } = parsedConfig;
const outputConfig: ParsedOutputWorkerConfig = { ...rest, manifest };
const workerOutputDir = path.join(getWorkersDir(root), outputConfig.name);
fs.mkdirSync(workerOutputDir, { recursive: true });
await fsp.mkdir(workerOutputDir, { recursive: true });
const configPath = getWorkerConfigPath(root, outputConfig.name);
fs.writeFileSync(configPath, JSON.stringify(outputConfig));
await fsp.writeFile(configPath, JSON.stringify(outputConfig));
}
11 changes: 11 additions & 0 deletions packages/config/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
export * from "./public";
export {
BUILD_OUTPUT_ROOT,
BUILD_OUTPUT_VERSION,
cleanBuildOutputDir,
getWorkerAssetsDir,
getWorkerBundleDir,
getWorkerConfigPath,
getWorkersDir,
WORKER_CONFIG_FILENAME,
writeOutputWorkerConfig,
} from "./build-output";
export {
InputWorkerSchema,
OutputWorkerSchema,
Expand Down
2 changes: 1 addition & 1 deletion packages/config/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ export const ModuleTypeSchema = z.enum([
"esm",
"cjs",
"python",
"pythonRequirement",
"python-requirement",
"wasm",
"text",
"data",
Expand Down
32 changes: 32 additions & 0 deletions packages/vite-plugin-cloudflare/src/__tests__/build-output.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, it } from "vitest";
import { detectModuleType } from "../plugins/build-output";

describe("detectModuleType", () => {
const cases: Array<{ filename: string; expected: string }> = [
{ filename: "entry.js", expected: "esm" },
{ filename: "entry.mjs", expected: "esm" },
{ filename: "lib.wasm", expected: "wasm" },
{ filename: "raw.bin", expected: "data" },
{ filename: "greeting.txt", expected: "text" },
{ filename: "page.html", expected: "text" },
{ filename: "query.sql", expected: "text" },
{ filename: "data.json", expected: "json" },
{ filename: "bundle.js.map", expected: "sourcemap" },
{ filename: "unknown.xyz", expected: "data" },
// Case-insensitive on extension
{ filename: "ENTRY.JS", expected: "esm" },
{ filename: "LIB.WASM", expected: "wasm" },
// No extension → default `data`
{ filename: "LICENSE", expected: "data" },
// Nested paths — only the extension matters
{ filename: "chunks/foo.js", expected: "esm" },
{ filename: "chunks/foo.wasm", expected: "wasm" },
];

it.for(cases)(
"maps $filename → $expected",
({ filename, expected }, { expect }) => {
expect(detectModuleType(filename)).toBe(expected);
}
);
});
11 changes: 5 additions & 6 deletions packages/vite-plugin-cloudflare/src/build-output-preview.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import assert from "node:assert";
import * as fs from "node:fs";
import { normalizeAndValidateConfig } from "@cloudflare/workers-utils";
import {
getWorkersDir,
convertToWranglerConfig,
getWorkerAssetsDir,
getWorkerBundleDir,
getWorkerConfigPath,
getWorkersDir,
OutputWorkerSchema,
WORKER_CONFIG_FILENAME,
} from "./build-output";
} from "@cloudflare/config";
import { normalizeAndValidateConfig } from "@cloudflare/workers-utils";
import type { ModuleType } from "@cloudflare/config";
import type { Unstable_Config } from "wrangler";

Expand Down Expand Up @@ -47,9 +49,6 @@ export async function readBuildOutputWorkers(
);
}

const { OutputWorkerSchema, convertToWranglerConfig } =
await import("@cloudflare/config");

return workerNames.map((workerName) => {
const configPath = getWorkerConfigPath(root, workerName);
assert(
Expand Down
2 changes: 1 addition & 1 deletion packages/vite-plugin-cloudflare/src/miniflare-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ function toMiniflareModuleType(type: ModuleType): ModuleRuleType | null {
return "Data";
case "python":
return "PythonModule";
case "pythonRequirement":
case "python-requirement":
return null;
case "sourcemap":
return null;
Expand Down
21 changes: 8 additions & 13 deletions packages/vite-plugin-cloudflare/src/plugin-config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import * as fs from "node:fs";
import * as path from "node:path";
import {
convertToWranglerConfig,
generateTypes,
InputWorkerSchema,
loadConfig,
resolveWorkerDefinition,
} from "@cloudflare/config";
import { parseStaticRouting } from "@cloudflare/workers-shared/utils/configuration/parseStaticRouting";
import { defu } from "defu";
import * as vite from "vite";
Expand Down Expand Up @@ -770,16 +777,6 @@ async function loadNewConfig(options: {
);
}

// Dynamic import so users who don't enable `experimental.newConfig` never
// pay the cost of loading `@cloudflare/config` (and its Node module hooks).
const {
loadConfig,
InputWorkerSchema,
convertToWranglerConfig,
generateTypes: generateTypesFn,
resolveWorkerDefinition,
} = await import("@cloudflare/config");

const { config: rawExport, dependencies } = await loadConfig(configPath);

const resolved = await resolveWorkerDefinition(rawExport, {
Expand All @@ -799,7 +796,6 @@ async function loadNewConfig(options: {
writeWorkerConfigurationDts({
root: options.root,
configPath,
generateTypes: generateTypesFn,
});
}

Expand All @@ -823,12 +819,11 @@ async function loadNewConfig(options: {
function writeWorkerConfigurationDts(options: {
root: string;
configPath: string;
generateTypes: (opts: { configPath: string; packageName?: string }) => string;
}): void {
const outputPath = path.resolve(options.root, TYPES_OUTPUT_FILENAME);
const relativeConfigPath =
"./" + path.relative(options.root, options.configPath);
const content = options.generateTypes({
const content = generateTypes({
configPath: relativeConfigPath,
packageName: EXPERIMENTAL_CONFIG_PKG,
});
Expand Down
49 changes: 42 additions & 7 deletions packages/vite-plugin-cloudflare/src/plugins/build-output.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from "node:assert";
import { detectModuleType, writeOutputWorkerConfig } from "../build-output";
import * as path from "node:path";
import { writeOutputWorkerConfig } from "@cloudflare/config";
import { MAIN_ENTRY_NAME } from "../cloudflare-environment";
import { createPlugin } from "../utils";
import type { ModuleType } from "@cloudflare/config";
Expand All @@ -10,7 +11,7 @@ import type { ModuleType } from "@cloudflare/config";
*/
export const buildOutputPlugin = createPlugin("build-output", (ctx) => {
return {
writeBundle(_options, bundle) {
async writeBundle(_, bundle) {
if (ctx.isChildEnvironment(this.environment.name)) {
return;
}
Expand All @@ -24,7 +25,10 @@ export const buildOutputPlugin = createPlugin("build-output", (ctx) => {
workerNewConfig,
"Expected parsedNewConfig on assets-only resolved config"
);
writeOutputWorkerConfig(ctx.resolvedViteConfig.root, workerNewConfig);
await writeOutputWorkerConfig(
ctx.resolvedViteConfig.root,
workerNewConfig
);
return;
}

Expand Down Expand Up @@ -65,10 +69,41 @@ export const buildOutputPlugin = createPlugin("build-output", (ctx) => {
modules[fileName] = { type: detectModuleType(fileName) };
}

writeOutputWorkerConfig(ctx.resolvedViteConfig.root, workerNewConfig, {
mainModule: entryChunk.fileName,
modules,
});
await writeOutputWorkerConfig(
ctx.resolvedViteConfig.root,
workerNewConfig,
{
mainModule: entryChunk.fileName,
modules,
}
);
},
};
});

/**
* Map a bundle filename to its declared module type.
*/
export function detectModuleType(filename: string): ModuleType {
const ext = path.extname(filename).toLowerCase();

switch (ext) {
case ".js":
case ".mjs":
return "esm";
case ".wasm":
return "wasm";
case ".bin":
return "data";
case ".txt":
case ".html":
case ".sql":
return "text";
case ".json":
return "json";
case ".map":
return "sourcemap";
default:
return "data";
}
}
Loading
Loading