Skip to content

Commit 0551501

Browse files
committed
WIP: proper no-bundle in Pages
1 parent 0a77990 commit 0551501

File tree

17 files changed

+370
-99
lines changed

17 files changed

+370
-99
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "pages-workerjs-directory",
3+
"version": "0.0.0",
4+
"private": true,
5+
"sideEffects": false,
6+
"scripts": {
7+
"check:type": "tsc",
8+
"dev": "npx wrangler pages dev public --port 8794",
9+
"test": "npx vitest",
10+
"test:ci": "npx vitest"
11+
},
12+
"devDependencies": {
13+
"undici": "^5.9.1"
14+
},
15+
"engines": {
16+
"node": ">=16.13"
17+
}
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default {
2+
async fetch(request, env) {
3+
const { pathname } = new URL(request.url);
4+
if (pathname !== "/") {
5+
return new Response((await import(`./${pathname.slice(1)}`)).default);
6+
}
7+
8+
return env.ASSETS.fetch(request);
9+
},
10+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "test";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<h1>Hello, world!</h1>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { resolve } from "node:path";
2+
import { fetch } from "undici";
3+
import { describe, it } from "vitest";
4+
import { runWranglerPagesDev } from "../../shared/src/run-wrangler-long-lived";
5+
6+
describe.concurrent("Pages _worker.js/ directory", () => {
7+
it("should support non-bundling with 'dev'", async ({ expect }) => {
8+
const { ip, port, stop } = await runWranglerPagesDev(
9+
resolve(__dirname, ".."),
10+
"public",
11+
["--port=0"]
12+
);
13+
await expect(
14+
fetch(`http://${ip}:${port}/`).then((resp) => resp.text())
15+
).resolves.toContain("Hello, world!");
16+
await expect(
17+
fetch(`http://${ip}:${port}/other-script`).then((resp) => resp.text())
18+
).resolves.toContain("test");
19+
await stop();
20+
});
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"esModuleInterop": true,
5+
"module": "CommonJS",
6+
"lib": ["ES2020"],
7+
"types": ["node"],
8+
"moduleResolution": "node",
9+
"noEmit": true
10+
},
11+
"include": ["tests", "../../node-types.d.ts"]
12+
}

packages/wrangler/src/__tests__/pages/functions-build.test.ts

+63
Original file line numberDiff line numberDiff line change
@@ -449,4 +449,67 @@ export default {
449449
hello.js:2:36: ERROR: Could not resolve \\"node:async_hooks\\""
450450
`);
451451
});
452+
453+
it("should compile a _worker.js/ directory", async () => {
454+
mkdirSync("public");
455+
mkdirSync("public/_worker.js");
456+
writeFileSync(
457+
"public/_worker.js/index.js",
458+
`
459+
export default {
460+
async fetch(request, env) {
461+
return new Response("Hello from _worker.js/index.js");
462+
},
463+
};`
464+
);
465+
writeFileSync(
466+
"public/_worker.js/cat.js",
467+
`
468+
export const cat = "cat";`
469+
);
470+
471+
await runWrangler(
472+
`pages functions build --outfile=public/_worker.bundle --compatibility-flag=nodejs_compat`
473+
);
474+
475+
expect(existsSync("public/_worker.bundle")).toBe(true);
476+
expect(std.out).toMatchInlineSnapshot(
477+
`"🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose"`
478+
);
479+
480+
const workerBundleContents = readFileSync("public/_worker.bundle", "utf-8");
481+
const workerBundleWithConstantData = replaceRandomWithConstantData(
482+
workerBundleContents,
483+
[
484+
[/------formdata-undici-0.[0-9]*/g, "------formdata-undici-0.test"],
485+
[/functionsWorker-0.[0-9]*.js/g, "functionsWorker-0.test.js"],
486+
]
487+
);
488+
489+
expect(workerBundleWithConstantData).toMatchInlineSnapshot(`
490+
"------formdata-undici-0.test
491+
Content-Disposition: form-data; name=\\"metadata\\"
492+
493+
{\\"main_module\\":\\"index.js\\"}
494+
------formdata-undici-0.test
495+
Content-Disposition: form-data; name=\\"index.js\\"; filename=\\"index.js\\"
496+
Content-Type: application/javascript+module
497+
498+
499+
export default {
500+
async fetch(request, env) {
501+
return new Response(\\"Hello from _worker.js/index.js\\");
502+
},
503+
};
504+
------formdata-undici-0.test
505+
Content-Disposition: form-data; name=\\"cat.js\\"; filename=\\"cat.js\\"
506+
Content-Type: application/javascript+module
507+
508+
509+
export const cat = \\"cat\\";
510+
------formdata-undici-0.test--"
511+
`);
512+
513+
expect(std.err).toMatchInlineSnapshot(`""`);
514+
});
452515
});

packages/wrangler/src/api/dev.ts

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { startApiDev, startDev } from "../dev";
33
import { logger } from "../logger";
44

55
import type { Environment } from "../config";
6+
import type { Rule } from "../config/environment";
67
import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
78
import type { RequestInit, Response, RequestInfo } from "undici";
89

@@ -42,6 +43,9 @@ export interface UnstableDevOptions {
4243
bucket_name: string;
4344
preview_bucket_name?: string;
4445
}[];
46+
bundleEntrypoint?: boolean;
47+
moduleRoot?: string;
48+
rules?: Rule[];
4549
logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"]
4650
inspect?: boolean;
4751
local?: boolean;
@@ -150,6 +154,7 @@ export async function unstable_dev(
150154
},
151155
config: options?.config,
152156
env: options?.env,
157+
bundleEntrypoint: !!options?.bundleEntrypoint,
153158
bundle: options?.bundle,
154159
compatibilityDate: options?.compatibilityDate,
155160
compatibilityFlags: options?.compatibilityFlags,

packages/wrangler/src/api/pages/publish.tsx

+29-9
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { existsSync, readFileSync } from "node:fs";
1+
import { existsSync, lstatSync, readFileSync } from "node:fs";
22
import { tmpdir } from "node:os";
33
import { join, resolve as resolvePath } from "node:path";
44
import { cwd } from "node:process";
@@ -17,6 +17,7 @@ import {
1717
} from "../../pages/functions/buildWorker";
1818
import { validateRoutes } from "../../pages/functions/routes-validation";
1919
import { upload } from "../../pages/upload";
20+
import traverseModuleGraph from "../../traverse-module-graph";
2021
import { createUploadWorkerBundleContents } from "./create-worker-bundle-contents";
2122
import type { BundleResult } from "../../bundle";
2223
import type { Project, Deployment } from "@cloudflare/types";
@@ -95,9 +96,10 @@ export async function publish({
9596
_redirects: string | undefined,
9697
_routesGenerated: string | undefined,
9798
_routesCustom: string | undefined,
99+
_workerJSDirectory = false,
98100
_workerJS: string | undefined;
99101

100-
const workerScriptPath = resolvePath(directory, "_worker.js");
102+
const _workerPath = resolvePath(directory, "_worker.js");
101103

102104
try {
103105
_headers = readFileSync(join(directory, "_headers"), "utf-8");
@@ -116,7 +118,10 @@ export async function publish({
116118
} catch {}
117119

118120
try {
119-
_workerJS = readFileSync(workerScriptPath, "utf-8");
121+
_workerJSDirectory = lstatSync(_workerPath).isDirectory();
122+
if (!_workerJSDirectory) {
123+
_workerJS = readFileSync(_workerPath, "utf-8");
124+
}
120125
} catch {}
121126

122127
// Grab the bindings from the API, we need these for shims and other such hacky inserts
@@ -243,13 +248,28 @@ export async function publish({
243248
* When using a _worker.js file, the entire /functions directory is ignored
244249
* – this includes its routing and middleware characteristics.
245250
*/
246-
if (_workerJS) {
251+
if (_workerJSDirectory) {
252+
workerBundle = await traverseModuleGraph(
253+
{
254+
file: resolvePath(join(_workerPath, "index.js")),
255+
directory: resolvePath(_workerPath),
256+
format: "modules",
257+
moduleRoot: resolvePath(_workerPath),
258+
},
259+
[
260+
{
261+
type: "ESModule",
262+
globs: ["**/*.js"],
263+
},
264+
]
265+
);
266+
} else if (_workerJS) {
247267
if (bundle) {
248268
const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
249269
workerBundle = await buildRawWorker({
250-
workerScriptPath,
270+
workerScriptPath: _workerPath,
251271
outfile,
252-
directory: directory ?? ".",
272+
directory,
253273
local: false,
254274
sourcemap: true,
255275
watch: false,
@@ -258,13 +278,13 @@ export async function publish({
258278
nodejsCompat,
259279
});
260280
} else {
261-
await checkRawWorker(workerScriptPath, () => {});
262-
// TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769
281+
await checkRawWorker(_workerPath, () => {});
282+
// TODO: Let users configure this in the future.
263283
workerBundle = {
264284
modules: [],
265285
dependencies: {},
266286
stop: undefined,
267-
resolvedEntryPointPath: workerScriptPath,
287+
resolvedEntryPointPath: _workerPath,
268288
bundleType: "esm",
269289
};
270290
}

packages/wrangler/src/bundle.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export async function bundleWorker(
117117
entry: Entry,
118118
destination: string,
119119
options: {
120+
bundle?: boolean;
120121
serveAssetsFromWorker: boolean;
121122
assets?: StaticAssetsConfig;
122123
betaD1Shims?: string[];
@@ -149,6 +150,7 @@ export async function bundleWorker(
149150
}
150151
): Promise<BundleResult> {
151152
const {
153+
bundle = true,
152154
serveAssetsFromWorker,
153155
betaD1Shims,
154156
doBindings,
@@ -350,7 +352,7 @@ export async function bundleWorker(
350352

351353
const buildOptions: esbuild.BuildOptions & { metafile: true } = {
352354
entryPoints: [inputEntry.file],
353-
bundle: true,
355+
bundle,
354356
absWorkingDir: entry.directory,
355357
outdir: destination,
356358
entryNames: entryName || path.parse(entry.file).name,

packages/wrangler/src/dev.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
printWranglerBanner,
2929
} from "./index";
3030
import type { Config, Environment } from "./config";
31-
import type { Route } from "./config/environment";
31+
import type { Route, Rule } from "./config/environment";
3232
import type { LoggerLevel } from "./logger";
3333
import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types";
3434
import type { CfWorkerInit } from "./worker";
@@ -334,6 +334,9 @@ export type AdditionalDevProps = {
334334
preview_bucket_name?: string;
335335
}[];
336336
d1Databases?: Environment["d1_databases"];
337+
bundleEntrypoint?: boolean;
338+
moduleRoot?: string;
339+
rules?: Rule[];
337340
};
338341

339342
type StartDevOptions = DevArguments &
@@ -424,7 +427,8 @@ export async function startDev(args: StartDevOptions) {
424427
zone={zoneId}
425428
host={host}
426429
routes={routes}
427-
rules={getRules(configParam)}
430+
bundleEntrypoint={!!args.bundleEntrypoint}
431+
rules={args.rules ?? getRules(configParam)}
428432
legacyEnv={isLegacyEnv(configParam)}
429433
minify={args.minify ?? configParam.minify}
430434
legacyNodeCompat={legacyNodeCompat}
@@ -560,7 +564,8 @@ export async function startApiDev(args: StartDevOptions) {
560564
zone: zoneId,
561565
host: host,
562566
routes: routes,
563-
rules: getRules(configParam),
567+
bundleEntrypoint: !!args.bundleEntrypoint,
568+
rules: args.rules ?? getRules(configParam),
564569
legacyEnv: isLegacyEnv(configParam),
565570
minify: args.minify ?? configParam.minify,
566571
legacyNodeCompat,
@@ -687,7 +692,7 @@ async function validateDevServerSettings(
687692
config: Config
688693
) {
689694
const entry = await getEntry(
690-
{ assets: args.assets, script: args.script },
695+
{ assets: args.assets, script: args.script, moduleRoot: args.moduleRoot },
691696
config,
692697
"dev"
693698
);

packages/wrangler/src/dev/dev.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ export type DevProps = {
116116
initialPort: number;
117117
initialIp: string;
118118
inspectorPort: number;
119+
bundleEntrypoint: boolean;
119120
rules: Config["rules"];
120121
accountId: string | undefined;
121122
initialMode: "local" | "remote";
@@ -271,6 +272,7 @@ function DevSession(props: DevSessionProps) {
271272
entry: props.entry,
272273
destination: directory,
273274
jsxFactory: props.jsxFactory,
275+
bundleEntrypoint: props.bundleEntrypoint,
274276
rules: props.rules,
275277
jsxFragment: props.jsxFragment,
276278
serveAssetsFromWorker: Boolean(
@@ -294,6 +296,7 @@ function DevSession(props: DevSessionProps) {
294296
testScheduled: props.testScheduled ?? false,
295297
experimentalLocal: props.experimentalLocal,
296298
});
299+
console.log(bundle);
297300

298301
// TODO(queues) support remote wrangler dev
299302
if (

0 commit comments

Comments
 (0)