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 {};
|
|||
|
|
};
|
|||
|
|
}
|