/* eslint-disable no-extend-native -- 该文件用于集中安装旧浏览器缺失的原生 API。 */ import { deserialize, serialize } from '@ungap/structured-clone'; import 'wicg-inert'; type AtFunction = (index: number) => unknown; type StructuredCloneFunction = ( value: T, options?: StructuredSerializeOptions, ) => T; type WithResolversResult = { promise: Promise; reject: (reason?: unknown) => void; resolve: (value: PromiseLike | T) => void; }; const objectConstructor = Object as ObjectConstructor & { hasOwn?: (object: unknown, property: PropertyKey) => boolean; }; const arrayPrototype = Array.prototype as unknown[] & { at?: AtFunction; toReversed?: () => unknown[]; toSorted?: ( compareFn?: (left: unknown, right: unknown) => number, ) => unknown[]; toSpliced?: ( start: number, deleteCount?: number, ...items: unknown[] ) => unknown[]; }; const stringPrototype = String.prototype as string & { at?: AtFunction; }; const promiseConstructor = Promise as PromiseConstructor & { withResolvers?: () => WithResolversResult; }; const urlConstructor = URL as typeof URL & { canParse?: (url: string | URL, base?: string | URL) => boolean; }; const browserGlobal = globalThis as typeof globalThis & { structuredClone?: StructuredCloneFunction; }; /** * 将索引转换为 Array.prototype.at 与 String.prototype.at 使用的整数。 */ function toIntegerOrInfinity(index: number): number { const numericIndex = Number(index); if (Number.isNaN(numericIndex) || numericIndex === 0) return 0; return numericIndex > 0 ? Math.floor(numericIndex) : Math.ceil(numericIndex); } /** * 安装 Object.hasOwn 兼容实现。 */ export function installObjectHasOwnPolyfill() { if (typeof objectConstructor.hasOwn === 'function') return; Object.defineProperty(Object, 'hasOwn', { configurable: true, value(object: unknown, property: PropertyKey) { if (object === null || object === undefined) { throw new TypeError('Cannot convert undefined or null to object'); } // eslint-disable-next-line unicorn/new-for-builtins -- Object() 保留 ToObject 语义。 return Object.prototype.hasOwnProperty.call(Object(object), property); }, writable: true, }); } /** * 安装 Array.prototype.at 兼容实现。 */ export function installArrayAtPolyfill() { if (typeof arrayPrototype.at === 'function') return; Object.defineProperty(Array.prototype, 'at', { configurable: true, value(this: ArrayLike | null | undefined, index: number): unknown { if (this === null || this === undefined) { throw new TypeError('Array.prototype.at called on null or undefined'); } const target = this as ArrayLike; const numericLength = Number(target.length); const length = Number.isNaN(numericLength) || numericLength <= 0 ? 0 : Math.min(Math.floor(numericLength), Number.MAX_SAFE_INTEGER); const relativeIndex = toIntegerOrInfinity(index); const actualIndex = relativeIndex >= 0 ? relativeIndex : length + relativeIndex; if (actualIndex < 0 || actualIndex >= length) return undefined; return target[actualIndex]; }, writable: true, }); } /** * 安装 String.prototype.at 兼容实现。 */ export function installStringAtPolyfill() { if (typeof stringPrototype.at === 'function') return; Object.defineProperty(String.prototype, 'at', { configurable: true, value(this: unknown, index: number): string | undefined { if (this === null || this === undefined) { throw new TypeError('String.prototype.at called on null or undefined'); } const target = String(this); const relativeIndex = toIntegerOrInfinity(index); const actualIndex = relativeIndex >= 0 ? relativeIndex : target.length + relativeIndex; return actualIndex < 0 || actualIndex >= target.length ? undefined : target[actualIndex]; }, writable: true, }); } /** * 安装不会修改原数组的数组复制方法。 */ export function installArrayCopyingPolyfills() { if (typeof arrayPrototype.toReversed !== 'function') { Object.defineProperty(Array.prototype, 'toReversed', { configurable: true, value() { return Array.prototype.slice.call(this).reverse(); }, writable: true, }); } if (typeof arrayPrototype.toSorted !== 'function') { Object.defineProperty(Array.prototype, 'toSorted', { configurable: true, value(compareFn?: (left: unknown, right: unknown) => number) { return Array.prototype.slice.call(this).sort(compareFn); }, writable: true, }); } if (typeof arrayPrototype.toSpliced !== 'function') { Object.defineProperty(Array.prototype, 'toSpliced', { configurable: true, value(start: number, deleteCount?: number, ...items: unknown[]) { const copy = Array.prototype.slice.call(this); if (arguments.length === 1) { copy.splice(start); } else { copy.splice(start, deleteCount as number, ...items); } return copy; }, writable: true, }); } } /** * 安装 Promise.withResolvers 兼容实现。 */ export function installPromiseWithResolversPolyfill() { // eslint-disable-next-line n/no-unsupported-features/es-syntax -- 此处专门检测并补齐该新 API。 if (typeof promiseConstructor.withResolvers === 'function') return; Object.defineProperty(Promise, 'withResolvers', { configurable: true, value(this: PromiseConstructor): WithResolversResult { let reject!: (reason?: unknown) => void; let resolve!: (value: PromiseLike | T) => void; const promise = new this((resolvePromise, rejectPromise) => { resolve = resolvePromise; reject = rejectPromise; }); return { promise, reject, resolve }; }, writable: true, }); } /** * 安装可处理循环引用、Map、Set 与日期的 structuredClone 兼容实现。 */ export function installStructuredClonePolyfill() { if (typeof browserGlobal.structuredClone === 'function') return; Object.defineProperty(globalThis, 'structuredClone', { configurable: true, value: (value: T) => deserialize(serialize(value)) as T, writable: true, }); } /** * 安装 URL.canParse 兼容实现。 */ export function installUrlCanParsePolyfill() { if (typeof urlConstructor.canParse === 'function') return; Object.defineProperty(URL, 'canParse', { configurable: true, value(url: string | URL, base?: string | URL) { try { const parsedUrl = arguments.length > 1 ? new URL(String(url), String(base)) : new URL(String(url)); return parsedUrl instanceof URL; } catch { return false; } }, writable: true, }); } /** * 在业务模块加载前安装 Chrome 90 缺失的运行时能力。 */ export function installLegacyBrowserPolyfills() { installObjectHasOwnPolyfill(); installArrayAtPolyfill(); installStringAtPolyfill(); installArrayCopyingPolyfills(); installPromiseWithResolversPolyfill(); installStructuredClonePolyfill(); installUrlCanParsePolyfill(); } installLegacyBrowserPolyfills();