Files
EasyFlow/easyflow-ui-admin/app/src/polyfills.ts
陈子默 27e50a7624 fix: 兼容 Chrome 90 前端运行环境
- 为管理端与用户中心补齐旧浏览器运行时 API 和回归测试

- 增加现代 CSS 与选择器降级,保持现有界面效果

- 固定 Chrome 90 构建目标并补充兼容依赖
2026-07-16 14:50:06 +08:00

242 lines
7.2 KiB
TypeScript

/* eslint-disable no-extend-native -- 该文件用于集中安装旧浏览器缺失的原生 API。 */
import { deserialize, serialize } from '@ungap/structured-clone';
import 'wicg-inert';
type AtFunction = (index: number) => unknown;
type StructuredCloneFunction = <T>(
value: T,
options?: StructuredSerializeOptions,
) => T;
type WithResolversResult<T> = {
promise: Promise<T>;
reject: (reason?: unknown) => void;
resolve: (value: PromiseLike<T> | 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?: <T>() => WithResolversResult<T>;
};
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<unknown> | 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<unknown>;
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<T>(this: PromiseConstructor): WithResolversResult<T> {
let reject!: (reason?: unknown) => void;
let resolve!: (value: PromiseLike<T> | T) => void;
const promise = new this<T>((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: <T>(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();