From 2a0dd5d17dc459b2bfa61c8858c7fd211b4ae222 Mon Sep 17 00:00:00 2001 From: maoxiaoke Date: Fri, 3 Dec 2021 16:49:24 +0800 Subject: [PATCH 01/10] =?UTF-8?q?feat:=20=F0=9F=8E=B8=202.7.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9a76f30a..235d5e8c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ice/stark", - "version": "2.6.2", + "version": "2.7.0", "description": "Icestark is a JavaScript library for multiple projects, Ice workbench solution.", "scripts": { "install:deps": "rm -rf node_modules && rm -rf ./packages/*/node_modules && yarn install && lerna exec -- npm install", From 84dead44c187e61648c9131de3aec927717484cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Fri, 3 Dec 2021 17:15:48 +0800 Subject: [PATCH 02/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20add=20missing=20pro?= =?UTF-8?q?ps=20for=20lifecycles=20(#440)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/icestark-app/CHANGELOG.md | 4 ++++ packages/icestark-app/package.json | 2 +- packages/icestark-app/src/index.ts | 2 ++ packages/icestark-app/src/registerAppEnter.ts | 7 ++++++- packages/icestark-app/src/registerAppLeave.ts | 3 ++- 5 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/icestark-app/CHANGELOG.md b/packages/icestark-app/CHANGELOG.md index 12ce1bbc..cf7f259f 100644 --- a/packages/icestark-app/CHANGELOG.md +++ b/packages/icestark-app/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.4.3 + +- [fix] add missing props for `registerAppEnter` & `registerAppLeave`. + ## 1.4.2 - [fix] Bind history to window when using `AppLink`. ([#428](https://github.com/ice-lab/icestark/pull/428)) diff --git a/packages/icestark-app/package.json b/packages/icestark-app/package.json index 3ce29234..7e103d0a 100644 --- a/packages/icestark-app/package.json +++ b/packages/icestark-app/package.json @@ -1,6 +1,6 @@ { "name": "@ice/stark-app", - "version": "1.4.2", + "version": "1.4.3", "description": "icestark-app is a JavaScript library for icestark, used by sub-application.", "scripts": { "build": "rm -rf lib && tsc", diff --git a/packages/icestark-app/src/index.ts b/packages/icestark-app/src/index.ts index 72042d47..0cf77553 100644 --- a/packages/icestark-app/src/index.ts +++ b/packages/icestark-app/src/index.ts @@ -8,3 +8,5 @@ export { default as appHistory } from './appHistory'; export { default as isInIcestark } from './isInIcestark'; export { default as AppLink } from './AppLink'; export { default as setLibraryName } from './setLibraryName'; + +export type { LifecycleProps } from './registerAppEnter'; diff --git a/packages/icestark-app/src/registerAppEnter.ts b/packages/icestark-app/src/registerAppEnter.ts index 44fded3b..c534c62f 100644 --- a/packages/icestark-app/src/registerAppEnter.ts +++ b/packages/icestark-app/src/registerAppEnter.ts @@ -1,6 +1,11 @@ import { setCache } from './cache'; -export default (callback?: () => void): void => { +export interface LifecycleProps { + container: HTMLElement | string; + customProps?: object; +} + +export default (callback?: (props: LifecycleProps) => void): void => { if (!callback) return; if (typeof callback !== 'function') { diff --git a/packages/icestark-app/src/registerAppLeave.ts b/packages/icestark-app/src/registerAppLeave.ts index 9c1794d0..0163e031 100644 --- a/packages/icestark-app/src/registerAppLeave.ts +++ b/packages/icestark-app/src/registerAppLeave.ts @@ -1,6 +1,7 @@ import { setCache } from './cache'; +import type { LifecycleProps } from './registerAppEnter'; -export default (callback?: () => void): void => { +export default (callback?: (props: LifecycleProps) => void): void => { if (!callback) return; if (typeof callback !== 'function') { From 479b7c2a082dfe01eb456acbf909e237f20314df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Mon, 6 Dec 2021 20:44:19 +0800 Subject: [PATCH 03/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20narrow=20import=20e?= =?UTF-8?q?rror=20(#467)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 narrow import error ✅ Closes: #466 --- src/util/loaders.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/util/loaders.ts b/src/util/loaders.ts index 5e0a75cb..77f33ab5 100644 --- a/src/util/loaders.ts +++ b/src/util/loaders.ts @@ -86,6 +86,7 @@ export async function loadScriptByImport(jsList: Asset[]): Promise Date: Tue, 7 Dec 2021 12:14:26 +0800 Subject: [PATCH 04/10] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20upgrade=20vi?= =?UTF-8?q?te-plugin-index-html=20usage=20(#462)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- website/docs/guide/use-child/others.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/guide/use-child/others.md b/website/docs/guide/use-child/others.md index 7efdece8..ad42cda7 100644 --- a/website/docs/guide/use-child/others.md +++ b/website/docs/guide/use-child/others.md @@ -532,12 +532,12 @@ import { defineConfig } from 'vite'; import vue from '@vitejs/plugin-vue'; + import htmlPlugin from 'vite-plugin-index-html'; - export default defineConfig({ plugins: [ vue(), + htmlPlugin({ -+ input: './src/main.ts' ++ input: './src/main.ts', // 指定确定的入口文件 ++ preserveEntrySignatures: "exports-only", // 确保入口文件导出生命周期函数 + }) ], From 2f42a89f4a68169424bb33b9b11a2fe02e054817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Wed, 8 Dec 2021 11:05:00 +0800 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20`appHistory`=20and?= =?UTF-8?q?=20``=20can=20both=20take=20state=20(#477)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 `appHistory` and `` can both take state ✅ Closes: #478 --- packages/icestark-app/CHANGELOG.md | 5 +- packages/icestark-app/package.json | 3 +- packages/icestark-app/src/AppLink.tsx | 27 ++++++-- packages/icestark-app/src/appHistory.ts | 15 +++-- .../icestark-app/src/util/normalizeArgs.ts | 16 +++++ packages/icestark-app/tests/index.spec.tsx | 17 +++++- packages/icestark-data/package.json | 1 - website/docs/api/ice-stark-app.md | 61 ++++++++++++++++--- 8 files changed, 120 insertions(+), 25 deletions(-) create mode 100644 packages/icestark-app/src/util/normalizeArgs.ts diff --git a/packages/icestark-app/CHANGELOG.md b/packages/icestark-app/CHANGELOG.md index cf7f259f..765cc6d8 100644 --- a/packages/icestark-app/CHANGELOG.md +++ b/packages/icestark-app/CHANGELOG.md @@ -1,12 +1,13 @@ # Changelog -## 1.4.3 +## 1.5.0 +- [feat] `appHistory` and `` can both take state. ([#478](https://github.com/ice-lab/icestark/pull/428)) - [fix] add missing props for `registerAppEnter` & `registerAppLeave`. ## 1.4.2 -- [fix] Bind history to window when using `AppLink`. ([#428](https://github.com/ice-lab/icestark/pull/428)) +- [fix] bind history to window when using `AppLink`. ([#428](https://github.com/ice-lab/icestark/pull/428)) ## 1.4.1 diff --git a/packages/icestark-app/package.json b/packages/icestark-app/package.json index 7e103d0a..ea4dfe24 100644 --- a/packages/icestark-app/package.json +++ b/packages/icestark-app/package.json @@ -1,6 +1,6 @@ { "name": "@ice/stark-app", - "version": "1.4.3", + "version": "1.5.0", "description": "icestark-app is a JavaScript library for icestark, used by sub-application.", "scripts": { "build": "rm -rf lib && tsc", @@ -42,7 +42,6 @@ "@types/jest": "^24.0.12", "@types/node": "^12.0.0", "codecov": "^3.4.0", - "eslint": "^5.16.0", "husky": "^2.2.0", "jest": "^24.7.1", "stylelint": "^10.1.0", diff --git a/packages/icestark-app/src/AppLink.tsx b/packages/icestark-app/src/AppLink.tsx index 0f6924e5..f70b8acb 100644 --- a/packages/icestark-app/src/AppLink.tsx +++ b/packages/icestark-app/src/AppLink.tsx @@ -1,8 +1,23 @@ import * as React from 'react'; import formatUrl from './util/formatUrl'; +interface To { + /** + * A string representing the path link to + */ + pathname: string; + /** + * A string representing the url search to + */ + search?: string; + /** + * A string representing the url state to + */ + state?: object; +} + export type AppLinkProps = { - to: string; + to: string | To; hashType?: boolean; replace?: boolean; message?: string; @@ -11,12 +26,16 @@ export type AppLinkProps = { const AppLink = (props: AppLinkProps) => { const { to, hashType, replace, message, children, ...rest } = props; - const linkTo = formatUrl(to, hashType); + + const _to = typeof to === 'object' ? (to.pathname + to.search) : to; + const _state = typeof to === 'object' ? to.state : {}; + + const linkTo = formatUrl(_to, hashType); return ( { + onClick={(e) => { e.preventDefault(); // eslint-disable-next-line no-alert if (message && window.confirm(message) === false) { @@ -28,7 +47,7 @@ const AppLink = (props: AppLinkProps) => { */ const changeState = window.history[replace ? 'replaceState' : 'pushState'].bind(window); - changeState({}, null, linkTo); + changeState(_state ?? {}, null, linkTo); }} > {children} diff --git a/packages/icestark-app/src/appHistory.ts b/packages/icestark-app/src/appHistory.ts index 34284c8d..907ab215 100644 --- a/packages/icestark-app/src/appHistory.ts +++ b/packages/icestark-app/src/appHistory.ts @@ -1,18 +1,21 @@ import formatUrl from './util/formatUrl'; +import normalizeArgs from './util/normalizeArgs'; const appHistory = { - push: (url: string, hashType?: boolean) => { + push: (url: string, state?: object | boolean, hashType?: boolean) => { + const [_state, _hashType] = normalizeArgs(state, hashType); window.history.pushState( - {}, + _state ?? {}, null, - formatUrl(url, hashType), + formatUrl(url, _hashType), ); }, - replace: (url: string, hashType?: boolean) => { + replace: (url: string, state?: object | boolean, hashType?: boolean) => { + const [_state, _hashType] = normalizeArgs(state, hashType); window.history.replaceState( - {}, + _state ?? {}, null, - formatUrl(url, hashType), + formatUrl(url, _hashType), ); }, }; diff --git a/packages/icestark-app/src/util/normalizeArgs.ts b/packages/icestark-app/src/util/normalizeArgs.ts new file mode 100644 index 00000000..2317b0de --- /dev/null +++ b/packages/icestark-app/src/util/normalizeArgs.ts @@ -0,0 +1,16 @@ +// `hashType' was relocated to the third argument. +const isDev = process.env.NODE_ENV === 'development'; + +const normalizeArgs = (state?: object | boolean, hashType?: boolean): [object, boolean] => { + if (typeof state === 'boolean') { + isDev && console.warn('[icestark]: hashType was relocated to the third argument.'); + return [{}, hashType ?? state]; + } + if (typeof state === 'object') { + return [state, hashType]; + } + + return [{}, hashType]; +}; + +export default normalizeArgs; diff --git a/packages/icestark-app/tests/index.spec.tsx b/packages/icestark-app/tests/index.spec.tsx index e41e22ea..c1da2e6a 100644 --- a/packages/icestark-app/tests/index.spec.tsx +++ b/packages/icestark-app/tests/index.spec.tsx @@ -12,6 +12,7 @@ import { } from '../src/index'; import { setCache, getCache } from '../src/cache'; import formatUrl from '../src/util/formatUrl'; +import normalizeArgs from '../src/util/normalizeArgs'; const namespace = 'ICESTARK'; @@ -146,4 +147,18 @@ describe('formatUrl', () => { expect(formatUrl('/seller', true)).toBe('#/seller'); }) -}) +}); + +describe('normalizeArgs', () => { + test('normalizeArgs', () => { + expect(normalizeArgs(true)).toEqual([{}, true]); + expect(normalizeArgs(false, true)).toEqual([{}, true]); + + expect(normalizeArgs({ framework: 'icestark' })).toEqual([{ framework: 'icestark' }, undefined]); + expect(normalizeArgs({ framework: 'icestark' }, true)).toEqual([{ framework: 'icestark' }, true]); + expect(normalizeArgs({ framework: 'icestark' }, false)).toEqual([{ framework: 'icestark' }, false]); + + expect(normalizeArgs()).toEqual([{}, undefined]); + expect(normalizeArgs(null)).toEqual([null, undefined]); + }) +}); diff --git a/packages/icestark-data/package.json b/packages/icestark-data/package.json index fdd0cad6..66231a2e 100644 --- a/packages/icestark-data/package.json +++ b/packages/icestark-data/package.json @@ -42,7 +42,6 @@ "@types/jest": "^24.0.12", "@types/node": "^12.0.0", "codecov": "^3.4.0", - "eslint": "^5.16.0", "husky": "^2.2.0", "jest": "^24.7.1", "stylelint": "^10.1.0", diff --git a/website/docs/api/ice-stark-app.md b/website/docs/api/ice-stark-app.md index 7d08188f..656f1b71 100644 --- a/website/docs/api/ice-stark-app.md +++ b/website/docs/api/ice-stark-app.md @@ -50,8 +50,9 @@ import isInIcestark from '@ice/stark-app/lib/isInIcestark'; #### appHistory.push -- 类型:`function` -- 代码示例: +- 类型定义:` (url: string, state?: object, hashType?: boolean) => void` + +代码示例一:跳转 url 页面 ```js import React from 'react'; @@ -62,7 +63,7 @@ export default class SelfLink extends React.Component { return ( { - appHistory.push('/home'); + appHistory.push('/home?name=ice'); }} > selfLink @@ -72,20 +73,33 @@ export default class SelfLink extends React.Component { } ``` +代码示例二:传递 `state` + +```js +appHistory.push('/home?name=ice', { framework: 'icestark' }); +``` + +代码示例三:设置为 hash 路由模式 + +```js +appHistory.push('/home?name=ice', {}, true); +} +``` + #### appHistory.replace -- 类型:`function` +- 函数类型定义:- 类型定义:` (url: string, state?: object, hashType?: boolean) => void` - 代码示例参考 `appHistory.push` ## AppLink -提供声明式的,可访问的导航,表示本次跳转需要重新加载静态资源。微应用内部跳转仍然使用 `Link` +提供声明式的,可访问的导航,表示本次跳转需要重新加载静态资源。微应用内部跳转仍然使用 `Link`。 #### to 目标路径,同 `Link` 中的 `to` 保持一致 ,必填 -- 类型:`string` +- 类型:`string | object` - 默认值:`-` #### replace @@ -109,7 +123,27 @@ export default class SelfLink extends React.Component { - 类型:`boolean` - 默认值:`false` -代码示例: +代码示例一:`to` 为字符串,传递 query 参数 + +```js +import React from 'react'; +import { Link } from 'react-router-dom'; +import { AppLink } from '@ice/stark'; + +export default class SelfLink extends React.Component { + render() { + return ( +
+ // 应用间路由跳转,并携带 query 查询参数 + 使用 AppLink 跳转到小二平台的列表页 + 跳转到商家平台详情页 +
+ ); + } +} +``` + +代码示例二:`to` 为简单对象,传递 state ```js import React from 'react'; @@ -117,11 +151,19 @@ import { Link } from 'react-router-dom'; import { AppLink } from '@ice/stark'; export default class SelfLink extends React.Component { - // 商家平台代码 render() { return (
- 使用 AppLink 跳转到小二平台的列表页 + // 应用间路由跳转,并传递 state、query + 使用 AppLink 跳转到小二平台的列表页 跳转到商家平台详情页
); @@ -129,6 +171,7 @@ export default class SelfLink extends React.Component { } ``` + ## registerAppEnter 提供快速注册当前应用加载前的回调事件 From ea4ec31ff868e76709c559e58350199a876d7c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Wed, 8 Dec 2021 19:05:14 +0800 Subject: [PATCH 06/10] refact/create micro app (#479) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 createMicroApp --- src/apps.ts | 143 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 95 insertions(+), 48 deletions(-) diff --git a/src/apps.ts b/src/apps.ts index 4c83ba31..540bb66d 100644 --- a/src/apps.ts +++ b/src/apps.ts @@ -243,77 +243,124 @@ function combineLifecyle(lifecycle: ModuleLifeCycle, appConfig: AppConfig) { return combinedLifecyle; } -export function getAppConfigForLoad(app: string | AppConfig, options?: AppLifecylceOptions) { - if (typeof app === 'string') { - return getAppConfig(app); - } +function registerAppBeforeLoad(app: AppConfig, options?: AppLifecylceOptions) { const { name } = app; const appIndex = getAppNames().indexOf(name); + if (appIndex === -1) { registerMicroApp(app, options); } else { updateAppConfig(name, app); } + return getAppConfig(name); } -export async function createMicroApp(app: string | AppConfig, appLifecyle?: AppLifecylceOptions, configuration?: StartConfiguration) { - const appConfig = getAppConfigForLoad(app, appLifecyle); - const appName = appConfig && appConfig.name; +async function loadApp(app: MicroApp) { + const { title, name, configuration } = app; - if (appConfig && appName) { - // add configuration to every micro app - const userConfiguration = globalConfiguration; - Object.keys(configuration || {}).forEach((key) => { - userConfiguration[key] = configuration[key]; - }); - updateAppConfig(appName, { configuration: userConfiguration }); + if (title) { + document.title = title; + } - const { container, basename, activePath } = appConfig; + updateAppConfig(name, { status: LOADING_ASSETS }); - if (container) { - setCache('root', container); + let lifeCycle: ModuleLifeCycle = {}; + try { + lifeCycle = await loadAppModule(app); + // in case of app status modified by unload event + if (getAppStatus(name) === LOADING_ASSETS) { + updateAppConfig(name, { ...lifeCycle, status: NOT_MOUNTED }); } + } catch (err) { + configuration.onError(err); + updateAppConfig(name, { status: LOAD_ERROR }); + } + if (lifeCycle.mount) { + await mountMicroApp(name); + } +} - const { basename: frameworkBasename } = userConfiguration; +function mergeThenUpdateAppConfig(name: string, configuration?: StartConfiguration) { + const appConfig = getAppConfig(name); - if (shouldSetBasename(activePath, basename)) { - setCache('basename', getAppBasename(activePath, frameworkBasename, basename)); - } + if (!appConfig) { + return; + } - // check status of app - if (appConfig.status === NOT_LOADED || appConfig.status === LOAD_ERROR) { - if (appConfig.title) document.title = appConfig.title; - updateAppConfig(appName, { status: LOADING_ASSETS }); - let lifeCycle: ModuleLifeCycle = {}; - try { - lifeCycle = await loadAppModule(appConfig); - // in case of app status modified by unload event - if (getAppStatus(appName) === LOADING_ASSETS) { - updateAppConfig(appName, { ...lifeCycle, status: NOT_MOUNTED }); - } - } catch (err) { - userConfiguration.onError(err); - updateAppConfig(appName, { status: LOAD_ERROR }); - } - if (lifeCycle.mount) { - await mountMicroApp(appConfig.name); - } - } else if (appConfig.status === UNMOUNTED) { + const { umd, sandbox } = appConfig; + + // Generate appSandbox + const appSandbox = createSandbox(sandbox) as Sandbox; + + // Merge loadScriptMode + const sandboxEnabled = sandbox && !appSandbox.sandboxDisabled; + const loadScriptMode = appConfig.loadScriptMode ?? (umd || sandboxEnabled ? 'fetch' : 'script'); + + // Merge global configuration + const cfgs = { + ...globalConfiguration, + ...configuration, + }; + + updateAppConfig(name, { + appSandbox, + loadScriptMode, + configuration: cfgs, + }); +} + +export async function createMicroApp( + app: string | AppConfig, + appLifecyle?: AppLifecylceOptions, + configuration?: StartConfiguration, +) { + const appName = typeof app === 'string' ? app : app.name; + + if (typeof app !== 'string') { + registerAppBeforeLoad(app, appLifecyle); + } + + mergeThenUpdateAppConfig(appName, configuration); + + const appConfig = getAppConfig(appName); + + if (!appConfig || !appName) { + console.error(`[icestark] fail to get app config of ${appName}`); + return null; + } + + const { container, basename, activePath, configuration: userConfiguration } = appConfig; + + if (container) { + setCache('root', container); + } + + const { basename: frameworkBasename } = userConfiguration; + + if (shouldSetBasename(activePath, basename)) { + setCache('basename', getAppBasename(activePath, frameworkBasename, basename)); + } + + switch (appConfig.status) { + case NOT_LOADED: + case LOAD_ERROR: + await loadApp(appConfig); + break; + case UNMOUNTED: if (!appConfig.cached) { await loadAndAppendCssAssets(appConfig?.appAssets?.cssList || []); } await mountMicroApp(appConfig.name); - } else if (appConfig.status === NOT_MOUNTED) { + break; + case NOT_MOUNTED: await mountMicroApp(appConfig.name); - } else { - console.info(`[icestark] current status of app ${appName} is ${appConfig.status}`); - } - return getAppConfig(appName); - } else { - console.error(`[icestark] fail to get app config of ${appName}`); + break; + default: + break; } - return null; + + return getAppConfig(appName); } export async function mountMicroApp(appName: string) { From 842dc2cae89968bf333e674305431427abd4a8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Thu, 9 Dec 2021 11:23:56 +0800 Subject: [PATCH 07/10] =?UTF-8?q?fix:=20=F0=9F=90=9B=20empty=20search=20(#?= =?UTF-8?q?480)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/icestark-app/src/AppLink.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/icestark-app/src/AppLink.tsx b/packages/icestark-app/src/AppLink.tsx index f70b8acb..3e2632bb 100644 --- a/packages/icestark-app/src/AppLink.tsx +++ b/packages/icestark-app/src/AppLink.tsx @@ -27,7 +27,7 @@ export type AppLinkProps = { const AppLink = (props: AppLinkProps) => { const { to, hashType, replace, message, children, ...rest } = props; - const _to = typeof to === 'object' ? (to.pathname + to.search) : to; + const _to = typeof to === 'object' ? `${to.pathname}${to.search ?? ''}` : to; const _state = typeof to === 'object' ? to.state : {}; const linkTo = formatUrl(_to, hashType); From dcf008deca24de9aade8f9f550eadb999f054d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=A3=E5=90=92?= Date: Thu, 9 Dec 2021 14:26:19 +0800 Subject: [PATCH 08/10] feat/cache css (#374) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 cache css --- src/AppRouter.tsx | 4 +-- src/apps.ts | 47 ++++++++++++++++++---------- src/start.ts | 10 ++++-- src/util/globalConfiguration.ts | 5 +++ src/util/handleAssets.ts | 54 ++++++++++++++++++++++----------- src/util/loaders.ts | 4 +-- tests/AppRouter.spec.tsx | 2 -- tests/handleAssets.spec.tsx | 8 ++--- 8 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index 612c252e..c11f096f 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -50,7 +50,6 @@ export default class AppRouter extends React.Component
{ typeof err === 'string' ? err : err?.message }
, LoadingComponent:
Loading...
, NotFoundComponent:
NotFound
, - shouldAssetsRemove: () => true, onAppEnter: () => {}, onAppLeave: () => {}, onLoadingApp: () => {}, @@ -93,7 +92,6 @@ export default class AppRouter extends React.Component Promise | void; unmount?: (props: LifecycleProps) => Promise | void; @@ -57,7 +59,7 @@ export interface BaseConfig extends PathOption { * @deprecated */ umd?: boolean; - loadScriptMode?: 'fetch' | 'script' | 'import'; + loadScriptMode?: LoadScriptMode; checkActive?: (url: string) => boolean; appAssets?: Assets; props?: object; @@ -168,8 +170,7 @@ export async function loadAppModule(appConfig: AppConfig) { let lifecycle: ModuleLifeCycle = {}; onLoadingApp(appConfig); - const appSandbox = createSandbox(appConfig.sandbox) as Sandbox; - const { url, container, entry, entryContent, name, scriptAttributes = [], umd } = appConfig; + const { url, container, entry, entryContent, name, scriptAttributes = [], loadScriptMode, appSandbox } = appConfig; const appAssets = url ? getUrlAssets(url) : await getEntryAssets({ root: container, entry, @@ -178,30 +179,37 @@ export async function loadAppModule(appConfig: AppConfig) { assetsCacheKey: name, fetch, }); - updateAppConfig(appConfig.name, { appAssets, appSandbox }); - /** - * LoadScriptMode has the first priority - */ - const loadScriptMode = appConfig.loadScriptMode ?? (umd ? 'fetch' : 'script'); + updateAppConfig(appConfig.name, { appAssets }); + + const cacheCss = shouldCacheCss(loadScriptMode); switch (loadScriptMode) { case 'import': await loadAndAppendCssAssets([ ...appAssets.cssList, ...filterRemovedAssets(importCachedAssets[name] || [], ['LINK', 'STYLE']), - ]); + ], { + cacheCss, + fetch, + }); lifecycle = await loadScriptByImport(appAssets.jsList); // Not to handle script element temporarily. break; case 'fetch': - await loadAndAppendCssAssets(appAssets.cssList); - lifecycle = await loadScriptByFetch(appAssets.jsList, appSandbox); + await loadAndAppendCssAssets(appAssets.cssList, { + cacheCss, + fetch, + }); + lifecycle = await loadScriptByFetch(appAssets.jsList, appSandbox, fetch); break; default: await Promise.all([ - loadAndAppendCssAssets(appAssets.cssList), - loadAndAppendJsAssets(appAssets, { sandbox: appSandbox, fetch, scriptAttributes }), + loadAndAppendCssAssets(appAssets.cssList, { + cacheCss, + fetch, + }), + loadAndAppendJsAssets(appAssets, { scriptAttributes }), ]); lifecycle = getLifecyleByLibrary() || @@ -243,6 +251,10 @@ function combineLifecyle(lifecycle: ModuleLifeCycle, appConfig: AppConfig) { return combinedLifecyle; } +function shouldCacheCss(mode: LoadScriptMode) { + return temporaryState.shouldAssetsRemoveConfigured ? false : (mode !== 'script'); +} + function registerAppBeforeLoad(app: AppConfig, options?: AppLifecylceOptions) { const { name } = app; const appIndex = getAppNames().indexOf(name); @@ -336,7 +348,7 @@ export async function createMicroApp( setCache('root', container); } - const { basename: frameworkBasename } = userConfiguration; + const { basename: frameworkBasename, fetch } = userConfiguration; if (shouldSetBasename(activePath, basename)) { setCache('basename', getAppBasename(activePath, frameworkBasename, basename)); @@ -349,7 +361,10 @@ export async function createMicroApp( break; case UNMOUNTED: if (!appConfig.cached) { - await loadAndAppendCssAssets(appConfig?.appAssets?.cssList || []); + await loadAndAppendCssAssets(appConfig?.appAssets?.cssList || [], { + cacheCss: shouldCacheCss(appConfig.loadScriptMode), + fetch, + }); } await mountMicroApp(appConfig.name); break; diff --git a/src/start.ts b/src/start.ts index e4ee668a..0a548c6c 100644 --- a/src/start.ts +++ b/src/start.ts @@ -12,7 +12,8 @@ import { AppConfig, getMicroApps, createMicroApp, unmountMicroApp, clearMicroApp import { emptyAssets, recordAssets } from './util/handleAssets'; import { LOADING_ASSETS, MOUNTED } from './util/constant'; import { doPrefetch } from './util/prefetch'; -import globalConfiguration, { RouteType, StartConfiguration } from './util/globalConfiguration'; +import globalConfiguration, { temporaryState } from './util/globalConfiguration'; +import type { RouteType, StartConfiguration } from './util/globalConfiguration'; if (!window?.fetch) { throw new Error('[icestark] window.fetch not found, you need polyfill it'); @@ -22,7 +23,6 @@ interface OriginalStateFunction { (state: any, title: string, url?: string): void; } - let started = false; const originalPush: OriginalStateFunction = window.history.pushState; const originalReplace: OriginalStateFunction = window.history.replaceState; @@ -148,6 +148,12 @@ const unHijackEventListener = (): void => { }; function start(options?: StartConfiguration) { + // See https://github.com/ice-lab/icestark/issues/373#issuecomment-971366188 + // todos: remove it from 3.x + if (options?.shouldAssetsRemove && !temporaryState.shouldAssetsRemoveConfigured) { + temporaryState.shouldAssetsRemoveConfigured = true; + } + if (started) { console.log('icestark has been already started'); return; diff --git a/src/util/globalConfiguration.ts b/src/util/globalConfiguration.ts index f333596e..08b000ce 100644 --- a/src/util/globalConfiguration.ts +++ b/src/util/globalConfiguration.ts @@ -45,3 +45,8 @@ const globalConfiguration: StartConfiguration = { }; export default globalConfiguration; + +// todos: remove it from 3.x +export const temporaryState = { + shouldAssetsRemoveConfigured: false, +}; diff --git a/src/util/handleAssets.ts b/src/util/handleAssets.ts index e7a7b624..cb1034d1 100644 --- a/src/util/handleAssets.ts +++ b/src/util/handleAssets.ts @@ -93,9 +93,9 @@ export function appendCSS( if (type && type === AssetTypeEnum.INLINE) { const styleElement: HTMLStyleElement = document.createElement('style'); - styleElement.innerHTML = content; styleElement.id = id; styleElement.setAttribute(PREFIX, DYNAMIC); + styleElement.innerHTML = content; root.appendChild(styleElement); resolve(); return; @@ -660,11 +660,45 @@ export function cacheAssets(cacheKey: string): void { * @export * @param {Assets} assets */ -export async function loadAndAppendCssAssets(cssList: Array) { +export async function loadAndAppendCssAssets(cssList: Array, { + cacheCss = false, + fetch = defaultFetch, +}: { + cacheCss?: boolean; + fetch?: Fetch; +}) { const cssRoot: HTMLElement = document.getElementsByTagName('head')[0]; + if (cacheCss) { + let useLinks = false; + let cssContents = null; + + try { + // No need to cache css when running into `