-
Notifications
You must be signed in to change notification settings - Fork 40
Expand file tree
/
Copy pathpolyfills.ts
More file actions
148 lines (132 loc) · 5.29 KB
/
polyfills.ts
File metadata and controls
148 lines (132 loc) · 5.29 KB
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import * as esbuild from "esbuild";
import { createRequire } from "node:module";
import stdLibBrowser from "node-stdlib-browser";
import { fileURLToPath } from "node:url";
// Cache bundled polyfills
const polyfillCache: Map<string, string> = new Map();
function resolveCustomPolyfillSource(fileName: string): string {
return fileURLToPath(new URL(`../src/polyfills/${fileName}`, import.meta.url));
}
const require = createRequire(import.meta.url);
const WEB_STREAMS_PONYFILL_PATH = require.resolve(
"web-streams-polyfill/dist/ponyfill.js",
);
const CUSTOM_POLYFILL_ENTRY_POINTS = new Map([
["crypto", resolveCustomPolyfillSource("crypto.js")],
["path", resolveCustomPolyfillSource("path.js")],
["stream/web", resolveCustomPolyfillSource("stream-web.js")],
["util/types", resolveCustomPolyfillSource("util-types.js")],
["internal/webstreams/util", resolveCustomPolyfillSource("internal-webstreams-util.js")],
["internal/webstreams/adapters", resolveCustomPolyfillSource("internal-webstreams-adapters.js")],
["internal/webstreams/readablestream", resolveCustomPolyfillSource("internal-webstreams-readablestream.js")],
["internal/webstreams/writablestream", resolveCustomPolyfillSource("internal-webstreams-writablestream.js")],
["internal/webstreams/transformstream", resolveCustomPolyfillSource("internal-webstreams-transformstream.js")],
["internal/worker/js_transferable", resolveCustomPolyfillSource("internal-worker-js-transferable.js")],
["internal/test/binding", resolveCustomPolyfillSource("internal-test-binding.js")],
["internal/mime", resolveCustomPolyfillSource("internal-mime.js")],
]);
// node-stdlib-browser provides the mapping from Node.js stdlib to polyfill paths
// e.g., { path: "/path/to/path-browserify/index.js", fs: null, ... }
// We use this mapping instead of maintaining our own
/**
* Bundle a stdlib polyfill module using esbuild
*/
export async function bundlePolyfill(moduleName: string): Promise<string> {
const cached = polyfillCache.get(moduleName);
if (cached) return cached;
// Get the polyfill entry point from node-stdlib-browser
const entryPoint =
CUSTOM_POLYFILL_ENTRY_POINTS.get(moduleName) ??
stdLibBrowser[moduleName as keyof typeof stdLibBrowser];
if (!entryPoint) {
throw new Error(`No polyfill available for module: ${moduleName}`);
}
// Build alias mappings for all Node.js builtins
// This ensures nested dependencies (like crypto -> stream) are resolved correctly
const alias: Record<string, string> = {};
for (const [name, path] of Object.entries(stdLibBrowser)) {
if (path !== null) {
alias[name] = path;
alias[`node:${name}`] = path;
}
}
if (typeof stdLibBrowser.crypto === "string") {
alias.__secure_exec_crypto_browserify__ = stdLibBrowser.crypto;
}
if (typeof stdLibBrowser.path === "string") {
alias.__secure_exec_path_browserify__ = stdLibBrowser.path;
}
alias["web-streams-polyfill/dist/ponyfill.js"] = WEB_STREAMS_PONYFILL_PATH;
// Bundle using esbuild with CommonJS format
// This ensures proper module.exports handling for all module types including JSON
const result = await esbuild.build({
entryPoints: [entryPoint],
bundle: true,
write: false,
format: "cjs",
platform: "browser",
target: "es2020",
minify: false,
alias,
define: {
"process.env.NODE_ENV": '"production"',
global: "globalThis",
},
// Externalize 'process' - we provide our own process polyfill in the bridge.
// Without this, node-stdlib-browser's process polyfill gets bundled and
// overwrites globalThis.process, breaking process.argv modifications.
external: ["process"],
});
const code = result.outputFiles[0].text;
// Check if this is a JSON module (esbuild creates *_default but doesn't export it)
// For JSON modules, look for the default export pattern and extract it
const defaultExportMatch = code.match(/var\s+(\w+_default)\s*=\s*\{/);
let wrappedCode: string;
if (defaultExportMatch && !code.includes("module.exports")) {
// JSON module: wrap and return the default export object
const defaultVar = defaultExportMatch[1];
wrappedCode = `(function() {
${code}
return ${defaultVar};
})()`;
} else {
// Regular CommonJS module: wrap and return module.exports
wrappedCode = `(function() {
var module = { exports: {} };
var exports = module.exports;
${code}
return module.exports;
})()`;
}
polyfillCache.set(moduleName, wrappedCode);
return wrappedCode;
}
/**
* Get all available stdlib modules (those with non-null polyfills)
*/
export function getAvailableStdlib(): string[] {
return Object.keys(stdLibBrowser).filter(
(key) => stdLibBrowser[key as keyof typeof stdLibBrowser] !== null,
);
}
/**
* Check if a module has a polyfill available
* Note: fs returns null from node-stdlib-browser since we provide our own implementation
*/
export function hasPolyfill(moduleName: string): boolean {
// Strip node: prefix
const name = moduleName.replace(/^node:/, "");
if (CUSTOM_POLYFILL_ENTRY_POINTS.has(name)) {
return true;
}
const polyfill = stdLibBrowser[name as keyof typeof stdLibBrowser];
return polyfill !== undefined && polyfill !== null;
}
/**
* Pre-bundle all polyfills (for faster startup)
*/
export async function prebundleAllPolyfills(): Promise<Map<string, string>> {
const modules = getAvailableStdlib();
await Promise.all(modules.map((m) => bundlePolyfill(m)));
return new Map(polyfillCache);
}