From 0401c1aaaaa6732dd37190bae210107d8c18f057 Mon Sep 17 00:00:00 2001 From: Sunil Pai Date: Wed, 2 Oct 2024 18:15:24 +0100 Subject: [PATCH] feat: split worker bundle with esbuild --- .../wrangler/src/deployment-bundle/bundle.ts | 47 ++++++++++++++----- .../entry-point-from-metafile.ts | 36 +++++++++++++- .../deployment-bundle/guess-worker-format.ts | 4 +- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/packages/wrangler/src/deployment-bundle/bundle.ts b/packages/wrangler/src/deployment-bundle/bundle.ts index b676e906d87e..710a385077cf 100644 --- a/packages/wrangler/src/deployment-bundle/bundle.ts +++ b/packages/wrangler/src/deployment-bundle/bundle.ts @@ -15,7 +15,7 @@ import { rewriteNodeCompatBuildFailure, } from "./build-failures"; import { dedupeModulesByName } from "./dedupe-modules"; -import { getEntryPointFromMetafile } from "./entry-point-from-metafile"; +import { getEntryPointFromSplitBundleMetafile } from "./entry-point-from-metafile"; import { asyncLocalStoragePlugin } from "./esbuild-plugins/als-external"; import { cloudflareInternalPlugin } from "./esbuild-plugins/cloudflare-internal"; import { configProviderPlugin } from "./esbuild-plugins/config-provider"; @@ -44,7 +44,13 @@ const escapeRegex = (str: string) => { export const COMMON_ESBUILD_OPTIONS = { // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/ target: "es2022", - loader: { ".js": "jsx", ".mjs": "jsx", ".cjs": "jsx" }, + loader: { + ".js": "jsx", + ".mjs": "jsx", + ".cjs": "jsx", + ".wasm": "binary", + ".DS_Store": "text", + }, } as const; /** @@ -383,15 +389,15 @@ export async function bundleWorker( entryPoints: [entry.file], bundle, absWorkingDir: entry.directory, - outdir: destination, + outdir: isOutfile ? path.dirname(destination) : destination, entryNames: entryName || path.parse(entryFile).name, - ...(isOutfile - ? { - outdir: undefined, - outfile: destination, - entryNames: undefined, - } - : {}), + // ...(isOutfile + // ? { + // outdir: undefined, + // outfile: destination, + // entryNames: undefined, + // } + // : {}), inject, external: bundle ? ["__STATIC_CONTENT_MANIFEST", ...(external ? external : [])] @@ -402,6 +408,7 @@ export async function bundleWorker( // Include a reference to the output folder in the sourcemap. // This is omitted by default, but we need it to properly resolve source paths in error output. sourceRoot: destination, + splitting: entry.format === "modules" ? true : false, minify, metafile: true, conditions: getBuildConditions(), @@ -463,6 +470,7 @@ export async function bundleWorker( let stop: BundleResult["stop"]; try { if (watch) { + // CHECK const ctx = await esbuild.context(buildOptions); await ctx.watch(); result = await initialBuildResultPromise; @@ -475,6 +483,7 @@ export async function bundleWorker( await ctx.dispose(); }; } else { + // CHECK result = await esbuild.build(buildOptions); // Even when we're not watching, we still want some way of cleaning up the // temporary directory when we don't need it anymore @@ -489,7 +498,12 @@ export async function bundleWorker( throw e; } - const entryPoint = getEntryPointFromMetafile(entryFile, result.metafile); + const entryPoint = getEntryPointFromSplitBundleMetafile( + entry.file, + entry.directory, + result.metafile + ); + const notExportedDOs = doBindings .filter((x) => !x.script_name && !entryPoint.exports.includes(x.class_name)) .map((x) => x.class_name); @@ -518,6 +532,17 @@ export async function bundleWorker( const modules = dedupeModulesByName([ ...moduleCollector.modules, ...additionalModules, + ...Object.entries(result.metafile.outputs) + .filter(([name]) => { + return !name.endsWith(".map") && name !== entryPoint.relativePath; + }) + // TODO: this is a hack to see what CI says + .map(([name, _output]) => ({ + name: path.relative(path.dirname(entryPoint.relativePath), name), + content: fs.readFileSync(name), + type: "esm" as const, + filePath: path.resolve(name), + })), ]); await writeAdditionalModules(modules, path.dirname(resolvedEntryPointPath)); diff --git a/packages/wrangler/src/deployment-bundle/entry-point-from-metafile.ts b/packages/wrangler/src/deployment-bundle/entry-point-from-metafile.ts index bae26b363f03..12dea36235d3 100644 --- a/packages/wrangler/src/deployment-bundle/entry-point-from-metafile.ts +++ b/packages/wrangler/src/deployment-bundle/entry-point-from-metafile.ts @@ -1,11 +1,12 @@ import assert from "node:assert"; +import path from "node:path"; import type { Metafile } from "esbuild"; /** * Compute entry-point information such as path, exports and dependencies * from the output of esbuild. */ -export function getEntryPointFromMetafile( +export function getEntryPointFromSimpleMetafile( entryFile: string, metafile: Metafile ) { @@ -34,3 +35,36 @@ export function getEntryPointFromMetafile( dependencies: entryPoint.inputs, }; } + +/** + * Compute entry-point information such as path, exports and dependencies + * from the output of esbuild. + */ +export function getEntryPointFromSplitBundleMetafile( + entryFile: string, + entryDirectory: string, + metafile: Metafile +) { + const relativeEntryFile = path.relative(entryDirectory, entryFile); + const entryPointDetails = Object.entries(metafile.outputs).find( + ([_output, { entryPoint }]) => { + if (entryPoint) { + return entryPoint === relativeEntryFile; + } + return false; + } + ); + + if (!entryPointDetails) { + throw new Error( + `Cannot find entry-point "${entryFile}" in generated bundle.` + ); + } + + // console.log("entryPointDetails", entryPointDetails); + return { + relativePath: entryPointDetails[0], + exports: entryPointDetails[1].exports, + dependencies: entryPointDetails[1].inputs, + }; +} diff --git a/packages/wrangler/src/deployment-bundle/guess-worker-format.ts b/packages/wrangler/src/deployment-bundle/guess-worker-format.ts index d8d8df0a295c..50a4c764a9de 100644 --- a/packages/wrangler/src/deployment-bundle/guess-worker-format.ts +++ b/packages/wrangler/src/deployment-bundle/guess-worker-format.ts @@ -3,7 +3,7 @@ import * as esbuild from "esbuild"; import { UserError } from "../errors"; import { logger } from "../logger"; import { COMMON_ESBUILD_OPTIONS } from "./bundle"; -import { getEntryPointFromMetafile } from "./entry-point-from-metafile"; +import { getEntryPointFromSimpleMetafile } from "./entry-point-from-metafile"; import type { CfScriptFormat } from "./worker"; /** @@ -45,7 +45,7 @@ export default async function guessWorkerFormat( // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const metafile = result.metafile!; - const { exports } = getEntryPointFromMetafile(entryFile, metafile); + const { exports } = getEntryPointFromSimpleMetafile(entryFile, metafile); let guessedWorkerFormat: CfScriptFormat; if (exports.length > 0) { if (exports.includes("default")) {