summaryrefslogtreecommitdiff
path: root/node_modules/@esbuild-plugins/node-modules-polyfill/src/index.ts
blob: f7397e4eaaa9bafe7dc1eea85b0ba384755349b2 (plain)
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