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.
Unified · Split
| @@ -75,6 +75,9 @@ interface CloudPluginDeps { · 74 unmodified lines | |||
| 75 | readonly globalOutbound?: null; | 75 | readonly globalOutbound?: null; |
| 76 | }) => { readonly getEntrypoint: () => unknown }; | 76 | }) => { readonly getEntrypoint: () => unknown }; |
| 77 | }; | 77 | }; |
| 78 | readonly workerAssets?: { | ||
| 79 | readonly fetch: (request: Request) => Promise<Response>; | ||
| 80 | }; | ||
| 78 | } | 81 | } |
| 79 | 82 | ||
| 80 | const base64ToArrayBuffer = (value: string): ArrayBuffer => { | 83 | const base64ToArrayBuffer = (value: string): ArrayBuffer => { |
| @@ -82,6 +85,20 @@ const base64ToArrayBuffer = (value: string): ArrayBuffer => { · 1 unmodified line | |||
| 82 | return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); | 85 | return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); |
| 83 | }; | 86 | }; |
| 84 | 87 | ||
| 88 | const assetRequest = (path: string): Request => new Request(new URL(path, "https://assets.local")); | ||
| 89 | |||
| 90 | const fetchAsset = async ( | ||
| 91 | assets: { readonly fetch: (request: Request) => Promise<Response> }, | ||
| 92 | path: string, | ||
| 93 | ): Promise<Response> => { | ||
| 94 | const response = await assets.fetch(assetRequest(path)); | ||
| 95 | if (!response.ok) { | ||
| 96 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: worker artifact asset fetch must reject the dynamic bundler setup | ||
| 97 | throw new Error(`failed to fetch worker-bundler asset ${path}: ${response.status}`); | ||
| 98 | } | ||
| 99 | return response; | ||
| 100 | }; | ||
| 101 | |||
| 85 | export default defineExecutorConfig({ | 102 | export default defineExecutorConfig({ |
| 86 | plugins: ({ | 103 | plugins: ({ |
| 87 | workosCredentials, | 104 | workosCredentials, |
| @@ -89,6 +106,7 @@ export default defineExecutorConfig({ · 1 unmodified line | |||
| 89 | activeToolkitSlug, | 106 | activeToolkitSlug, |
| 90 | allowLocalNetwork, | 107 | allowLocalNetwork, |
| 91 | workerLoader, | 108 | workerLoader, |
| 109 | workerAssets, | ||
| 92 | }: CloudPluginDeps = {}) => | 110 | }: CloudPluginDeps = {}) => |
| 93 | [ | 111 | [ |
| 94 | openApiHttpPlugin({ | 112 | openApiHttpPlugin({ |
| @@ -107,9 +125,27 @@ export default defineExecutorConfig({ · 12 unmodified lines | |||
| 107 | loader: workerLoader, | 125 | loader: workerLoader, |
| 108 | artifact: async () => { | 126 | artifact: async () => { |
| 109 | const artifact = await import("virtual:executor/worker-bundler-artifact"); | 127 | const artifact = await import("virtual:executor/worker-bundler-artifact"); |
| 128 | if (artifact.source !== undefined && artifact.wasmBase64 !== undefined) { | ||
| 129 | return { | ||
| 130 | source: artifact.source, | ||
| 131 | wasm: base64ToArrayBuffer(artifact.wasmBase64), | ||
| 132 | }; | ||
| 133 | } | ||
| 134 | if (workerAssets === undefined) { | ||
| 135 | // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: worker artifact asset binding is required for production bundler setup | ||
| 136 | throw new Error("worker-bundler artifact assets binding is unavailable"); | ||
| 137 | } | ||
| 138 | const [source, wasm] = await Promise.all([ | ||
| 139 | fetchAsset(workerAssets, artifact.sourcePath).then((response) => | ||
| 140 | response.text(), | ||
| 141 | ), | ||
| 142 | fetchAsset(workerAssets, artifact.wasmPath).then((response) => | ||
| 143 | response.arrayBuffer(), | ||
| 144 | ), | ||
| 145 | ]); | ||
| 110 | return { | 146 | return { |
| 111 | source: artifact.source, | 147 | source, |
| 112 | wasm: base64ToArrayBuffer(artifact.wasmBase64), | 148 | wasm, |
| 113 | }; | 149 | }; |
| 114 | }, | 150 | }, |
| 115 | }), | 151 | }), |
| @@ -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 | ); | ||
| @@ -24,6 +24,7 @@ const steps = [ · 23 unmodified lines | |||
| 24 | // imports, so they must be built before vite starts. | 24 | // imports, so they must be built before vite starts. |
| 25 | "turbo run build --filter @executor-js/vite-plugin --filter @executor-js/react --filter @executor-js/plugin-apps", | 25 | "turbo run build --filter @executor-js/vite-plugin --filter @executor-js/react --filter @executor-js/plugin-apps", |
| 26 | "vite build", | 26 | "vite build", |
| 27 | "node scripts/assert-worker-bundler-assets.mjs", | ||
| 27 | ]; | 28 | ]; |
| 28 | 29 | ||
| 29 | for (const step of steps) { | 30 | for (const step of steps) { |
| @@ -64,6 +64,7 @@ const cloudPluginFactory = executorConfig.plugins as (deps: { · 63 unmodified lines | |||
| 64 | readonly activeToolkitSlug?: string; | 64 | readonly activeToolkitSlug?: string; |
| 65 | readonly allowLocalNetwork?: boolean; | 65 | readonly allowLocalNetwork?: boolean; |
| 66 | readonly workerLoader?: WorkerLoader; | 66 | readonly workerLoader?: WorkerLoader; |
| 67 | readonly workerAssets?: { readonly fetch: (request: Request) => Promise<Response> }; | ||
| 67 | }) => readonly AnyPlugin[]; | 68 | }) => readonly AnyPlugin[]; |
| 68 | 69 | ||
| 69 | // Fresh plugin instances per request, carrying the Worker env's WorkOS Vault | 70 | // Fresh plugin instances per request, carrying the Worker env's WorkOS Vault |
| @@ -80,6 +81,7 @@ export const CloudPluginsProvider: Layer.Layer<PluginsProvider> = Layer.succeed( · 10 unmodified lines | |||
| 80 | context?.mcpResource?.kind === "toolkit" ? context.mcpResource.slug : undefined, | 81 | context?.mcpResource?.kind === "toolkit" ? context.mcpResource.slug : undefined, |
| 81 | allowLocalNetwork: env.ALLOW_LOCAL_NETWORK === "true", | 82 | allowLocalNetwork: env.ALLOW_LOCAL_NETWORK === "true", |
| 82 | workerLoader: env.LOADER, | 83 | workerLoader: env.LOADER, |
| 84 | workerAssets: env.ASSETS, | ||
| 83 | }), | 85 | }), |
| 84 | }); | 86 | }); |
| 85 | 87 | ||
| @@ -22,6 +22,10 @@ declare global { · 21 unmodified lines | |||
| 22 | DATABASE_URL?: string; | 22 | DATABASE_URL?: string; |
| 23 | EXECUTOR_DIRECT_DATABASE_URL?: string; | 23 | EXECUTOR_DIRECT_DATABASE_URL?: string; |
| 24 | 24 | ||
| 25 | // Static asset binding emitted by @cloudflare/vite-plugin in | ||
| 26 | // dist/server/wrangler.json as assets.directory = "../client". | ||
| 27 | ASSETS: { readonly fetch: (request: Request) => Promise<Response> }; | ||
| 28 | |||
| 25 | // Plugin blob seam backend (wrangler.jsonc `r2_buckets`). Declared here | 29 | // Plugin blob seam backend (wrangler.jsonc `r2_buckets`). Declared here |
| 26 | // (optional) rather than regenerating worker-configuration.d.ts: test | 30 | // (optional) rather than regenerating worker-configuration.d.ts: test |
| 27 | // workers and older local setups run without the binding, and the db | 31 | // workers and older local setups run without the binding, and the db |
| @@ -1,4 +1,6 @@ | |||
| 1 | declare module "virtual:executor/worker-bundler-artifact" { | 1 | declare module "virtual:executor/worker-bundler-artifact" { |
| 2 | export const source: string; | 2 | export const sourcePath: string; |
| 3 | export const wasmBase64: string; | 3 | export const wasmPath: string; |
| 4 | export const source: string | undefined; | ||
| 5 | export const wasmBase64: string | undefined; | ||
| 4 | } | 6 | } |
| @@ -4,6 +4,12 @@ · 3 unmodified lines | |||
| 4 | "compatibility_date": "2025-04-01", | 4 | "compatibility_date": "2025-04-01", |
| 5 | "compatibility_flags": ["nodejs_compat"], | 5 | "compatibility_flags": ["nodejs_compat"], |
| 6 | "main": "src/server.ts", | 6 | "main": "src/server.ts", |
| 7 | // The vite plugin fills in assets.directory (dist/client); the binding must | ||
| 8 | // be declared here or env.ASSETS does not exist at runtime. The apps plugin | ||
| 9 | // fetches the worker-bundler artifact through it on first sync. | ||
| 10 | "assets": { | ||
| 11 | "binding": "ASSETS", | ||
| 12 | }, | ||
| 7 | "routes": [ | 13 | "routes": [ |
| 8 | { | 14 | { |
| 9 | "pattern": "executor.sh", | 15 | "pattern": "executor.sh", |
| @@ -1,22 +1,74 @@ | |||
| 1 | import { createHash } from "node:crypto"; | ||
| 2 | import { mkdir, writeFile } from "node:fs/promises"; | ||
| 3 | import { join } from "node:path"; | ||
| 1 | import type { Plugin } from "vite"; | 4 | import type { Plugin } from "vite"; |
| 2 | 5 | ||
| 3 | import { bundledWorkerBundler } from "./pipeline/worker-bundler-artifact"; | 6 | import { bundledWorkerBundler } from "./pipeline/worker-bundler-artifact"; |
| 4 | 7 | ||
| 5 | const virtualId = "virtual:executor/worker-bundler-artifact"; | 8 | const virtualId = "virtual:executor/worker-bundler-artifact"; |
| 6 | const resolvedVirtualId = `\0${virtualId}`; | 9 | const resolvedVirtualId = `\0${virtualId}`; |
| 10 | const clientAssetsDir = "dist/client/assets"; | ||
| 7 | 11 | ||
| 8 | export const workerBundlerArtifact = (): Plugin => ({ | 12 | const digest = (value: string | Uint8Array): string => |
| 9 | name: "executor-worker-bundler-artifact", | 13 | createHash("sha256").update(value).digest("hex").slice(0, 12); |
| 10 | resolveId(id) { | 14 | |
| 11 | return id === virtualId ? resolvedVirtualId : null; | 15 | const artifactPaths = (artifact: { |
| 12 | }, | 16 | readonly source: string; |
| 13 | async load(id) { | 17 | readonly wasm: Uint8Array; |
| 14 | if (id !== resolvedVirtualId) return null; | 18 | }): { |
| 15 | const artifact = await bundledWorkerBundler(); | 19 | readonly sourcePath: string; |
| 16 | return [ | 20 | readonly wasmPath: string; |
| 17 | `export const source = ${JSON.stringify(artifact.source)};`, | 21 | } => ({ |
| 18 | `export const wasmBase64 = ${JSON.stringify(Buffer.from(artifact.wasm).toString("base64"))};`, | 22 | sourcePath: `/assets/worker-bundler-source-${digest(artifact.source)}.js`, |
| 19 | "export default { source, wasmBase64 };", | 23 | wasmPath: `/assets/worker-bundler-esbuild-wasm-${digest(artifact.wasm)}.wasm`, |
| 20 | ].join("\n"); | ||
| 21 | }, | ||
| 22 | }); | 24 | }); |
| 25 | |||
| 26 | export const workerBundlerArtifact = (): Plugin => { | ||
| 27 | let command: "build" | "serve" = "build"; | ||
| 28 | |||
| 29 | return { | ||
| 30 | name: "executor-worker-bundler-artifact", | ||
| 31 | configResolved(config) { | ||
| 32 | command = config.command; | ||
| 33 | }, | ||
| 34 | resolveId(id) { | ||
| 35 | return id === virtualId ? resolvedVirtualId : null; | ||
| 36 | }, | ||
| 37 | async load(id) { | ||
| 38 | if (id !== resolvedVirtualId) return null; | ||
| 39 | const artifact = await bundledWorkerBundler(); | ||
| 40 | const paths = artifactPaths(artifact); | ||
| 41 | const output = [ | ||
| 42 | `export const sourcePath = ${JSON.stringify(paths.sourcePath)};`, | ||
| 43 | `export const wasmPath = ${JSON.stringify(paths.wasmPath)};`, | ||
| 44 | ]; | ||
| 45 | |||
| 46 | if (command === "serve") { | ||
| 47 | output.push( | ||
| 48 | `export const source = ${JSON.stringify(artifact.source)};`, | ||
| 49 | `export const wasmBase64 = ${JSON.stringify(Buffer.from(artifact.wasm).toString("base64"))};`, | ||
| 50 | "export default { sourcePath, wasmPath, source, wasmBase64 };", | ||
| 51 | ); | ||
| 52 | } else { | ||
| 53 | output.push( | ||
| 54 | "export const source = undefined;", | ||
| 55 | "export const wasmBase64 = undefined;", | ||
| 56 | "export default { sourcePath, wasmPath, source, wasmBase64 };", | ||
| 57 | ); | ||
| 58 | } | ||
| 59 | |||
| 60 | return output.join("\n"); | ||
| 61 | }, | ||
| 62 | async closeBundle() { | ||
| 63 | if (command !== "build") return; | ||
| 64 | const artifact = await bundledWorkerBundler(); | ||
| 65 | const paths = artifactPaths(artifact); | ||
| 66 | const outputDir = join(process.cwd(), clientAssetsDir); | ||
| 67 | await mkdir(outputDir, { recursive: true }); | ||
| 68 | await Promise.all([ | ||
| 69 | writeFile(join(process.cwd(), "dist/client", paths.sourcePath), artifact.source), | ||
| 70 | writeFile(join(process.cwd(), "dist/client", paths.wasmPath), artifact.wasm), | ||
| 71 | ]); | ||
| 72 | }, | ||
| 73 | }; | ||
| 74 | }; | ||