523 lines
21 KiB
TypeScript
523 lines
21 KiB
TypeScript
// 浏览器端 Node.js 内置模块 polyfill
|
||
// 用于加载 webpack/ncc 打包的官方音源插件
|
||
// 提供 http/https (基于 fetch)、stream、util、events、crypto、url、zlib、path、fs、buffer 等
|
||
|
||
function createEventEmitter(): any {
|
||
const listeners: Record<string, Function[]> = {};
|
||
const self: any = {
|
||
on(event: string, fn: Function) {
|
||
(listeners[event] = listeners[event] || []).push(fn);
|
||
return self;
|
||
},
|
||
once(event: string, fn: Function) {
|
||
const wrapper = (...args: any[]) => {
|
||
self.off(event, wrapper);
|
||
fn(...args);
|
||
};
|
||
return self.on(event, wrapper);
|
||
},
|
||
off(event: string, fn: Function) {
|
||
if (listeners[event]) {
|
||
listeners[event] = listeners[event].filter((f: Function) => f !== fn);
|
||
}
|
||
return self;
|
||
},
|
||
emit(event: string, ...args: any[]) {
|
||
const fns = listeners[event] || [];
|
||
for (const fn of fns) {
|
||
try { fn.apply(null, args); } catch (_) {}
|
||
}
|
||
return true;
|
||
},
|
||
addListener: function() { return self.on.apply(self, arguments as any); },
|
||
removeListener: function() { return self.off.apply(self, arguments as any); },
|
||
removeAllListeners(event?: string) {
|
||
if (event) delete listeners[event];
|
||
else { for (const k in listeners) delete listeners[k]; }
|
||
return self;
|
||
},
|
||
listeners(event: string) { return listeners[event] || []; },
|
||
};
|
||
return self;
|
||
}
|
||
|
||
function createStreamBase(): any {
|
||
function Stream() {}
|
||
Stream.prototype.pipe = function(dest: any) { return dest; };
|
||
Stream.prototype.on = function(_e: string, _fn: Function) { return this; };
|
||
Stream.prototype.emit = function() { return true; };
|
||
return Stream;
|
||
}
|
||
|
||
function createReadable(): any {
|
||
function Readable() {}
|
||
Readable.prototype.pipe = function(dest: any) { return dest; };
|
||
Readable.prototype.on = function() { return this; };
|
||
Readable.prototype.once = function() { return this; };
|
||
Readable.prototype.emit = function() { return true; };
|
||
Readable.prototype.read = function() { return null; };
|
||
Readable.prototype.pause = function() { return this; };
|
||
Readable.prototype.resume = function() { return this; };
|
||
Readable.prototype.push = function() { return this; };
|
||
return Readable;
|
||
}
|
||
|
||
function createWritable(): any {
|
||
function Writable() {}
|
||
Writable.prototype.write = function() { return true; };
|
||
Writable.prototype.end = function() { return this; };
|
||
Writable.prototype.on = function() { return this; };
|
||
Writable.prototype.once = function() { return this; };
|
||
Writable.prototype.emit = function() { return true; };
|
||
return Writable;
|
||
}
|
||
|
||
function createTransform(): any {
|
||
function Transform() {}
|
||
Transform.prototype.pipe = function(dest: any) { return dest; };
|
||
Transform.prototype.write = function() { return true; };
|
||
Transform.prototype.end = function() { return this; };
|
||
Transform.prototype.on = function() { return this; };
|
||
Transform.prototype.once = function() { return this; };
|
||
Transform.prototype.emit = function() { return true; };
|
||
return Transform;
|
||
}
|
||
|
||
function createPassThrough(): any {
|
||
function PassThrough() {}
|
||
PassThrough.prototype.pipe = function(dest: any) { return dest; };
|
||
PassThrough.prototype.write = function() { return true; };
|
||
PassThrough.prototype.end = function() { return this; };
|
||
PassThrough.prototype.on = function() { return this; };
|
||
PassThrough.prototype.once = function() { return this; };
|
||
PassThrough.prototype.emit = function() { return true; };
|
||
return PassThrough;
|
||
}
|
||
|
||
function parseUrl(u: string): any {
|
||
try {
|
||
const url = new URL(u, typeof window !== 'undefined' ? window.location.href : 'http://localhost/');
|
||
return {
|
||
protocol: url.protocol,
|
||
hostname: url.hostname,
|
||
host: url.host,
|
||
port: url.port || (url.protocol === 'https:' ? '443' : '80'),
|
||
pathname: url.pathname,
|
||
path: url.pathname + url.search,
|
||
search: url.search,
|
||
query: Object.fromEntries(url.searchParams.entries()),
|
||
hash: url.hash,
|
||
href: url.href,
|
||
auth: url.username ? url.username + (url.password ? ':' + url.password : '') : '',
|
||
};
|
||
} catch {
|
||
return { protocol: '', hostname: '', host: '', pathname: '', path: '', href: u, port: '80' };
|
||
}
|
||
}
|
||
|
||
// http/https 模块 - 用 fetch 实现
|
||
function createHttpModule(isHttps: boolean): any {
|
||
function request(options: any, callback?: (res: any) => void): any {
|
||
let url: string;
|
||
let opts: any = typeof options === 'string' ? {} : { ...options };
|
||
if (typeof options === 'string') {
|
||
url = options;
|
||
} else if (options && typeof options === 'object') {
|
||
const protocol = opts.protocol || (isHttps ? 'https:' : 'http:');
|
||
const host = opts.hostname || opts.host || 'localhost';
|
||
const port = opts.port ? ':' + opts.port : '';
|
||
const path = opts.path || opts.pathname || '/';
|
||
url = protocol + '//' + host + port + path;
|
||
} else {
|
||
url = String(options);
|
||
}
|
||
|
||
const method = opts.method || 'GET';
|
||
const headers = opts.headers || {};
|
||
|
||
const req: any = createEventEmitter();
|
||
let requestBodyChunks: Uint8Array[] = [];
|
||
let ended = false;
|
||
|
||
req.write = function(chunk: any) {
|
||
if (chunk) requestBodyChunks.push(typeof chunk === 'string' ? new TextEncoder().encode(chunk) : chunk);
|
||
return true;
|
||
};
|
||
req.end = function(chunk: any) {
|
||
if (chunk) req.write(chunk);
|
||
if (ended) return;
|
||
ended = true;
|
||
|
||
let body: any = undefined;
|
||
if (method !== 'GET' && method !== 'HEAD' && requestBodyChunks.length > 0) {
|
||
if (requestBodyChunks.length === 1) body = requestBodyChunks[0];
|
||
else {
|
||
const total = requestBodyChunks.reduce((a, b) => a + b.length, 0);
|
||
const merged = new Uint8Array(total);
|
||
let offset = 0;
|
||
for (const c of requestBodyChunks) { merged.set(c, offset); offset += c.length; }
|
||
body = merged;
|
||
}
|
||
}
|
||
|
||
fetch(url, {
|
||
method,
|
||
headers,
|
||
body,
|
||
signal: opts.signal,
|
||
mode: 'cors',
|
||
credentials: 'omit',
|
||
} as any).then((resp: any) => {
|
||
const res: any = createEventEmitter();
|
||
res.statusCode = resp.status;
|
||
res.statusMessage = resp.statusText;
|
||
res.headers = {};
|
||
try {
|
||
resp.headers.forEach((v: string, k: string) => { res.headers[k.toLowerCase()] = v; });
|
||
} catch {}
|
||
res.setEncoding = function() { return res; };
|
||
res.pipe = function(dest: any) {
|
||
resp.arrayBuffer().then((buf: ArrayBuffer) => {
|
||
if (dest && typeof dest.write === 'function') dest.write(new Uint8Array(buf));
|
||
if (dest && typeof dest.end === 'function') dest.end();
|
||
res.emit('end');
|
||
}).catch((err: Error) => res.emit('error', err));
|
||
return dest;
|
||
};
|
||
res.text = () => resp.text();
|
||
res.json = () => resp.json();
|
||
res.body = resp.body;
|
||
if (callback) callback(res);
|
||
req.emit('response', res);
|
||
|
||
// 如果没有 callback,等待 body 然后发射 data/end 事件
|
||
if (!callback) {
|
||
resp.arrayBuffer().then((buf: ArrayBuffer) => {
|
||
res.emit('data', new Uint8Array(buf));
|
||
res.emit('end');
|
||
}).catch((err: Error) => res.emit('error', err));
|
||
}
|
||
}).catch((err: Error) => {
|
||
req.emit('error', err);
|
||
});
|
||
return req;
|
||
};
|
||
req.abort = function() { req.emit('abort'); };
|
||
req.setTimeout = function() { return req; };
|
||
req.setHeader = function(name: string, value: string) { (headers as any)[name.toLowerCase()] = value; return req; };
|
||
req.getHeader = function(name: string) { return (headers as any)[name.toLowerCase()]; };
|
||
req.removeHeader = function(name: string) { delete (headers as any)[name.toLowerCase()]; };
|
||
|
||
// 如果用户立即调用 req.end(),我们需要在 next tick 开始 - 但实际上用户必须自己调用 end
|
||
return req;
|
||
}
|
||
|
||
function get(options: any, callback?: (res: any) => void): any {
|
||
const req = request(options, callback);
|
||
req.end();
|
||
return req;
|
||
}
|
||
|
||
return {
|
||
request,
|
||
get,
|
||
createServer: () => ({ listen: () => {}, close: () => {} }),
|
||
Agent: function() { this.maxSockets = 50; },
|
||
globalAgent: { maxSockets: 50 },
|
||
STATUS_CODES: {},
|
||
};
|
||
}
|
||
|
||
// util 模块
|
||
const utilModule: any = {
|
||
inspect: (obj: any) => {
|
||
try { return JSON.stringify(obj); } catch { return String(obj); }
|
||
},
|
||
format: function() { return Array.prototype.slice.call(arguments).join(' '); },
|
||
inherits: function(ctor: any, superCtor: any) {
|
||
if (!superCtor || !superCtor.prototype) return;
|
||
ctor.super_ = superCtor;
|
||
const origProto = ctor.prototype;
|
||
ctor.prototype = Object.create(superCtor.prototype, {
|
||
constructor: { value: ctor, enumerable: false, writable: true, configurable: true },
|
||
});
|
||
// 保留原型上原有的属性
|
||
if (origProto) {
|
||
for (const k in origProto) {
|
||
if (Object.prototype.hasOwnProperty.call(origProto, k)) {
|
||
ctor.prototype[k] = origProto[k];
|
||
}
|
||
}
|
||
}
|
||
},
|
||
promisify: function(fn: any) {
|
||
return function() {
|
||
const args = Array.prototype.slice.call(arguments);
|
||
return new Promise((resolve, reject) => {
|
||
args.push((err: any, ...rest: any[]) => {
|
||
if (err) reject(err);
|
||
else resolve(rest.length === 1 ? rest[0] : rest);
|
||
});
|
||
try { fn.apply(null, args); } catch (e) { reject(e); }
|
||
});
|
||
};
|
||
},
|
||
callbackify: function(fn: any) {
|
||
return function() {
|
||
const args = Array.prototype.slice.call(arguments);
|
||
const cb = args.pop();
|
||
fn.apply(null, args).then((r: any) => cb(null, r)).catch((e: Error) => cb(e));
|
||
};
|
||
},
|
||
TextDecoder: typeof (globalThis as any).TextDecoder !== 'undefined' ? (globalThis as any).TextDecoder : function(this: any) { this.decode = function() { return ''; }; },
|
||
TextEncoder: typeof (globalThis as any).TextEncoder !== 'undefined' ? (globalThis as any).TextEncoder : function(this: any) { this.encode = function(s: string) { return s; }; },
|
||
types: { isDate: (x: any) => x instanceof Date, isRegExp: (x: any) => x instanceof RegExp, isError: (x: any) => x instanceof Error, isArray: Array.isArray, isFunction: (x: any) => typeof x === 'function' },
|
||
};
|
||
|
||
// crypto 模块
|
||
const cryptoModule: any = {
|
||
createHash: function(algo: string) {
|
||
const chunks: string[] = [];
|
||
return {
|
||
update: function(chunk: any) { chunks.push(String(chunk)); return this; },
|
||
digest: function(encoding?: string) {
|
||
// 简化:不做真正的哈希,返回一个稳定的伪随机值
|
||
const s = algo + ':' + chunks.join('');
|
||
let h = 0;
|
||
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
||
const hex = Math.abs(h).toString(16).padStart(16, '0') + Math.abs(h * 31).toString(16).padStart(16, '0');
|
||
if (encoding === 'hex') return hex;
|
||
if (encoding === 'base64') return btoa(hex);
|
||
return hex;
|
||
},
|
||
};
|
||
},
|
||
createHmac: function(algo: string, key: any) {
|
||
const chunks: string[] = [];
|
||
return {
|
||
update: function(chunk: any) { chunks.push(String(chunk)); return this; },
|
||
digest: function(encoding?: string) {
|
||
const s = 'hmac:' + algo + ':' + key + ':' + chunks.join('');
|
||
let h = 0;
|
||
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
|
||
const hex = Math.abs(h).toString(16).padStart(16, '0') + Math.abs(h * 17).toString(16).padStart(16, '0');
|
||
if (encoding === 'hex') return hex;
|
||
if (encoding === 'base64') return btoa(hex);
|
||
return hex;
|
||
},
|
||
};
|
||
},
|
||
randomBytes: function(n: number, cb?: (err: Error | null, buf: any) => void) {
|
||
const arr = new Uint8Array(n);
|
||
if (typeof (globalThis as any).crypto !== 'undefined' && (globalThis as any).crypto.getRandomValues) {
|
||
(globalThis as any).crypto.getRandomValues(arr);
|
||
} else {
|
||
for (let i = 0; i < n; i++) arr[i] = Math.floor(Math.random() * 256);
|
||
}
|
||
const buf: any = {
|
||
buffer: arr,
|
||
length: n,
|
||
toString: function(enc?: string) {
|
||
if (enc === 'hex') {
|
||
let s = '';
|
||
for (let i = 0; i < n; i++) s += arr[i].toString(16).padStart(2, '0');
|
||
return s;
|
||
}
|
||
if (enc === 'base64') {
|
||
let s = '';
|
||
for (let i = 0; i < n; i++) s += String.fromCharCode(arr[i]);
|
||
return btoa(s);
|
||
}
|
||
return String.fromCharCode.apply(null, arr as any);
|
||
},
|
||
};
|
||
if (cb) { setTimeout(() => cb(null, buf), 0); return undefined; }
|
||
return buf;
|
||
},
|
||
randomUUID: function() {
|
||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
const r = Math.random() * 16 | 0;
|
||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||
return v.toString(16);
|
||
});
|
||
},
|
||
};
|
||
|
||
// 完整的 polyfill 注册表
|
||
export function createNodePolyfills(): Record<string, any> {
|
||
const streamMod: any = {
|
||
Stream: createStreamBase(),
|
||
Readable: createReadable(),
|
||
Writable: createWritable(),
|
||
Duplex: createTransform(),
|
||
Transform: createTransform(),
|
||
PassThrough: createPassThrough(),
|
||
pipeline: (_a: any, b: any, cb?: () => void) => { if (cb) cb(); return b; },
|
||
finished: (_s: any, cb: () => void) => { cb(); },
|
||
};
|
||
streamMod.Stream.prototype = Object.create(createEventEmitter());
|
||
streamMod.Readable.prototype = Object.create(streamMod.Stream.prototype);
|
||
streamMod.Writable.prototype = Object.create(streamMod.Stream.prototype);
|
||
streamMod.Duplex.prototype = Object.create(streamMod.Stream.prototype);
|
||
streamMod.Transform.prototype = Object.create(streamMod.Duplex.prototype);
|
||
streamMod.PassThrough.prototype = Object.create(streamMod.Transform.prototype);
|
||
|
||
const bufferModule: any = {
|
||
from: (x: any) => {
|
||
if (x instanceof Uint8Array) return x;
|
||
if (typeof x === 'string') return new TextEncoder().encode(x);
|
||
if (Array.isArray(x)) return new Uint8Array(x);
|
||
return new Uint8Array(x || 0);
|
||
},
|
||
alloc: (n: number) => new Uint8Array(n),
|
||
allocUnsafe: (n: number) => new Uint8Array(n),
|
||
isBuffer: () => false,
|
||
concat: (arrs: any[]) => {
|
||
const total = arrs.reduce((a, b) => a + (b?.length || 0), 0);
|
||
const merged = new Uint8Array(total);
|
||
let offset = 0;
|
||
for (const c of arrs) { if (c) { merged.set(c, offset); offset += c.length; } }
|
||
return merged;
|
||
},
|
||
byteLength: (x: any) => typeof x === 'string' ? new TextEncoder().encode(x).length : (x?.length || 0),
|
||
compare: (_a: any, _b: any) => 0,
|
||
isEncoding: () => true,
|
||
};
|
||
|
||
return {
|
||
http: createHttpModule(false),
|
||
https: createHttpModule(true),
|
||
http2: createHttpModule(true),
|
||
stream: streamMod,
|
||
util: utilModule,
|
||
events: { EventEmitter: function() { return createEventEmitter(); } },
|
||
crypto: cryptoModule,
|
||
url: {
|
||
parse: parseUrl,
|
||
format: (o: any) => o.href || `${o.protocol}//${o.host}${o.path || o.pathname || ''}`,
|
||
resolve: (_from: string, to: string) => { try { return new URL(to, _from).href; } catch { return to; } },
|
||
URL: typeof (globalThis as any).URL !== 'undefined' ? (globalThis as any).URL : function() {},
|
||
URLSearchParams: typeof (globalThis as any).URLSearchParams !== 'undefined' ? (globalThis as any).URLSearchParams : function() {},
|
||
},
|
||
zlib: {
|
||
gunzipSync: (d: any) => d,
|
||
inflateSync: (d: any) => d,
|
||
deflateSync: (d: any) => d,
|
||
gzipSync: (d: any) => d,
|
||
gunzip: (d: any, cb: (err: Error | null, out: any) => void) => { setTimeout(() => cb(null, d), 0); },
|
||
inflate: (d: any, cb: (err: Error | null, out: any) => void) => { setTimeout(() => cb(null, d), 0); },
|
||
createGunzip: () => { const p = Object.create(streamMod.Transform.prototype); p.write = () => true; p.end = function() { this.emit && this.emit('end'); return this; }; return p; },
|
||
createInflate: () => { const p = Object.create(streamMod.Transform.prototype); p.write = () => true; p.end = function() { this.emit && this.emit('end'); return this; }; return p; },
|
||
createGzip: () => { const p = Object.create(streamMod.Transform.prototype); p.write = () => true; p.end = function() { this.emit && this.emit('end'); return this; }; return p; },
|
||
},
|
||
path: {
|
||
join: function() { return Array.prototype.slice.call(arguments).filter(Boolean).join('/').replace(/\/+/g, '/'); },
|
||
resolve: function() { return Array.prototype.slice.call(arguments).filter(Boolean).join('/'); },
|
||
dirname: (p: string) => String(p).split('/').slice(0, -1).join('/') || '/',
|
||
basename: (p: string, ext?: string) => { const base = String(p).split('/').pop() || ''; return ext && base.endsWith(ext) ? base.slice(0, -ext.length) : base; },
|
||
extname: (p: string) => { const m = String(p).match(/\.[^.]*$/); return m ? m[0] : ''; },
|
||
sep: '/', delimiter: ':',
|
||
parse: (p: string) => { const parts = String(p).split('/'); return { root: '/', dir: parts.slice(0, -1).join('/'), base: parts[parts.length - 1], ext: '', name: parts[parts.length - 1].replace(/\.[^.]*$/, '') }; },
|
||
normalize: (p: string) => p,
|
||
isAbsolute: (p: string) => p && p[0] === '/',
|
||
relative: (_from: string, to: string) => to,
|
||
},
|
||
fs: {
|
||
readFileSync: () => '',
|
||
writeFileSync: () => {},
|
||
existsSync: () => false,
|
||
createWriteStream: () => { const s = Object.create(streamMod.Writable.prototype); s.write = () => true; s.end = function() { this.emit && this.emit('finish'); return this; }; return s; },
|
||
createReadStream: () => { const s = Object.create(streamMod.Readable.prototype); s.pipe = (d: any) => { s.emit && s.emit('end'); return d; }; return s; },
|
||
readFile: (_path: any, enc: any, cb: (err: Error | null, data: any) => void) => { if (typeof enc === 'function') cb = enc; setTimeout(() => cb(null, ''), 0); },
|
||
writeFile: (_p: any, _d: any, cb?: (err: Error | null) => void) => { if (typeof cb === 'function') setTimeout(() => cb(null), 0); },
|
||
mkdirSync: () => {},
|
||
statSync: () => ({ isDirectory: () => false, isFile: () => true, size: 0, mtime: new Date() }),
|
||
unlinkSync: () => {},
|
||
promises: {
|
||
readFile: async () => '',
|
||
writeFile: async () => {},
|
||
mkdir: async () => {},
|
||
},
|
||
},
|
||
os: {
|
||
platform: () => 'browser',
|
||
type: () => 'Browser',
|
||
release: () => '1.0',
|
||
tmpdir: () => '/tmp',
|
||
homedir: () => '/',
|
||
EOL: '\n',
|
||
arch: () => 'x64',
|
||
cpus: () => [],
|
||
totalmem: () => 0,
|
||
freemem: () => 0,
|
||
},
|
||
assert: {
|
||
ok: function() {},
|
||
equal: function() {},
|
||
deepEqual: function() {},
|
||
strictEqual: function() {},
|
||
throws: function() {},
|
||
notEqual: function() {},
|
||
ifError: function() {},
|
||
},
|
||
dns: {
|
||
lookup: (_host: string, cb: (err: Error | null, addr: string, family: number) => void) => { setTimeout(() => cb(null, '127.0.0.1', 4), 0); },
|
||
resolve: (_host2: string, cb: (err: Error | null, addrs: string[]) => void) => { setTimeout(() => cb(null, ['127.0.0.1']), 0); },
|
||
},
|
||
buffer: bufferModule,
|
||
Buffer: bufferModule,
|
||
string_decoder: {
|
||
StringDecoder: function() {
|
||
this.write = function(x: any) { return typeof x === 'string' ? x : new TextDecoder().decode(x); };
|
||
this.end = function(x: any) { return x ? this.write(x) : ''; };
|
||
},
|
||
},
|
||
querystring: {
|
||
parse: (s: string) => {
|
||
const out: Record<string, string> = {};
|
||
const q = String(s || '').replace(/^\?/, '');
|
||
if (!q) return out;
|
||
for (const pair of q.split('&')) {
|
||
const [k, v] = pair.split('=');
|
||
if (k) out[decodeURIComponent(k)] = v !== undefined ? decodeURIComponent(v) : '';
|
||
}
|
||
return out;
|
||
},
|
||
stringify: (obj: any) => Object.entries(obj || {}).map(([k, v]) => encodeURIComponent(String(k)) + '=' + encodeURIComponent(String(v))).join('&'),
|
||
},
|
||
tty: { isatty: () => false, ReadStream: function() {}, WriteStream: function() {} },
|
||
net: {
|
||
createServer: () => ({ listen: () => {}, close: () => {}, on: function() { return this; } }),
|
||
createConnection: () => createEventEmitter(),
|
||
Socket: function() { return createEventEmitter(); },
|
||
},
|
||
tls: {
|
||
connect: () => createEventEmitter(),
|
||
TLSSocket: function() { return createEventEmitter(); },
|
||
createSecureContext: () => ({}),
|
||
},
|
||
child_process: {
|
||
spawn: () => createEventEmitter(),
|
||
exec: (_cmd: string, cb: (err: Error | null, stdout: string, stderr: string) => void) => setTimeout(() => cb(null, '', ''), 0),
|
||
execSync: () => '',
|
||
},
|
||
cluster: { isWorker: false, isMaster: true, fork: function() {}, on: function() { return this; } },
|
||
module: { Module: function() {} },
|
||
vm: { runInNewContext: (code: string, sandbox: any) => { const fn = new Function(...Object.keys(sandbox || {}), code); return fn(...Object.values(sandbox || {})); } },
|
||
perf_hooks: { performance: typeof (globalThis as any).performance !== 'undefined' ? (globalThis as any).performance : { now: () => Date.now() } },
|
||
console,
|
||
};
|
||
}
|
||
|
||
// 为给定的 webpack bundle 代码提供完整的沙箱环境
|
||
export function createSandboxRequire(): (id: string) => any {
|
||
const polyfills = createNodePolyfills();
|
||
return function(id: string): any {
|
||
if (polyfills[id]) return polyfills[id];
|
||
// 处理子路径,如 stream/transform, crypto/hash 等
|
||
const base = id.split('/')[0];
|
||
if (polyfills[base]) return polyfills[base];
|
||
return {};
|
||
};
|
||
}
|