178 lines
4.8 KiB
TypeScript
178 lines
4.8 KiB
TypeScript
/**
|
|
* @author Kuitos
|
|
* @since 2019-05-15
|
|
*/
|
|
|
|
import { isFunction, snakeCase } from 'lodash';
|
|
import { FrameworkConfiguration } from './interfaces';
|
|
|
|
export function toArray<T>(array: T | T[]): T[] {
|
|
return Array.isArray(array) ? array : [array];
|
|
}
|
|
|
|
export function sleep(ms: number) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
/**
|
|
* run a callback after next tick
|
|
* @param cb
|
|
*/
|
|
export function nextTick(cb: () => void): void {
|
|
Promise.resolve().then(cb);
|
|
}
|
|
|
|
const constructableMap = new WeakMap<Function, boolean>();
|
|
export function isConstructable(fn: () => any | FunctionConstructor) {
|
|
if (constructableMap.has(fn)) {
|
|
return constructableMap.get(fn);
|
|
}
|
|
|
|
const constructableFunctionRegex = /^function\b\s[A-Z].*/;
|
|
const classRegex = /^class\b/;
|
|
|
|
// 有 prototype 并且 prototype 上有定义一系列非 constructor 属性,则可以认为是一个构造函数
|
|
const constructable =
|
|
(fn.prototype && fn.prototype.constructor === fn && Object.getOwnPropertyNames(fn.prototype).length > 1) ||
|
|
constructableFunctionRegex.test(fn.toString()) ||
|
|
classRegex.test(fn.toString());
|
|
constructableMap.set(fn, constructable);
|
|
return constructable;
|
|
}
|
|
|
|
/**
|
|
* in safari
|
|
* typeof document.all === 'undefined' // true
|
|
* typeof document.all === 'function' // true
|
|
* We need to discriminate safari for better performance
|
|
*/
|
|
const naughtySafari = typeof document.all === 'function' && typeof document.all === 'undefined';
|
|
export const isCallable = naughtySafari
|
|
? (fn: any) => typeof fn === 'function' && typeof fn !== 'undefined'
|
|
: (fn: any) => typeof fn === 'function';
|
|
|
|
const boundedMap = new WeakMap<CallableFunction, boolean>();
|
|
export function isBoundedFunction(fn: CallableFunction) {
|
|
if (boundedMap.has(fn)) {
|
|
return boundedMap.get(fn);
|
|
}
|
|
/*
|
|
indexOf is faster than startsWith
|
|
see https://jsperf.com/string-startswith/72
|
|
*/
|
|
const bounded = fn.name.indexOf('bound ') === 0 && !fn.hasOwnProperty('prototype');
|
|
boundedMap.set(fn, bounded);
|
|
return bounded;
|
|
}
|
|
|
|
/**
|
|
* fastest(at most time) unique array method
|
|
* @see https://jsperf.com/array-filter-unique/30
|
|
*/
|
|
export function uniq(array: PropertyKey[]) {
|
|
return array.filter(function filter(this: string[], element) {
|
|
return element in this ? false : ((this as any)[element] = true);
|
|
}, {});
|
|
}
|
|
|
|
export function getDefaultTplWrapper(id: string, name: string) {
|
|
return (tpl: string) => `<div id="${getWrapperId(id)}" data-name="${name}">${tpl}</div>`;
|
|
}
|
|
|
|
export function getWrapperId(id: string) {
|
|
return `__qiankun_microapp_wrapper_for_${snakeCase(id)}__`;
|
|
}
|
|
|
|
/** 校验子应用导出的 生命周期 对象是否正确 */
|
|
export function validateExportLifecycle(exports: any) {
|
|
const { bootstrap, mount, unmount } = exports ?? {};
|
|
return isFunction(bootstrap) && isFunction(mount) && isFunction(unmount);
|
|
}
|
|
|
|
class Deferred<T> {
|
|
promise: Promise<T>;
|
|
|
|
resolve!: (value?: T | PromiseLike<T>) => void;
|
|
|
|
reject!: (reason?: any) => void;
|
|
|
|
constructor() {
|
|
this.promise = new Promise((resolve, reject) => {
|
|
this.resolve = resolve;
|
|
this.reject = reject;
|
|
});
|
|
}
|
|
}
|
|
|
|
export { Deferred };
|
|
|
|
const supportsUserTiming =
|
|
typeof performance !== 'undefined' &&
|
|
typeof performance.mark === 'function' &&
|
|
typeof performance.clearMarks === 'function' &&
|
|
typeof performance.measure === 'function' &&
|
|
typeof performance.clearMeasures === 'function';
|
|
|
|
export function performanceMark(markName: string) {
|
|
if (supportsUserTiming) {
|
|
performance.mark(markName);
|
|
}
|
|
}
|
|
|
|
export function performanceMeasure(measureName: string, markName: string) {
|
|
if (supportsUserTiming) {
|
|
performance.measure(measureName, markName);
|
|
performance.clearMarks(markName);
|
|
performance.clearMeasures(measureName);
|
|
}
|
|
}
|
|
|
|
export function isEnableScopedCSS(opt: FrameworkConfiguration) {
|
|
if (typeof opt.sandbox !== 'object') {
|
|
return false;
|
|
}
|
|
|
|
if (opt.sandbox.strictStyleIsolation) {
|
|
return false;
|
|
}
|
|
|
|
return !!opt.sandbox.experimentalStyleIsolation;
|
|
}
|
|
|
|
/**
|
|
* copy from https://developer.mozilla.org/zh-CN/docs/Using_XPath
|
|
* @param el
|
|
* @param xml
|
|
*/
|
|
export function getXPathForElement(el: Node, xml: Document) {
|
|
let xpath = '';
|
|
let pos;
|
|
let tmpEle;
|
|
let element = el;
|
|
|
|
while (element !== xml.documentElement) {
|
|
pos = 0;
|
|
tmpEle = element;
|
|
while (tmpEle) {
|
|
if (tmpEle.nodeType === 1 && tmpEle.nodeName === element.nodeName) {
|
|
// If it is ELEMENT_NODE of the same name
|
|
pos += 1;
|
|
}
|
|
tmpEle = tmpEle.previousSibling;
|
|
}
|
|
|
|
xpath = `*[name()='${element.nodeName}' and namespace-uri()='${
|
|
element.namespaceURI === null ? '' : element.namespaceURI
|
|
}'][${pos}]/${xpath}`;
|
|
|
|
element = element.parentNode!;
|
|
}
|
|
|
|
xpath = `/*[name()='${xml.documentElement.nodeName}' and namespace-uri()='${
|
|
element.namespaceURI === null ? '' : element.namespaceURI
|
|
}']/${xpath}`;
|
|
xpath = xpath.replace(/\/$/, '');
|
|
|
|
return xpath;
|
|
}
|