esbuild-plugins-node-modules-polyfill
Version:
Polyfills nodejs builtin modules and globals for the browser.
228 lines (223 loc) • 10.1 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) {
__defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let node_module = require("node:module");
let node_path = require("node:path");
node_path = __toESM(node_path);
let node_process = require("node:process");
node_process = __toESM(node_process);
let local_pkg = require("local-pkg");
let esbuild = require("esbuild");
let resolve_exports = require("resolve.exports");
//#region src/lib/utils/util.ts
const escapeRegex = (str) => str.replace(/[$()*+.?[\\\]^{|}]/g, "\\$&").replace(/-/g, "\\x2d");
const commonJsTemplate = ({ importPath }) => `export * from '${importPath}'`;
const normalizeNodeBuiltinPath = (path) => path.replace(/^node:/, "").replace(/\/$/, "");
//#endregion
//#region src/lib/polyfill.ts
async function polyfillPath(importPath) {
if (!node_module.builtinModules.includes(importPath)) throw new Error(`Node.js does not have ${importPath} in its builtin modules`);
const jspmPath = (0, node_path.resolve)(require.resolve(`@jspm/core/nodelibs/${importPath}`), "../../.." + (importPath.includes("/") ? "/.." : ""));
const exportPath = (0, resolve_exports.resolve)(await (0, local_pkg.loadPackageJSON)(jspmPath), `./nodelibs/${importPath}`, { browser: true });
const exportFullPath = (0, local_pkg.resolveModule)((0, node_path.join)(jspmPath, exportPath?.[0] ?? ""));
if (!exportPath || !exportFullPath) throw new Error("resolving failed, please try creating an issue in https://github.com/imranbarbhuiya/esbuild-plugins-node-modules-polyfill");
return exportFullPath;
}
const polyfillPathCache = /* @__PURE__ */ new Map();
const polyfillOverrides = /* @__PURE__ */ new Map();
const setPolyfillOverrides = (overrides) => {
polyfillOverrides.clear();
for (const [moduleName, customPath] of Object.entries(overrides)) {
const normalizedModuleName = normalizeNodeBuiltinPath(moduleName);
polyfillOverrides.set(normalizedModuleName, customPath);
}
};
const getCachedPolyfillPath = (importPath) => {
const normalizedImportPath = normalizeNodeBuiltinPath(importPath);
const override = polyfillOverrides.get(normalizedImportPath);
if (override) return Promise.resolve(override);
const cachedPromise = polyfillPathCache.get(normalizedImportPath);
if (cachedPromise) return cachedPromise;
const promise = polyfillPath(normalizedImportPath);
polyfillPathCache.set(normalizedImportPath, promise);
return promise;
};
const polyfillContentAndTransform = async (importPath) => {
return (await (0, esbuild.build)({
write: false,
format: "esm",
bundle: true,
entryPoints: [await getCachedPolyfillPath(importPath)]
})).outputFiles[0].text.replace(/eval\(/g, "(0,eval)(");
};
const polyfillContentCache = /* @__PURE__ */ new Map();
const getCachedPolyfillContent = (importPath) => {
const normalizedImportPath = normalizeNodeBuiltinPath(importPath);
const cachedPromise = polyfillContentCache.get(normalizedImportPath);
if (cachedPromise) return cachedPromise;
const promise = polyfillContentAndTransform(normalizedImportPath);
polyfillContentCache.set(normalizedImportPath, promise);
return promise;
};
//#endregion
//#region src/lib/plugin.ts
const NAME = "node-modules-polyfills";
const loader = async (args) => {
try {
const isCommonjs = args.namespace.endsWith("commonjs");
const resolved = await getCachedPolyfillPath(args.path);
const resolveDir = node_path.default.dirname(resolved);
if (isCommonjs) return {
loader: "js",
contents: commonJsTemplate({ importPath: args.path }),
resolveDir
};
return {
loader: "js",
contents: await getCachedPolyfillContent(args.path),
resolveDir
};
} catch (error) {
console.error("node-modules-polyfill", error);
return {
contents: `export {}`,
loader: "js"
};
}
};
const nodeModulesPolyfillPlugin = (options = {}) => {
const { globals = {}, modules: modulesOption = node_module.builtinModules, fallback = "none", formatError, namespace = NAME, name = NAME, overrides = {} } = options;
if (namespace.endsWith("commonjs")) throw new Error(`namespace ${namespace} must not end with commonjs`);
if (namespace.endsWith("empty")) throw new Error(`namespace ${namespace} must not end with empty`);
if (namespace.endsWith("error")) throw new Error(`namespace ${namespace} must not end with error`);
if (Object.keys(overrides).length > 0) setPolyfillOverrides(overrides);
const modules = Array.isArray(modulesOption) ? Object.fromEntries(modulesOption.map((mod) => [mod, true])) : modulesOption;
const commonjsNamespace = `${namespace}-commonjs`;
const emptyNamespace = `${namespace}-empty`;
const errorNamespace = `${namespace}-error`;
const shouldDetectErrorModules = fallback === "error" || Object.values(modules).includes("error");
return {
name,
setup: ({ onLoad, onResolve, onEnd, initialOptions }) => {
if (shouldDetectErrorModules && initialOptions.write !== false) throw new Error(`The "write" build option must be set to false when using the "error" polyfill type`);
const root = initialOptions.absWorkingDir ?? node_process.default.cwd();
if (initialOptions.define && !initialOptions.define.global) initialOptions.define.global = "globalThis";
else if (!initialOptions.define) initialOptions.define = { global: "globalThis" };
initialOptions.inject = initialOptions.inject ?? [];
if (globals.Buffer) initialOptions.inject.push(node_path.default.resolve(__dirname, "../globals/Buffer.js"));
if (globals.process) initialOptions.inject.push(node_path.default.resolve(__dirname, "../globals/process.js"));
onLoad({
filter: /.*/,
namespace: emptyNamespace
}, () => ({
loader: "js",
contents: "module.exports = {}"
}));
onLoad({
filter: /.*/,
namespace: errorNamespace
}, (args) => ({
loader: "js",
contents: `module.exports = ${JSON.stringify(`__POLYFILL_ERROR_START__::MODULE::${args.path}::IMPORTER::${args.pluginData.importer}::__POLYFILL_ERROR_END__`)}`
}));
onLoad({
filter: /.*/,
namespace
}, loader);
onLoad({
filter: /.*/,
namespace: commonjsNamespace
}, loader);
const bundledModules = fallback === "none" ? Object.keys(modules).filter((moduleName) => node_module.builtinModules.includes(moduleName)) : node_module.builtinModules;
const filter = new RegExp(`^(?:node:)?(?:${bundledModules.map(escapeRegex).join("|")})$`);
const resolver = async (args) => {
const result = {
empty: {
namespace: emptyNamespace,
path: args.path,
sideEffects: false
},
error: {
namespace: errorNamespace,
path: args.path,
sideEffects: false,
pluginData: { importer: node_path.default.relative(root, args.importer).replace(/\\/g, "/") }
},
none: void 0
};
if (initialOptions.platform === "browser") {
const browserFieldValue = (await (0, local_pkg.loadPackageJSON)(args.resolveDir))?.browser;
if (typeof browserFieldValue === "string") return;
const browserFieldValueForModule = browserFieldValue?.[args.path];
if (browserFieldValueForModule === false) return result.empty;
if (browserFieldValueForModule !== void 0) return;
}
const moduleName = normalizeNodeBuiltinPath(args.path);
const polyfillOption = modules[moduleName] ?? modules[`node:${moduleName}`];
if (!polyfillOption) return result[fallback];
if (polyfillOption === "error" || polyfillOption === "empty") return result[polyfillOption];
if (!await getCachedPolyfillPath(moduleName).catch(() => null)) return result[fallback];
return {
namespace: !(args.namespace === commonjsNamespace) && args.kind === "require-call" ? commonjsNamespace : namespace,
path: args.path,
sideEffects: false
};
};
onResolve({ filter }, resolver);
onEnd(async ({ outputFiles = [] }) => {
if (!shouldDetectErrorModules) return;
const errors = [];
const { outfile, outExtension = {} } = initialOptions;
const jsExtension = outfile ? node_path.default.extname(outfile) : outExtension[".js"] || ".js";
const jsFiles = outputFiles.filter((file) => node_path.default.extname(file.path) === jsExtension);
for (const file of jsFiles) {
const matches = file.text.matchAll(/__POLYFILL_ERROR_START__::MODULE::(?<moduleName>.+?)::IMPORTER::(?<importer>.+?)::__POLYFILL_ERROR_END__/g);
for (const { groups } of matches) {
const { moduleName, importer } = groups;
const polyfillExists = await getCachedPolyfillPath(moduleName).catch(() => null) !== null;
errors.push({
pluginName: name,
text: polyfillExists ? `Polyfill has not been configured for "${moduleName}", imported by "${importer}"` : `Polyfill does not exist for "${moduleName}", imported by "${importer}"`,
...formatError ? await formatError({
moduleName,
importer,
polyfillExists
}) : {}
});
}
}
return { errors };
});
}
};
};
//#endregion
exports.commonJsTemplate = commonJsTemplate;
exports.escapeRegex = escapeRegex;
exports.nodeModulesPolyfillPlugin = nodeModulesPolyfillPlugin;
exports.normalizeNodeBuiltinPath = normalizeNodeBuiltinPath;
//# sourceMappingURL=index.js.map