Skip to content

Commit d9bccf7

Browse files
petebacondarwinmrbbot
authored andcommitted
implement module finding in normal bundling if the find_additional_modules flag is on
1 parent 359e5ab commit d9bccf7

29 files changed

+732
-258
lines changed

.prettierignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ packages/wrangler/CHANGELOG.md
77
packages/jest-environment-wrangler/CHANGELOG.md
88
packages/wranglerjs-compat-webpack-plugin/lib
99
packages/wrangler-devtools/built-devtools
10+
packages/wrangler-devtools/.cipd
11+
packages/wrangler-devtools/depot
12+
packages/wrangler-devtools/devtools-frontend
1013
packages/edge-preview-authenticated-proxy/package.json
1114
packages/format-errors/package.json
1215
packages/**/dist/**
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "additional-modules",
3+
"version": "0.0.1",
4+
"private": true,
5+
"scripts": {
6+
"build": "wrangler deploy --dry-run --outdir=dist",
7+
"check:type": "tsc",
8+
"deploy": "wrangler deploy",
9+
"start": "wrangler dev",
10+
"test": "vitest run",
11+
"test:ci": "vitest run",
12+
"test:watch": "vitest",
13+
"type:tests": "tsc -p ./test/tsconfig.json"
14+
},
15+
"devDependencies": {
16+
"@cloudflare/workers-types": "^4.20230724.0",
17+
"wrangler": "*"
18+
}
19+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "bundled";
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default "dynamic";
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import dep from "./dep";
2+
import text from "./text.txt";
3+
4+
export default <ExportedHandler>{
5+
async fetch(request) {
6+
const url = new URL(request.url);
7+
if (url.pathname === "/dep") {
8+
return new Response(dep);
9+
}
10+
if (url.pathname === "/text") {
11+
return new Response(text);
12+
}
13+
if (url.pathname === "/dynamic") {
14+
return new Response((await import("./dynamic.js")).default);
15+
}
16+
if (url.pathname.startsWith("/lang/")) {
17+
const language = url.pathname.substring("/lang/".length);
18+
return new Response(
19+
(await import(`./lang/${language}.js`)).default.hello
20+
);
21+
}
22+
return new Response("Not Found", { status: 404 });
23+
},
24+
};
25+
26+
// addEventListener("fetch", () => {});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default { hello: "hello" };
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default { hello: "bonjour" };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.txt" {
2+
const value: string;
3+
export default value;
4+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import assert from "node:assert";
2+
import childProcess from "node:child_process";
3+
import { existsSync } from "node:fs";
4+
import fs from "node:fs/promises";
5+
import os from "node:os";
6+
import path from "node:path";
7+
import {
8+
runWranglerDev,
9+
wranglerEntryPath,
10+
} from "../../shared/src/run-wrangler-long-lived";
11+
import { describe, beforeAll, afterAll, expect, test } from "vitest";
12+
import { setTimeout } from "node:timers/promises";
13+
import { fetch } from "undici";
14+
15+
async function getTmpDir() {
16+
return fs.mkdtemp(path.join(os.tmpdir(), "wrangler-modules-"));
17+
}
18+
19+
type WranglerDev = Awaited<ReturnType<typeof runWranglerDev>>;
20+
function get(worker: WranglerDev, pathname: string) {
21+
const url = `http://${worker.ip}:${worker.port}${pathname}`;
22+
// Setting the `MF-Original-URL` header will make Miniflare think this is
23+
// coming from a `dispatchFetch()` request, meaning it won't return the pretty
24+
// error page, and we'll be able to parse errors as JSON.
25+
return fetch(url, { headers: { "MF-Original-URL": url } });
26+
}
27+
28+
async function retry<T>(closure: () => Promise<T>, max = 30): Promise<T> {
29+
for (let attempt = 1; attempt <= max; attempt++) {
30+
try {
31+
return await closure();
32+
} catch (e) {
33+
if (attempt === max) throw e;
34+
}
35+
await setTimeout(1_000);
36+
}
37+
assert.fail("Unreachable");
38+
}
39+
40+
describe("find_additional_modules dev", () => {
41+
let tmpDir: string;
42+
let worker: WranglerDev;
43+
44+
beforeAll(async () => {
45+
// Copy over files to a temporary directory as we'll be modifying them
46+
tmpDir = await getTmpDir();
47+
await fs.cp(
48+
path.resolve(__dirname, "..", "src"),
49+
path.join(tmpDir, "src"),
50+
{ recursive: true }
51+
);
52+
await fs.cp(
53+
path.resolve(__dirname, "..", "wrangler.toml"),
54+
path.join(tmpDir, "wrangler.toml")
55+
);
56+
57+
worker = await runWranglerDev(tmpDir, ["--port=0"]);
58+
});
59+
afterAll(async () => {
60+
await worker.stop();
61+
await fs.rm(tmpDir, { recursive: true, force: true });
62+
});
63+
64+
test("supports bundled modules", async () => {
65+
const res = await get(worker, "/dep");
66+
expect(await res.text()).toBe("bundled");
67+
});
68+
test("supports text modules", async () => {
69+
const res = await get(worker, "/text");
70+
expect(await res.text()).toBe("test\n");
71+
});
72+
test("supports dynamic imports", async () => {
73+
const res = await get(worker, "/dynamic");
74+
expect(await res.text()).toBe("dynamic");
75+
});
76+
test("supports variable dynamic imports", async () => {
77+
const res = await get(worker, "/lang/en");
78+
expect(await res.text()).toBe("hello");
79+
});
80+
81+
test("watches additional modules", async () => {
82+
const srcDir = path.join(tmpDir, "src");
83+
84+
// Update dynamically imported file
85+
await fs.writeFile(
86+
path.join(srcDir, "dynamic.js"),
87+
'export default "new dynamic";'
88+
);
89+
await retry(async () => {
90+
const res = await get(worker, "/dynamic");
91+
assert.strictEqual(await res.text(), "new dynamic");
92+
});
93+
94+
// Delete dynamically imported file
95+
await fs.rm(path.join(srcDir, "lang", "en.js"));
96+
const res = await retry(async () => {
97+
const res = await get(worker, "/lang/en");
98+
assert.strictEqual(res.status, 500);
99+
return res;
100+
});
101+
const error = (await res.json()) as { message?: string };
102+
expect(error.message).toBe('No such module "lang/en.js".');
103+
104+
// Create new dynamically imported file in new directory
105+
await fs.mkdir(path.join(srcDir, "lang", "en"));
106+
await fs.writeFile(
107+
path.join(srcDir, "lang", "en", "us.js"),
108+
'export default { hello: "hey" };'
109+
);
110+
await retry(async () => {
111+
const res = await get(worker, "/lang/en/us");
112+
assert.strictEqual(await res.text(), "hey");
113+
});
114+
115+
// Update newly created file
116+
await fs.writeFile(
117+
path.join(srcDir, "lang", "en", "us.js"),
118+
'export default { hello: "bye" };'
119+
);
120+
await retry(async () => {
121+
const res = await get(worker, "/lang/en/us");
122+
assert.strictEqual(await res.text(), "bye");
123+
});
124+
});
125+
});
126+
127+
function build(cwd: string, outDir: string) {
128+
return childProcess.spawnSync(
129+
process.execPath,
130+
[wranglerEntryPath, "deploy", "--dry-run", `--outdir=${outDir}`],
131+
{ cwd }
132+
);
133+
}
134+
135+
describe("find_additional_modules deploy", () => {
136+
let tmpDir: string;
137+
beforeAll(async () => {
138+
tmpDir = await getTmpDir();
139+
});
140+
afterAll(async () => {
141+
await fs.rm(tmpDir, { recursive: true, force: true });
142+
});
143+
144+
test("doesn't bundle additional modules", async () => {
145+
const outDir = path.join(tmpDir, "out");
146+
const result = await build(path.resolve(__dirname, ".."), outDir);
147+
expect(result.status).toBe(0);
148+
149+
// Check additional modules marked external, but other dependencies bundled
150+
const bundledEntryPath = path.join(outDir, "index.js");
151+
const bundledEntry = await fs.readFile(bundledEntryPath, "utf8");
152+
expect(bundledEntry).toMatchInlineSnapshot(`
153+
"// src/dep.ts
154+
var dep_default = \\"bundled\\";
155+
156+
// src/index.ts
157+
import text from \\"./text.txt\\";
158+
var src_default = {
159+
async fetch(request) {
160+
const url = new URL(request.url);
161+
if (url.pathname === \\"/dep\\") {
162+
return new Response(dep_default);
163+
}
164+
if (url.pathname === \\"/text\\") {
165+
return new Response(text);
166+
}
167+
if (url.pathname === \\"/dynamic\\") {
168+
return new Response((await import(\\"./dynamic.js\\")).default);
169+
}
170+
if (url.pathname.startsWith(\\"/lang/\\")) {
171+
const language = url.pathname.substring(\\"/lang/\\".length);
172+
return new Response(
173+
(await import(\`./lang/\${language}.js\`)).default.hello
174+
);
175+
}
176+
return new Response(\\"Not Found\\", { status: 404 });
177+
}
178+
};
179+
export {
180+
src_default as default
181+
};
182+
//# sourceMappingURL=index.js.map
183+
"
184+
`);
185+
186+
// Check additional modules included in output
187+
expect(existsSync(path.join(outDir, "text.txt"))).toBe(true);
188+
expect(existsSync(path.join(outDir, "dynamic.js"))).toBe(true);
189+
expect(existsSync(path.join(outDir, "lang", "en.js"))).toBe(true);
190+
expect(existsSync(path.join(outDir, "lang", "fr.js"))).toBe(true);
191+
});
192+
193+
test("fails with service worker entrypoint", async () => {
194+
// Write basic service worker with `find_additional_modules` enabled
195+
const serviceWorkerDir = path.join(tmpDir, "service-worker");
196+
await fs.mkdir(serviceWorkerDir, { recursive: true });
197+
await fs.writeFile(
198+
path.join(serviceWorkerDir, "index.js"),
199+
"addEventListener('fetch', (e) => e.respondWith(new Response()))"
200+
);
201+
await fs.writeFile(
202+
path.join(serviceWorkerDir, "wrangler.toml"),
203+
[
204+
'name="service-worker-test"',
205+
'main = "index.js"',
206+
'compatibility_date = "2023-08-01"',
207+
"find_additional_modules = true",
208+
].join("\n")
209+
);
210+
211+
// Try build, and check fails
212+
const serviceWorkerOutDir = path.join(tmpDir, "service-worker-out");
213+
const result = await build(serviceWorkerDir, serviceWorkerOutDir);
214+
expect(result.status).toBe(1);
215+
expect(result.stderr.toString()).toContain(
216+
"`find_additional_modules` can only be used with an ES module entrypoint."
217+
);
218+
});
219+
});

0 commit comments

Comments
 (0)