UNPKG

@esbuild-plugins/node-modules-polyfill

Version:
134 lines (117 loc) 4.64 kB
import { OnResolveArgs, Plugin } from 'esbuild' import escapeStringRegexp from 'escape-string-regexp' import fs from 'fs' import path from 'path' import esbuild from 'esbuild' import { builtinsPolyfills } from './polyfills' // import { NodeResolvePlugin } from '@esbuild-plugins/node-resolve' const NAME = 'node-modules-polyfills' const NAMESPACE = NAME function removeEndingSlash(importee) { if (importee && importee.slice(-1) === '/') { importee = importee.slice(0, -1) } return importee } export interface NodePolyfillsOptions { name?: string namespace?: string } export function NodeModulesPolyfillPlugin( options: NodePolyfillsOptions = {}, ): Plugin { const { namespace = NAMESPACE, name = NAME } = options if (namespace.endsWith('commonjs')) { throw new Error(`namespace ${namespace} must not end with commonjs`) } // this namespace is needed to make ES modules expose their default export to require: require('assert') will give you import('assert').default const commonjsNamespace = namespace + '-commonjs' const polyfilledBuiltins = builtinsPolyfills() const polyfilledBuiltinsNames = [...polyfilledBuiltins.keys()] return { name, setup: function setup({ onLoad, onResolve, initialOptions }) { // polyfills contain global keyword, it must be defined if (initialOptions?.define && !initialOptions.define?.global) { initialOptions.define['global'] = 'globalThis' } else if (!initialOptions?.define) { initialOptions.define = { global: 'globalThis' } } // TODO these polyfill module cannot import anything, is that ok? async function loader( args: esbuild.OnLoadArgs, ): Promise<esbuild.OnLoadResult> { try { const argsPath = args.path.replace(/^node:/, '') const isCommonjs = args.namespace.endsWith('commonjs') const resolved = polyfilledBuiltins.get( removeEndingSlash(argsPath), ) const contents = await ( await fs.promises.readFile(resolved) ).toString() let resolveDir = path.dirname(resolved) if (isCommonjs) { return { loader: 'js', contents: commonJsTemplate({ importPath: argsPath, }), resolveDir, } } return { loader: 'js', contents, resolveDir, } } catch (e) { console.error('node-modules-polyfill', e) return { contents: `export {}`, loader: 'js', } } } onLoad({ filter: /.*/, namespace }, loader) onLoad({ filter: /.*/, namespace: commonjsNamespace }, loader) const filter = new RegExp( [ ...polyfilledBuiltinsNames, ...polyfilledBuiltinsNames.map((n) => `node:${n}`), ] .map(escapeStringRegexp) .join('|'), // TODO builtins could end with slash, keep in mind in regex ) async function resolver(args: OnResolveArgs) { const argsPath = args.path.replace(/^node:/, '') const ignoreRequire = args.namespace === commonjsNamespace if (!polyfilledBuiltins.has(argsPath)) { return } const isCommonjs = !ignoreRequire && args.kind === 'require-call' return { namespace: isCommonjs ? commonjsNamespace : namespace, path: argsPath, } } onResolve({ filter }, resolver) // onResolve({ filter: /.*/, namespace }, resolver) }, } } function commonJsTemplate({ importPath }) { return ` const polyfill = require('${importPath}') if (polyfill && polyfill.default) { module.exports = polyfill.default for (let k in polyfill) { module.exports[k] = polyfill[k] } } else if (polyfill) { module.exports = polyfill } ` } export default NodeModulesPolyfillPlugin