From da298606632743b469192b92af4cb11d4ef40866 Mon Sep 17 00:00:00 2001 From: Greg Brimble Date: Thu, 20 Apr 2023 21:24:24 +0100 Subject: [PATCH 1/2] `_worker.js/` directory support in Pages --- .changeset/cuddly-rules-rest.md | 5 + .../pages-workerjs-directory/package.json | 18 ++++ .../public/_worker.js/index.js | 10 ++ .../public/_worker.js/other-script.js | 1 + .../public/index.html | 1 + .../tests/index.test.ts | 21 ++++ .../pages-workerjs-directory/tsconfig.json | 12 +++ .../__tests__/pages/functions-build.test.ts | 70 ++++++++++++++ packages/wrangler/src/api/dev.ts | 5 + packages/wrangler/src/api/pages/publish.tsx | 67 +++++++++++-- packages/wrangler/src/bundle.ts | 6 +- packages/wrangler/src/dev.tsx | 13 ++- packages/wrangler/src/dev/dev.tsx | 2 + packages/wrangler/src/dev/start-server.ts | 94 ++++++++++++------ packages/wrangler/src/dev/use-esbuild.ts | 96 +++++++++++++------ packages/wrangler/src/entry.ts | 3 +- packages/wrangler/src/pages/build.ts | 85 ++++++++++++---- packages/wrangler/src/pages/dev.ts | 30 ++++-- .../src/pages/functions/buildWorker.ts | 3 + 19 files changed, 442 insertions(+), 100 deletions(-) create mode 100644 .changeset/cuddly-rules-rest.md create mode 100644 fixtures/pages-workerjs-directory/package.json create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/index.js create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/other-script.js create mode 100644 fixtures/pages-workerjs-directory/public/index.html create mode 100644 fixtures/pages-workerjs-directory/tests/index.test.ts create mode 100644 fixtures/pages-workerjs-directory/tsconfig.json diff --git a/.changeset/cuddly-rules-rest.md b/.changeset/cuddly-rules-rest.md new file mode 100644 index 000000000000..6d102be5778e --- /dev/null +++ b/.changeset/cuddly-rules-rest.md @@ -0,0 +1,5 @@ +--- +"wrangler": patch +--- + +feat: Add support for the undocumented `_worker.js/` directory in Pages diff --git a/fixtures/pages-workerjs-directory/package.json b/fixtures/pages-workerjs-directory/package.json new file mode 100644 index 000000000000..a2fd7e5e418b --- /dev/null +++ b/fixtures/pages-workerjs-directory/package.json @@ -0,0 +1,18 @@ +{ + "name": "pages-workerjs-directory", + "version": "0.0.0", + "private": true, + "sideEffects": false, + "scripts": { + "check:type": "tsc", + "dev": "npx wrangler pages dev public --port 8794", + "test": "npx vitest", + "test:ci": "npx vitest" + }, + "devDependencies": { + "undici": "^5.9.1" + }, + "engines": { + "node": ">=16.13" + } +} diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/index.js b/fixtures/pages-workerjs-directory/public/_worker.js/index.js new file mode 100644 index 000000000000..3380296683bc --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/index.js @@ -0,0 +1,10 @@ +export default { + async fetch(request, env) { + const { pathname } = new URL(request.url); + if (pathname !== "/") { + return new Response((await import(`./${pathname.slice(1)}`)).default); + } + + return env.ASSETS.fetch(request); + }, +}; diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js b/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js new file mode 100644 index 000000000000..58c57157d36c --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/other-script.js @@ -0,0 +1 @@ +export default "test"; diff --git a/fixtures/pages-workerjs-directory/public/index.html b/fixtures/pages-workerjs-directory/public/index.html new file mode 100644 index 000000000000..9f735a6307ae --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/index.html @@ -0,0 +1 @@ +

Hello, world!

diff --git a/fixtures/pages-workerjs-directory/tests/index.test.ts b/fixtures/pages-workerjs-directory/tests/index.test.ts new file mode 100644 index 000000000000..2db76f11382a --- /dev/null +++ b/fixtures/pages-workerjs-directory/tests/index.test.ts @@ -0,0 +1,21 @@ +import { resolve } from "node:path"; +import { fetch } from "undici"; +import { describe, it } from "vitest"; +import { runWranglerPagesDev } from "../../shared/src/run-wrangler-long-lived"; + +describe.concurrent("Pages _worker.js/ directory", () => { + it("should support non-bundling with 'dev'", async ({ expect }) => { + const { ip, port, stop } = await runWranglerPagesDev( + resolve(__dirname, ".."), + "public", + ["--port=0"] + ); + await expect( + fetch(`http://${ip}:${port}/`).then((resp) => resp.text()) + ).resolves.toContain("Hello, world!"); + await expect( + fetch(`http://${ip}:${port}/other-script`).then((resp) => resp.text()) + ).resolves.toContain("test"); + await stop(); + }); +}); diff --git a/fixtures/pages-workerjs-directory/tsconfig.json b/fixtures/pages-workerjs-directory/tsconfig.json new file mode 100644 index 000000000000..6eb14e3584b7 --- /dev/null +++ b/fixtures/pages-workerjs-directory/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2020", + "esModuleInterop": true, + "module": "CommonJS", + "lib": ["ES2020"], + "types": ["node"], + "moduleResolution": "node", + "noEmit": true + }, + "include": ["tests", "../../node-types.d.ts"] +} diff --git a/packages/wrangler/src/__tests__/pages/functions-build.test.ts b/packages/wrangler/src/__tests__/pages/functions-build.test.ts index bf28996512c0..6076d44c2cfa 100644 --- a/packages/wrangler/src/__tests__/pages/functions-build.test.ts +++ b/packages/wrangler/src/__tests__/pages/functions-build.test.ts @@ -449,4 +449,74 @@ export default { hello.js:2:36: ERROR: Could not resolve \\"node:async_hooks\\"" `); }); + + it("should compile a _worker.js/ directory", async () => { + mkdirSync("public"); + mkdirSync("public/_worker.js"); + writeFileSync( + "public/_worker.js/index.js", + ` +import { cat } from "./cat.js"; + +export default { + async fetch(request, env) { + return new Response("Hello from _worker.js/index.js" + cat); + }, +};` + ); + writeFileSync( + "public/_worker.js/cat.js", + ` +export const cat = "cat";` + ); + + await runWrangler( + `pages functions build --outfile=public/_worker.bundle --compatibility-flag=nodejs_compat` + ); + + expect(existsSync("public/_worker.bundle")).toBe(true); + expect(std.out).toMatchInlineSnapshot(` + "🚧 'wrangler pages ' is a beta command. Please report any issues to https://github.com/cloudflare/workers-sdk/issues/new/choose + ✨ Compiled Worker successfully" + `); + + const workerBundleContents = readFileSync("public/_worker.bundle", "utf-8"); + const workerBundleWithConstantData = replaceRandomWithConstantData( + workerBundleContents, + [ + [/------formdata-undici-0.[0-9]*/g, "------formdata-undici-0.test"], + [/functionsWorker-0.[0-9]*.js/g, "functionsWorker-0.test.js"], + ] + ); + + expect(workerBundleWithConstantData).toMatchInlineSnapshot(` + "------formdata-undici-0.test + Content-Disposition: form-data; name=\\"metadata\\" + + {\\"main_module\\":\\"functionsWorker-0.test.js\\"} + ------formdata-undici-0.test + Content-Disposition: form-data; name=\\"functionsWorker-0.test.js\\"; filename=\\"functionsWorker-0.test.js\\" + Content-Type: application/javascript+module + + import { cat } from \\"./cat.js\\"; + var worker_default = { + async fetch(request, env) { + return new Response(\\"Hello from _worker.js/index.js\\" + cat); + } + }; + export { + worker_default as default + }; + + ------formdata-undici-0.test + Content-Disposition: form-data; name=\\"cat.js\\"; filename=\\"cat.js\\" + Content-Type: application/javascript+module + + + export const cat = \\"cat\\"; + ------formdata-undici-0.test--" + `); + + expect(std.err).toMatchInlineSnapshot(`""`); + }); }); diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index ecd86996ac3d..91edae672074 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -3,6 +3,7 @@ import { startApiDev, startDev } from "../dev"; import { logger } from "../logger"; import type { Environment } from "../config"; +import type { Rule } from "../config/environment"; import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types"; import type { RequestInit, Response, RequestInfo } from "undici"; @@ -42,6 +43,9 @@ export interface UnstableDevOptions { bucket_name: string; preview_bucket_name?: string; }[]; + bundleEntrypoint?: boolean; + moduleRoot?: string; + rules?: Rule[]; logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"] inspect?: boolean; local?: boolean; @@ -150,6 +154,7 @@ export async function unstable_dev( }, config: options?.config, env: options?.env, + bundleEntrypoint: !!options?.bundleEntrypoint, bundle: options?.bundle, compatibilityDate: options?.compatibilityDate, compatibilityFlags: options?.compatibilityFlags, diff --git a/packages/wrangler/src/api/pages/publish.tsx b/packages/wrangler/src/api/pages/publish.tsx index 5d7eb431b3b4..922f6c00fbe0 100644 --- a/packages/wrangler/src/api/pages/publish.tsx +++ b/packages/wrangler/src/api/pages/publish.tsx @@ -1,4 +1,4 @@ -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, lstatSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join, resolve as resolvePath } from "node:path"; import { cwd } from "node:process"; @@ -17,6 +17,7 @@ import { } from "../../pages/functions/buildWorker"; import { validateRoutes } from "../../pages/functions/routes-validation"; import { upload } from "../../pages/upload"; +import traverseModuleGraph from "../../traverse-module-graph"; import { createUploadWorkerBundleContents } from "./create-worker-bundle-contents"; import type { BundleResult } from "../../bundle"; import type { Project, Deployment } from "@cloudflare/types"; @@ -95,9 +96,10 @@ export async function publish({ _redirects: string | undefined, _routesGenerated: string | undefined, _routesCustom: string | undefined, + _workerJSDirectory = false, _workerJS: string | undefined; - const workerScriptPath = resolvePath(directory, "_worker.js"); + const _workerPath = resolvePath(directory, "_worker.js"); try { _headers = readFileSync(join(directory, "_headers"), "utf-8"); @@ -116,7 +118,10 @@ export async function publish({ } catch {} try { - _workerJS = readFileSync(workerScriptPath, "utf-8"); + _workerJSDirectory = lstatSync(_workerPath).isDirectory(); + if (!_workerJSDirectory) { + _workerJS = readFileSync(_workerPath, "utf-8"); + } } catch {} // Grab the bindings from the API, we need these for shims and other such hacky inserts @@ -243,13 +248,55 @@ export async function publish({ * When using a _worker.js file, the entire /functions directory is ignored * – this includes its routing and middleware characteristics. */ - if (_workerJS) { + if (_workerJSDirectory) { + const traverseModuleGraphResult = await traverseModuleGraph( + { + file: resolvePath(join(_workerPath, "index.js")), + directory: resolvePath(_workerPath), + format: "modules", + moduleRoot: resolvePath(_workerPath), + }, + [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + ); + + const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); + const bundleResult = await buildRawWorker({ + workerScriptPath: _workerPath, + bundle: false, + outfile, + directory, + local: false, + sourcemap: true, + watch: false, + onEnd: () => {}, + betaD1Shims: d1Databases, + nodejsCompat, + }); + + workerBundle = { + modules: (traverseModuleGraphResult?.modules ?? + bundleResult?.modules) as BundleResult["modules"], + dependencies: (bundleResult?.dependencies ?? + traverseModuleGraphResult?.dependencies) as BundleResult["dependencies"], + resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? + traverseModuleGraphResult?.resolvedEntryPointPath) as BundleResult["resolvedEntryPointPath"], + bundleType: (bundleResult?.bundleType ?? + traverseModuleGraphResult?.bundleType) as BundleResult["bundleType"], + stop: bundleResult?.stop, + sourceMapPath: bundleResult?.sourceMapPath, + }; + } else if (_workerJS) { if (bundle) { const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); workerBundle = await buildRawWorker({ - workerScriptPath, + workerScriptPath: _workerPath, outfile, - directory: directory ?? ".", + directory, local: false, sourcemap: true, watch: false, @@ -258,17 +305,19 @@ export async function publish({ nodejsCompat, }); } else { - await checkRawWorker(workerScriptPath, () => {}); - // TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769 + await checkRawWorker(_workerPath, () => {}); + // TODO: Let users configure this in the future. workerBundle = { modules: [], dependencies: {}, stop: undefined, - resolvedEntryPointPath: workerScriptPath, + resolvedEntryPointPath: _workerPath, bundleType: "esm", }; } + } + if (_workerJS || _workerJSDirectory) { const workerBundleContents = await createUploadWorkerBundleContents( workerBundle as BundleResult ); diff --git a/packages/wrangler/src/bundle.ts b/packages/wrangler/src/bundle.ts index 77021fdf2bb2..7719cd41101a 100644 --- a/packages/wrangler/src/bundle.ts +++ b/packages/wrangler/src/bundle.ts @@ -117,6 +117,7 @@ export async function bundleWorker( entry: Entry, destination: string, options: { + bundle?: boolean; serveAssetsFromWorker: boolean; assets?: StaticAssetsConfig; betaD1Shims?: string[]; @@ -149,6 +150,7 @@ export async function bundleWorker( } ): Promise { const { + bundle = true, serveAssetsFromWorker, betaD1Shims, doBindings, @@ -350,7 +352,7 @@ export async function bundleWorker( const buildOptions: esbuild.BuildOptions & { metafile: true } = { entryPoints: [inputEntry.file], - bundle: true, + bundle, absWorkingDir: entry.directory, outdir: destination, entryNames: entryName || path.parse(entry.file).name, @@ -362,7 +364,7 @@ export async function bundleWorker( } : {}), inject, - external: ["__STATIC_CONTENT_MANIFEST"], + external: bundle ? ["__STATIC_CONTENT_MANIFEST"] : undefined, format: entry.format === "modules" ? "esm" : "iife", target: COMMON_ESBUILD_OPTIONS.target, sourcemap: sourcemap ?? true, // this needs to use ?? to accept false diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 238809ca3e7b..5e1e52148100 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -28,7 +28,7 @@ import { printWranglerBanner, } from "./index"; import type { Config, Environment } from "./config"; -import type { Route } from "./config/environment"; +import type { Route, Rule } from "./config/environment"; import type { LoggerLevel } from "./logger"; import type { EnablePagesAssetsServiceBindingOptions } from "./miniflare-cli/types"; import type { CfWorkerInit } from "./worker"; @@ -334,6 +334,9 @@ export type AdditionalDevProps = { preview_bucket_name?: string; }[]; d1Databases?: Environment["d1_databases"]; + bundleEntrypoint?: boolean; + moduleRoot?: string; + rules?: Rule[]; }; type StartDevOptions = DevArguments & @@ -424,7 +427,8 @@ export async function startDev(args: StartDevOptions) { zone={zoneId} host={host} routes={routes} - rules={getRules(configParam)} + bundleEntrypoint={!!args.bundleEntrypoint} + rules={args.rules ?? getRules(configParam)} legacyEnv={isLegacyEnv(configParam)} minify={args.minify ?? configParam.minify} legacyNodeCompat={legacyNodeCompat} @@ -560,7 +564,8 @@ export async function startApiDev(args: StartDevOptions) { zone: zoneId, host: host, routes: routes, - rules: getRules(configParam), + bundleEntrypoint: !!args.bundleEntrypoint, + rules: args.rules ?? getRules(configParam), legacyEnv: isLegacyEnv(configParam), minify: args.minify ?? configParam.minify, legacyNodeCompat, @@ -687,7 +692,7 @@ async function validateDevServerSettings( config: Config ) { const entry = await getEntry( - { assets: args.assets, script: args.script }, + { assets: args.assets, script: args.script, moduleRoot: args.moduleRoot }, config, "dev" ); diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index 998332e7c15e..fdb3a2e4c824 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -116,6 +116,7 @@ export type DevProps = { initialPort: number; initialIp: string; inspectorPort: number; + bundleEntrypoint: boolean; rules: Config["rules"]; accountId: string | undefined; initialMode: "local" | "remote"; @@ -271,6 +272,7 @@ function DevSession(props: DevSessionProps) { entry: props.entry, destination: directory, jsxFactory: props.jsxFactory, + bundleEntrypoint: props.bundleEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( diff --git a/packages/wrangler/src/dev/start-server.ts b/packages/wrangler/src/dev/start-server.ts index 3a56f1ef0ee0..c0b111763b6e 100644 --- a/packages/wrangler/src/dev/start-server.ts +++ b/packages/wrangler/src/dev/start-server.ts @@ -92,6 +92,7 @@ export async function startDevServer( entry: props.entry, destination: directory.name, jsxFactory: props.jsxFactory, + bundleEntrypoint: props.bundleEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( @@ -205,6 +206,7 @@ async function runEsbuild({ destination, jsxFactory, jsxFragment, + bundleEntrypoint, rules, assets, betaD1Shims, @@ -227,6 +229,7 @@ async function runEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; + bundleEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; betaD1Shims?: string[]; @@ -247,40 +250,71 @@ async function runEsbuild({ }): Promise { if (!destination) return; + let traverseModuleGraphResult: + | Awaited> + | undefined; + let bundleResult: Awaited> | undefined; + if (noBundle) { + traverseModuleGraphResult = await traverseModuleGraph(entry, rules); + } + + if (bundleEntrypoint || !noBundle) { + bundleResult = await bundleWorker(entry, destination, { + bundle: !noBundle, + disableModuleCollection: noBundle, + serveAssetsFromWorker, + jsxFactory, + jsxFragment, + rules, + tsconfig, + minify, + legacyNodeCompat, + nodejsCompat, + define, + checkFetch: true, + assets: assets && { + ...assets, + // disable the cache in dev + bypassCache: true, + }, + betaD1Shims, + workerDefinitions, + services, + firstPartyWorkerDevFacade, + targetConsumer: "dev", // We are starting a dev server + testScheduled, + local, + experimentalLocal, + doBindings, + }); + } + const { - resolvedEntryPointPath, - bundleType, modules, dependencies, + resolvedEntryPointPath, + bundleType, sourceMapPath, - }: Awaited> = noBundle - ? await traverseModuleGraph(entry, rules) - : await bundleWorker(entry, destination, { - serveAssetsFromWorker, - jsxFactory, - jsxFragment, - rules, - tsconfig, - minify, - legacyNodeCompat, - nodejsCompat, - define, - checkFetch: true, - assets: assets && { - ...assets, - // disable the cache in dev - bypassCache: true, - }, - betaD1Shims, - workerDefinitions, - services, - firstPartyWorkerDevFacade, - targetConsumer: "dev", // We are starting a dev server - testScheduled, - local, - experimentalLocal, - doBindings, - }); + }: Awaited> = { + modules: (traverseModuleGraphResult?.modules ?? + bundleResult?.modules) as Awaited< + ReturnType + >["modules"], + dependencies: (bundleResult?.dependencies ?? + traverseModuleGraphResult?.dependencies) as Awaited< + ReturnType + >["dependencies"], + resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? + traverseModuleGraphResult?.resolvedEntryPointPath) as Awaited< + ReturnType + >["resolvedEntryPointPath"], + bundleType: (bundleResult?.bundleType ?? + traverseModuleGraphResult?.bundleType) as Awaited< + ReturnType + >["bundleType"], + stop: bundleResult?.stop, + sourceMapPath: bundleResult?.sourceMapPath, + }; return { id: 0, diff --git a/packages/wrangler/src/dev/use-esbuild.ts b/packages/wrangler/src/dev/use-esbuild.ts index 9e7121eeb01c..af2ec05a99a8 100644 --- a/packages/wrangler/src/dev/use-esbuild.ts +++ b/packages/wrangler/src/dev/use-esbuild.ts @@ -26,6 +26,7 @@ export function useEsbuild({ destination, jsxFactory, jsxFragment, + bundleEntrypoint, rules, assets, serveAssetsFromWorker, @@ -49,6 +50,7 @@ export function useEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; + bundleEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; define: Config["define"]; @@ -100,42 +102,73 @@ export function useEsbuild({ async function build() { if (!destination) return; + let traverseModuleGraphResult: + | Awaited> + | undefined; + let bundleResult: Awaited> | undefined; + if (noBundle) { + traverseModuleGraphResult = await traverseModuleGraph(entry, rules); + } + + if (bundleEntrypoint || !noBundle) { + bundleResult = await bundleWorker(entry, destination, { + bundle: !noBundle, + disableModuleCollection: noBundle, + serveAssetsFromWorker, + jsxFactory, + jsxFragment, + rules, + watch: watchMode, + tsconfig, + minify, + legacyNodeCompat, + nodejsCompat, + betaD1Shims, + doBindings: durableObjects.bindings, + define, + checkFetch: true, + assets: assets && { + ...assets, + // disable the cache in dev + bypassCache: true, + }, + workerDefinitions, + services, + firstPartyWorkerDevFacade, + local, + targetConsumer, + testScheduled, + experimentalLocal, + }); + } + const { - resolvedEntryPointPath, - bundleType, modules, dependencies, + resolvedEntryPointPath, + bundleType, stop, sourceMapPath, - }: Awaited> = noBundle - ? await traverseModuleGraph(entry, rules) - : await bundleWorker(entry, destination, { - serveAssetsFromWorker, - jsxFactory, - jsxFragment, - rules, - watch: watchMode, - tsconfig, - minify, - legacyNodeCompat, - nodejsCompat, - betaD1Shims, - doBindings: durableObjects.bindings, - define, - checkFetch: true, - assets: assets && { - ...assets, - // disable the cache in dev - bypassCache: true, - }, - workerDefinitions, - services, - firstPartyWorkerDevFacade, - local, - targetConsumer, - testScheduled, - experimentalLocal, - }); + }: Awaited> = { + modules: (traverseModuleGraphResult?.modules ?? + bundleResult?.modules) as Awaited< + ReturnType + >["modules"], + dependencies: (bundleResult?.dependencies ?? + traverseModuleGraphResult?.dependencies) as Awaited< + ReturnType + >["dependencies"], + resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? + traverseModuleGraphResult?.resolvedEntryPointPath) as Awaited< + ReturnType + >["resolvedEntryPointPath"], + bundleType: (bundleResult?.bundleType ?? + traverseModuleGraphResult?.bundleType) as Awaited< + ReturnType + >["bundleType"], + stop: bundleResult?.stop, + sourceMapPath: bundleResult?.sourceMapPath, + }; // Capture the `stop()` method to use as the `useEffect()` destructor. stopWatching = stop; @@ -180,6 +213,7 @@ export function useEsbuild({ jsxFactory, jsxFragment, serveAssetsFromWorker, + bundleEntrypoint, rules, tsconfig, exit, diff --git a/packages/wrangler/src/entry.ts b/packages/wrangler/src/entry.ts index 5f7da5e4355b..8984704d6bff 100644 --- a/packages/wrangler/src/entry.ts +++ b/packages/wrangler/src/entry.ts @@ -35,6 +35,7 @@ export async function getEntry( script?: string; format?: CfScriptFormat | undefined; assets?: string | undefined; + moduleRoot?: string; }, config: Config, command: "dev" | "publish" | "types" @@ -113,7 +114,7 @@ export async function getEntry( file, directory, format, - moduleRoot: config.base_dir ?? path.dirname(file), + moduleRoot: args.moduleRoot ?? config.base_dir ?? path.dirname(file), }; } diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index 268d85d43594..7052c0c91ae3 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -1,9 +1,17 @@ -import { existsSync, mkdirSync, writeFileSync } from "node:fs"; -import { basename, dirname, relative, resolve as resolvePath } from "node:path"; +import { existsSync, lstatSync, mkdirSync, writeFileSync } from "node:fs"; +import { + basename, + dirname, + join, + relative, + resolve, + resolve as resolvePath, +} from "node:path"; import { createUploadWorkerBundleContents } from "../api/pages/create-worker-bundle-contents"; import { FatalError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; +import traverseModuleGraph from "../traverse-module-graph"; import { buildFunctions } from "./buildFunctions"; import { isInPagesCI } from "./constants"; import { @@ -208,21 +216,64 @@ export const Handler = async (args: PagesBuildArgs) => { * and if we were able to resolve _worker.js */ if (workerScriptPath) { - /** - * `buildRawWorker` builds `_worker.js`, but doesn't give us the bundle - * we want to return, which includes the external dependencies (like wasm, - * binary, text). Let's output that build result to memory and only write - * to disk once we have the final bundle - */ - bundle = await buildRawWorker({ - workerScriptPath, - outdir, - directory: buildOutputDirectory, - local: false, - sourcemap, - watch, - betaD1Shims: d1Databases, - }); + if (lstatSync(workerScriptPath).isDirectory()) { + const entrypoint = resolve(join(workerScriptPath, "index.js")); + + const traverseModuleGraphResult = await traverseModuleGraph( + { + file: entrypoint, + directory: resolve(workerScriptPath), + format: "modules", + moduleRoot: resolve(workerScriptPath), + }, + [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + ); + + const bundleResult = await buildRawWorker({ + workerScriptPath: entrypoint, + bundle: false, + outdir, + directory: buildOutputDirectory, + local: false, + sourcemap, + watch, + betaD1Shims: d1Databases, + }); + + bundle = { + modules: (traverseModuleGraphResult?.modules ?? + bundleResult?.modules) as BundleResult["modules"], + dependencies: (bundleResult?.dependencies ?? + traverseModuleGraphResult?.dependencies) as BundleResult["dependencies"], + resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? + traverseModuleGraphResult?.resolvedEntryPointPath) as BundleResult["resolvedEntryPointPath"], + bundleType: (bundleResult?.bundleType ?? + traverseModuleGraphResult?.bundleType) as BundleResult["bundleType"], + stop: bundleResult?.stop, + sourceMapPath: bundleResult?.sourceMapPath, + }; + } else { + /** + * `buildRawWorker` builds `_worker.js`, but doesn't give us the bundle + * we want to return, which includes the external dependencies (like wasm, + * binary, text). Let's output that build result to memory and only write + * to disk once we have the final bundle + */ + bundle = await buildRawWorker({ + workerScriptPath, + outdir, + directory: buildOutputDirectory, + local: false, + sourcemap, + watch, + betaD1Shims: d1Databases, + }); + } } else { try { /** diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index edf1ed96ee3c..9e3dd658ca85 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -1,5 +1,5 @@ import { execSync, spawn } from "node:child_process"; -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, lstatSync, readFileSync } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { watch } from "chokidar"; @@ -255,7 +255,12 @@ export const Handler = async ({ directory !== undefined ? join(directory, singleWorkerScriptPath) : singleWorkerScriptPath; + const usingWorkerDirectory = + existsSync(workerScriptPath) && lstatSync(workerScriptPath).isDirectory(); const usingWorkerScript = existsSync(workerScriptPath); + // TODO: Here lies a known bug. If you specify both `--bundle` and `--no-bundle`, this behavior is undefined and you will get unexpected results. + // There is no sane way to get the true value out of yargs, so here we are. + const enableBundling = bundle ?? !noBundle; const functionsDirectory = "./functions"; let usingFunctions = !usingWorkerScript && existsSync(functionsDirectory); @@ -270,9 +275,6 @@ export const Handler = async ({ await checkRawWorker(workerScriptPath, () => scriptReadyResolve()); }; - // TODO: Here lies a known bug. If you specify both `--bundle` and `--no-bundle`, this behavior is undefined and you will get unexpected results. - // There is no sane way to get the true value out of yargs, so here we are. - const enableBundling = bundle ?? !noBundle; if (enableBundling) { // We want to actually run the `_worker.js` script through the bundler // So update the final path to the script that will be uploaded and @@ -281,7 +283,9 @@ export const Handler = async ({ runBuild = async () => { try { await buildRawWorker({ - workerScriptPath, + workerScriptPath: usingWorkerDirectory + ? join(workerScriptPath, "index.js") + : workerScriptPath, outfile: scriptPath, directory: directory ?? ".", nodejsCompat, @@ -396,7 +400,10 @@ export const Handler = async ({ let entrypoint = scriptPath; // custom _routes.json apply only to Functions or Advanced Mode Pages projects - if (directory && (usingFunctions || usingWorkerScript)) { + if ( + directory && + (usingFunctions || usingWorkerScript || usingWorkerDirectory) + ) { const routesJSONPath = join(directory, "_routes.json"); if (existsSync(routesJSONPath)) { @@ -538,6 +545,17 @@ export const Handler = async ({ r2: r2s.map((binding) => { return { binding: binding.toString(), bucket_name: "" }; }), + bundleEntrypoint: true, + moduleRoot: workerScriptPath, + rules: usingWorkerDirectory + ? [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + : undefined, + bundle: enableBundling, persist, persistTo, inspect: undefined, diff --git a/packages/wrangler/src/pages/functions/buildWorker.ts b/packages/wrangler/src/pages/functions/buildWorker.ts index b6f3e59a6893..7bb3de201c74 100644 --- a/packages/wrangler/src/pages/functions/buildWorker.ts +++ b/packages/wrangler/src/pages/functions/buildWorker.ts @@ -164,6 +164,7 @@ export type RawOptions = { outfile?: string; outdir?: string; directory: string; + bundle?: boolean; minify?: boolean; sourcemap?: boolean; watch?: boolean; @@ -188,6 +189,7 @@ export function buildRawWorker({ outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`), outdir, directory, + bundle = true, minify = false, sourcemap = false, watch = false, @@ -207,6 +209,7 @@ export function buildRawWorker({ }, outdir ? resolve(outdir) : resolve(outfile), { + bundle, minify, sourcemap, watch, From 6853fb713f0509f055fc2714e0ee3a5619aa9240 Mon Sep 17 00:00:00 2001 From: Greg Brimble Date: Mon, 1 May 2023 23:59:52 +0100 Subject: [PATCH 2/2] Refactor some of the shared no bundle logic and address misc PR comments --- .changeset/cuddly-rules-rest.md | 2 +- fixtures/local-mode-tests/package.json | 21 +----- .../public/_worker.js/add.wasm | Bin 0 -> 41 bytes .../public/_worker.js/index.js | 13 ++++ .../public/_worker.js/static.js | 1 + .../tests/index.test.ts | 26 +++++++- .../__tests__/pages/functions-build.test.ts | 12 ++-- packages/wrangler/src/api/dev.ts | 4 +- packages/wrangler/src/api/pages/publish.tsx | 63 +++++------------- packages/wrangler/src/bundle.ts | 1 + packages/wrangler/src/dev.tsx | 6 +- packages/wrangler/src/dev/dev.tsx | 4 +- packages/wrangler/src/dev/start-server.ts | 47 +++---------- packages/wrangler/src/dev/use-esbuild.ts | 51 ++++---------- packages/wrangler/src/pages/build.ts | 59 +++------------- packages/wrangler/src/pages/dev.ts | 2 +- .../src/pages/functions/buildWorker.ts | 54 +++++++++++++++ 17 files changed, 160 insertions(+), 206 deletions(-) create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/add.wasm create mode 100644 fixtures/pages-workerjs-directory/public/_worker.js/static.js diff --git a/.changeset/cuddly-rules-rest.md b/.changeset/cuddly-rules-rest.md index 6d102be5778e..2b61ef0d788d 100644 --- a/.changeset/cuddly-rules-rest.md +++ b/.changeset/cuddly-rules-rest.md @@ -1,5 +1,5 @@ --- -"wrangler": patch +"wrangler": minor --- feat: Add support for the undocumented `_worker.js/` directory in Pages diff --git a/fixtures/local-mode-tests/package.json b/fixtures/local-mode-tests/package.json index 08c068ae8bf1..cb065cdd3b98 100644 --- a/fixtures/local-mode-tests/package.json +++ b/fixtures/local-mode-tests/package.json @@ -9,25 +9,8 @@ "main": "index.js", "scripts": { "check:type": "tsc && tsc -p tests/tsconfig.json", - "test": "cross-env NODE_ENV=local-testing NODE_OPTIONS=--experimental-vm-modules npx jest --forceExit", - "test:ci": "cross-env NODE_ENV=local-testing NODE_OPTIONS=--experimental-vm-modules npx jest --forceExit" - }, - "jest": { - "restoreMocks": true, - "testRegex": ".*.(test|spec)\\.[jt]sx?$", - "testTimeout": 30000, - "transform": { - "^.+\\.c?(t|j)sx?$": [ - "esbuild-jest", - { - "sourcemap": true - } - ] - }, - "transformIgnorePatterns": [ - "node_modules/(?!find-up|locate-path|p-locate|p-limit|p-timeout|p-queue|yocto-queue|path-exists|execa|strip-final-newline|npm-run-path|path-key|onetime|mimic-fn|human-signals|is-stream|get-port|supports-color|pretty-bytes)", - "wrangler-dist/cli.js" - ] + "test": "npx vitest", + "test:ci": "npx vitest" }, "devDependencies": { "@cloudflare/workers-types": "^4.20221111.1", diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/add.wasm b/fixtures/pages-workerjs-directory/public/_worker.js/add.wasm new file mode 100644 index 0000000000000000000000000000000000000000..357f72da7a0db8add83699082fd51d46bf3352fb GIT binary patch literal 41 wcmZQbEY4+QU|?WmXG~zKuV<`hW@2PuXJ=$iOi5v2;NoOtXHZ~JV9eqM0DJxgJ^%m! literal 0 HcmV?d00001 diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/index.js b/fixtures/pages-workerjs-directory/public/_worker.js/index.js index 3380296683bc..fb55bdb57c75 100644 --- a/fixtures/pages-workerjs-directory/public/_worker.js/index.js +++ b/fixtures/pages-workerjs-directory/public/_worker.js/index.js @@ -1,6 +1,19 @@ +import staticMod from "./static.js"; +import add from "./add.wasm"; + export default { async fetch(request, env) { const { pathname } = new URL(request.url); + + if (pathname === "/wasm") { + const addModule = await WebAssembly.instantiate(add); + return new Response(addModule.exports.add(1, 2).toString()); + } + + if (pathname === "/static") { + return new Response(staticMod); + } + if (pathname !== "/") { return new Response((await import(`./${pathname.slice(1)}`)).default); } diff --git a/fixtures/pages-workerjs-directory/public/_worker.js/static.js b/fixtures/pages-workerjs-directory/public/_worker.js/static.js new file mode 100644 index 000000000000..dffa9ed5541b --- /dev/null +++ b/fixtures/pages-workerjs-directory/public/_worker.js/static.js @@ -0,0 +1 @@ +export default "static"; diff --git a/fixtures/pages-workerjs-directory/tests/index.test.ts b/fixtures/pages-workerjs-directory/tests/index.test.ts index 2db76f11382a..d4446d65f562 100644 --- a/fixtures/pages-workerjs-directory/tests/index.test.ts +++ b/fixtures/pages-workerjs-directory/tests/index.test.ts @@ -1,4 +1,7 @@ -import { resolve } from "node:path"; +import { execSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path, { join, resolve } from "node:path"; import { fetch } from "undici"; import { describe, it } from "vitest"; import { runWranglerPagesDev } from "../../shared/src/run-wrangler-long-lived"; @@ -13,9 +16,30 @@ describe.concurrent("Pages _worker.js/ directory", () => { await expect( fetch(`http://${ip}:${port}/`).then((resp) => resp.text()) ).resolves.toContain("Hello, world!"); + await expect( + fetch(`http://${ip}:${port}/wasm`).then((resp) => resp.text()) + ).resolves.toContain("3"); + await expect( + fetch(`http://${ip}:${port}/static`).then((resp) => resp.text()) + ).resolves.toContain("static"); await expect( fetch(`http://${ip}:${port}/other-script`).then((resp) => resp.text()) ).resolves.toContain("test"); await stop(); }); + + it("should bundle", async ({ expect }) => { + const dir = tmpdir(); + const file = join(dir, "./_worker.bundle"); + + execSync( + `npx wrangler pages functions build --build-output-directory public --outfile ${file} --bindings="{\\"d1_databases\\":{\\"FOO\\":{}}}"`, + { + cwd: path.resolve(__dirname, ".."), + } + ); + + expect(readFileSync(file, "utf-8")).toContain("D1_ERROR"); + expect(readFileSync(file, "utf-8")).toContain('"static"'); + }); }); diff --git a/packages/wrangler/src/__tests__/pages/functions-build.test.ts b/packages/wrangler/src/__tests__/pages/functions-build.test.ts index 6076d44c2cfa..01b8947eec7e 100644 --- a/packages/wrangler/src/__tests__/pages/functions-build.test.ts +++ b/packages/wrangler/src/__tests__/pages/functions-build.test.ts @@ -470,9 +470,7 @@ export default { export const cat = "cat";` ); - await runWrangler( - `pages functions build --outfile=public/_worker.bundle --compatibility-flag=nodejs_compat` - ); + await runWrangler(`pages functions build --outfile=public/_worker.bundle`); expect(existsSync("public/_worker.bundle")).toBe(true); expect(std.out).toMatchInlineSnapshot(` @@ -485,7 +483,8 @@ export const cat = "cat";` workerBundleContents, [ [/------formdata-undici-0.[0-9]*/g, "------formdata-undici-0.test"], - [/functionsWorker-0.[0-9]*.js/g, "functionsWorker-0.test.js"], + [/bundledWorker-0.[0-9]*.mjs/g, "bundledWorker-0.test.mjs"], + [/bundledWorker-0.[0-9]*.map/g, "bundledWorker-0.test.map"], ] ); @@ -493,9 +492,9 @@ export const cat = "cat";` "------formdata-undici-0.test Content-Disposition: form-data; name=\\"metadata\\" - {\\"main_module\\":\\"functionsWorker-0.test.js\\"} + {\\"main_module\\":\\"bundledWorker-0.test.mjs\\"} ------formdata-undici-0.test - Content-Disposition: form-data; name=\\"functionsWorker-0.test.js\\"; filename=\\"functionsWorker-0.test.js\\" + Content-Disposition: form-data; name=\\"bundledWorker-0.test.mjs\\"; filename=\\"bundledWorker-0.test.mjs\\" Content-Type: application/javascript+module import { cat } from \\"./cat.js\\"; @@ -507,6 +506,7 @@ export const cat = "cat";` export { worker_default as default }; + //# sourceMappingURL=bundledWorker-0.test.mjs.map ------formdata-undici-0.test Content-Disposition: form-data; name=\\"cat.js\\"; filename=\\"cat.js\\" diff --git a/packages/wrangler/src/api/dev.ts b/packages/wrangler/src/api/dev.ts index 91edae672074..27c1fbf1ad6a 100644 --- a/packages/wrangler/src/api/dev.ts +++ b/packages/wrangler/src/api/dev.ts @@ -43,7 +43,7 @@ export interface UnstableDevOptions { bucket_name: string; preview_bucket_name?: string; }[]; - bundleEntrypoint?: boolean; + processEntrypoint?: boolean; moduleRoot?: string; rules?: Rule[]; logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"] @@ -154,7 +154,7 @@ export async function unstable_dev( }, config: options?.config, env: options?.env, - bundleEntrypoint: !!options?.bundleEntrypoint, + processEntrypoint: !!options?.processEntrypoint, bundle: options?.bundle, compatibilityDate: options?.compatibilityDate, compatibilityFlags: options?.compatibilityFlags, diff --git a/packages/wrangler/src/api/pages/publish.tsx b/packages/wrangler/src/api/pages/publish.tsx index 922f6c00fbe0..36efa2b7e014 100644 --- a/packages/wrangler/src/api/pages/publish.tsx +++ b/packages/wrangler/src/api/pages/publish.tsx @@ -14,10 +14,10 @@ import { import { buildRawWorker, checkRawWorker, + traverseAndBuildWorkerJSDirectory, } from "../../pages/functions/buildWorker"; import { validateRoutes } from "../../pages/functions/routes-validation"; import { upload } from "../../pages/upload"; -import traverseModuleGraph from "../../traverse-module-graph"; import { createUploadWorkerBundleContents } from "./create-worker-bundle-contents"; import type { BundleResult } from "../../bundle"; import type { Project, Deployment } from "@cloudflare/types"; @@ -66,7 +66,7 @@ interface PagesPublishOptions { /** * Whether to run bundling on `_worker.js` before deploying. - * Default: false + * Default: true */ bundle?: boolean; @@ -96,9 +96,11 @@ export async function publish({ _redirects: string | undefined, _routesGenerated: string | undefined, _routesCustom: string | undefined, - _workerJSDirectory = false, + _workerJSIsDirectory = false, _workerJS: string | undefined; + bundle = bundle ?? true; + const _workerPath = resolvePath(directory, "_worker.js"); try { @@ -118,8 +120,8 @@ export async function publish({ } catch {} try { - _workerJSDirectory = lstatSync(_workerPath).isDirectory(); - if (!_workerJSDirectory) { + _workerJSIsDirectory = lstatSync(_workerPath).isDirectory(); + if (!_workerJSIsDirectory) { _workerJS = readFileSync(_workerPath, "utf-8"); } } catch {} @@ -245,51 +247,16 @@ export async function publish({ * Advanced Mode * https://developers.cloudflare.com/pages/platform/functions/#advanced-mode * - * When using a _worker.js file, the entire /functions directory is ignored + * When using a _worker.js file or _worker.js/ directory, the entire /functions directory is ignored * – this includes its routing and middleware characteristics. */ - if (_workerJSDirectory) { - const traverseModuleGraphResult = await traverseModuleGraph( - { - file: resolvePath(join(_workerPath, "index.js")), - directory: resolvePath(_workerPath), - format: "modules", - moduleRoot: resolvePath(_workerPath), - }, - [ - { - type: "ESModule", - globs: ["**/*.js"], - }, - ] - ); - - const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); - const bundleResult = await buildRawWorker({ - workerScriptPath: _workerPath, - bundle: false, - outfile, - directory, - local: false, - sourcemap: true, - watch: false, - onEnd: () => {}, - betaD1Shims: d1Databases, + if (_workerJSIsDirectory) { + workerBundle = await traverseAndBuildWorkerJSDirectory({ + workerJSDirectory: _workerPath, + buildOutputDirectory: directory, + d1Databases, nodejsCompat, }); - - workerBundle = { - modules: (traverseModuleGraphResult?.modules ?? - bundleResult?.modules) as BundleResult["modules"], - dependencies: (bundleResult?.dependencies ?? - traverseModuleGraphResult?.dependencies) as BundleResult["dependencies"], - resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? - traverseModuleGraphResult?.resolvedEntryPointPath) as BundleResult["resolvedEntryPointPath"], - bundleType: (bundleResult?.bundleType ?? - traverseModuleGraphResult?.bundleType) as BundleResult["bundleType"], - stop: bundleResult?.stop, - sourceMapPath: bundleResult?.sourceMapPath, - }; } else if (_workerJS) { if (bundle) { const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); @@ -317,7 +284,7 @@ export async function publish({ } } - if (_workerJS || _workerJSDirectory) { + if (_workerJS || _workerJSIsDirectory) { const workerBundleContents = await createUploadWorkerBundleContents( workerBundle as BundleResult ); @@ -351,7 +318,7 @@ export async function publish({ * Pages Functions * https://developers.cloudflare.com/pages/platform/functions/ */ - if (builtFunctions && !_workerJS) { + if (builtFunctions && !_workerJS && !_workerJSIsDirectory) { const workerBundleContents = await createUploadWorkerBundleContents( workerBundle as BundleResult ); diff --git a/packages/wrangler/src/bundle.ts b/packages/wrangler/src/bundle.ts index 7719cd41101a..c35f5b48bfe1 100644 --- a/packages/wrangler/src/bundle.ts +++ b/packages/wrangler/src/bundle.ts @@ -117,6 +117,7 @@ export async function bundleWorker( entry: Entry, destination: string, options: { + // When `bundle` is set to false, we apply shims to the Worker, but won't pull in any imports bundle?: boolean; serveAssetsFromWorker: boolean; assets?: StaticAssetsConfig; diff --git a/packages/wrangler/src/dev.tsx b/packages/wrangler/src/dev.tsx index 5e1e52148100..0173eb6812cb 100644 --- a/packages/wrangler/src/dev.tsx +++ b/packages/wrangler/src/dev.tsx @@ -334,7 +334,7 @@ export type AdditionalDevProps = { preview_bucket_name?: string; }[]; d1Databases?: Environment["d1_databases"]; - bundleEntrypoint?: boolean; + processEntrypoint?: boolean; moduleRoot?: string; rules?: Rule[]; }; @@ -427,7 +427,7 @@ export async function startDev(args: StartDevOptions) { zone={zoneId} host={host} routes={routes} - bundleEntrypoint={!!args.bundleEntrypoint} + processEntrypoint={!!args.processEntrypoint} rules={args.rules ?? getRules(configParam)} legacyEnv={isLegacyEnv(configParam)} minify={args.minify ?? configParam.minify} @@ -564,7 +564,7 @@ export async function startApiDev(args: StartDevOptions) { zone: zoneId, host: host, routes: routes, - bundleEntrypoint: !!args.bundleEntrypoint, + processEntrypoint: !!args.processEntrypoint, rules: args.rules ?? getRules(configParam), legacyEnv: isLegacyEnv(configParam), minify: args.minify ?? configParam.minify, diff --git a/packages/wrangler/src/dev/dev.tsx b/packages/wrangler/src/dev/dev.tsx index fdb3a2e4c824..29583a585daa 100644 --- a/packages/wrangler/src/dev/dev.tsx +++ b/packages/wrangler/src/dev/dev.tsx @@ -116,7 +116,7 @@ export type DevProps = { initialPort: number; initialIp: string; inspectorPort: number; - bundleEntrypoint: boolean; + processEntrypoint: boolean; rules: Config["rules"]; accountId: string | undefined; initialMode: "local" | "remote"; @@ -272,7 +272,7 @@ function DevSession(props: DevSessionProps) { entry: props.entry, destination: directory, jsxFactory: props.jsxFactory, - bundleEntrypoint: props.bundleEntrypoint, + processEntrypoint: props.processEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( diff --git a/packages/wrangler/src/dev/start-server.ts b/packages/wrangler/src/dev/start-server.ts index c0b111763b6e..de010964a142 100644 --- a/packages/wrangler/src/dev/start-server.ts +++ b/packages/wrangler/src/dev/start-server.ts @@ -92,7 +92,7 @@ export async function startDevServer( entry: props.entry, destination: directory.name, jsxFactory: props.jsxFactory, - bundleEntrypoint: props.bundleEntrypoint, + processEntrypoint: props.processEntrypoint, rules: props.rules, jsxFragment: props.jsxFragment, serveAssetsFromWorker: Boolean( @@ -206,7 +206,7 @@ async function runEsbuild({ destination, jsxFactory, jsxFragment, - bundleEntrypoint, + processEntrypoint, rules, assets, betaD1Shims, @@ -229,7 +229,7 @@ async function runEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; - bundleEntrypoint: boolean; + processEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; betaD1Shims?: string[]; @@ -258,7 +258,7 @@ async function runEsbuild({ traverseModuleGraphResult = await traverseModuleGraph(entry, rules); } - if (bundleEntrypoint || !noBundle) { + if (processEntrypoint || !noBundle) { bundleResult = await bundleWorker(entry, destination, { bundle: !noBundle, disableModuleCollection: noBundle, @@ -289,41 +289,16 @@ async function runEsbuild({ }); } - const { - modules, - dependencies, - resolvedEntryPointPath, - bundleType, - sourceMapPath, - }: Awaited> = { - modules: (traverseModuleGraphResult?.modules ?? - bundleResult?.modules) as Awaited< - ReturnType - >["modules"], - dependencies: (bundleResult?.dependencies ?? - traverseModuleGraphResult?.dependencies) as Awaited< - ReturnType - >["dependencies"], - resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? - traverseModuleGraphResult?.resolvedEntryPointPath) as Awaited< - ReturnType - >["resolvedEntryPointPath"], - bundleType: (bundleResult?.bundleType ?? - traverseModuleGraphResult?.bundleType) as Awaited< - ReturnType - >["bundleType"], - stop: bundleResult?.stop, - sourceMapPath: bundleResult?.sourceMapPath, - }; - return { id: 0, entry, - path: resolvedEntryPointPath, - type: bundleType, - modules, - dependencies, - sourceMapPath, + path: bundleResult?.resolvedEntryPointPath ?? entry.file, + type: + bundleResult?.bundleType ?? + (entry.format === "modules" ? "esm" : "commonjs"), + modules: traverseModuleGraphResult?.modules ?? bundleResult?.modules ?? [], + dependencies: bundleResult?.dependencies ?? {}, + sourceMapPath: bundleResult?.sourceMapPath, }; } diff --git a/packages/wrangler/src/dev/use-esbuild.ts b/packages/wrangler/src/dev/use-esbuild.ts index af2ec05a99a8..2285eb71676e 100644 --- a/packages/wrangler/src/dev/use-esbuild.ts +++ b/packages/wrangler/src/dev/use-esbuild.ts @@ -26,7 +26,7 @@ export function useEsbuild({ destination, jsxFactory, jsxFragment, - bundleEntrypoint, + processEntrypoint, rules, assets, serveAssetsFromWorker, @@ -50,7 +50,7 @@ export function useEsbuild({ destination: string | undefined; jsxFactory: string | undefined; jsxFragment: string | undefined; - bundleEntrypoint: boolean; + processEntrypoint: boolean; rules: Config["rules"]; assets: Config["assets"]; define: Config["define"]; @@ -110,7 +110,7 @@ export function useEsbuild({ traverseModuleGraphResult = await traverseModuleGraph(entry, rules); } - if (bundleEntrypoint || !noBundle) { + if (processEntrypoint || !noBundle) { bundleResult = await bundleWorker(entry, destination, { bundle: !noBundle, disableModuleCollection: noBundle, @@ -142,36 +142,8 @@ export function useEsbuild({ }); } - const { - modules, - dependencies, - resolvedEntryPointPath, - bundleType, - stop, - sourceMapPath, - }: Awaited> = { - modules: (traverseModuleGraphResult?.modules ?? - bundleResult?.modules) as Awaited< - ReturnType - >["modules"], - dependencies: (bundleResult?.dependencies ?? - traverseModuleGraphResult?.dependencies) as Awaited< - ReturnType - >["dependencies"], - resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? - traverseModuleGraphResult?.resolvedEntryPointPath) as Awaited< - ReturnType - >["resolvedEntryPointPath"], - bundleType: (bundleResult?.bundleType ?? - traverseModuleGraphResult?.bundleType) as Awaited< - ReturnType - >["bundleType"], - stop: bundleResult?.stop, - sourceMapPath: bundleResult?.sourceMapPath, - }; - // Capture the `stop()` method to use as the `useEffect()` destructor. - stopWatching = stop; + stopWatching = bundleResult?.stop; // if "noBundle" is true, then we need to manually watch the entry point and // trigger "builds" when it changes @@ -189,11 +161,14 @@ export function useEsbuild({ setBundle({ id: 0, entry, - path: resolvedEntryPointPath, - type: bundleType, - modules, - dependencies, - sourceMapPath, + path: bundleResult?.resolvedEntryPointPath ?? entry.file, + type: + bundleResult?.bundleType ?? + (entry.format === "modules" ? "esm" : "commonjs"), + modules: + traverseModuleGraphResult?.modules ?? bundleResult?.modules ?? [], + dependencies: bundleResult?.dependencies ?? {}, + sourceMapPath: bundleResult?.sourceMapPath, }); } @@ -213,7 +188,7 @@ export function useEsbuild({ jsxFactory, jsxFragment, serveAssetsFromWorker, - bundleEntrypoint, + processEntrypoint, rules, tsconfig, exit, diff --git a/packages/wrangler/src/pages/build.ts b/packages/wrangler/src/pages/build.ts index 7052c0c91ae3..af17e3b77115 100644 --- a/packages/wrangler/src/pages/build.ts +++ b/packages/wrangler/src/pages/build.ts @@ -1,17 +1,9 @@ import { existsSync, lstatSync, mkdirSync, writeFileSync } from "node:fs"; -import { - basename, - dirname, - join, - relative, - resolve, - resolve as resolvePath, -} from "node:path"; +import { basename, dirname, relative, resolve as resolvePath } from "node:path"; import { createUploadWorkerBundleContents } from "../api/pages/create-worker-bundle-contents"; import { FatalError } from "../errors"; import { logger } from "../logger"; import * as metrics from "../metrics"; -import traverseModuleGraph from "../traverse-module-graph"; import { buildFunctions } from "./buildFunctions"; import { isInPagesCI } from "./constants"; import { @@ -20,7 +12,10 @@ import { FunctionsNoRoutesError, getFunctionsNoRoutesWarning, } from "./errors"; -import { buildRawWorker } from "./functions/buildWorker"; +import { + buildRawWorker, + traverseAndBuildWorkerJSDirectory, +} from "./functions/buildWorker"; import { pagesBetaWarning } from "./utils"; import type { BundleResult } from "../bundle"; import type { @@ -217,46 +212,12 @@ export const Handler = async (args: PagesBuildArgs) => { */ if (workerScriptPath) { if (lstatSync(workerScriptPath).isDirectory()) { - const entrypoint = resolve(join(workerScriptPath, "index.js")); - - const traverseModuleGraphResult = await traverseModuleGraph( - { - file: entrypoint, - directory: resolve(workerScriptPath), - format: "modules", - moduleRoot: resolve(workerScriptPath), - }, - [ - { - type: "ESModule", - globs: ["**/*.js"], - }, - ] - ); - - const bundleResult = await buildRawWorker({ - workerScriptPath: entrypoint, - bundle: false, - outdir, - directory: buildOutputDirectory, - local: false, - sourcemap, - watch, - betaD1Shims: d1Databases, + bundle = await traverseAndBuildWorkerJSDirectory({ + workerJSDirectory: workerScriptPath, + buildOutputDirectory, + d1Databases, + nodejsCompat, }); - - bundle = { - modules: (traverseModuleGraphResult?.modules ?? - bundleResult?.modules) as BundleResult["modules"], - dependencies: (bundleResult?.dependencies ?? - traverseModuleGraphResult?.dependencies) as BundleResult["dependencies"], - resolvedEntryPointPath: (bundleResult?.resolvedEntryPointPath ?? - traverseModuleGraphResult?.resolvedEntryPointPath) as BundleResult["resolvedEntryPointPath"], - bundleType: (bundleResult?.bundleType ?? - traverseModuleGraphResult?.bundleType) as BundleResult["bundleType"], - stop: bundleResult?.stop, - sourceMapPath: bundleResult?.sourceMapPath, - }; } else { /** * `buildRawWorker` builds `_worker.js`, but doesn't give us the bundle diff --git a/packages/wrangler/src/pages/dev.ts b/packages/wrangler/src/pages/dev.ts index 9e3dd658ca85..d4dc3afe8077 100644 --- a/packages/wrangler/src/pages/dev.ts +++ b/packages/wrangler/src/pages/dev.ts @@ -545,7 +545,7 @@ export const Handler = async ({ r2: r2s.map((binding) => { return { binding: binding.toString(), bucket_name: "" }; }), - bundleEntrypoint: true, + processEntrypoint: true, moduleRoot: workerScriptPath, rules: usingWorkerDirectory ? [ diff --git a/packages/wrangler/src/pages/functions/buildWorker.ts b/packages/wrangler/src/pages/functions/buildWorker.ts index 7bb3de201c74..f12c77c49d42 100644 --- a/packages/wrangler/src/pages/functions/buildWorker.ts +++ b/packages/wrangler/src/pages/functions/buildWorker.ts @@ -7,7 +7,9 @@ import { bundleWorker } from "../../bundle"; import { FatalError } from "../../errors"; import { logger } from "../../logger"; import { getBasePath } from "../../paths"; +import traverseModuleGraph from "../../traverse-module-graph"; import { D1_BETA_PREFIX } from "../../worker"; +import type { BundleResult } from "../../bundle"; import type { Plugin } from "esbuild"; export type Options = { @@ -237,6 +239,58 @@ export function buildRawWorker({ ); } +export async function traverseAndBuildWorkerJSDirectory({ + workerJSDirectory, + buildOutputDirectory, + d1Databases, + nodejsCompat, +}: { + workerJSDirectory: string; + buildOutputDirectory: string; + d1Databases?: string[]; + nodejsCompat?: boolean; +}): Promise { + const entrypoint = resolve(join(workerJSDirectory, "index.js")); + + const traverseModuleGraphResult = await traverseModuleGraph( + { + file: entrypoint, + directory: resolve(workerJSDirectory), + format: "modules", + moduleRoot: resolve(workerJSDirectory), + }, + [ + { + type: "ESModule", + globs: ["**/*.js"], + }, + ] + ); + + const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`); + const bundleResult = await buildRawWorker({ + workerScriptPath: entrypoint, + bundle: false, + outfile, + directory: buildOutputDirectory, + local: false, + sourcemap: true, + watch: false, + onEnd: () => {}, + betaD1Shims: d1Databases, + nodejsCompat, + }); + + return { + modules: traverseModuleGraphResult.modules, + dependencies: bundleResult.dependencies, + resolvedEntryPointPath: bundleResult.resolvedEntryPointPath, + bundleType: bundleResult.bundleType, + stop: bundleResult.stop, + sourceMapPath: bundleResult.sourceMapPath, + }; +} + /** * Creates an esbuild plugin that can notify Wrangler (via the `onEnd()`) * when the build completes.