Skip to content

Instantly share code, notes, and snippets.

@connorjclark
Created October 7, 2022 22:40
Show Gist options
  • Save connorjclark/5535d586af717e1d503cc71de31d840c to your computer and use it in GitHub Desktop.
Save connorjclark/5535d586af717e1d503cc71de31d840c to your computer and use it in GitHub Desktop.
// This is @esbuild-plugins/node-modules-polyfill, but using a better library
// for polyfills. It was done in this PR which has not merged:
// https://github.com/remorses/esbuild-plugins/pull/19
import fs from 'fs';
import path from 'path';
import escapeStringRegexp from 'escape-string-regexp';
import esbuild from 'esbuild';
import {getModules as builtinsPolyfills} from 'rollup-plugin-polyfill-node/dist/modules.js';
import polyfillLib from 'rollup-plugin-polyfill-node/dist/polyfills.js';
// 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;
}
function nodeModulesPolyfillPlugin(
options = {}
) {
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
) {
try {
const isCommonjs = args.namespace.endsWith('commonjs');
const key = removeEndingSlash(args.path);
const contents = polyfilledBuiltins.get(key) || polyfillLib[key + '.js'];
const resolveDir = path.dirname(key);
if (isCommonjs) {
return {
loader: 'js',
contents: commonJsTemplate({
importPath: args.path,
}),
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.map(escapeStringRegexp).join('|') // TODO builtins could end with slash, keep in mind in regex
);
async function resolver(args) {
const ignoreRequire = args.namespace === commonjsNamespace;
let key;
if (args.path.startsWith('\0polyfill-node.')) {
key = args.path.replace('\0polyfill-node.', '');
} else {
if (args.path.startsWith('.')) {
key = path.join(path.dirname(args.importer), args.path);
} else {
key = args.path;
}
}
if (!polyfilledBuiltins.has(key) && !polyfillLib[key + '.js']) {
return;
}
const isCommonjs =
!ignoreRequire && args.kind === 'require-call';
return {
namespace: isCommonjs ? commonjsNamespace : namespace,
path: key,
};
}
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 {nodeModulesPolyfillPlugin};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment