70bf21e064
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
Handle Changesets / Handle Changesets (push) Has been cancelled
Semgrep OSS scan / semgrep-oss (push) Has been cancelled
208 lines
6.3 KiB
TypeScript
208 lines
6.3 KiB
TypeScript
import childProcess from "node:child_process";
|
|
import { existsSync } from "node:fs";
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { setTimeout } from "node:timers/promises";
|
|
import { removeDir } from "@fixture/shared/src/fs-helpers";
|
|
import { fetch } from "undici";
|
|
import { afterAll, assert, beforeAll, describe, test } from "vitest";
|
|
import {
|
|
runWranglerDev,
|
|
wranglerEntryPath,
|
|
} from "../../shared/src/run-wrangler-long-lived";
|
|
|
|
async function getTmpDir() {
|
|
return fs.mkdtemp(path.join(os.tmpdir(), "wrangler-modules-"));
|
|
}
|
|
|
|
type WranglerDev = Awaited<ReturnType<typeof runWranglerDev>>;
|
|
function get(worker: WranglerDev, pathname: string) {
|
|
const url = `http://${worker.ip}:${worker.port}${pathname}`;
|
|
// Disable Miniflare's pretty error page, so we can parse errors as JSON
|
|
return fetch(url, { headers: { "MF-Disable-Pretty-Error": "true" } });
|
|
}
|
|
|
|
async function retry<T>(closure: () => Promise<T>, max = 30): Promise<T> {
|
|
for (let attempt = 1; attempt <= max; attempt++) {
|
|
try {
|
|
return await closure();
|
|
} catch (e) {
|
|
if (attempt === max) throw e;
|
|
}
|
|
await setTimeout(1_000);
|
|
}
|
|
assert.fail("Unreachable");
|
|
}
|
|
|
|
describe("wildcard imports: dev", () => {
|
|
let tmpDir: string;
|
|
let worker: WranglerDev;
|
|
|
|
beforeAll(async () => {
|
|
// Copy over files to a temporary directory as we'll be modifying them
|
|
tmpDir = await getTmpDir();
|
|
await fs.cp(
|
|
path.resolve(__dirname, "..", "src"),
|
|
path.join(tmpDir, "src"),
|
|
{ recursive: true }
|
|
);
|
|
await fs.cp(
|
|
path.resolve(__dirname, "..", "wrangler.jsonc"),
|
|
path.join(tmpDir, "wrangler.jsonc")
|
|
);
|
|
|
|
worker = await runWranglerDev(tmpDir, ["--port=0", "--inspector-port=0"]);
|
|
});
|
|
afterAll(async () => {
|
|
await worker.stop();
|
|
removeDir(tmpDir, { fireAndForget: true });
|
|
});
|
|
|
|
test("supports bundled modules", async ({ expect }) => {
|
|
const res = await get(worker, "/dep");
|
|
expect(await res.text()).toBe("bundled");
|
|
});
|
|
test("supports text modules", async ({ expect }) => {
|
|
const res = await get(worker, "/text");
|
|
expect(await res.text()).toBe("test\n");
|
|
});
|
|
test("supports dynamic imports", async ({ expect }) => {
|
|
const res = await get(worker, "/dynamic");
|
|
expect(await res.text()).toBe("dynamic");
|
|
});
|
|
test("supports commonjs lazy imports", async ({ expect }) => {
|
|
const res = await get(worker, "/common");
|
|
expect(await res.text()).toBe("common");
|
|
});
|
|
test("supports variable dynamic imports", async ({ expect }) => {
|
|
const res = await get(worker, "/lang/en");
|
|
expect(await res.text()).toBe("hello");
|
|
});
|
|
|
|
test("watches wildcard modules", async ({ expect }) => {
|
|
const srcDir = path.join(tmpDir, "src");
|
|
|
|
// Update dynamically imported file
|
|
await fs.writeFile(
|
|
path.join(srcDir, "dynamic.js"),
|
|
'export default "new dynamic";'
|
|
);
|
|
await retry(async () => {
|
|
const res = await get(worker, "/dynamic");
|
|
assert.strictEqual(await res.text(), "new dynamic");
|
|
});
|
|
|
|
// Delete dynamically imported file
|
|
await fs.rm(path.join(srcDir, "lang", "en.js"));
|
|
const res = await retry(async () => {
|
|
const res = await get(worker, "/lang/en");
|
|
assert.strictEqual(res.status, 500);
|
|
return res;
|
|
});
|
|
const error = (await res.json()) as { message?: string };
|
|
expect(error.message).toBe("Module not found in bundle: ./lang/en.js");
|
|
|
|
// Create new dynamically imported file in new directory
|
|
await fs.mkdir(path.join(srcDir, "lang", "en"));
|
|
await fs.writeFile(
|
|
path.join(srcDir, "lang", "en", "us.js"),
|
|
'export default { hello: "hey" };'
|
|
);
|
|
await retry(async () => {
|
|
const res = await get(worker, "/lang/en/us");
|
|
assert.strictEqual(await res.text(), "hey");
|
|
});
|
|
|
|
// Update newly created file
|
|
await fs.writeFile(
|
|
path.join(srcDir, "lang", "en", "us.js"),
|
|
'export default { hello: "bye" };'
|
|
);
|
|
await retry(async () => {
|
|
const res = await get(worker, "/lang/en/us");
|
|
assert.strictEqual(await res.text(), "bye");
|
|
});
|
|
});
|
|
});
|
|
|
|
function build(cwd: string, outDir: string) {
|
|
return childProcess.spawnSync(
|
|
process.execPath,
|
|
[wranglerEntryPath, "deploy", "--dry-run", `--outdir=${outDir}`],
|
|
{ cwd }
|
|
);
|
|
}
|
|
|
|
describe("wildcard imports: deploy", () => {
|
|
let tmpDir: string;
|
|
beforeAll(async () => {
|
|
tmpDir = await getTmpDir();
|
|
});
|
|
afterAll(() => {
|
|
removeDir(tmpDir, { fireAndForget: true });
|
|
});
|
|
|
|
test("bundles wildcard modules", async ({ expect }) => {
|
|
const outDir = path.join(tmpDir, "out");
|
|
const result = build(path.resolve(__dirname, ".."), outDir);
|
|
expect(result.status).toBe(0);
|
|
|
|
// Check additional modules marked external, but other dependencies bundled
|
|
const bundledEntryPath = path.join(outDir, "index.js");
|
|
const bundledEntry = await fs.readFile(bundledEntryPath, "utf8");
|
|
const imports = [
|
|
`import text from "./4e1243bd22c66e76c2ba9eddc1f91394e57f9f83-text.txt";`,
|
|
`// src/common.cjs`,
|
|
`// src/lang/en.js`,
|
|
`// src/lang/fr.js`,
|
|
`// src/dynamic.js`,
|
|
`// import("./lang/**/*.js") in src/index.ts`,
|
|
];
|
|
for (const importStatement of imports) {
|
|
expect(bundledEntry).toContain(importStatement);
|
|
}
|
|
|
|
// Check additional modules included in output
|
|
expect(
|
|
existsSync(
|
|
path.join(outDir, "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83-text.txt")
|
|
)
|
|
).toBe(true);
|
|
});
|
|
|
|
test("fails with service worker entrypoint", async ({ expect }) => {
|
|
const serviceWorkerDir = path.join(tmpDir, "service-worker");
|
|
await fs.mkdir(serviceWorkerDir, { recursive: true });
|
|
await fs.writeFile(
|
|
path.join(serviceWorkerDir, "index.js"),
|
|
`
|
|
async function handleRequest(e) {
|
|
const url = new URL(e.request.url);
|
|
if (url.pathname === "/") {
|
|
return new Response("Hello World");
|
|
} else if(url.pathname.startsWith("/lang/")) {
|
|
const language = url.pathname.substring("/lang/".length);
|
|
return new Response((await import(\`./lang/\${language}.js\`)).default.hello);
|
|
}
|
|
return new Response("Not Found", { status: 404 });
|
|
}
|
|
|
|
addEventListener('fetch', (e) => e.respondWith(handleRequest(e)))`
|
|
);
|
|
await fs.writeFile(
|
|
path.join(serviceWorkerDir, "wrangler.toml"),
|
|
[
|
|
'name="service-worker-test"',
|
|
'main = "index.js"',
|
|
'compatibility_date = "2024-09-09"',
|
|
].join("\n")
|
|
);
|
|
|
|
// Try build, and check fails
|
|
const serviceWorkerOutDir = path.join(tmpDir, "service-worker-out");
|
|
const result = build(serviceWorkerDir, serviceWorkerOutDir);
|
|
expect(result.status).toBe(1);
|
|
});
|
|
});
|