History

Serve the worker-bundler artifact from static assets (#1378)

The publish bundler artifact, worker-bundler source and esbuild wasm, is fetched from static assets at first use instead of being inlined into the worker script, returning the script to its previous size. Cold-start latency regressed while the 19MB artifact chunk was part of the script blob.

Rhys Sullivan committed 4 months ago

Commit 56b60bf1 parent 63e19e78 files · +175 −18Browse files

Showing the changes to apps/cloud/scripts (2 of 8 files). Show all

Unified · Split

2 files
  1. apps/cloud/scripts/assert-worker-bundler-assets.mjs +54 −0
  2. apps/cloud/scripts/build.mjs +1 −0
apps/cloud/scripts/assert-worker-bundler-assets.mjs+54−0
@@ -0,0 +1,54 @@
1+import { readdirSync, readFileSync, statSync } from "node:fs";
2+import { join } from "node:path";
3+
4+const appRoot = new URL("..", import.meta.url);
5+const serverAssetsDir = new URL("dist/server/assets/", appRoot);
6+const clientAssetsDir = new URL("dist/client/assets/", appRoot);
7+const largeFileLimit = 3 * 1024 * 1024;
8+const sourceAssetMinimum = 512 * 1024;
9+
10+const fail = (message) => {
11+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build assertion script reports a failed artifact contract
12+ throw new Error(message);
13+};
14+
15+const fileSize = (dir, name) => statSync(join(dir.pathname, name)).size;
16+
17+const serverAssets = readdirSync(serverAssetsDir);
18+for (const name of serverAssets) {
19+ const path = join(serverAssetsDir.pathname, name);
20+ const size = statSync(path).size;
21+ if (size <= largeFileLimit) continue;
22+ if (name.includes("worker-bundler")) {
23+ fail(`server assets include oversized worker-bundler chunk ${name}`);
24+ }
25+ const sample = readFileSync(path, "utf8");
26+ if (sample.includes("worker-bundler")) {
27+ fail(`server assets include worker-bundler content in oversized chunk ${name}`);
28+ }
29+}
30+
31+const clientAssets = readdirSync(clientAssetsDir);
32+const source = clientAssets.find((name) => /^worker-bundler-source-[a-f0-9]{12}\.js$/.test(name));
33+const wasm = clientAssets.find((name) =>
34+ /^worker-bundler-esbuild-wasm-[a-f0-9]{12}\.wasm$/.test(name),
35+);
36+
37+if (source === undefined) fail("missing worker-bundler source asset");
38+if (wasm === undefined) fail("missing worker-bundler esbuild wasm asset");
39+
40+const sourceSize = fileSize(clientAssetsDir, source);
41+const wasmSize = fileSize(clientAssetsDir, wasm);
42+if (sourceSize <= sourceAssetMinimum) {
43+ fail(`worker-bundler source asset is unexpectedly small: ${source}`);
44+}
45+if (wasmSize <= largeFileLimit) fail(`worker-bundler wasm asset is unexpectedly small: ${wasm}`);
46+
47+const wasmMagic = readFileSync(join(clientAssetsDir.pathname, wasm)).subarray(0, 4);
48+if (!wasmMagic.equals(Buffer.from([0x00, 0x61, 0x73, 0x6d]))) {
49+ fail(`worker-bundler wasm asset does not start with wasm magic bytes: ${wasm}`);
50+}
51+
52+console.log(
53+ `[assert-worker-bundler-assets] ${source} (${sourceSize} bytes), ${wasm} (${wasmSize} bytes)`,
54+);
apps/cloud/scripts/build.mjs+1−0
@@ -24,6 +24,7 @@ const steps = [ · 23 unmodified lines
2424 // imports, so they must be built before vite starts.
2525 "turbo run build --filter @executor-js/vite-plugin --filter @executor-js/react --filter @executor-js/plugin-apps",
2626 "vite build",
27+ "node scripts/assert-worker-bundler-assets.mjs",
2728 ];
2829
2930 for (const step of steps) {