1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
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
|