diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100755 index 000000000..36346becc --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +const fabric = require('@umijs/fabric'); + +module.exports = { + ...fabric.eslint, + rules: { + ...fabric.eslint.rules, + '@typescript-eslint/prefer-interface': 0, + 'no-return-assign': 0 + }, +}; diff --git a/.gitignore b/.gitignore index 2996de842..0bd264732 100755 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ yarn.lock esm lib package-lock.json +.eslintcache \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..7b597d789 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,5 @@ +const fabric = require('@umijs/fabric'); + +module.exports = { + ...fabric.prettier, +}; diff --git a/package.json b/package.json index be968e7e0..7dbf7be9d 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,9 @@ "prepush": "npm run lint", "release": "np --no-cleanup --yolo --no-publish", "prepublishOnly": "npm run lint && npm run build", - "lint": "tslint 'src/**/*.ts'", + "lint": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:prettier", + "lint:prettier": "check-prettier lint", + "prettier": "prettier -c --write **/*", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { @@ -42,14 +44,15 @@ }, "devDependencies": { "@types/lodash": "^4.14.129", + "@umijs/fabric": "^1.1.10", "babel-plugin-import": "^1.12.1", + "check-prettier": "^1.0.3", "concurrently": "^4.1.2", "father-build": "^1.7.0", "husky": "^2.3.0", "np": "^5.0.3", + "prettier": "^1.18.2", "rimraf": "^3.0.0", - "tslint": "^5.16.0", - "tslint-eslint-rules": "^5.4.0", "typescript": "^3.4.5" } } diff --git a/src/effects.ts b/src/effects.ts index 5ceb0c021..7e37fddac 100644 --- a/src/effects.ts +++ b/src/effects.ts @@ -10,28 +10,35 @@ if (process.env.NODE_ENV === 'development') { } export function setDefaultMountApp(defaultAppLink: string) { - - window.addEventListener('single-spa:no-app-change', () => { - const mountedApps = getMountedApps(); - if (!mountedApps.length) { - navigateToUrl(defaultAppLink); - } - }, { once: true }); + window.addEventListener( + 'single-spa:no-app-change', + () => { + const mountedApps = getMountedApps(); + if (!mountedApps.length) { + navigateToUrl(defaultAppLink); + } + }, + { once: true }, + ); } export function runDefaultMountEffects(defaultAppLink: string) { - console.warn('runDefaultMountEffects will be removed in next version, please use setDefaultMountApp instead!'); + console.warn( + 'runDefaultMountEffects will be removed in next version, please use setDefaultMountApp instead!', + ); setDefaultMountApp(defaultAppLink); } export function runAfterFirstMounted(effect: () => void) { - - window.addEventListener('single-spa:first-mount', () => { - - if (process.env.NODE_ENV === 'development') { - console.timeEnd(firstMountLogLabel); - } - - effect(); - }, { once: true }); + window.addEventListener( + 'single-spa:first-mount', + () => { + if (process.env.NODE_ENV === 'development') { + console.timeEnd(firstMountLogLabel); + } + + effect(); + }, + { once: true }, + ); } diff --git a/src/hijackers/historyListener.ts b/src/hijackers/historyListener.ts index de1853561..67cd2463d 100644 --- a/src/hijackers/historyListener.ts +++ b/src/hijackers/historyListener.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/array-type */ + /** * @author Kuitos * @since 2019-04-11 @@ -6,7 +8,6 @@ import { isFunction, noop } from 'lodash'; export default function hijack() { - // FIXME umi unmount feature request // @see http://gitlab.alipay-inc.com/bigfish/bigfish/issues/1154 let rawHistoryListen = (..._: any[]) => noop; @@ -14,11 +15,9 @@ export default function hijack() { const historyUnListens: Array = []; if ((window as any).g_history && isFunction((window as any).g_history.listen)) { - rawHistoryListen = (window as any).g_history.listen.bind((window as any).g_history); (window as any).g_history.listen = (listener: typeof noop) => { - historyListeners.push(listener); const unListen = rawHistoryListen(listener); @@ -33,7 +32,6 @@ export default function hijack() { } return function free() { - let rebuild = noop; /* diff --git a/src/hijackers/index.ts b/src/hijackers/index.ts index 89da516ed..bc0554daf 100644 --- a/src/hijackers/index.ts +++ b/src/hijackers/index.ts @@ -9,11 +9,9 @@ import hijackTimer from './timer'; import hijackWindowListener from './windowListener'; export function hijack(): Freer[] { - return [ hijackTimer(), hijackWindowListener(), hijackHistoryListener(), ]; - } diff --git a/src/hijackers/timer.ts b/src/hijackers/timer.ts index 85d01e01d..4ab909b38 100644 --- a/src/hijackers/timer.ts +++ b/src/hijackers/timer.ts @@ -7,7 +7,6 @@ import { noop } from 'lodash'; import { sleep } from '../utils'; export default function hijack() { - const rawWindowInterval = window.setInterval; const rawWindowTimeout = window.setTimeout; const timerIds: number[] = []; diff --git a/src/hijackers/windowListener.ts b/src/hijackers/windowListener.ts index 22ceb95a7..7bf20d6af 100644 --- a/src/hijackers/windowListener.ts +++ b/src/hijackers/windowListener.ts @@ -6,30 +6,39 @@ import { noop } from 'lodash'; export default function hijack() { - const listenerMap = new Map(); const rawAddEventListener = window.addEventListener; const rawRemoveEventListener = window.removeEventListener; - window.addEventListener = - (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => { + window.addEventListener = ( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ) => { const listeners = listenerMap.get(type) || []; listenerMap.set(type, [...listeners, listener]); return rawAddEventListener.call(window, type, listener, options); }; - window.removeEventListener = - (type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions) => { + window.removeEventListener = ( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions, + ) => { const storedTypeListeners = listenerMap.get(type); - if (storedTypeListeners && storedTypeListeners.length && storedTypeListeners.indexOf(listener) !== -1) { + if ( + storedTypeListeners && storedTypeListeners.length + && storedTypeListeners.indexOf(listener) !== -1 + ) { storedTypeListeners.splice(storedTypeListeners.indexOf(listener), 1); } return rawRemoveEventListener.call(window, type, listener, options); }; return function free() { - - listenerMap.forEach((listeners, type) => [...listeners].forEach(listener => window.removeEventListener(type, listener))); + listenerMap + .forEach((listeners, type) => [...listeners] + .forEach(listener => window.removeEventListener(type, listener))); window.addEventListener = rawAddEventListener; window.removeEventListener = rawRemoveEventListener; diff --git a/src/index.ts b/src/index.ts index 78c2a918a..dc50f4e22 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/array-type */ + /** * @author Kuitos * @since 2019-04-25 @@ -32,7 +34,10 @@ function toArray(array: T | T[]): T[] { return Array.isArray(array) ? array : [array]; } -function execHooksChain(hooks: Array>, app: RegistrableApp): Promise { +function execHooksChain( + hooks: Array>, + app: RegistrableApp, +): Promise { if (hooks.length) { return hooks.reduce((chain, hook) => chain.then(() => hook(app)), Promise.resolve()); } @@ -40,14 +45,18 @@ function execHooksChain(hooks: Array>, app: Regis return Promise.resolve(); } -async function validateSingularMode(validate: StartOpts['singular'], app: RegistrableApp): Promise { +async function validateSingularMode( + validate: StartOpts['singular'], + app: RegistrableApp, +): Promise { return typeof validate === 'function' ? validate(app) : !!validate; } class Deferred { - promise: Promise; + resolve!: (value?: T | PromiseLike) => void; + reject!: (reason?: any) => void; constructor() { @@ -58,21 +67,28 @@ class Deferred { } } -export function registerMicroApps(apps: Array>, lifeCycles: LifeCycles = {}) { - - const { beforeUnmount = [], afterUnmount = [], afterMount = [], beforeMount = [], beforeLoad = [] } = lifeCycles; +export function registerMicroApps( + apps: Array>, + lifeCycles: LifeCycles = {}, +) { + const { + beforeUnmount = [], + afterUnmount = [], + afterMount = [], + beforeMount = [], + beforeLoad = [], + } = lifeCycles; microApps = [...microApps, ...apps]; let prevAppUnmountedDeferred: Deferred; apps.forEach(app => { - const { name, entry, render, activeRule, props = {} } = app; - registerApplication(name, + registerApplication( + name, async ({ name: appName }) => { - // 获取入口 html 模板及脚本加载器 const { template: appContent, execScripts } = await importEntry(entry); // as single-spa load and bootstrap new app parallel with other apps unmounting @@ -105,12 +121,9 @@ export function registerMicroApps(apps: Array - await validateSingularMode(singularMode, app) ? prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise : void 0, + async () => (await validateSingularMode(singularMode, app) ? prevAppUnmountedDeferred && prevAppUnmountedDeferred.promise : undefined), async () => execHooksChain(toArray(beforeMount), app), // 添加 mount hook, 确保每次应用加载前容器 dom 结构已经设置完毕 async () => render({ appContent, loading: true }), @@ -120,15 +133,14 @@ export function registerMicroApps(apps: Array render({ appContent, loading: false }), async () => execHooksChain(toArray(afterMount), app), // initialize the unmount defer after app mounted and resolve the defer after it unmounted - async () => await validateSingularMode(singularMode, app) ? prevAppUnmountedDeferred = new Deferred() : void 0, + async () => (await validateSingularMode(singularMode, app) ? (prevAppUnmountedDeferred = new Deferred()) : undefined), ], unmount: [ async () => execHooksChain(toArray(beforeUnmount), app), unmount, unmountSandbox, async () => execHooksChain(toArray(afterUnmount), app), - async () => - await validateSingularMode(singularMode, app) ? prevAppUnmountedDeferred && prevAppUnmountedDeferred.resolve() : void 0, + async () => (await validateSingularMode(singularMode, app) ? prevAppUnmountedDeferred && prevAppUnmountedDeferred.resolve() : undefined), ], }; }, @@ -149,7 +161,7 @@ let useJsSandbox = false; let singularMode: StartOpts['singular'] = false; export function start(opts: StartOpts = {}) { - + // eslint-disable-next-line no-underscore-dangle window.__POWERED_BY_QIANKUN__ = true; const { prefetch = true, jsSandbox = true, singular = true } = opts; diff --git a/src/interfaces.ts b/src/interfaces.ts index 49ee53975..e3d9e75ae 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -3,16 +3,18 @@ * @since 2019-05-16 */ -export type render = (props: { appContent: string, loading: boolean }) => any; -export type Entry = string | { - scripts?: string[]; - styles?: string[]; - html?: string; -}; +export type render = (props: { appContent: string; loading: boolean }) => any; +export type Entry = + | string + | { + scripts?: string[]; + styles?: string[]; + html?: string; + }; export type RegistrableApp = { name: string; // app name - entry: Entry; // app entry + entry: Entry; // app entry render: render; activeRule: (location: Location) => boolean; props?: T; // props pass through to app diff --git a/src/prefetch.ts b/src/prefetch.ts index c6783f06a..70f8caccc 100644 --- a/src/prefetch.ts +++ b/src/prefetch.ts @@ -13,7 +13,6 @@ import { RegistrableApp } from './interfaces'; * @param entry */ export function prefetch(entry: Entry) { - const requestIdleCallback = window.requestIdleCallback || noop; requestIdleCallback(async () => { @@ -21,21 +20,21 @@ export function prefetch(entry: Entry) { requestIdleCallback(getExternalStyleSheets); requestIdleCallback(getExternalScripts); }); - } export function prefetchAfterFirstMounted(apps: RegistrableApp[]) { - - window.addEventListener('single-spa:first-mount', () => { - - const mountedApps = getMountedApps(); - const notMountedApps = apps.filter(app => mountedApps.indexOf(app.name) === -1); - - if (process.env.NODE_ENV === 'development') { - console.log('prefetch starting...', notMountedApps); - } - - notMountedApps.forEach(app => prefetch(app.entry)); - }, { once: true }); - + window.addEventListener( + 'single-spa:first-mount', + () => { + const mountedApps = getMountedApps(); + const notMountedApps = apps.filter(app => mountedApps.indexOf(app.name) === -1); + + if (process.env.NODE_ENV === 'development') { + console.log('prefetch starting...', notMountedApps); + } + + notMountedApps.forEach(app => prefetch(app.entry)); + }, + { once: true }, + ); } diff --git a/src/sandbox.ts b/src/sandbox.ts index 06a5c7727..ed35042ad 100644 --- a/src/sandbox.ts +++ b/src/sandbox.ts @@ -23,16 +23,12 @@ function isPropConfigurable(target: object, prop: PropertyKey) { } function setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) { - if (value === undefined && toDelete) { delete (window as any)[prop]; - } else { - if (isPropConfigurable(window, prop) && typeof prop !== 'symbol') { - Object.defineProperty(window, prop, { writable: true, configurable: true }); - (window as any)[prop] = value; - } + } else if (isPropConfigurable(window, prop) && typeof prop !== 'symbol') { + Object.defineProperty(window, prop, { writable: true, configurable: true }); + (window as any)[prop] = value; } - } /** @@ -50,7 +46,6 @@ function setWindowProp(prop: PropertyKey, value: any, toDelete?: boolean) { * @param appName */ export function genSandbox(appName: string) { - // 沙箱期间新增的全局变量 const addedPropsMapInSandbox = new Map(); // 沙箱期间更新的全局变量 @@ -67,11 +62,8 @@ export function genSandbox(appName: string) { const boundValueSymbol = Symbol('bound value'); const sandbox = new Proxy(window, { - set(target: Window, p: PropertyKey, value: any): boolean { - if (inAppSandbox) { - if (!target.hasOwnProperty(p)) { addedPropsMapInSandbox.set(p, value); } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) { @@ -82,20 +74,22 @@ export function genSandbox(appName: string) { currentUpdatedPropsValueMapForSnapshot.set(p, value); // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据 + // eslint-disable-next-line no-param-reassign (target as any)[p] = value; return true; } if (process.env.NODE_ENV === 'development') { - console.warn(`Try to set window.${p.toString()} while js sandbox destroyed or not active in ${appName}!`); + console.warn( + `Try to set window.${p.toString()} while js sandbox destroyed or not active in ${appName}!`, + ); } return false; }, get(target: Window, p: PropertyKey) { - const value = (target as any)[p]; /* 仅绑定 !isConstructable && isCallable 的函数对象,如 window.console、window.atob 这类。目前没有完美的检测方式,这里通过 prototype 中是否还有可枚举的拓展方法的方式来判断 @@ -115,7 +109,6 @@ export function genSandbox(appName: string) { return value; }, - }); return { @@ -127,7 +120,6 @@ export function genSandbox(appName: string) { * 也可能是从 unmount 之后再次唤醒进入 mount */ async mount() { - /* ------------------------------------------ 因为有上下文依赖(window),以下代码执行顺序不能变 ------------------------------------------ */ /* ------------------------------------------ 1. 启动/恢复 快照 ------------------------------------------ */ @@ -157,11 +149,11 @@ export function genSandbox(appName: string) { * 恢复 global 状态,使其能回到应用加载之前的状态 */ async unmount() { - if (process.env.NODE_ENV === 'development') { - console.info(`${appName} modified global properties will be restore`, - [...addedPropsMapInSandbox.keys(), ...modifiedPropsOriginalValueMapInSandbox.keys()], - ); + console.info(`${appName} modified global properties will be restore`, [ + ...addedPropsMapInSandbox.keys(), + ...modifiedPropsOriginalValueMapInSandbox.keys(), + ]); } // record the rebuilders of window side effects (event listeners or timers) diff --git a/src/utils.ts b/src/utils.ts index 1bbbc4238..5d2d06dfd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,8 +12,10 @@ export function isConstructable(fn: () => void | FunctionConstructor) { const classRegex = /^class\b/; // 有 prototype 并且 prototype 上有定义一系列非 constructor 属性,则可以认为是一个构造函数 - return fn.prototype - && Object.getOwnPropertyNames(fn.prototype).filter(k => k !== 'constructor').length - || constructableFunctionRegex.test(fn.toString()) - || classRegex.test(fn.toString()); + return ( + (fn.prototype && + Object.getOwnPropertyNames(fn.prototype).filter(k => k !== 'constructor').length) || + constructableFunctionRegex.test(fn.toString()) || + classRegex.test(fn.toString()) + ); } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 584dab03f..000000000 --- a/tslint.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "defaultSeverity": "error", - "extends": [ - "tslint:recommended", - "tslint-eslint-rules" - ], - "jsRules": {}, - "rules": { - "indent": [ - true, - "spaces", - 2 - ], - "interface-name": false, - "quotemark": [ - true, - "single", - "jsx-double" - ], - "no-console": false, - "arrow-parens": [ - "ban-single-arg-parens" - ], - "object-literal-sort-keys": false, - "object-curly-spacing": [ - true, - "always" - ], - "member-access": false, - "member-ordering": [ - true, - { - "order": [ - "private-static-field", - "public-static-field", - "private-instance-field", - "public-instance-field", - "private-constructor", - "public-constructor", - "private-instance-method", - "protected-instance-method", - "public-static-method", - "public-instance-method" - ] - } - ], - "max-classes-per-file": [ - true, - 10, - "exclude-class-expressions" - ], - "no-unused-expression": true, - "max-line-length": [ - true, - 140 - ], - "interface-over-type-literal": false, - "semicolon": [ - true, - "always", - "strict-bound-class-methods" - ], - "trailing-comma": [ - true, - { - "singleline": "never", - "multiline": "always" - } - ], - "space-before-function-paren": [ - true, - { - "anonymous": "always", - "named": "never", - "asyncArrow": "always" - } - ], - "no-namespace": [ - true, - "allow-declarations" - ] - }, - "linterOptions": { - "exclude": [ - "**/*.json" - ] - } -} diff --git a/typings/index.d.ts b/typings/index.d.ts index d886a8791..93d415954 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -6,13 +6,13 @@ type RequestIdleCallbackOptions = { }; type RequestIdleCallbackDeadline = { readonly didTimeout: boolean; - timeRemaining: (() => number); + timeRemaining: () => number; }; interface Window { - requestIdleCallback: (( - callback: ((deadline: RequestIdleCallbackDeadline) => void), + requestIdleCallback: ( + callback: (deadline: RequestIdleCallbackDeadline) => void, opts?: RequestIdleCallbackOptions, - ) => RequestIdleCallbackHandle); - cancelIdleCallback: ((handle: RequestIdleCallbackHandle) => void); + ) => RequestIdleCallbackHandle; + cancelIdleCallback: (handle: RequestIdleCallbackHandle) => void; }