Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

_worker.js/ directory support in Pages #2966

Merged
merged 2 commits into from
May 5, 2023
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
5 changes: 5 additions & 0 deletions .changeset/cuddly-rules-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": minor
---

feat: Add support for the undocumented `_worker.js/` directory in Pages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be documented?

21 changes: 2 additions & 19 deletions fixtures/local-mode-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions fixtures/pages-workerjs-directory/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
Binary file not shown.
23 changes: 23 additions & 0 deletions fixtures/pages-workerjs-directory/public/_worker.js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import staticMod from "./static.js";
import add from "./add.wasm";

export default {
GregBrimble marked this conversation as resolved.
Show resolved Hide resolved
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);
}

return env.ASSETS.fetch(request);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "test";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default "static";
1 change: 1 addition & 0 deletions fixtures/pages-workerjs-directory/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Hello, world!</h1>
45 changes: 45 additions & 0 deletions fixtures/pages-workerjs-directory/tests/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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";

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}/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"');
});
});
12 changes: 12 additions & 0 deletions fixtures/pages-workerjs-directory/tsconfig.json
Original file line number Diff line number Diff line change
@@ -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"]
}
70 changes: 70 additions & 0 deletions packages/wrangler/src/__tests__/pages/functions-build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
};`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.trim()

);
writeFileSync(
"public/_worker.js/cat.js",
`
export const cat = "cat";`
);

await runWrangler(`pages functions build --outfile=public/_worker.bundle`);

expect(existsSync("public/_worker.bundle")).toBe(true);
expect(std.out).toMatchInlineSnapshot(`
"🚧 'wrangler pages <command>' 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"],
[/bundledWorker-0.[0-9]*.mjs/g, "bundledWorker-0.test.mjs"],
[/bundledWorker-0.[0-9]*.map/g, "bundledWorker-0.test.map"],
]
);

expect(workerBundleWithConstantData).toMatchInlineSnapshot(`
"------formdata-undici-0.test
Content-Disposition: form-data; name=\\"metadata\\"

{\\"main_module\\":\\"bundledWorker-0.test.mjs\\"}
------formdata-undici-0.test
Content-Disposition: form-data; name=\\"bundledWorker-0.test.mjs\\"; filename=\\"bundledWorker-0.test.mjs\\"
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
};
//# sourceMappingURL=bundledWorker-0.test.mjs.map

------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(`""`);
});
});
5 changes: 5 additions & 0 deletions packages/wrangler/src/api/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -42,6 +43,9 @@ export interface UnstableDevOptions {
bucket_name: string;
preview_bucket_name?: string;
}[];
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"]
inspect?: boolean;
local?: boolean;
Expand Down Expand Up @@ -150,6 +154,7 @@ export async function unstable_dev(
},
config: options?.config,
env: options?.env,
processEntrypoint: !!options?.processEntrypoint,
bundle: options?.bundle,
compatibilityDate: options?.compatibilityDate,
compatibilityFlags: options?.compatibilityFlags,
Expand Down
40 changes: 28 additions & 12 deletions packages/wrangler/src/api/pages/publish.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -14,6 +14,7 @@ import {
import {
buildRawWorker,
checkRawWorker,
traverseAndBuildWorkerJSDirectory,
} from "../../pages/functions/buildWorker";
import { validateRoutes } from "../../pages/functions/routes-validation";
import { upload } from "../../pages/upload";
Expand Down Expand Up @@ -65,7 +66,7 @@ interface PagesPublishOptions {

/**
* Whether to run bundling on `_worker.js` before deploying.
* Default: false
* Default: true
*/
bundle?: boolean;

Expand Down Expand Up @@ -95,9 +96,12 @@ export async function publish({
_redirects: string | undefined,
_routesGenerated: string | undefined,
_routesCustom: string | undefined,
_workerJSIsDirectory = false,
_workerJS: string | undefined;

const workerScriptPath = resolvePath(directory, "_worker.js");
bundle = bundle ?? true;

const _workerPath = resolvePath(directory, "_worker.js");

try {
_headers = readFileSync(join(directory, "_headers"), "utf-8");
Expand All @@ -116,7 +120,10 @@ export async function publish({
} catch {}

try {
_workerJS = readFileSync(workerScriptPath, "utf-8");
_workerJSIsDirectory = lstatSync(_workerPath).isDirectory();
if (!_workerJSIsDirectory) {
_workerJS = readFileSync(_workerPath, "utf-8");
}
} catch {}

// Grab the bindings from the API, we need these for shims and other such hacky inserts
Expand Down Expand Up @@ -240,16 +247,23 @@ 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 (_workerJS) {
if (_workerJSIsDirectory) {
workerBundle = await traverseAndBuildWorkerJSDirectory({
workerJSDirectory: _workerPath,
buildOutputDirectory: directory,
d1Databases,
nodejsCompat,
});
} 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,
Expand All @@ -258,17 +272,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 || _workerJSIsDirectory) {
const workerBundleContents = await createUploadWorkerBundleContents(
workerBundle as BundleResult
GregBrimble marked this conversation as resolved.
Show resolved Hide resolved
);
Expand Down Expand Up @@ -302,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
);
Expand Down
7 changes: 5 additions & 2 deletions packages/wrangler/src/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ 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;
betaD1Shims?: string[];
Expand Down Expand Up @@ -149,6 +151,7 @@ export async function bundleWorker(
}
): Promise<BundleResult> {
const {
bundle = true,
serveAssetsFromWorker,
betaD1Shims,
doBindings,
Expand Down Expand Up @@ -350,7 +353,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,
Expand All @@ -362,7 +365,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
Expand Down
Loading