diff --git a/docs/README.md b/docs/README.md index 2b93b7c..aa4c7f7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ title: qiankun hero: title: qiankun - desc: Probably the most complete micro-frontends solution you ever met🧐 + desc: Probably the most complete micro-frontends solution you ever met🧐 actions: - text: Get Started → link: /guide @@ -33,7 +33,7 @@ loadMicroApp({ entry: '//localhost:7100', container: '#container', props: { - slogan: 'Hello Qiankun' + slogan: 'Hello Qiankun', }, }); ``` diff --git a/docs/README.zh.md b/docs/README.zh.md index caad8aa..dc76e77 100644 --- a/docs/README.zh.md +++ b/docs/README.zh.md @@ -2,9 +2,9 @@ title: qiankun hero: title: qiankun - desc: 可能是你见过最完善的微前端解决方案🧐 + desc: 可能是你见过最完善的微前端解决方案🧐 actions: - - text: 快速开始 → + - text: 快速开始 → link: /zh/guide features: - title: 简单 @@ -33,7 +33,7 @@ loadMicroApp({ entry: '//localhost:7100', container: '#container', props: { - slogan: 'Hello Qiankun' + slogan: 'Hello Qiankun', }, }); ``` diff --git a/docs/api/README.md b/docs/api/README.md index 127a5b1..3d37fe9 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -30,41 +30,41 @@ By linking the micro-application to some url rules, the function of automaticall - If configured as `object`, the value of `html` is the html content string of the micro application, not the access address of the micro application. The `publicPath` of the micro application will be set to `/`. - container - `string | HTMLElement` - required,A selector or Element instance of the container node of a micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`. - - activeRule - - `string | (location: Location) => boolean | Array boolean> ` - required,activation rules for micro-apps. + - activeRule - - `string | (location: Location) => boolean | Array boolean> ` - required,activation rules for micro-apps. - * Support direct configuration of string or string array, such as `activeRule: '/app1'` or `activeRule: ['/app1', '/app2']`, when configured as a string, it will directly follow the path part in the url Do a prefix match. A successful match indicates that the current application will be activated. - * Support to configure an active function or a group of active functions. The function will pass in the current location as a parameter. When the function returns true, it indicates that the current micro application will be activated. Such as `location => location.pathname.startsWith ('/app1')`. + - Support direct configuration of string or string array, such as `activeRule: '/app1'` or `activeRule: ['/app1', '/app2']`, when configured as a string, it will directly follow the path part in the url Do a prefix match. A successful match indicates that the current application will be activated. + - Support to configure an active function or a group of active functions. The function will pass in the current location as a parameter. When the function returns true, it indicates that the current micro application will be activated. Such as `location => location.pathname.startsWith ('/app1')`. Example rules: `'/app1'` - * ✅ https://app.com/app1 + - ✅ https://app.com/app1 - * ✅ https://app.com/app1/anything/everything + - ✅ https://app.com/app1/anything/everything - * 🚫 https://app.com/app2 + - 🚫 https://app.com/app2 `'/users/:userId/profile'` - * ✅ https://app.com/users/123/profile - * ✅ https://app.com/users/123/profile/sub-profile/ - * 🚫 https://app.com/users//profile/sub-profile/ - * 🚫 https://app.com/users/profile/sub-profile/ + - ✅ https://app.com/users/123/profile + - ✅ https://app.com/users/123/profile/sub-profile/ + - 🚫 https://app.com/users//profile/sub-profile/ + - 🚫 https://app.com/users/profile/sub-profile/ `'/pathname/#/hash'` - * ✅ https://app.com/pathname/#/hash - * ✅ https://app.com/pathname/#/hash/route/nested - * 🚫 https://app.com/pathname#/hash/route/nested - * 🚫 https://app.com/pathname#/another-hash + - ✅ https://app.com/pathname/#/hash + - ✅ https://app.com/pathname/#/hash/route/nested + - 🚫 https://app.com/pathname#/hash/route/nested + - 🚫 https://app.com/pathname#/another-hash `['/pathname/#/hash', '/app1']` - * ✅ https://app.com/pathname/#/hash/route/nested - * ✅ https://app.com/app1/anything/everything - * 🚫 https://app.com/pathname/app1 - * 🚫 https://app.com/app2 + - ✅ https://app.com/pathname/#/hash/route/nested + - ✅ https://app.com/app1/anything/everything + - 🚫 https://app.com/pathname/app1 + - 🚫 https://app.com/app2 This function is called when the browser url changes, and `activeRule` returns `true` to indicate that the subapplication needs to be activated. @@ -102,14 +102,12 @@ By linking the micro-application to some url rules, the function of automaticall activeRule: '/react', props: { name: 'kuitos', - } - } + }, + }, ], { - beforeLoad: app => console.log('before load', app.name), - beforeMount: [ - app => console.log('before mount', app.name), - ], + beforeLoad: (app) => console.log('before load', app.name), + beforeMount: [(app) => console.log('before mount', app.name)], }, ); ``` @@ -151,8 +149,7 @@ By linking the micro-application to some url rules, the function of automaticall } ``` - notice: - @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten) + notice: @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten) - singular - `boolean | ((app: RegistrableApp) => Promise);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. default is `true`. @@ -161,7 +158,7 @@ By linking the micro-application to some url rules, the function of automaticall - getPublicPath - `(entry: Entry) => string` - optional,The parameter is the entry value of the micro application. - getTemplate - `(tpl: string) => string` - optional - + - excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking - Usage @@ -224,16 +221,18 @@ A criterion for judging whether the business is closely related: Look at ### `loadMicroApp(app, configuration?)` -* Parameters - * app - `LoadableApp` - Required, basic information of micro application - * name - `string` - Required, the name of the micro application must be unique among the micro applications. - * entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required, The entry of the micro application(The detailed description is the same as above). - * container - `string | HTMLElement` - Required, selector or Element instance of the container node of the micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`. - * props - `object` - Optional, the data that needs to be passed to the micro-application during initialization. +- Parameters - * configuration - `Configuration` - Optional, configuration information of the micro application + - app - `LoadableApp` - Required, basic information of micro application - * sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - optional, whether to open the js sandbox, default is `true`. + - name - `string` - Required, the name of the micro application must be unique among the micro applications. + - entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required, The entry of the micro application(The detailed description is the same as above). + - container - `string | HTMLElement` - Required, selector or Element instance of the container node of the micro application. Such as `container: '#root'` or `container: document.querySelector('#root')`. + - props - `object` - Optional, the data that needs to be passed to the micro-application during initialization. + + - configuration - `Configuration` - Optional, configuration information of the micro application + + - sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - optional, whether to open the js sandbox, default is `true`. When configured as `{strictStyleIsolation: true}`, qiankun will convert the container dom of each application to a [shadow dom](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), to ensure that the style of the application will not leak to the global. @@ -244,48 +243,36 @@ A criterion for judging whether the business is closely related: Look at .app-main { font-size: 14px; } - + div[data-qiankun-react16] .app-main { font-size: 14px; } ``` - - notice: - @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten) - - * singular - `boolean | ((app: RegistrableApp) => Promise);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. Default is `false`. - - * fetch - `Function` - Optional, custom fetch method. - - * getPublicPath - `(url: string) => string` - Optional,The parameter is the entry value of the micro application. - - * getTemplate - `(tpl: string) => string` - Optional - - * excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking - -* 返回值 - `MicroApp` - Micro application examples - * mount(): Promise<null>; - * unmount(): Promise<null>; - * update(customProps: object): Promise<any>; - * getStatus(): - | "NOT_LOADED" - | "LOADING_SOURCE_CODE" - | "NOT_BOOTSTRAPPED" - | "BOOTSTRAPPING" - | "NOT_MOUNTED" - | "MOUNTING" - | "MOUNTED" - | "UPDATING" - | "UNMOUNTING" - | "UNLOADING" - | "SKIP_BECAUSE_BROKEN" - | "LOAD_ERROR"; - * loadPromise: Promise<null>; - * bootstrapPromise: Promise<null>; - * mountPromise: Promise<null>; - * unmountPromise: Promise<null>; -* Usage + notice: @keyframes, @font-face, @import, @page are not supported (i.e. will not be rewritten) + + - singular - `boolean | ((app: RegistrableApp) => Promise);` - Optional, whether it is a singleton scenario, singleton means just rendered one micro app at one time. Default is `false`. + + - fetch - `Function` - Optional, custom fetch method. + + - getPublicPath - `(url: string) => string` - Optional,The parameter is the entry value of the micro application. + + - getTemplate - `(tpl: string) => string` - Optional + + - excludeAssetFilter - `(assetUrl: string) => boolean` - optional,some special dynamic loaded micro app resources should not be handled by qiankun hijacking + +- 返回值 - `MicroApp` - Micro application examples + + - mount(): Promise<null>; + - unmount(): Promise<null>; + - update(customProps: object): Promise<any>; + - getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR"; + - loadPromise: Promise<null>; + - bootstrapPromise: Promise<null>; + - mountPromise: Promise<null>; + - unmountPromise: Promise<null>; + +- Usage Load a micro application manually. @@ -302,21 +289,23 @@ A criterion for judging whether the business is closely related: Look at } ``` -* Sample +- Sample ```jsx import { loadMicroApp } from 'qiankun'; import React from 'react'; class App extends React.Component { - containerRef = React.createRef(); microApp = null; componentDidMount() { - this.microApp = loadMicroApp( - { name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { brand: 'qiankun' } }, - ); + this.microApp = loadMicroApp({ + name: 'app1', + entry: '//localhost:1234', + container: this.containerRef.current, + props: { brand: 'qiankun' }, + }); } componentWillUnmount() { @@ -336,10 +325,12 @@ A criterion for judging whether the business is closely related: Look at ### `prefetchApps(apps, importEntryOpts?)` - Parameters + - apps - `AppMetadata[]` - Required - list of preloaded apps - importEntryOpts - Optional - Load configuration - Type + - `AppMetadata` - name - `string` - Required - Application name - entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - Required,The entry address of the microapp @@ -353,7 +344,10 @@ A criterion for judging whether the business is closely related: Look at ```ts import { prefetchApps } from 'qiankun'; - prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ]) + prefetchApps([ + { name: 'app1', entry: '//locahost:7001' }, + { name: 'app2', entry: '//locahost:7002' }, + ]); ``` ## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler) @@ -373,7 +367,7 @@ A criterion for judging whether the business is closely related: Look at ```ts import { addGlobalUncaughtErrorHandler } from 'qiankun'; - addGlobalUncaughtErrorHandler(event => console.log(event)); + addGlobalUncaughtErrorHandler((event) => console.log(event)); ``` ## `removeGlobalUncaughtErrorHandler(handler)` @@ -417,6 +411,7 @@ A criterion for judging whether the business is closely related: Look at - Sample Master: + ```ts import { initGlobalState, MicroAppStateActions } from 'qiankun'; @@ -432,10 +427,10 @@ A criterion for judging whether the business is closely related: Look at ``` Slave: + ```ts // get actions from mount export function mount(props) { - props.onGlobalStateChange((state, prev) => { // state: new state; prev old state console.log(state, prev); diff --git a/docs/api/README.zh.md b/docs/api/README.zh.md index 0261790..487c832 100644 --- a/docs/api/README.zh.md +++ b/docs/api/README.zh.md @@ -32,39 +32,39 @@ toc: menu - activeRule - `string | (location: Location) => boolean | Array boolean> ` - 必选,微应用的激活规则。 - * 支持直接配置字符串或字符串数组,如 `activeRule: '/app1'` 或 `activeRule: ['/app1', '/app2']`,当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。 - * 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 `location => location.pathname.startsWith('/app1')`。 + - 支持直接配置字符串或字符串数组,如 `activeRule: '/app1'` 或 `activeRule: ['/app1', '/app2']`,当配置为字符串时会直接跟 url 中的路径部分做前缀匹配,匹配成功表明当前应用会被激活。 + - 支持配置一个 active function 函数或一组 active function。函数会传入当前 location 作为参数,函数返回 true 时表明当前微应用会被激活。如 `location => location.pathname.startsWith('/app1')`。 规则示例: `'/app1'` - * ✅ https://app.com/app1 + - ✅ https://app.com/app1 - * ✅ https://app.com/app1/anything/everything + - ✅ https://app.com/app1/anything/everything - * 🚫 https://app.com/app2 + - 🚫 https://app.com/app2 `'/users/:userId/profile'` - * ✅ https://app.com/users/123/profile - * ✅ https://app.com/users/123/profile/sub-profile/ - * 🚫 https://app.com/users//profile/sub-profile/ - * 🚫 https://app.com/users/profile/sub-profile/ + - ✅ https://app.com/users/123/profile + - ✅ https://app.com/users/123/profile/sub-profile/ + - 🚫 https://app.com/users//profile/sub-profile/ + - 🚫 https://app.com/users/profile/sub-profile/ `'/pathname/#/hash'` - * ✅ https://app.com/pathname/#/hash - * ✅ https://app.com/pathname/#/hash/route/nested - * 🚫 https://app.com/pathname#/hash/route/nested - * 🚫 https://app.com/pathname#/another-hash + - ✅ https://app.com/pathname/#/hash + - ✅ https://app.com/pathname/#/hash/route/nested + - 🚫 https://app.com/pathname#/hash/route/nested + - 🚫 https://app.com/pathname#/another-hash `['/pathname/#/hash', '/app1']` - * ✅ https://app.com/pathname/#/hash/route/nested - * ✅ https://app.com/app1/anything/everything - * 🚫 https://app.com/pathname/app1 - * 🚫 https://app.com/app2 + - ✅ https://app.com/pathname/#/hash/route/nested + - ✅ https://app.com/app1/anything/everything + - 🚫 https://app.com/pathname/app1 + - 🚫 https://app.com/app2 浏览器 url 发生变化会调用 activeRule 里的规则,`activeRule` 任意一个返回 `true` 时表明该微应用需要被激活。 @@ -102,14 +102,12 @@ toc: menu activeRule: '/react', props: { name: 'kuitos', - } - } + }, + }, ], { - beforeLoad: app => console.log('before load', app.name), - beforeMount: [ - app => console.log('before mount', app.name), - ], + beforeLoad: (app) => console.log('before load', app.name), + beforeMount: [(app) => console.log('before mount', app.name)], }, ); ``` @@ -155,8 +153,7 @@ toc: menu } ``` - 注意: - @keyframes, @font-face, @import, @page 将不被支持 (i.e. 不会被改写) + 注意: @keyframes, @font-face, @import, @page 将不被支持 (i.e. 不会被改写) - singular - `boolean | ((app: RegistrableApp) => Promise);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `true`。 @@ -228,16 +225,18 @@ toc: menu ### `loadMicroApp(app, configuration?)` -* 参数 - * app - `LoadableApp` - 必选,微应用的基础信息 - * name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。 - * entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的入口(详细说明同上)。 - * container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。 - * props - `object` - 可选,初始化时需要传递给微应用的数据。 +- 参数 - * configuration - `Configuration` - 可选,微应用的配置信息 + - app - `LoadableApp` - 必选,微应用的基础信息 - * sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。 + - name - `string` - 必选,微应用的名称,微应用之间必须确保唯一。 + - entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的入口(详细说明同上)。 + - container - `string | HTMLElement` - 必选,微应用的容器节点的选择器或者 Element 实例。如`container: '#root'` 或 `container: document.querySelector('#root')`。 + - props - `object` - 可选,初始化时需要传递给微应用的数据。 + + - configuration - `Configuration` - 可选,微应用的配置信息 + + - sandbox - `boolean` | `{ strictStyleIsolation?: boolean, experimentalStyleIsolation?: boolean }` - 可选,是否开启沙箱,默认为 `true`。 默认情况下沙箱可以确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离。当配置为 `{ strictStyleIsolation: true }` 时表示开启严格的样式隔离模式。这种模式下 qiankun 会为每个微应用的容器包裹上一个 [shadow dom](https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM) 节点,从而确保微应用的样式不会对全局造成影响。 @@ -258,42 +257,30 @@ toc: menu } ``` - 注意事项: - 目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写) + 注意事项: 目前 @keyframes, @font-face, @import, @page 等规则不会支持 (i.e. 不会被改写) - * singular - `boolean | ((app: RegistrableApp) => Promise);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `false`。 + - singular - `boolean | ((app: RegistrableApp) => Promise);` - 可选,是否为单实例场景,单实例指的是同一时间只会渲染一个微应用。默认为 `false`。 - * fetch - `Function` - 可选,自定义的 fetch 方法。 + - fetch - `Function` - 可选,自定义的 fetch 方法。 - * getPublicPath - `(entry: Entry) => string` - 可选,参数是微应用的 entry 值。 + - getPublicPath - `(entry: Entry) => string` - 可选,参数是微应用的 entry 值。 - * getTemplate - `(tpl: string) => string` - 可选 - - * excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被qiankun 劫持处理 + - getTemplate - `(tpl: string) => string` - 可选 -* 返回值 - `MicroApp` - 微应用实例 - * mount(): Promise<null>; - * unmount(): Promise<null>; - * update(customProps: object): Promise<any>; - * getStatus(): - | "NOT_LOADED" - | "LOADING_SOURCE_CODE" - | "NOT_BOOTSTRAPPED" - | "BOOTSTRAPPING" - | "NOT_MOUNTED" - | "MOUNTING" - | "MOUNTED" - | "UPDATING" - | "UNMOUNTING" - | "UNLOADING" - | "SKIP_BECAUSE_BROKEN" - | "LOAD_ERROR"; - * loadPromise: Promise<null>; - * bootstrapPromise: Promise<null>; - * mountPromise: Promise<null>; - * unmountPromise: Promise<null>; + - excludeAssetFilter - `(assetUrl: string) => boolean` - 可选,指定部分特殊的动态加载的微应用资源(css/js) 不被 qiankun 劫持处理 -* 用法 +- 返回值 - `MicroApp` - 微应用实例 + + - mount(): Promise<null>; + - unmount(): Promise<null>; + - update(customProps: object): Promise<any>; + - getStatus(): | "NOT_LOADED" | "LOADING_SOURCE_CODE" | "NOT_BOOTSTRAPPED" | "BOOTSTRAPPING" | "NOT_MOUNTED" | "MOUNTING" | "MOUNTED" | "UPDATING" | "UNMOUNTING" | "UNLOADING" | "SKIP_BECAUSE_BROKEN" | "LOAD_ERROR"; + - loadPromise: Promise<null>; + - bootstrapPromise: Promise<null>; + - mountPromise: Promise<null>; + - unmountPromise: Promise<null>; + +- 用法 手动加载一个微应用。 @@ -310,21 +297,23 @@ toc: menu } ``` -* 示例 +- 示例 ```jsx import { loadMicroApp } from 'qiankun'; import React from 'react'; class App extends React.Component { - containerRef = React.createRef(); microApp = null; componentDidMount() { - this.microApp = loadMicroApp( - { name: 'app1', entry: '//localhost:1234', container: this.containerRef.current, props: { brand: 'qiankun' } }, - ); + this.microApp = loadMicroApp({ + name: 'app1', + entry: '//localhost:1234', + container: this.containerRef.current, + props: { brand: 'qiankun' }, + }); } componentWillUnmount() { @@ -344,10 +333,12 @@ toc: menu ### `prefetchApps(apps, importEntryOpts?)` - 参数 + - apps - `AppMetadata[]` - 必选 - 预加载的应用列表 - importEntryOpts - 可选 - 加载配置 - 类型 + - `AppMetadata` - name - `string` - 必选 - 应用名 - entry - `string | { scripts?: string[]; styles?: string[]; html?: string }` - 必选,微应用的 entry 地址 @@ -361,7 +352,10 @@ toc: menu ```ts import { prefetchApps } from 'qiankun'; - prefetchApps([ { name: 'app1', entry: '//locahost:7001' }, { name: 'app2', entry: '//locahost:7002' } ]) + prefetchApps([ + { name: 'app1', entry: '//locahost:7001' }, + { name: 'app2', entry: '//locahost:7002' }, + ]); ``` ## [addErrorHandler/removeErrorHandler](https://single-spa.js.org/docs/api#adderrorhandler) @@ -381,7 +375,7 @@ toc: menu ```ts import { addGlobalUncaughtErrorHandler } from 'qiankun'; - addGlobalUncaughtErrorHandler(event => console.log(event)); + addGlobalUncaughtErrorHandler((event) => console.log(event)); ``` ## `removeGlobalUncaughtErrorHandler(handler)` @@ -425,6 +419,7 @@ toc: menu - 示例 主应用: + ```ts import { initGlobalState, MicroAppStateActions } from 'qiankun'; @@ -440,10 +435,10 @@ toc: menu ``` 微应用: + ```ts // 从生命周期 mount 中获取通信方法,使用方式和 master 一致 export function mount(props) { - props.onGlobalStateChange((state, prev) => { // state: 变更后的状态; prev 变更前的状态 console.log(state, prev); diff --git a/docs/cookbook/README.md b/docs/cookbook/README.md index a9737d1..a15e22f 100644 --- a/docs/cookbook/README.md +++ b/docs/cookbook/README.md @@ -18,11 +18,11 @@ When registering micro apps, `activeRule` needs to be written like this: ```js registerMicroApps([ - { - name: 'app', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/app', + { + name: 'app', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/app', }, ]); ``` @@ -31,33 +31,37 @@ registerMicroApps([ 2. When the micro app is in the `hash` mode, the performance of the three routes is inconsistent - | routing | main app jump `/app/#/about` | special configuration | - | ---------------| -------------------------------| --------------------------| - | vue-router | Response `about` routing | none | - | react-router | not responding `about` routing | none | - | angular-router | Response `about` routing | need to set `--base-href` | + | routing | main app jump `/app/#/about` | special configuration | + | -------------- | ------------------------------ | ------------------------- | + | vue-router | Response `about` routing | none | + | react-router | not responding `about` routing | none | + | angular-router | Response `about` routing | need to set `--base-href` | - `Angular` app set `--base-href` in `package.json`: - ```diff - - "start": "ng serve", - + "start": "ng serve --base-href /angular9", - - "build": "ng build", - + "build": "ng build --base-href /angular9", - ``` + `Angular` app set `--base-href` in `package.json`: - After bundled and deployed, the `angular` micro app can be accessed by the main app, but the lazy-loaded route during independent access will report an error and the path is incorrect. There are two solutions: + ```diff + - "start": "ng serve", + + "start": "ng serve --base-href /angular9", + - "build": "ng build", + + "build": "ng build --base-href /angular9", + ``` - - Solution 1: Modify `public-path.js` to: + After bundled and deployed, the `angular` micro app can be accessed by the main app, but the lazy-loaded route during independent access will report an error and the path is incorrect. There are two solutions: - ```js - __webpack_public_path__ = window.__POWERED_BY_QIANKUN__ ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ : `http://${ip}:${port}/`; // Fill in your actual deployment address - ``` - - Solution 2: Modify the bundling command and deploy the micro app in the `angular9` directory: + - Solution 1: Modify `public-path.js` to: - ```diff - - "build": "ng build", - + "build": "ng build --base-href /angular9 --deploy-url /angular9/", - ``` + ```js + __webpack_public_path__ = window.__POWERED_BY_QIANKUN__ + ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + : `http://${ip}:${port}/`; // Fill in your actual deployment address + ``` + + - Solution 2: Modify the bundling command and deploy the micro app in the `angular9` directory: + + ```diff + - "build": "ng build", + + "build": "ng build --base-href /angular9 --deploy-url /angular9/", + ``` ### activeRule uses hash to distinguish micro apps @@ -66,13 +70,13 @@ When the micro apps are all in the `hash` mode, `hash` can be used to distinguis When registering micro apps, `activeRule` needs to be written like this: ```js -const getActiveRule = hash => location => location.hash.startsWith(hash); +const getActiveRule = (hash) => (location) => location.hash.startsWith(hash); registerMicroApps([ - { - name: 'app-hash', - entry: 'http://localhost:8080', - container: '#container', - activeRule: getActiveRule('#/app-hash'), + { + name: 'app-hash', + entry: 'http://localhost:8080', + container: '#container', + activeRule: getActiveRule('#/app-hash'), // Here you can also write `activeRule:'#/app-hash'` directly, // but if the main app is in history mode or the main app is deployed in a non-root directory, this writing will not take effect. }, @@ -91,9 +95,9 @@ const routes = [ component: Home, children: [ // All other routes are written here - ] - } -] + ], + }, +]; ``` ### When there are multiple micro apps at the same time @@ -148,7 +152,7 @@ Suppose we have a main app and 6 micro apps ( respectively `vue-hash`, `vue-hist At this time, you need to set the `publicPath` and the route `base` of the `history` mode when the micro app is built, and then bundle them into the corresponding directory. | app | routing base | publicPath | real access path | -| --------------- | ------------------------| ------------------------| ---------------------------------------------| +| --------------- | ----------------------- | ----------------------- | -------------------------------------------- | | vue-hash | none | /child/vue-hash/ | http://localhost:8080/child/vue-hash/ | | vue-history | /child/vue-history/ | /child/vue-history/ | http://localhost:8080/child/vue-history/ | | react-hash | none | /child/react-hash/ | http://localhost:8080/child/react-hash/ | @@ -158,61 +162,72 @@ At this time, you need to set the `publicPath` and the route `base` of the `hist - `vue-history` micro app - Routing's base configuration: - ```js - base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/', - ``` - Webpack's publicPath configuration(`vue.config.js`): - ```js - module.exports = { - publicPath: '/child/vue-history/' - } - ``` + Routing's base configuration: + + ```js + base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/', + ``` + + Webpack's publicPath configuration(`vue.config.js`): + + ```js + module.exports = { + publicPath: '/child/vue-history/', + }; + ``` - `react-history` micro app - Routing's base configuration: - ```html - - ``` - Webpack's publicPath configuration: - ```js - module.exports = { - output: { - publicPath: '/child/react-history/', - } - } - ``` + Routing's base configuration: + + ```html + + ``` + + Webpack's publicPath configuration: + + ```js + module.exports = { + output: { + publicPath: '/child/react-history/', + }, + }; + ``` - `angular-history` micro app - Routing's base configuration: - ```js - providers: [{ - provide: APP_BASE_HREF, - useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/' - }] - ``` - The `publicPath` of webpack is set by `deploy-url`, modify `package.json`: - ```diff - - "build": "ng build", - + "build": "ng build --deploy-url /child/angular-history/", - ``` + Routing's base configuration: + + ```js + providers: [ + { + provide: APP_BASE_HREF, + useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/', + }, + ]; + ``` + + The `publicPath` of webpack is set by `deploy-url`, modify `package.json`: + + ```diff + - "build": "ng build", + + "build": "ng build --deploy-url /child/angular-history/", + ``` Then the `registerMicroApps` function at this time is like this (you need to ensure that `activeRule` and `entry` are different): ```js registerMicroApps([ { - name: 'app-vue-hash', + name: 'app-vue-hash', entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/ - container: '#container', - activeRule: '/app-vue-hash', + container: '#container', + activeRule: '/app-vue-hash', }, - { + { name: 'app-vue-history', entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/ - container: '#container', + container: '#container', activeRule: '/app-vue-history', }, // angular and react same as above @@ -270,7 +285,7 @@ the `Nginx` proxy configuration of the main app is: ``` /app1/ { proxy_pass http://www.b.com/app1/; - proxy_set_header Host $host:$server_port; + proxy_set_header Host $host:$server_port; } ``` @@ -279,10 +294,10 @@ When the main app registers micro apps, `entry` can be a relative path, and `act ```js registerMicroApps([ { - name: 'app1', + name: 'app1', entry: '/app1/', // http://localhost:8080/app1/ - container: '#container', - activeRule: '/child-app1', + container: '#container', + activeRule: '/child-app1', }, ], ``` @@ -293,8 +308,8 @@ For micro apps bundled by `webpack`, the `publicPath` bundled by the micro app's module.exports = { output: { publicPath: `/app1/`, - } -} + }, +}; ``` After adding `/app1/` to the `publicPath` of the micro app, it must be deployed in the `/app1` directory, otherwise it cannot be accessed independently. @@ -321,4 +336,4 @@ The basic modification of `registerMicroApps` function is as follows: The basic modification of the `start` function is as follows: 1. The `jsSandbox` configuration has been removed and changed to `sandbox`, and the optional values have also been modified. -2. Added `getPublicPath` and `getTemplate` to replace `RegisterMicroAppsOpts`. \ No newline at end of file +2. Added `getPublicPath` and `getTemplate` to replace `RegisterMicroAppsOpts`. diff --git a/docs/cookbook/README.zh.md b/docs/cookbook/README.zh.md index 7d337e8..638a9ec 100644 --- a/docs/cookbook/README.zh.md +++ b/docs/cookbook/README.zh.md @@ -18,11 +18,11 @@ toc: menu ```js registerMicroApps([ - { - name: 'app', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/app', + { + name: 'app', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/app', }, ]); ``` @@ -31,33 +31,37 @@ registerMicroApps([ 2. 当微应用是 `hash` 模式时,三种路由的表现不一致 - | 路由 | 主应用跳转/app/#/about | 特殊配置 | - | ---------------| ----------------------| --------------------| - | vue-router | 响应 about 路由 | 无 | - | react-router | 不响应 about 路由 | 无 | - | angular-router | 响应 about 路由 | 需要设置 --base-href | + | 路由 | 主应用跳转/app/#/about | 特殊配置 | + | -------------- | ---------------------- | -------------------- | + | vue-router | 响应 about 路由 | 无 | + | react-router | 不响应 about 路由 | 无 | + | angular-router | 响应 about 路由 | 需要设置 --base-href | - `angular` 应用在 `package.json` 里面设置 `--base-href`: - ```diff - - "start": "ng serve", - + "start": "ng serve --base-href /angular9", - - "build": "ng build", - + "build": "ng build --base-href /angular9", - ``` + `angular` 应用在 `package.json` 里面设置 `--base-href`: - 打包部署后,`angular` 微应用可以被主应用访问。但是独立访问时,懒加载的路由会报错,路径不正确。这里有两个解决办法: + ```diff + - "start": "ng serve", + + "start": "ng serve --base-href /angular9", + - "build": "ng build", + + "build": "ng build --base-href /angular9", + ``` - - 方法1:修改 `public-path.js` 为: + 打包部署后,`angular` 微应用可以被主应用访问。但是独立访问时,懒加载的路由会报错,路径不正确。这里有两个解决办法: - ```js - __webpack_public_path__ = window.__POWERED_BY_QIANKUN__ ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ : `http://${ip}:${port}/`; // 填写你的实际部署地址 - ``` - - 方法2:修改打包命令,并且将微应用部署在 `angular9` 目录: + - 方法 1:修改 `public-path.js` 为: - ```diff - - "build": "ng build", - + "build": "ng build --base-href /angular9 --deploy-url /angular9/", - ``` + ```js + __webpack_public_path__ = window.__POWERED_BY_QIANKUN__ + ? window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ + : `http://${ip}:${port}/`; // 填写你的实际部署地址 + ``` + + - 方法 2:修改打包命令,并且将微应用部署在 `angular9` 目录: + + ```diff + - "build": "ng build", + + "build": "ng build --base-href /angular9 --deploy-url /angular9/", + ``` ### `activeRule` 使用 `location.hash` 区分微应用 @@ -66,13 +70,13 @@ registerMicroApps([ 注册微应用时 `activeRule` 需要这样写: ```js -const getActiveRule = hash => location => location.hash.startsWith(hash); +const getActiveRule = (hash) => (location) => location.hash.startsWith(hash); registerMicroApps([ - { - name: 'app-hash', - entry: 'http://localhost:8080', - container: '#container', - activeRule: getActiveRule('#/app-hash'), + { + name: 'app-hash', + entry: 'http://localhost:8080', + container: '#container', + activeRule: getActiveRule('#/app-hash'), // 这里也可以直接写 activeRule: '#/app-hash',但是如果主应用是 history 模式或者主应用部署在非根目录,这样写不会生效。 }, ]); @@ -90,9 +94,9 @@ const routes = [ component: Home, children: [ // 其他的路由都写到这里 - ] - } -] + ], + }, +]; ``` ### 同时存在多个微应用时 @@ -105,7 +109,7 @@ const routes = [ **建议**:主应用和微应用都是独立开发和部署,即它们都属于不同的仓库和服务。 -### 场景1:主应用和微应用部署到同一个服务器(同一个IP和端口) +### 场景 1:主应用和微应用部署到同一个服务器(同一个 IP 和端口) 如果服务器数量有限,或不能跨域等原因需要把主应用和微应用部署到一起。 @@ -113,7 +117,7 @@ const routes = [ 微应用想部署在非根目录,在微应用打包之前需要做两件事: -1. 必须配置 `webpack` 构建时的 `publicPath` 为**目录名称**,更多信息请看 [webpack 官方说明](https://www.webpackjs.com/configuration/output/#output-publicpath) 和 [vue-cli3 的官方说明](https://cli.vuejs.org/zh/config/#publicpath) +1. 必须配置 `webpack` 构建时的 `publicPath` 为**目录名称**,更多信息请看 [webpack 官方说明](https://www.webpackjs.com/configuration/output/#output-publicpath) 和 [vue-cli3 的官方说明](https://cli.vuejs.org/zh/config/#publicpath) 2. `history` 路由的微应用需要设置 `base` ,值为**目录名称**,用于独立访问时使用。 @@ -125,9 +129,10 @@ const routes = [ 具体的部署有以下两种方式,选择其一即可。 -#### 方案1:微应用都放在在一个特殊名称(**不会和微应用重名**)的文件夹下(**建议使用**) +#### 方案 1:微应用都放在在一个特殊名称(**不会和微应用重名**)的文件夹下(**建议使用**) 假设我们有一个主应用和 6 个微应用(分别为 `vue-hash`、`vue-history`、`react-hash`、`react-history`、`angular-hash`、`angular-history` ),打包后如下放置: + ``` └── html/ # 根文件夹 | @@ -145,8 +150,8 @@ const routes = [ 此时需要设置微应用构建时的 `publicPath` 和 `history` 模式的路由 `base`,然后才能打包放到对应的目录里。 -| 项目 | 路由 base | publicPath | 真实访问路径 | -| --------------- | ------------------------| ------------------------| ---------------------------------------------| +| 项目 | 路由 base | publicPath | 真实访问路径 | +| --------------- | ----------------------- | ----------------------- | -------------------------------------------- | | vue-hash | 无 | /child/vue-hash/ | http://localhost:8080/child/vue-hash/ | | vue-history | /child/vue-history/ | /child/vue-history/ | http://localhost:8080/child/vue-history/ | | react-hash | 无 | /child/react-hash/ | http://localhost:8080/child/react-hash/ | @@ -156,61 +161,72 @@ const routes = [ - vue-history 微应用 - 路由设置: - ```js - base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/', - ``` - webpack 打包 publicPath 配置(`vue.config.js`): - ```js - module.exports = { - publicPath: '/child/vue-history/' - } - ``` + 路由设置: + + ```js + base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/child/vue-history/', + ``` + + webpack 打包 publicPath 配置(`vue.config.js`): + + ```js + module.exports = { + publicPath: '/child/vue-history/', + }; + ``` - react-history 微应用 - 路由设置: - ```html - - ``` - webpack 打包 publicPath 配置: - ```js - module.exports = { - output: { - publicPath: '/child/react-history/', - } - } - ``` + 路由设置: + + ```html + + ``` + + webpack 打包 publicPath 配置: + + ```js + module.exports = { + output: { + publicPath: '/child/react-history/', + }, + }; + ``` - angular-history 微应用 - 路由设置: - ```js - providers: [{ - provide: APP_BASE_HREF, - useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/' - }] - ``` - webpack 打包的 `publicPath` 通过 `deploy-url` 来修改,修改 `package.json`: - ```diff - - "build": "ng build", - + "build": "ng build --deploy-url /child/angular-history/", - ``` + 路由设置: + + ```js + providers: [ + { + provide: APP_BASE_HREF, + useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular/' : '/child/angular-history/', + }, + ]; + ``` + + webpack 打包的 `publicPath` 通过 `deploy-url` 来修改,修改 `package.json`: + + ```diff + - "build": "ng build", + + "build": "ng build --deploy-url /child/angular-history/", + ``` 那么此时的注册函数是这样的(需要保证 `activeRule` 和 `entry` 不同): ```js registerMicroApps([ { - name: 'app-vue-hash', + name: 'app-vue-hash', entry: '/child/vue-hash/', // http://localhost:8080/child/vue-hash/ - container: '#container', - activeRule: '/app-vue-hash', + container: '#container', + activeRule: '/app-vue-hash', }, - { + { name: 'app-vue-history', entry: '/child/vue-history/', // http://localhost:8080/child/vue-history/ - container: '#container', + container: '#container', activeRule: '/app-vue-history', }, // angular 和 react 同上 @@ -239,7 +255,7 @@ server { } ``` -#### 方案2:微应用直接放在二级目录,但是设置特殊的 `activeRule` +#### 方案 2:微应用直接放在二级目录,但是设置特殊的 `activeRule` ``` └── html/ # 根文件夹 @@ -257,7 +273,7 @@ server { 基本操作和上面是一样的,只要保证 `activeRule` 和微应用的存放路径名不一样即可。 -### 场景2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问 +### 场景 2:主应用和微应用部署在不同的服务器,使用 Nginx 代理访问 一般这么做是因为**不允许主应用跨域访问微应用**,做法就是将主应用服务器上一个特殊路径的请求全部转发到微应用的服务器上,即通过代理实现“微应用部署在主应用服务器上”的效果。 @@ -268,7 +284,7 @@ server { ``` /app1/ { proxy_pass http://www.b.com/app1/; - proxy_set_header Host $host:$server_port; + proxy_set_header Host $host:$server_port; } ``` @@ -277,10 +293,10 @@ server { ```js registerMicroApps([ { - name: 'app1', + name: 'app1', entry: '/app1/', // http://localhost:8080/app1/ - container: '#container', - activeRule: '/child-app1', + container: '#container', + activeRule: '/child-app1', }, ], ``` @@ -291,8 +307,8 @@ registerMicroApps([ module.exports = { output: { publicPath: `/app1/`, - } -} + }, +}; ``` 微应用打包的 `publicPath` 加上 `/app1/` 之后,必须部署在 `/app1` 目录,否则无法独立访问。 @@ -319,4 +335,4 @@ if ($http_custom_referer != "main") { `start` 函数基本修改如下: 1. `jsSandbox` 配置去掉,改为 `sandbox` ,可选值也修改了。 -2. 新增了 `getPublicPath` 和 `getTemplate` ,用于替代`RegisterMicroAppsOpts`。 \ No newline at end of file +2. 新增了 `getPublicPath` 和 `getTemplate` ,用于替代`RegisterMicroAppsOpts`。 diff --git a/docs/faq/README.md b/docs/faq/README.md index fdc29c7..214e68f 100644 --- a/docs/faq/README.md +++ b/docs/faq/README.md @@ -21,6 +21,7 @@ To solve the exception, try the following steps: 4. Check your `package.json` name field is unique between sub apps. 5. Check if the entry js in the sub-app's entry HTML is the last script to load. If not, move the order to make it be the last, or manually mark the entry js as `entry` in the HTML, such as: + ```html {2} @@ -29,8 +30,7 @@ To solve the exception, try the following steps: 6. If the development environment is OK but the production environment is not, check whether the `index.html` and `entry js` of the micro app are returned normally, for example, `404.html` is returned. -7. If you're using webpack5, please see [here](https://github.com/umijs/qiankun/issues/1092) -If it still not works after the steps above, this is usually due to browser compatibility issues. Try to **set the webpack `output.library` of the broken sub app the same with your main app registration for your app**, such as: +7. If you're using webpack5, please see [here](https://github.com/umijs/qiankun/issues/1092) If it still not works after the steps above, this is usually due to browser compatibility issues. Try to **set the webpack `output.library` of the broken sub app the same with your main app registration for your app**, such as: Such as here is the main configuration: @@ -64,48 +64,50 @@ module.exports = { This error thrown as the container DOM does not exist after the micro app is loaded. The possible reasons are: 1. The root id of the micro app conflicts with other DOM, and the solution is to modify the search range of the root id. - - `vue` micro app: - ```js - function render(props = {}) { - const { container } = props; - instance = new Vue({ - router, - store, - render: h => h(App), - }).$mount(container ? container.querySelector('#app') : '#app'); - } - export async function mount(props) { - render(props); - } - ``` - `react` micro app: - ```js - function render(props) { - const { container } = props; - ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); - } - export async function mount(props) { - render(props); - } - export async function unmount(props) { - const { container } = props; - ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); - } - ``` - + `vue` micro app: + + ```js + function render(props = {}) { + const { container } = props; + instance = new Vue({ + router, + store, + render: (h) => h(App), + }).$mount(container ? container.querySelector('#app') : '#app'); + } + export async function mount(props) { + render(props); + } + ``` + + `react` micro app: + + ```js + function render(props) { + const { container } = props; + ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); + } + export async function mount(props) { + render(props); + } + export async function unmount(props) { + const { container } = props; + ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); + } + ``` + 2. Some js of micro app use `document.write`, such as AMAP 1.x version, Tencent Map 2.x version. - If it is caused by the map js, see if the upgrade can be resolved, for example, upgrade the AMAP map to version 2.x. + If it is caused by the map js, see if the upgrade can be resolved, for example, upgrade the AMAP map to version 2.x. - If the upgrade cannot be resolved, it is recommended to put the map on the main app to load. The micro app also introduces this map js (used in run independently), but add the `ignore` attribute to the ` - ``` + ```html + + ``` - In other cases, please do not use `document.write`. + In other cases, please do not use `document.write`. ## `Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!` @@ -126,38 +128,38 @@ It must be ensured that the routing page of the main app is also loaded when the `vue` + `vue-router` main app: 1. When the main app registers this route, add a `*` to `path`, **Note: If this route has other sub-routes, you need to register another route, just use this component**. - ```js - const routes = [ - { - path: '/portal/*', - name: 'portal', - component: () => import('../views/Portal.vue'), - } - ] - ``` + ```js + const routes = [ + { + path: '/portal/*', + name: 'portal', + component: () => import('../views/Portal.vue'), + }, + ]; + ``` 2. The `activeRule` of the micro app needs to include the route `path` of the main app. - ```js - registerMicroApps([ - { - name: 'app1', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/portal/app1', - }, - ]); - ``` + ```js + registerMicroApps([ + { + name: 'app1', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/portal/app1', + }, + ]); + ``` 3. Call the `start` function in the `mounted` cycle of the `Portal.vue` component, **be careful not to call it repeatedly**. - ```js - import { start } from 'qiankun'; - export default { - mounted() { - if (!window.qiankunStarted) { - window.qiankunStarted = true; - start(); - } - }, - } - ``` + ```js + import { start } from 'qiankun'; + export default { + mounted() { + if (!window.qiankunStarted) { + window.qiankunStarted = true; + start(); + } + }, + }; + ``` `react` + `react-router` main app:only need to make the activeRule of the sub app include the route of the main app. @@ -165,40 +167,39 @@ It must be ensured that the routing page of the main app is also loaded when the 1. The main app registers a wildcard sub route for this route, and the content is empty. - ```ts - const routes: Routes = [ - { - path: 'portal', - component: PortalComponent, - children: [ - { path: '**', component: EmptyComponent }, - ], - }, - ]; - ``` + ```ts + const routes: Routes = [ + { + path: 'portal', + component: PortalComponent, + children: [{ path: '**', component: EmptyComponent }], + }, + ]; + ``` + 2. The `activeRule` of the micro app needs to include the route `path` of the main app. - ```js - registerMicroApps([ - { - name: 'app1', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/portal/app1', - }, - ]); - ``` + ```js + registerMicroApps([ + { + name: 'app1', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/portal/app1', + }, + ]); + ``` 3. Call the `start` function in the `ngAfterViewInit` cycle of this routing component, **be careful not to call it repeatedly**. - ```ts - import { start } from 'qiankun'; - export class PortalComponent implements AfterViewInit { - ngAfterViewInit(): void { - if (!window.qiankunStarted) { - window.qiankunStarted = true; - start(); - } - } - } - ``` + ```ts + import { start } from 'qiankun'; + export class PortalComponent implements AfterViewInit { + ngAfterViewInit(): void { + if (!window.qiankunStarted) { + window.qiankunStarted = true; + start(); + } + } + } + ``` ## Vue Router Error - `Uncaught TypeError: Cannot redefine property: $router` @@ -208,7 +209,7 @@ There are three lines code in the `vue-router` as followed, and it will access ` ```javascript if (inBrowser && window.Vue) { - window.Vue.use(VueRouter) + window.Vue.use(VueRouter); } ``` @@ -259,203 +260,195 @@ There are mainly the following solutions: 2. Use the `url-loader` of `webpack` to package font files and images as `base64` (suitable for projects with small font files and images)(**recommended**) - ```js - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - }, - ], - }, - ], - }, - }; - ``` +```js +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + }, + ], + }, + ], + }, +}; +``` - `vue-cli3` project: +`vue-cli3` project: - ```js - module.exports = { - chainWebpack: (config) => { - config.module - .rule('fonts') - .use('url-loader') - .loader('url-loader') - .options({}) - .end() - config.module - .rule('images') - .use('url-loader') - .loader('url-loader') - .options({}) - .end() - }, - } - ``` +```js +module.exports = { + chainWebpack: (config) => { + config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end(); + config.module.rule('images').use('url-loader').loader('url-loader').options({}).end(); + }, +}; +``` 3. Use the `file-loader` of `webpack` to inject the full path when packaging it (suitable for projects with large font files and images) - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp)$/i, - use: [ - { - loader: 'file-loader', - options: { - name: 'img/[name].[hash:8].[ext]', - publicPath - }, - }, - ], - }, - { - test: /\.(woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - }, - }, - ], - }, - ], - }, - }; - ``` - - `vue-cli3` project: - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - chainWebpack: (config) => { - const fontRule = config.module.rule('fonts'); - fontRule.uses.clear(); - fontRule - .use('file-loader') - .loader('file-loader') - .options({ - name: 'fonts/[name].[hash:8].[ext]', - publicPath - }) - .end() - const imgRule = config.module.rule('images'); - imgRule.uses.clear(); - imgRule - .use('file-loader') - .loader('file-loader') - .options({ - name: 'img/[name].[hash:8].[ext]', - publicPath - }) - .end() - }, - } - ``` - -4. Combine the two schemes, convert small files to `base64`, and inject path prefixes for large files - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - fallback: { - loader: 'file-loader', - options: { - name: 'img/[name].[hash:8].[ext]', - publicPath - } - } - }, - ], - }, - { - test: /\.(woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - fallback: { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - } - } - }, - ], - }, - ], - }, - }; - ``` - - `vue-cli3` project: - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - chainWebpack: (config) => { - config.module.rule('fonts') - .use('url-loader') - .loader('url-loader') - .options({ - limit: 4096, // Less than 4kb will be packaged as base64 - fallback: { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - } - } - }) - .end(); - config.module.rule('images') - .use('url-loader') - .loader('url-loader') - .options({ - limit: 4096, // Less than 4kb will be packaged as base64 - fallback: { +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp)$/i, + use: [ + { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]', - publicPath - } - } - }) - }, - } - ``` + publicPath, + }, + }, + ], + }, + { + test: /\.(woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + ], + }, + ], + }, +}; +``` + +`vue-cli3` project: + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + chainWebpack: (config) => { + const fontRule = config.module.rule('fonts'); + fontRule.uses.clear(); + fontRule + .use('file-loader') + .loader('file-loader') + .options({ + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }) + .end(); + const imgRule = config.module.rule('images'); + imgRule.uses.clear(); + imgRule + .use('file-loader') + .loader('file-loader') + .options({ + name: 'img/[name].[hash:8].[ext]', + publicPath, + }) + .end(); + }, +}; +``` + +4. Combine the two schemes, convert small files to `base64`, and inject path prefixes for large files + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + fallback: { + loader: 'file-loader', + options: { + name: 'img/[name].[hash:8].[ext]', + publicPath, + }, + }, + }, + ], + }, + { + test: /\.(woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + fallback: { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +`vue-cli3` project: + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + chainWebpack: (config) => { + config.module + .rule('fonts') + .use('url-loader') + .loader('url-loader') + .options({ + limit: 4096, // Less than 4kb will be packaged as base64 + fallback: { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + }) + .end(); + config.module + .rule('images') + .use('url-loader') + .loader('url-loader') + .options({ + limit: 4096, // Less than 4kb will be packaged as base64 + fallback: { + loader: 'file-loader', + options: { + name: 'img/[name].[hash:8].[ext]', + publicPath, + }, + }, + }); + }, +}; +``` 5. The `vue-cli3` project can package `css` into `js` without generating files separately (not recommended, only suitable for projects with less `css`) - Configuration reference [vue-cli3 official website](https://cli.vuejs.org/zh/config/#css-extract): +Configuration reference [vue-cli3 official website](https://cli.vuejs.org/zh/config/#css-extract): - ```js - module.exports = { - css: { - extract: false - }, - } - ``` +```js +module.exports = { + css: { + extract: false, + }, +}; +``` ## Must a sub app asset support cors? @@ -489,7 +482,7 @@ Example for antd: ```jsx import { ConfigProvider } from 'antd'; - + export const MyApp = () => ( @@ -585,10 +578,11 @@ In singular mode, you can use the `excludeAssetFilter` parameter to release this If you use JSONP in not-singular mode, simply using `excludeAssetFilter` does not achieve good results, because each application is isolated by the sandbox; you can provide a unified JSONP tool in the main application, and the subapplication just calls the tool. ## 404 after refresh of child application? -It is usually because you are routing in Browser mode, which requires the server to open it. -Specific configuration mode reference: -* [HTML5 History Mode](https://router.vuejs.org/guide/essentials/history-mode.html) -* [browserRouter](https://reactrouter.com/web/api/BrowserRouter) + +It is usually because you are routing in Browser mode, which requires the server to open it. Specific configuration mode reference: + +- [HTML5 History Mode](https://router.vuejs.org/guide/essentials/history-mode.html) +- [browserRouter](https://reactrouter.com/web/api/BrowserRouter) ## How to configure the 404 page in the main application? @@ -597,16 +591,17 @@ First of all, you cannot use the wildcard `*`. You can register the 404 page as Take `vue-router` as an example, the pseudo code is as follows: ```js -const childrenPath = ['/app1','/app2']; +const childrenPath = ['/app1', '/app2']; router.beforeEach((to, from, next) => { - if(to.name) {// There is a name attribute, indicating that it is the route of the main project - next() + if (to.name) { + // There is a name attribute, indicating that it is the route of the main project + next(); } - if(childrenPath.some(item => to.path.includes(item))){ - next() + if (childrenPath.some((item) => to.path.includes(item))) { + next(); } - next({ name: '404' }) -}) + next({ name: '404' }); +}); ``` ## How to jump between micro apps? @@ -615,11 +610,10 @@ router.beforeEach((to, from, next) => { -The main application judges the micro application based on the `path` - It is not possible to directly use the routing instance of the micro-application to jump between micro-applications in the `history` mode or to jump to the main application page. The reason is that the routing instance jumps of the micro-application are all based on the `base` of the route. There are two ways to jump: - - 1. `history.pushState()`: [mdn usage introduction](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState) - 2. Pass the routing instance of the main application to the micro application through `props`, and the micro application will jump to this routing instance. +It is not possible to directly use the routing instance of the micro-application to jump between micro-applications in the `history` mode or to jump to the main application page. The reason is that the routing instance jumps of the micro-application are all based on the `base` of the route. There are two ways to jump: +1. `history.pushState()`: [mdn usage introduction](https://developer.mozilla.org/zh-CN/docs/Web/API/History/pushState) +2. Pass the routing instance of the main application to the micro application through `props`, and the micro application will jump to this routing instance. ## After the microapp file is updated, the old version of the file is still accessed @@ -635,15 +629,15 @@ location = /index.html { ## micro app styles was lost when using config entry -Some scenarios we had to use config entry to load micro app (** not recommended **): +Some scenarios we had to use config entry to load micro app (** not recommended **): ```js loadMicroApp({ name: 'configEntry', entry: { scripts: ['//t.com/t.js'], - styles: ['//t.com/t.css'] - } + styles: ['//t.com/t.css'], + }, }); ``` @@ -651,9 +645,10 @@ Since there is no HTML attached to entry JS for microapp, the mount hook simply ```js export async function mount(props) { - ReactDOM.render(, props.container); + ReactDOM.render(, props.container); } ``` + As `props.container` is not an empty container and will contain information such as the style sheet that the microapp registers through the styles configuration, when we render directly for the container that the react application is applying with 'props.container', all the original DOM structures in the container will be overwritten, causing the style sheet to be lost. We need to build an empty render container for micro applications that use Config Entry to mount react applications: @@ -682,12 +677,12 @@ export async function mount(props) { As the requests to pull micro-app entry are all cross-domain, when your micro-app relies on cookies (such as authentication), you need to customize the fetch method to enable the cors mode: -* If you load the microapps through [registerMicroApps](/api#registermicroappsapps-lifecycles), you need to configure a custom fetch in the start method, such as: +- If you load the microapps through [registerMicroApps](/api#registermicroappsapps-lifecycles), you need to configure a custom fetch in the start method, such as: ```js import { start } from 'qiankun'; - - start({ + + start({ fetch(url, ...args) { // Enable cors mode for the specified microapp if (url === 'http://app.alipay.com/entry.html') { @@ -697,17 +692,17 @@ As the requests to pull micro-app entry are all cross-domain, when your micro-ap credentials: 'include', }); } - + return window.fetch(url, ...args); - } + }, }); ``` -* If you load the microapp via [loadMicroApp](/api#loadmicroappapp-configuration), you need to configure a custom fetch when invoking, such as: +- If you load the microapp via [loadMicroApp](/api#loadmicroappapp-configuration), you need to configure a custom fetch when invoking, such as: ```js import { loadMicroApp } from 'qiankun'; - + loadMicroApp(app, { fetch(url, ...args) { // Enable cors mode for the specified microapp @@ -718,8 +713,8 @@ As the requests to pull micro-app entry are all cross-domain, when your micro-ap credentials: 'include', }); } - + return window.fetch(url, ...args); - } + }, }); ``` diff --git a/docs/faq/README.zh.md b/docs/faq/README.zh.md index 379c349..9ab44da 100644 --- a/docs/faq/README.zh.md +++ b/docs/faq/README.zh.md @@ -30,7 +30,7 @@ qiankun 抛出这个错误是因为无法从微应用的 entry js 中识别出 6. 如果开发环境可以,生产环境不行,检查微应用的 `index.html` 和 `entry js` 是否正常返回,比如说返回了 `404.html`。 -7. 如果你正在使用 webpack5,请看[这个issues](https://github.com/umijs/qiankun/issues/1092) +7. 如果你正在使用 webpack5,请看[这个 issues](https://github.com/umijs/qiankun/issues/1092) 如果在上述步骤完成后仍有问题,通常说明是浏览器兼容性问题导致的。可以尝试 **将有问题的微应用的 webpack `output.library` 配置成跟主应用中注册的 `name` 字段一致**,如: @@ -67,47 +67,49 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在 1. 微应用的根 `id` 与其他 DOM 冲突。解决办法是:修改根 `id` 的查找范围。 - `vue` 微应用: - ```js - function render(props = {}) { - const { container } = props; - instance = new Vue({ - router, - store, - render: h => h(App), - }).$mount(container ? container.querySelector('#app') : '#app'); - } - export async function mount(props) { - render(props); - } - ``` + `vue` 微应用: - `react` 微应用: - ```js - function render(props) { - const { container } = props; - ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); - } - export async function mount(props) { - render(props); - } - export async function unmount(props) { - const { container } = props; - ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); - } - ``` + ```js + function render(props = {}) { + const { container } = props; + instance = new Vue({ + router, + store, + render: (h) => h(App), + }).$mount(container ? container.querySelector('#app') : '#app'); + } + export async function mount(props) { + render(props); + } + ``` + + `react` 微应用: + + ```js + function render(props) { + const { container } = props; + ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); + } + export async function mount(props) { + render(props); + } + export async function unmount(props) { + const { container } = props; + ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); + } + ``` 2. 微应用的某些 js 里面使用了 `document.write`,比如高德地图 1.x 版本,腾讯地图 2.x 版本。 - 如果是地图 js 导致的,先看看升级能否解决,比如说高德地图升级到 2.x 版本即可。 + 如果是地图 js 导致的,先看看升级能否解决,比如说高德地图升级到 2.x 版本即可。 - 如果升级无法解决,建议将地图放到主应用加载,微应用也引入这个地图 js(独立运行时使用),但是给 ` - ``` + ```html + + ``` - 如果是其他的情况,请不要使用 `document.write` 。 + 如果是其他的情况,请不要使用 `document.write` 。 ## `Application died in status NOT_MOUNTED: Target container with #container not existed while xxx mounting!` @@ -128,38 +130,38 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在 `vue` + `vue-router` 技术栈的主应用: 1. 主应用注册这个路由时给 `path` 加一个 `*`,**注意:如果这个路由有其他子路由,需要另外注册一个路由,仍然使用这个组件即可**。 - ```js - const routes = [ - { - path: '/portal/*', - name: 'portal', - component: () => import('../views/Portal.vue'), - } - ] - ``` + ```js + const routes = [ + { + path: '/portal/*', + name: 'portal', + component: () => import('../views/Portal.vue'), + }, + ]; + ``` 2. 微应用的 `activeRule` 需要包含主应用的这个路由 `path`。 - ```js - registerMicroApps([ - { - name: 'app1', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/portal/app1', - }, - ]); - ``` + ```js + registerMicroApps([ + { + name: 'app1', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/portal/app1', + }, + ]); + ``` 3. 在 `Portal.vue` 这个组件的 `mounted` 周期调用 `start` 函数,**注意不要重复调用**。 - ```js - import { start } from 'qiankun'; - export default { - mounted() { - if (!window.qiankunStarted) { - window.qiankunStarted = true; - start(); - } - }, - } - ``` + ```js + import { start } from 'qiankun'; + export default { + mounted() { + if (!window.qiankunStarted) { + window.qiankunStarted = true; + start(); + } + }, + }; + ``` `react` + `react-router` 技术栈的主应用:只需要让微应用的 `activeRule` 包含主应用的这个路由即可。 @@ -167,40 +169,40 @@ qiankun 抛出这个错误是因为微应用加载后容器 DOM 节点不存在 1. 主应用给这个路由注册一个通配符的子路由,内容为空。 - ```ts - const routes: Routes = [ - { - path: 'portal', - component: PortalComponent, - children: [ - { path: '**', component: EmptyComponent }, - ], - }, - ]; - ``` + ```ts + const routes: Routes = [ + { + path: 'portal', + component: PortalComponent, + children: [{ path: '**', component: EmptyComponent }], + }, + ]; + ``` + 2. 微应用的 `activeRule` 需要包含主应用的这个路由 `path`。 - ```js - registerMicroApps([ - { - name: 'app1', - entry: 'http://localhost:8080', - container: '#container', - activeRule: '/portal/app1', - }, - ]); - ``` + ```js + registerMicroApps([ + { + name: 'app1', + entry: 'http://localhost:8080', + container: '#container', + activeRule: '/portal/app1', + }, + ]); + ``` 3. 在这个路由组件的 `ngAfterViewInit` 周期调用 `start` 函数,**注意不要重复调用**。 - ```ts - import { start } from 'qiankun'; - export class PortalComponent implements AfterViewInit { - ngAfterViewInit(): void { - if (!window.qiankunStarted) { - window.qiankunStarted = true; - start(); - } - } - } - ``` + ```ts + import { start } from 'qiankun'; + export class PortalComponent implements AfterViewInit { + ngAfterViewInit(): void { + if (!window.qiankunStarted) { + window.qiankunStarted = true; + start(); + } + } + } + ``` + ## Vue Router 报错 `Uncaught TypeError: Cannot redefine property: $router` qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙箱,在微应用中访问 `window.Vue` 时,会先在自己的 window 里查找有没有 `Vue` 属性,如果没有就去父应用里查找。 @@ -209,7 +211,7 @@ qiankun 中的代码使用 Proxy 去代理父页面的 window,来实现的沙 ```javascript if (inBrowser && window.Vue) { - window.Vue.use(VueRouter) + window.Vue.use(VueRouter); } ``` @@ -262,203 +264,195 @@ runtime publicPath 主要解决的是微应用动态载入的 脚本、样式、 2. 借助 `webpack` 的 `url-loader` 将字体文件和图片打包成 `base64`(适用于字体文件和图片体积小的项目)(**推荐**) - ```js - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - }, - ], - }, - ], - }, - }; - ``` +```js +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp|woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + }, + ], + }, + ], + }, +}; +``` - `vue-cli3` 项目写法: +`vue-cli3` 项目写法: - ```js - module.exports = { - chainWebpack: (config) => { - config.module - .rule('fonts') - .use('url-loader') - .loader('url-loader') - .options({}) - .end() - config.module - .rule('images') - .use('url-loader') - .loader('url-loader') - .options({}) - .end() - }, - } - ``` +```js +module.exports = { + chainWebpack: (config) => { + config.module.rule('fonts').use('url-loader').loader('url-loader').options({}).end(); + config.module.rule('images').use('url-loader').loader('url-loader').options({}).end(); + }, +}; +``` 3. 借助 `webpack` 的 `file-loader` ,在打包时给其注入完整路径(适用于字体文件和图片体积比较大的项目) - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp)$/i, - use: [ - { - loader: 'file-loader', - options: { - name: 'img/[name].[hash:8].[ext]', - publicPath - }, - }, - ], - }, - { - test: /\.(woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - }, - }, - ], - }, - ], - }, - }; - ``` - - `vue-cli3` 项目写法: - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - chainWebpack: (config) => { - const fontRule = config.module.rule('fonts'); - fontRule.uses.clear(); - fontRule - .use('file-loader') - .loader('file-loader') - .options({ - name: 'fonts/[name].[hash:8].[ext]', - publicPath - }) - .end() - const imgRule = config.module.rule('images'); - imgRule.uses.clear(); - imgRule - .use('file-loader') - .loader('file-loader') - .options({ - name: 'img/[name].[hash:8].[ext]', - publicPath - }) - .end() - }, - } - ``` - -4. 将两种方案结合起来,小文件转 `base64` ,大文件注入路径前缀 - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - module: { - rules: [ - { - test: /\.(png|jpe?g|gif|webp)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - fallback: { - loader: 'file-loader', - options: { - name: 'img/[name].[hash:8].[ext]', - publicPath - } - } - }, - ], - }, - { - test: /\.(woff2?|eot|ttf|otf)$/i, - use: [ - { - loader: 'url-loader', - options: {}, - fallback: { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - } - } - }, - ], - }, - ], - }, - }; - ``` - - `vue-cli3` 项目写法: - - ```js - const publicPath = process.env.NODE_ENV === "production" ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; - module.exports = { - chainWebpack: (config) => { - config.module.rule('fonts') - .use('url-loader') - .loader('url-loader') - .options({ - limit: 4096, // 小于4kb将会被打包成 base64 - fallback: { - loader: 'file-loader', - options: { - name: 'fonts/[name].[hash:8].[ext]', - publicPath - } - } - }) - .end(); - config.module.rule('images') - .use('url-loader') - .loader('url-loader') - .options({ - limit: 4096, // 小于4kb将会被打包成 base64 - fallback: { +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp)$/i, + use: [ + { loader: 'file-loader', options: { name: 'img/[name].[hash:8].[ext]', - publicPath - } - } - }) - }, - } - ``` + publicPath, + }, + }, + ], + }, + { + test: /\.(woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + ], + }, + ], + }, +}; +``` + +`vue-cli3` 项目写法: + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + chainWebpack: (config) => { + const fontRule = config.module.rule('fonts'); + fontRule.uses.clear(); + fontRule + .use('file-loader') + .loader('file-loader') + .options({ + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }) + .end(); + const imgRule = config.module.rule('images'); + imgRule.uses.clear(); + imgRule + .use('file-loader') + .loader('file-loader') + .options({ + name: 'img/[name].[hash:8].[ext]', + publicPath, + }) + .end(); + }, +}; +``` + +4. 将两种方案结合起来,小文件转 `base64` ,大文件注入路径前缀 + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + module: { + rules: [ + { + test: /\.(png|jpe?g|gif|webp)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + fallback: { + loader: 'file-loader', + options: { + name: 'img/[name].[hash:8].[ext]', + publicPath, + }, + }, + }, + ], + }, + { + test: /\.(woff2?|eot|ttf|otf)$/i, + use: [ + { + loader: 'url-loader', + options: {}, + fallback: { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + }, + ], + }, + ], + }, +}; +``` + +`vue-cli3` 项目写法: + +```js +const publicPath = process.env.NODE_ENV === 'production' ? 'https://qiankun.umijs.org/' : `http://localhost:${port}`; +module.exports = { + chainWebpack: (config) => { + config.module + .rule('fonts') + .use('url-loader') + .loader('url-loader') + .options({ + limit: 4096, // 小于4kb将会被打包成 base64 + fallback: { + loader: 'file-loader', + options: { + name: 'fonts/[name].[hash:8].[ext]', + publicPath, + }, + }, + }) + .end(); + config.module + .rule('images') + .use('url-loader') + .loader('url-loader') + .options({ + limit: 4096, // 小于4kb将会被打包成 base64 + fallback: { + loader: 'file-loader', + options: { + name: 'img/[name].[hash:8].[ext]', + publicPath, + }, + }, + }); + }, +}; +``` 5. `vue-cli3` 项目可以将 `css` 打包到 `js`里面,不单独生成文件(不推荐,仅适用于 `css` 较少的项目) - 配置参考 [vue-cli3 官网](https://cli.vuejs.org/zh/config/#css-extract): +配置参考 [vue-cli3 官网](https://cli.vuejs.org/zh/config/#css-extract): - ```js - module.exports = { - css: { - extract: false - }, - } - ``` +```js +module.exports = { + css: { + extract: false, + }, +}; +``` ## 微应用静态资源一定要支持跨域吗? @@ -490,7 +484,7 @@ import { start } from 'qiankun'; start({ getTemplate(tpl) { return tpl.replace(' - ``` + Add the following content to the `` tag in the `src/index.html` of the micro app, which is used when the micro app is accessed independently. + + ```html + + + ``` 6. Fix `ng build` comand's error report, modify `tsconfig.json` file, reference[issues/431](https://github.com/umijs/qiankun/issues/431). - ```diff - - "target": "es2015", - + "target": "es5", - + "typeRoots": [ - + "node_modules/@types" - + ], - ``` + ```diff + - "target": "es2015", + + "target": "es5", + + "typeRoots": [ + + "node_modules/@types" + + ], + ``` 7. In order to prevent the conflict of `` when the main app or other micro apps are also `angular`, it is recommended to add a unique id to ``, such as Say the current app name. - src/index.html : - ```diff - - - + - ``` + src/index.html : - src/app/app.component.ts : - ```diff - - selector: 'app-root', - + selector: '#angular9 app-root', - ``` + ```diff + - + + + ``` + + src/app/app.component.ts : + + ```diff + - selector: 'app-root', + + selector: '#angular9 app-root', + ``` Of course, you can also choose to use the `single-spa-angular` plugin, refer to[ single-spa-angular official website](https://single-spa.js.org/docs/ecosystem-angular) 和 [angular demo](https://github.com/umijs/qiankun/tree/master/examples/angular9) @@ -418,6 +425,7 @@ Modify `angular.json`, `[packageName] > architect > build > builder` is the same - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/dev-server:generic", ``` + ### Micro app built without webpack Some apps that are not built by `webpack`, such as `jQuery` app, `jsp` app, can be handled according to this. @@ -430,49 +438,49 @@ example: 1. declare entry script - ```diff - - - - - - Purehtml Example - - -
- Purehtml Example -
- + ```diff + + + + + + Purehtml Example + + +
+ Purehtml Example +
+ - + - - ``` + + + + ``` 2. export lifecycles in the entry - ```javascript - const render = ($) => { - $('#purehtml-container').html("Hello, render with jQuery"); - return Promise.resolve(); - } + ```javascript + const render = ($) => { + $('#purehtml-container').html('Hello, render with jQuery'); + return Promise.resolve(); + }; - (global => { - global['purehtml'] = { - bootstrap: () => { - console.log('purehtml bootstrap'); - return Promise.resolve(); - }, - mount: () => { - console.log('purehtml mount'); - return render($) - }, - unmount: () => { - console.log('purehtml unmount'); - return Promise.resolve(); - }, - }; - })(window); - ``` + ((global) => { + global['purehtml'] = { + bootstrap: () => { + console.log('purehtml bootstrap'); + return Promise.resolve(); + }, + mount: () => { + console.log('purehtml mount'); + return render($); + }, + unmount: () => { + console.log('purehtml unmount'); + return Promise.resolve(); + }, + }; + })(window); + ``` refer to the [purehtml examples](https://github.com/umijs/qiankun/tree/master/examples/purehtml) diff --git a/docs/guide/tutorial.zh.md b/docs/guide/tutorial.zh.md index 34f8b4c..b7abcaa 100644 --- a/docs/guide/tutorial.zh.md +++ b/docs/guide/tutorial.zh.md @@ -47,13 +47,13 @@ start(); ## 微应用 -微应用分为有 `webpack` 构建和无 `webpack` 构建项目,有 `webpack` 的微应用(主要是指Vue、React、Angular)需要做的事情有: +微应用分为有 `webpack` 构建和无 `webpack` 构建项目,有 `webpack` 的微应用(主要是指 Vue、React、Angular)需要做的事情有: 1. 新增 `public-path.js` 文件,用于修改运行时的 `publicPath`。[什么是运行时的 publicPath ?](https://webpack.docschina.org/guides/public-path/#on-the-fly)。 - - 注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。 - + +注意:运行时的 publicPath 和构建时的 publicPath 是不同的,两者不能等价替代。 + 2. 微应用建议使用 `history` 模式的路由,需要设置路由 `base`,值和它的 `activeRule` 是一样的。 3. 在入口文件最顶部引入 `public-path.js`,修改并导出三个生命周期函数。 @@ -69,176 +69,181 @@ start(); 1. 在 `src` 目录新增 `public-path.js`: - ```js - if (window.__POWERED_BY_QIANKUN__) { - __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; - } - ``` + ```js + if (window.__POWERED_BY_QIANKUN__) { + __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; + } + ``` + 2. 设置 `history` 模式路由的 `base`: - ```html - - ``` + ```html + + ``` 3. 入口文件 `index.js` 修改,为了避免根 id `#root` 与其他的 DOM 冲突,需要限制查找范围。 - ```js - import './public-path'; - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; + ```js + import './public-path'; + import React from 'react'; + import ReactDOM from 'react-dom'; + import App from './App'; - function render(props) { - const { container } = props; - ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); - } + function render(props) { + const { container } = props; + ReactDOM.render(, container ? container.querySelector('#root') : document.querySelector('#root')); + } - if (!window.__POWERED_BY_QIANKUN__) { - render({}); - } + if (!window.__POWERED_BY_QIANKUN__) { + render({}); + } - export async function bootstrap() { - console.log('[react16] react app bootstraped'); - } + export async function bootstrap() { + console.log('[react16] react app bootstraped'); + } - export async function mount(props) { - console.log('[react16] props from main framework', props); - render(props); - } + export async function mount(props) { + console.log('[react16] props from main framework', props); + render(props); + } - export async function unmount(props) { - const { container } = props; - ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); - } - ``` + export async function unmount(props) { + const { container } = props; + ReactDOM.unmountComponentAtNode(container ? container.querySelector('#root') : document.querySelector('#root')); + } + ``` 4. 修改 `webpack` 配置 - 安装插件 `@rescripts/cli`,当然也可以选择其他的插件,例如 `react-app-rewired`。 - ```dash - npm i -D @rescripts/cli - ``` + 安装插件 `@rescripts/cli`,当然也可以选择其他的插件,例如 `react-app-rewired`。 - 根目录新增 `.rescriptsrc.js`: - ```js - const { name } = require('./package'); + ```dash + npm i -D @rescripts/cli + ``` - module.exports = { - webpack: config => { - config.output.library = `${name}-[name]`; - config.output.libraryTarget = 'umd'; - config.output.jsonpFunction = `webpackJsonp_${name}`; - config.output.globalObject = 'window'; + 根目录新增 `.rescriptsrc.js`: - return config; - }, + ```js + const { name } = require('./package'); - devServer: _ => { - const config = _; + module.exports = { + webpack: (config) => { + config.output.library = `${name}-[name]`; + config.output.libraryTarget = 'umd'; + config.output.jsonpFunction = `webpackJsonp_${name}`; + config.output.globalObject = 'window'; - config.headers = { - 'Access-Control-Allow-Origin': '*', - }; - config.historyApiFallback = true; - config.hot = false; - config.watchContentBase = false; - config.liveReload = false; + return config; + }, - return config; - }, - }; - ``` + devServer: (_) => { + const config = _; + + config.headers = { + 'Access-Control-Allow-Origin': '*', + }; + config.historyApiFallback = true; + config.hot = false; + config.watchContentBase = false; + config.liveReload = false; + + return config; + }, + }; + ``` + + 修改 `package.json`: + + ```diff + - "start": "react-scripts start", + + "start": "rescripts start", + - "build": "react-scripts build", + + "build": "rescripts build", + - "test": "react-scripts test", + + "test": "rescripts test", + - "eject": "react-scripts eject" + ``` - 修改 `package.json`: - ```diff - - "start": "react-scripts start", - + "start": "rescripts start", - - "build": "react-scripts build", - + "build": "rescripts build", - - "test": "react-scripts test", - + "test": "rescripts test", - - "eject": "react-scripts eject" - ``` ### Vue 微应用 以 `vue-cli 3+` 生成的 `vue 2.x` 项目为例,`vue 3` 版本等稳定后再补充。 1. 在 `src` 目录新增 `public-path.js`: - ```js - if (window.__POWERED_BY_QIANKUN__) { - __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; - } - ``` + ```js + if (window.__POWERED_BY_QIANKUN__) { + __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; + } + ``` -3. 入口文件 `main.js` 修改,为了避免根 id `#app` 与其他的 DOM 冲突,需要限制查找范围。 +2. 入口文件 `main.js` 修改,为了避免根 id `#app` 与其他的 DOM 冲突,需要限制查找范围。 - ```js - import './public-path'; - import Vue from 'vue'; - import VueRouter from 'vue-router'; - import App from './App.vue'; - import routes from './router'; - import store from './store'; + ```js + import './public-path'; + import Vue from 'vue'; + import VueRouter from 'vue-router'; + import App from './App.vue'; + import routes from './router'; + import store from './store'; - Vue.config.productionTip = false; + Vue.config.productionTip = false; - let router = null; - let instance = null; - function render(props = {}) { - const { container } = props; - router = new VueRouter({ - base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/', - mode: 'history', - routes, - }); + let router = null; + let instance = null; + function render(props = {}) { + const { container } = props; + router = new VueRouter({ + base: window.__POWERED_BY_QIANKUN__ ? '/app-vue/' : '/', + mode: 'history', + routes, + }); - instance = new Vue({ - router, - store, - render: h => h(App), - }).$mount(container ? container.querySelector('#app') : '#app'); - } + instance = new Vue({ + router, + store, + render: (h) => h(App), + }).$mount(container ? container.querySelector('#app') : '#app'); + } - // 独立运行时 - if (!window.__POWERED_BY_QIANKUN__) { - render(); - } + // 独立运行时 + if (!window.__POWERED_BY_QIANKUN__) { + render(); + } - export async function bootstrap() { - console.log('[vue] vue app bootstraped'); - } - export async function mount(props) { - console.log('[vue] props from main framework', props); - render(props); - } - export async function unmount() { - instance.$destroy(); - instance.$el.innerHTML = ''; - instance = null; - router = null; - } - ``` + export async function bootstrap() { + console.log('[vue] vue app bootstraped'); + } + export async function mount(props) { + console.log('[vue] props from main framework', props); + render(props); + } + export async function unmount() { + instance.$destroy(); + instance.$el.innerHTML = ''; + instance = null; + router = null; + } + ``` 3. 打包配置修改(`vue.config.js`): - ```js - const { name } = require('./package'); - module.exports = { - devServer: { - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }, - configureWebpack: { - output: { - library: `${name}-[name]`, - libraryTarget: 'umd',// 把微应用打包成 umd 库格式 - jsonpFunction: `webpackJsonp_${name}`, - }, - }, - }; - ``` + ```js + const { name } = require('./package'); + module.exports = { + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }, + configureWebpack: { + output: { + library: `${name}-[name]`, + libraryTarget: 'umd', // 把微应用打包成 umd 库格式 + jsonpFunction: `webpackJsonp_${name}`, + }, + }, + }; + ``` ### Angular 微应用 @@ -246,151 +251,153 @@ start(); 1. 在 `src` 目录新增 `public-path.js` 文件,内容为: - ```js - if (window.__POWERED_BY_QIANKUN__) { - // eslint-disable-next-line no-undef - __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; - } - ``` + ```js + if (window.__POWERED_BY_QIANKUN__) { + // eslint-disable-next-line no-undef + __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; + } + ``` 2. 设置 `history` 模式路由的 `base`,`src/app/app-routing.module.ts` 文件: - - ```diff - + import { APP_BASE_HREF } from '@angular/common'; - @NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule], - // @ts-ignore - + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }] - }) - ``` + ```diff + + import { APP_BASE_HREF } from '@angular/common'; + + @NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule], + // @ts-ignore + + providers: [{ provide: APP_BASE_HREF, useValue: window.__POWERED_BY_QIANKUN__ ? '/app-angular' : '/' }] + }) + ``` 3. 修改入口文件,`src/main.ts` 文件。 - ```ts - import './public-path'; - import { enableProdMode, NgModuleRef } from '@angular/core'; - import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; - import { AppModule } from './app/app.module'; - import { environment } from './environments/environment'; + ```ts + import './public-path'; + import { enableProdMode, NgModuleRef } from '@angular/core'; + import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + import { AppModule } from './app/app.module'; + import { environment } from './environments/environment'; - if (environment.production) { - enableProdMode(); - } + if (environment.production) { + enableProdMode(); + } - let app: void | NgModuleRef; - async function render() { - app = await platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch(err => console.error(err)); - } - if (!(window as any).__POWERED_BY_QIANKUN__) { - render(); - } + let app: void | NgModuleRef; + async function render() { + app = await platformBrowserDynamic() + .bootstrapModule(AppModule) + .catch((err) => console.error(err)); + } + if (!(window as any).__POWERED_BY_QIANKUN__) { + render(); + } - export async function bootstrap (props: Object) { - console.log(props); - } + export async function bootstrap(props: Object) { + console.log(props); + } - export async function mount (props: Object) { - render(); - } + export async function mount(props: Object) { + render(); + } - export async function unmount (props: Object) { - console.log(props); - // @ts-ignore - app.destroy(); - } - ``` + export async function unmount(props: Object) { + console.log(props); + // @ts-ignore + app.destroy(); + } + ``` 4. 修改 `webpack` 打包配置 - 先安装 `@angular-builders/custom-webpack` 插件,**注意:`angular 9` 项目只能安装 `9.x` 版本,`angular 10` 项目可以安装最新版**。 + 先安装 `@angular-builders/custom-webpack` 插件,**注意:`angular 9` 项目只能安装 `9.x` 版本,`angular 10` 项目可以安装最新版**。 - ```bash - npm i @angular-builders/custom-webpack@9.2.0 -D - ``` + ```bash + npm i @angular-builders/custom-webpack@9.2.0 -D + ``` - 在根目录增加 `custom-webpack.config.js` ,内容为: + 在根目录增加 `custom-webpack.config.js` ,内容为: - ```js - const appName = require('./package.json').name; - module.exports = { - devServer: { - headers: { - 'Access-Control-Allow-Origin': '*', - }, - }, - output: { - library: `${appName}-[name]`, - libraryTarget: 'umd', - jsonpFunction: `webpackJsonp_${appName}`, - }, - }; - ``` + ```js + const appName = require('./package.json').name; + module.exports = { + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }, + output: { + library: `${appName}-[name]`, + libraryTarget: 'umd', + jsonpFunction: `webpackJsonp_${appName}`, + }, + }; + ``` - 修改 `angular.json`,将 `[packageName] > architect > build > builder` 和 `[packageName] > architect > serve > builder` 的值改为我们安装的插件,将我们的打包配置文件加入到 `[packageName] > architect > build > options`。 + 修改 `angular.json`,将 `[packageName] > architect > build > builder` 和 `[packageName] > architect > serve > builder` 的值改为我们安装的插件,将我们的打包配置文件加入到 `[packageName] > architect > build > options`。 - ```diff - - "builder": "@angular-devkit/build-angular:browser", - + "builder": "@angular-builders/custom-webpack:browser", - "options": { - + "customWebpackConfig": { - + "path": "./custom-webpack.config.js" - + } - } - ``` + ```diff + - "builder": "@angular-devkit/build-angular:browser", + + "builder": "@angular-builders/custom-webpack:browser", + "options": { + + "customWebpackConfig": { + + "path": "./custom-webpack.config.js" + + } + } + ``` - ```diff - - "builder": "@angular-devkit/build-angular:dev-server", - + "builder": "@angular-builders/custom-webpack:dev-server", - ``` + ```diff + - "builder": "@angular-devkit/build-angular:dev-server", + + "builder": "@angular-builders/custom-webpack:dev-server", + ``` 5. 解决 `zone.js` 的问题 - - 在**父应用**引入 `zone.js`,需要在 `import qiankun` 之前引入。 - 将微应用的 `src/polyfills.ts` 里面的引入 `zone.js` 代码删掉。 + 在**父应用**引入 `zone.js`,需要在 `import qiankun` 之前引入。 - ```diff - - import 'zone.js/dist/zone'; - ``` + 将微应用的 `src/polyfills.ts` 里面的引入 `zone.js` 代码删掉。 - 在微应用的 `src/index.html` 里面的 `` 标签加上下面内容,微应用独立访问时使用。 + ```diff + - import 'zone.js/dist/zone'; + ``` - ```html - - - ``` + 在微应用的 `src/index.html` 里面的 `` 标签加上下面内容,微应用独立访问时使用。 + + ```html + + + ``` 6. 修正 `ng build` 打包报错问题,修改 `tsconfig.json` 文件,参考[issues/431](https://github.com/umijs/qiankun/issues/431) - ```diff - - "target": "es2015", - + "target": "es5", - + "typeRoots": [ - + "node_modules/@types" - + ], - ``` + ```diff + - "target": "es2015", + + "target": "es5", + + "typeRoots": [ + + "node_modules/@types" + + ], + ``` 7. 为了防止主应用或其他微应用也为 `angular` 时,`` 会冲突的问题,建议给`` 加上一个唯一的 id,比如说当前应用名称。 - src/index.html : - ```diff - - - + - ``` + src/index.html : - src/app/app.component.ts : - ```diff - - selector: 'app-root', - + selector: '#angular9 app-root', - ``` + ```diff + - + + + ``` + + src/app/app.component.ts : + + ```diff + - selector: 'app-root', + + selector: '#angular9 app-root', + ``` 当然,也可以选择使用 `single-spa-angular` 插件,参考[ single-spa-angular 的官网](https://single-spa.js.org/docs/ecosystem-angular) 和 [angular demo](https://github.com/umijs/qiankun/tree/master/examples/angular9) -(**补充**)angular7 项目除了第4步以外,其他的步骤和 angular9 一模一样。angular7 修改 `webpack` 打包配置的步骤如下: +(**补充**)angular7 项目除了第 4 步以外,其他的步骤和 angular9 一模一样。angular7 修改 `webpack` 打包配置的步骤如下: 除了安装 `angular-builders/custom-webpack` 插件的 7.x 版本外,还需要安装 `angular-builders/dev-server`。 @@ -429,49 +436,49 @@ npm i @angular-builders/dev-server -D 1. 声明 entry 入口 - ```diff - - - - - - Purehtml Example - - -
- Purehtml Example -
- + ```diff + + + + + + Purehtml Example + + +
+ Purehtml Example +
+ - + - - ``` + + + + ``` 2. 在 entry js 里声明 lifecycles - ```js - const render = ($) => { - $('#purehtml-container').html("Hello, render with jQuery"); - return Promise.resolve(); - } + ```js + const render = ($) => { + $('#purehtml-container').html('Hello, render with jQuery'); + return Promise.resolve(); + }; - (global => { - global['purehtml'] = { - bootstrap: () => { - console.log('purehtml bootstrap'); - return Promise.resolve(); - }, - mount: () => { - console.log('purehtml mount'); - return render($); - }, - unmount: () => { - console.log('purehtml unmount'); - return Promise.resolve(); - }, - }; - })(window); - ``` + ((global) => { + global['purehtml'] = { + bootstrap: () => { + console.log('purehtml bootstrap'); + return Promise.resolve(); + }, + mount: () => { + console.log('purehtml mount'); + return render($); + }, + unmount: () => { + console.log('purehtml unmount'); + return Promise.resolve(); + }, + }; + })(window); + ``` 你也可以直接参照 examples 中 purehtml 部分的[代码](https://github.com/umijs/qiankun/tree/master/examples/purehtml) @@ -480,4 +487,3 @@ npm i @angular-builders/dev-server -D ### umi-qiankun 项目 `umi-qiankun` 的教程请移步 [umi 官网](https://umijs.org/zh-CN/plugins/plugin-qiankun) 和 [umi-qiankun 的官方 demo](https://github.com/umijs/umi-plugin-qiankun/tree/master/examples) - diff --git a/package.json b/package.json index 26acd1e..6d8cb0c 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "husky": "^2.3.0", "jest": "^25.2.2", "levenary": "^1.1.1", - "lint-staged": "^9.4.2", + "lint-staged": "^10.5.4", "np": "^5.0.3", "npm-run-all": "^4.1.5", "prettier": "^2.1.2",