diff --git a/src/lib/plugin.ts b/src/lib/plugin.ts index 9d370f0..4469f2f 100644 --- a/src/lib/plugin.ts +++ b/src/lib/plugin.ts @@ -1,9 +1,7 @@ import { builtinModules } from 'node:module'; import path from 'node:path'; -import { polyfillContent, polyfillPath } from 'modern-node-polyfills'; - -import { escapeRegex, commonJsTemplate, removeEndingSlash } from './utils/util.js'; +import { escapeRegex, commonJsTemplate, getCachedPolyfillPath, getCachedPolyfillContent } from './utils/util.js'; import type { OnResolveArgs, Plugin } from 'esbuild'; import type esbuild from 'esbuild'; @@ -19,9 +17,7 @@ const loader = async (args: esbuild.OnLoadArgs): Promise = try { const isCommonjs = args.namespace.endsWith('commonjs'); - const resolved = await polyfillPath(removeEndingSlash(args.path)); - const contents = (await polyfillContent(removeEndingSlash(args.path))).replaceAll('eval(', '(0,eval)('); - + const resolved = await getCachedPolyfillPath(args.path); const resolveDir = path.dirname(resolved); if (isCommonjs) { @@ -34,6 +30,8 @@ const loader = async (args: esbuild.OnLoadArgs): Promise = }; } + const contents = await getCachedPolyfillContent(args.path); + return { loader: 'js', contents, @@ -72,7 +70,7 @@ export const nodeModulesPolyfillPlugin = (options: NodePolyfillsOptions = {}): P const resolver = async (args: OnResolveArgs) => { const ignoreRequire = args.namespace === commonjsNamespace; - const pollyfill = await polyfillPath(args.path).catch(() => null); + const pollyfill = await getCachedPolyfillPath(args.path).catch(() => null); if (!pollyfill) { return; diff --git a/src/lib/utils/util.ts b/src/lib/utils/util.ts index 860a0cb..7a1138d 100644 --- a/src/lib/utils/util.ts +++ b/src/lib/utils/util.ts @@ -1,12 +1,10 @@ /* eslint-disable unicorn/prefer-string-replace-all -- node v14 doesn't supports string.replaceAll*/ +import { polyfillContent, polyfillPath } from 'modern-node-polyfills'; + export const escapeRegex = (str: string) => { return str.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&').replace(/-/g, '\\x2d'); }; -export const removeEndingSlash = (str: string) => { - return str.replace(/\/$/, ''); -}; - export const commonJsTemplate = ({ importPath }: { importPath: string }) => { return ` const polyfill = require('${importPath}') @@ -20,3 +18,40 @@ if (polyfill && polyfill.default) { } `; }; + +const normalizeNodeBuiltinPath = (path: string) => { + return path.replace(/^node:/, '').replace(/\/$/, ''); +}; + +const polyfillPathCache: Map> = new Map(); +export const getCachedPolyfillPath = (_importPath: string): Promise => { + const normalizedImportPath = normalizeNodeBuiltinPath(_importPath); + + const cachedPromise = polyfillPathCache.get(normalizedImportPath); + if (cachedPromise) { + return cachedPromise; + } + + const promise = polyfillPath(normalizedImportPath); + polyfillPathCache.set(normalizedImportPath, promise); + return promise; +}; + +const polyfillContentAndTransform = async (importPath: string) => { + const content = await polyfillContent(importPath); + return content.replace(/eval\(/g, '(0,eval)('); +}; + +const polyfillContentCache: Map> = new Map(); +export const getCachedPolyfillContent = (_importPath: string): Promise => { + const normalizedImportPath = normalizeNodeBuiltinPath(_importPath); + + const cachedPromise = polyfillContentCache.get(normalizedImportPath); + if (cachedPromise) { + return cachedPromise; + } + + const promise = polyfillContentAndTransform(normalizedImportPath); + polyfillContentCache.set(normalizedImportPath, promise); + return promise; +};