Skip to content

Commit

Permalink
refactor(wrangler): Explicitely pick node compat plugins for each mode (
Browse files Browse the repository at this point in the history
#7387)

* refactor: cleanup & simplify

* refactor(wrangler): Explicitely pick node compat plugins for each mode

* Update packages/wrangler/src/deployment-bundle/esbuild-plugins/hybrid-nodejs-compat.ts

Co-authored-by: Pete Bacon Darwin <[email protected]>

* fixup! renamed vars (review feedback)

* fixup! format

---------

Co-authored-by: Pete Bacon Darwin <[email protected]>
  • Loading branch information
vicb and petebacondarwin authored Nov 30, 2024
1 parent 4aed2d6 commit ff8f5ae
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 67 deletions.
22 changes: 2 additions & 20 deletions packages/wrangler/src/deployment-bundle/bundle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import * as fs from "node:fs";
import * as path from "node:path";
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
import chalk from "chalk";
import * as esbuild from "esbuild";
import {
Expand All @@ -18,12 +16,9 @@ import {
} from "./build-failures";
import { dedupeModulesByName } from "./dedupe-modules";
import { getEntryPointFromMetafile } 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";
import { nodejsHybridPlugin } from "./esbuild-plugins/hybrid-nodejs-compat";
import { nodejsCompatPlugin } from "./esbuild-plugins/nodejs-compat";
import { standardURLPlugin } from "./esbuild-plugins/standard-url";
import { getNodeJSCompatPlugins } from "./esbuild-plugins/nodejs-plugins";
import { writeAdditionalModules } from "./find-additional-modules";
import { noopModuleCollector } from "./module-collection";
import type { Config } from "../config";
Expand Down Expand Up @@ -440,20 +435,7 @@ export async function bundleWorker(
plugins: [
aliasPlugin,
moduleCollector.plugin,
...(nodejsCompatMode === "als" ? [asyncLocalStoragePlugin] : []),
...(nodejsCompatMode === "legacy"
? [
NodeGlobalsPolyfills({ buffer: true }),
standardURLPlugin(),
NodeModulesPolyfills(),
]
: []),
// Runtime Node.js compatibility (will warn if not using nodejs compat flag and are trying to import from a Node.js builtin).
...(nodejsCompatMode === "v1" || nodejsCompatMode !== "v2"
? [nodejsCompatPlugin(nodejsCompatMode === "v1")]
: []),
// Hybrid Node.js compatibility
...(nodejsCompatMode === "v2" ? [nodejsHybridPlugin()] : []),
...getNodeJSCompatPlugins(nodejsCompatMode ?? null),
cloudflareInternalPlugin,
buildResultPlugin,
...(plugins || []),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ function handleRequireCallsToNodeJSBuiltins(build: PluginBuild) {
({ path }) => {
return {
contents: dedent`
import libDefault from '${path}';
module.exports = libDefault;`,
import libDefault from '${path}';
module.exports = libDefault;`,
loader: "js",
};
}
Expand Down Expand Up @@ -150,8 +150,8 @@ function handleUnenvAliasedPackages(
({ path }) => {
return {
contents: dedent`
import * as esm from '${path}';
module.exports = __cf_cjs(esm);
import * as esm from '${path}';
module.exports = __cf_cjs(esm);
`,
loader: "js",
};
Expand Down Expand Up @@ -188,7 +188,10 @@ function handleNodeJSGlobals(
const { importStatement, exportName } = getGlobalInject(inject[globalName]);

return {
contents: `${importStatement}\nglobalThis.${globalName} = ${exportName};`,
contents: dedent`
${importStatement}
globalThis.${globalName} = ${exportName};
`,
};
});
}
Expand All @@ -213,38 +216,21 @@ function getGlobalInject(globalInject: string | string[]) {
}

/**
* Encodes a case sensitive string to lowercase string by prefixing all uppercase letters
* with $ and turning them into lowercase letters.
* Encodes a case sensitive string to lowercase string.
*
* - Escape $ with another $ ("$" -> "$$")
* - Escape uppercase letters with $ and turn them into lowercase letters ("L" -> "$L")
*
* This function exists because ESBuild requires that all resolved paths are case insensitive.
* Without this transformation, ESBuild will clobber /foo/bar.js with /foo/Bar.js
*
* This is important to support `inject` config for `performance` and `Performance` introduced
* in https://github.com/unjs/unenv/pull/257
*/
export function encodeToLowerCase(str: string): string {
return str
.replaceAll(/\$/g, () => "$$")
.replaceAll(/[A-Z]/g, (letter) => `$${letter.toLowerCase()}`);
return str.replace(/[A-Z$]/g, (escape) => `$${escape.toLowerCase()}`);
}

/**
* Decodes a string lowercased using `encodeToLowerCase` to the original strings
*/
export function decodeFromLowerCase(str: string): string {
let out = "";
let i = 0;
while (i < str.length - 1) {
if (str[i] === "$") {
i++;
out += str[i].toUpperCase();
} else {
out += str[i];
}
i++;
}
if (i < str.length) {
out += str[i];
}
return out;
return str.replace(/\$[a-z$]/g, (escaped) => escaped[1].toUpperCase());
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,32 @@ import chalk from "chalk";
import { logger } from "../../logger";
import { dedent } from "../../utils/dedent";
import type { Plugin } from "esbuild";
import type { NodeJSCompatMode } from "miniflare";

/**
* An esbuild plugin that will mark any `node:...` imports as external.
* An esbuild plugin that will:
* - mark any `node:...` imports as external
* - warn if there are node imports (if not in v1 mode)
*
* Applies to: null, als, legacy and v1 modes.
*/
export const nodejsCompatPlugin: (silenceWarnings: boolean) => Plugin = (
silenceWarnings
) => ({
export const nodejsCompatPlugin = (mode: NodeJSCompatMode): Plugin => ({
name: "nodejs_compat-imports",
setup(pluginBuild) {
// Infinite loop detection
const seen = new Set<string>();

// Prevent multiple warnings per package
const warnedPackaged = new Map<string, string[]>();
const warnedPackages = new Map<string, string[]>();

pluginBuild.onStart(() => {
seen.clear();
warnedPackaged.clear();
warnedPackages.clear();
});
pluginBuild.onResolve(
{ filter: /node:.*/ },
async ({ path, kind, resolveDir, ...opts }) => {
const specifier = `${path}:${kind}:${resolveDir}:${opts.importer}`;
async ({ path, kind, resolveDir, importer }) => {
const specifier = `${path}:${kind}:${resolveDir}:${importer}`;
if (seen.has(specifier)) {
return;
}
Expand All @@ -35,18 +38,15 @@ export const nodejsCompatPlugin: (silenceWarnings: boolean) => Plugin = (
const result = await pluginBuild.resolve(path, {
kind,
resolveDir,
importer: opts.importer,
importer,
});

if (result.errors.length > 0) {
// esbuild couldn't resolve the package
// We should warn the user, but not fail the build

let pathWarnedPackaged = warnedPackaged.get(path);
if (pathWarnedPackaged === undefined) {
warnedPackaged.set(path, (pathWarnedPackaged = []));
}
pathWarnedPackaged.push(opts.importer);
const pathWarnedPackages = warnedPackages.get(path) ?? [];
pathWarnedPackages.push(importer);
warnedPackages.set(path, pathWarnedPackages);

return { external: true };
}
Expand All @@ -64,10 +64,10 @@ export const nodejsCompatPlugin: (silenceWarnings: boolean) => Plugin = (
pluginBuild.onEnd(() => {
if (
pluginBuild.initialOptions.format === "iife" &&
warnedPackaged.size > 0
warnedPackages.size > 0
) {
const paths = new Intl.ListFormat("en-US").format(
Array.from(warnedPackaged.keys())
Array.from(warnedPackages.keys())
.map((p) => `"${p}"`)
.sort()
);
Expand All @@ -90,8 +90,8 @@ export const nodejsCompatPlugin: (silenceWarnings: boolean) => Plugin = (
// Wait until the build finishes to log warnings, so that all files which import a package
// can be collated
pluginBuild.onEnd(() => {
if (!silenceWarnings) {
warnedPackaged.forEach((importers: string[], path: string) => {
if (mode !== "v1") {
warnedPackages.forEach((importers: string[], path: string) => {
logger.warn(
dedent`
The package "${path}" wasn't found on the file system but is built into node.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import NodeGlobalsPolyfills from "@esbuild-plugins/node-globals-polyfill";
import NodeModulesPolyfills from "@esbuild-plugins/node-modules-polyfill";
import { asyncLocalStoragePlugin } from "./als-external";
import { nodejsHybridPlugin } from "./hybrid-nodejs-compat";
import { nodejsCompatPlugin } from "./nodejs-compat";
import { standardURLPlugin } from "./standard-url";
import type { Plugin } from "esbuild";
import type { NodeJSCompatMode } from "miniflare";

/**
* Returns the list of ESBuild plugins to use for a given compat mode.
*/
export function getNodeJSCompatPlugins(mode: NodeJSCompatMode): Plugin[] {
switch (mode) {
case "als":
return [asyncLocalStoragePlugin, nodejsCompatPlugin(mode)];
case "legacy":
return [
NodeGlobalsPolyfills({ buffer: true }),
standardURLPlugin(),
NodeModulesPolyfills(),
nodejsCompatPlugin(mode),
];
case "v1":
return [nodejsCompatPlugin(mode)];
case "v2":
return [nodejsHybridPlugin()];
case null:
return [nodejsCompatPlugin(mode)];
}
}

0 comments on commit ff8f5ae

Please sign in to comment.