diff --git a/dev/children/angular11/src/main.ts b/dev/children/angular11/src/main.ts index d501962ac..31b0a83a4 100644 --- a/dev/children/angular11/src/main.ts +++ b/dev/children/angular11/src/main.ts @@ -11,7 +11,8 @@ if (environment.production) { declare global { interface Window { microApp: any - __MICRO_APP_NAME__: string + mount: CallableFunction + unmount: CallableFunction __MICRO_APP_ENVIRONMENT__: string } } @@ -25,37 +26,36 @@ declare global { // }) // .catch(err => console.error(err)) -// console.log('微应用child-angular11渲染了'); +// console.log('微应用child-angular11渲染了 -- 默认模式'); // // 监听卸载操作 -// window.addEventListener("unmount", function () { +// window.unmount = () => { // app.destroy(); // app = null; -// console.log('微应用child-angular11卸载了'); -// }) +// console.log('微应用child-angular11卸载了 --- 默认模式'); +// } // ----------分割线---umd模式------两种模式任选其一-------------- // let app = null; -// 将渲染操作放入 mount 函数 -async function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = async () => { app = await platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)) - console.log('微应用child-angular11渲染了'); + console.log('微应用child-angular11渲染了 -- UMD模式'); } -// 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数 +window.unmount = () => { app.destroy(); app = null; - console.log('微应用child-angular11卸载了'); + console.log('微应用child-angular11卸载了 --- UMD模式'); } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount(); +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount(); } + +// -------------------分割线-------------------- // diff --git a/dev/children/react16/src/index.js b/dev/children/react16/src/index.js index cba39fc5b..e68ed6fbd 100644 --- a/dev/children/react16/src/index.js +++ b/dev/children/react16/src/index.js @@ -53,40 +53,38 @@ window.addEventListener('appstate-change', function (e) { // document.getElementById('root') // ); -// // 监听卸载 -// window.addEventListener('unmount', function () { +// // 注册unmount函数,卸载时会自动执行 +// window.unmount = () => { // ReactDOM.unmountComponentAtNode(document.getElementById('root')); -// console.log('微应用react16卸载了 -- 自定义事件unmount'); -// }) +// console.log('微应用react16卸载了 -- 默认模式'); +// } // console.timeEnd('react#16'); /* ----------------------分割线-umd模式--------------------- */ -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { ReactDOM.render( , document.getElementById('root') ); - console.log('微应用react16渲染了 -- 来自umd-mount'); + console.log('微应用react16渲染了 -- UMD模式'); console.timeEnd('react#16'); } -function unmount () { - console.log('微应用react16卸载了 -- 来自umd-unmount'); +// 👇 将卸载操作放入 unmount 函数 +window.unmount = () => { // 卸载时关闭弹窗 notification.destroy() - // 卸载应用 ReactDOM.unmountComponentAtNode(document.getElementById('root')); + console.log('微应用react16卸载了 -- UMD模式'); } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount(); +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } diff --git a/dev/children/react17/src/index.js b/dev/children/react17/src/index.js index 9205d1baf..19716756b 100644 --- a/dev/children/react17/src/index.js +++ b/dev/children/react17/src/index.js @@ -8,7 +8,8 @@ import reportWebVitals from './reportWebVitals'; // 发送数据 window.microApp?.dispatch({'from': '来自微应用react17的数据' + (+new Date())}) -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { ReactDOM.render( @@ -16,32 +17,21 @@ function mount () { document.getElementById('root') ); - console.log("微应用react17渲染来了 -- 来自umd-mount"); + console.log("微应用react17渲染来了 -- UMD模式"); } -function unmount () { - console.log("微应用react17卸载了 -- 来自umd-unmount"); - // 卸载应用 +// 👇 将卸载操作放入 unmount 函数 +window.unmount = () => { ReactDOM.unmountComponentAtNode(document.getElementById("root")); + console.log("微应用react17卸载了 -- UMD模式"); } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount(); +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); - -window.addEventListener('popstate', (e) => { - console.log('popstate', e) -}) - -window.addEventListener('hashchange', (e) => { - console.log('hashchange', e) -}) diff --git a/dev/children/vue2/src/main.js b/dev/children/vue2/src/main.js index a5dd000ec..d0833199b 100644 --- a/dev/children/vue2/src/main.js +++ b/dev/children/vue2/src/main.js @@ -36,47 +36,35 @@ let app = null // }).$mount('#app') // // 监听卸载 -// window.addEventListener('unmount', function () { +// window.unmount = () => { // app.$destroy() // app.$el.innerHTML = '' // app = null -// console.log('微应用vue2卸载了 -- 自定义事件unmount') -// }) +// console.log('微应用vue2卸载了 -- 默认模式') +// } // -------------------分割线-umd模式------------------ // -export function mount (props) { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { app = new Vue({ router, render: h => h(App), - }).$mount(props?.container?.querySelector('#app') || '#app') - console.log("微应用vue2渲染了 -- 来自umd-mount") + }).$mount('#app') + console.log("微应用vue2渲染了 -- UMD模式") } -// 卸载应用 -export function unmount () { +// 👇 将卸载操作放入 unmount 函数 +window.unmount = () => { app.$destroy() app.$el.innerHTML = '' app = null - console.log("微应用vue2卸载了 -- 来自umd-unmount") + console.log("微应用vue2卸载了 -- UMD模式") } -export function bootstrap() { - -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } -window.addEventListener('popstate', (e) => { - console.log('子应用vue2 popstate', e) -}) - -window.addEventListener('hashchange', (e) => { - console.log('子应用vue2 hashchange', e, e.newURL, e.oldURL) -}) +// -------------------分割线------------------ // diff --git a/dev/children/vue3/src/main.js b/dev/children/vue3/src/main.js index 89878e3a9..0c32196b6 100644 --- a/dev/children/vue3/src/main.js +++ b/dev/children/vue3/src/main.js @@ -1,33 +1,42 @@ // import './public-path' import { createApp } from 'vue' import { createRouter, createWebHistory } from 'vue-router' -// import ElementPlus from 'element-plus' +import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import Antd from 'ant-design-vue'; import 'ant-design-vue/dist/antd.css'; import routes from './router' import App from './App.vue' + +// -------------------分割线-默认模式------------------ // // const app = createApp(App) +// const router = createRouter({ +// history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || '/micro-app/vue3/'), +// routes, +// }) // app.use(ElementPlus) // app.use(Antd) // app.use(router) // app.mount('#app') -// console.log('微应用vue3渲染了') +// console.log('微应用vue3渲染了 -- 默认模式') // // 监听卸载 -// window.addEventListener('unmount', function () { -// console.log('微应用vue3卸载了') +// window.unmount = () => { +// console.log('微应用vue3卸载了 -- 默认模式') // // 卸载应用 // app.unmount() -// }) +// } + + +// -------------------分割线-umd模式------------------ // let app = null let router = null let history = null -// 将渲染操作放入 mount 函数 -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { history = createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || '/micro-app/vue3/') router = createRouter({ history, @@ -40,24 +49,22 @@ function mount () { app.use(router) app.mount('#app') - console.log('微应用child-vue3渲染了') + console.log('微应用child-vue3渲染了 -- UMD模式') } -// 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数 +window.unmount = () => { app?.unmount() history?.destroy() app = null router = null history = null - console.log('微应用child-vue3卸载了') + console.log('微应用child-vue3卸载了 -- UMD模式') } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - // @ts-ignore - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } + +// -------------------分割线------------------ // diff --git a/docs/1.x/zh-cn/advanced.md b/docs/1.x/zh-cn/advanced.md index 2b785efe2..5bac747d2 100644 --- a/docs/1.x/zh-cn/advanced.md +++ b/docs/1.x/zh-cn/advanced.md @@ -34,255 +34,3 @@ microApp.start({ > [!NOTE] > 1、如果跨域请求带cookie,那么`Access-Control-Allow-Origin`不能设置为`*`,这一点需要注意 - - -## 2、性能&内存优化 -`micro-app`支持两种渲染微前端的模式,默认模式和umd模式。 - -- **默认模式:**子应用在初次渲染和后续渲染时会顺序执行所有js,以保证多次渲染的一致性。 -- **umd模式:**子应用暴露出`mount`、`unmount`方法,此时只在初次渲染时执行所有js,后续渲染只会执行这两个方法,在多次渲染时具有更好的性能和内存表现。 - -**我的项目是否需要切换为umd模式?** - -如果子应用渲染和卸载不频繁,那么使用默认模式即可,如果子应用渲染和卸载非常频繁建议使用umd模式。 - -**切换为umd模式:子应用在window上注册mount和unmount方法** - - - -#### ** React ** -```js -// index.js -import React from "react" -import ReactDOM from "react-dom" -import App from './App' - -// 👇 将渲染操作放入 mount 函数 -function mount () { - ReactDOM.render(, document.getElementById("root")) -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - ReactDOM.unmountComponentAtNode(document.getElementById("root")) -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() -} -``` - -#### ** Vue2 ** - -```js -// main.js -import Vue from 'vue' -import router from './router' -import App from './App.vue' - -let app = null -// 👇 将渲染操作放入 mount 函数 -function mount () { - app = new Vue({ - router, - render: h => h(App), - }).$mount('#app') -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - app.$destroy() - app.$el.innerHTML = '' - app = null -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() -} -``` - -#### ** Vue3 ** - -```js -// main.js -import { createApp } from 'vue' -import * as VueRouter from 'vue-router' -import routes from './router' -import App from './App.vue' - -let app = null -let router = null -let history = null -// 👇 将渲染操作放入 mount 函数 -function mount () { - history = VueRouter.createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || '/') - router = VueRouter.createRouter({ - history, - routes, - }) - - app = createApp(App) - app.use(router) - app.mount('#app') -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - app.unmount() - history.destroy() - app = null - router = null - history = null -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() -} -``` - -#### ** Angular ** -以`angular11`为例。 - -```js -// main.ts -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; -import { AppModule } from './app/app.module'; - -declare global { - interface Window { - microApp: any - __MICRO_APP_NAME__: string - __MICRO_APP_ENVIRONMENT__: string - } -} - -let app = null; -// 👇 将渲染操作放入 mount 函数 -async function mount () { - app = await platformBrowserDynamic() - .bootstrapModule(AppModule) - .catch(err => console.error(err)) -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - // angular在部分场景下执行destroy时会删除根元素app-root,此时可删除app.destroy()以避免这个问题 - app.destroy(); - app = null; -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount(); -} -``` - - -#### ** Vite ** -因为vite作为子应用时关闭了沙箱,导致`__MICRO_APP_ENVIRONMENT__`和`__MICRO_APP_NAME__`两个变量失效,所以需要自行判断是否微前端环境以及手动填写应用name值。 - -这里以 vue3 + vue-router4 为例: -```js -// main.js -import { createApp } from 'vue' -import * as VueRouter from 'vue-router' -import routes from './router' -import App from './App.vue' - -let app = null -let router = null -let history = null -// 👇 将渲染操作放入 mount 函数 -function mount () { - history = VueRouter.createWebHashHistory() - router = VueRouter.createRouter({ - history, - routes, - }) - - app = createApp(App) - app.use(router) - app.mount('#app') -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - app.unmount() - history.destroy() - app = null - router = null - history = null -} - -// 微前端环境下,注册mount和unmount方法 -if (如果是微前端环境) { - // 应用的name值,即 元素的name属性值 - window[`micro-app-${应用的name值}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() -} -``` - -#### ** 其它 ** -```js -// entry.js - -// 👇 将渲染操作放入 mount 函数 -function mount () { - ... -} - -// 👇 将卸载操作放入 unmount 函数 -function unmount () { - ... -} - -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() -} -``` - - -#### 自定义名称 - -通常注册函数的形式为 `window['micro-app-${window.__MICRO_APP_NAME__}'] = {}`,但也支持自定义名称,`window['自定义的名称'] = {}` - -自定义的值需要在``标签中通过`library`属性指定。 - -```html - -``` - -> [!NOTE] -> -> 1、mount和unmount方法都是必须的 -> -> 2、nextjs, nuxtjs等ssr框架作为子应用时暂不支持umd模式 -> -> 3、因为注册了`unmount`函数,所以卸载监听事件 `window.addEventListener('unmount', () => {})` 就不需要了 -> -> 4、umd模式下,因为初次渲染和后续渲染逻辑不同,可能会出现一些问题,如:[#138](https://github.com/micro-zoe/micro-app/issues/138) diff --git a/docs/1.x/zh-cn/changelog.md b/docs/1.x/zh-cn/changelog.md index 52c6c7d76..242eaf555 100644 --- a/docs/1.x/zh-cn/changelog.md +++ b/docs/1.x/zh-cn/changelog.md @@ -8,6 +8,18 @@ --- +### 1.0.0-alpha.5 + +`2022-08-01` + +- **New** + + - 🆕 新增子应用全局钩子函数`mount`, `unmount`,简化UMD模式接入步骤。 + +- **Update** + - 🚀 更新了1.0版本文档 + + ### 1.0.0-alpha.4 `2022-07-28` diff --git a/docs/1.x/zh-cn/framework/angular.md b/docs/1.x/zh-cn/framework/angular.md index ff4a84647..f2bae61d5 100644 --- a/docs/1.x/zh-cn/framework/angular.md +++ b/docs/1.x/zh-cn/framework/angular.md @@ -84,10 +84,10 @@ platformBrowserDynamic() // 监听卸载操作 -window.addEventListener('unmount', function () { +window.unmount = () => { app.destroy(); app = null; -}) +} ``` @@ -95,7 +95,7 @@ window.addEventListener('unmount', function () { 以下配置是针对子应用的,它们是可选的,建议根据实际情况选择设置。 #### 1、开启umd模式,优化内存和性能 -`micro-app`支持两种渲染微前端的模式,默认模式和umd模式。 +MicroApp支持两种渲染微前端的模式,默认模式和umd模式。 - **默认模式:**子应用在初次渲染和后续渲染时会顺序执行所有js,以保证多次渲染的一致性。 - **umd模式:**子应用暴露出`mount`、`unmount`方法,此时只在初次渲染时执行所有js,后续渲染只会执行这两个方法,在多次渲染时具有更好的性能和内存表现。 @@ -110,47 +110,40 @@ import { AppModule } from './app/app.module'; declare global { interface Window { microApp: any - __MICRO_APP_NAME__: string + mount: CallableFunction + unmount: CallableFunction __MICRO_APP_ENVIRONMENT__: string } } let app = null; -// 👇 将渲染操作放入 mount 函数 -async function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = async () => { app = await platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)) } -// 👇 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数 +window.unmount = () => { // angular在部分场景下执行destroy时会删除根元素app-root,此时可删除app.destroy()以避免这个问题 app.destroy(); app = null; } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount(); +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount(); } ``` -> [!NOTE] -> -> 1、mount和unmount方法都是必须的 -> -> 2、因为注册了`unmount`函数,此时上述步骤2中监听卸载事件可以省略 #### 2、设置 webpack.jsonpFunction 如果微前端正常运行,则可以忽略这一步。 如果子应用资源加载混乱导致渲染失败,可以尝试设置`jsonpFunction`来解决,因为相同的`jsonpFunction`名称会导致资源污染。 -这种情况常见于基座和子应用都是通过`create-react-app`等脚手架创建的项目。 +这种情况常见于基座和子应用都是通过`create-react-app`等脚手架创建的react项目,vue项目中并不常见。 **解决方式:修改子应用的webpack配置** diff --git a/docs/1.x/zh-cn/framework/react.md b/docs/1.x/zh-cn/framework/react.md index 74d38f2f3..022bb8986 100644 --- a/docs/1.x/zh-cn/framework/react.md +++ b/docs/1.x/zh-cn/framework/react.md @@ -47,21 +47,21 @@ headers: { } ``` -#### 2、监听卸载事件 -子应用被卸载时会接受到一个名为`unmount`的事件,在此可以进行卸载相关操作。 +#### 2、注册卸载函数 +子应用卸载时会自动执行`window.unmount`,在此可以进行卸载相关操作。 ```js // index.js -window.addEventListener('unmount', function () { +window.unmount = () => { ReactDOM.unmountComponentAtNode(document.getElementById('root')) -}) +} ``` ### 可选设置 以下配置是针对子应用的,它们是可选的,建议根据实际情况选择设置。 #### 1、开启umd模式,优化内存和性能 -`micro-app`支持两种渲染微前端的模式,默认模式和umd模式。 +MicroApp支持两种渲染微前端的模式,默认模式和umd模式。 - **默认模式:**子应用在初次渲染和后续渲染时会顺序执行所有js,以保证多次渲染的一致性。 - **umd模式:**子应用暴露出`mount`、`unmount`方法,此时只在初次渲染时执行所有js,后续渲染只会执行这两个方法,在多次渲染时具有更好的性能和内存表现。 @@ -74,33 +74,25 @@ import React from "react" import ReactDOM from "react-dom" import App from './App' -// 👇 将渲染操作放入 mount 函数 -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { ReactDOM.render(, document.getElementById("root")) } -// 👇 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数 +window.unmount = () => { ReactDOM.unmountComponentAtNode(document.getElementById("root")) } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } ``` -> [!NOTE] -> -> 1、mount和unmount方法都是必须的 -> -> 2、因为注册了`unmount`函数,此时上述步骤2中监听卸载事件可以省略 #### 2、设置 webpack.jsonpFunction -如果微前端正常运行,则可以忽略这一步。 +如果微前端正常运行,可以忽略这一步。 如果子应用资源加载混乱导致渲染失败,可以尝试设置`jsonpFunction`来解决,因为相同的`jsonpFunction`名称会导致资源污染。 diff --git a/docs/1.x/zh-cn/framework/vue.md b/docs/1.x/zh-cn/framework/vue.md index 2b92aba96..f177c6c3a 100644 --- a/docs/1.x/zh-cn/framework/vue.md +++ b/docs/1.x/zh-cn/framework/vue.md @@ -46,8 +46,8 @@ devServer: { } ``` -#### 2、监听卸载事件 -子应用被卸载时会接受到一个名为`unmount`的事件,在此可以进行卸载相关操作。 +#### 2、注册卸载函数 +子应用卸载时会自动执行`window.unmount`,在此可以进行卸载相关操作。 @@ -57,10 +57,10 @@ devServer: { // main.js const app = new Vue(...) -// 监听卸载操作 -window.addEventListener('unmount', function () { +// 卸载应用 +window.unmount = () => { app.$destroy() -}) +} ``` #### ** Vue3 ** @@ -69,10 +69,10 @@ window.addEventListener('unmount', function () { const app = createApp(App) app.mount('#app') -// 监听卸载操作 -window.addEventListener('unmount', function () { +// 卸载应用 +window.unmount = () => { app.unmount() -}) +} ``` @@ -81,7 +81,7 @@ window.addEventListener('unmount', function () { 以下配置是针对子应用的,它们是可选的,建议根据实际情况选择设置。 #### 1、开启umd模式,优化内存和性能 -`micro-app`支持两种渲染微前端的模式,默认模式和umd模式。 +MicroApp支持两种渲染微前端的模式,默认模式和umd模式。 - **默认模式:**子应用在初次渲染和后续渲染时会顺序执行所有js,以保证多次渲染的一致性。 - **umd模式:**子应用暴露出`mount`、`unmount`方法,此时只在初次渲染时执行所有js,后续渲染只会执行这两个方法,在多次渲染时具有更好的性能和内存表现。 @@ -98,27 +98,24 @@ import router from './router' import App from './App.vue' let app = null -// 👇 将渲染操作放入 mount 函数 -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { app = new Vue({ router, render: h => h(App), }).$mount('#app') } -// 👇 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数 +window.unmount = () => { app.$destroy() app.$el.innerHTML = '' app = null } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } ``` @@ -133,8 +130,8 @@ import App from './App.vue' let app = null let router = null let history = null -// 👇 将渲染操作放入 mount 函数 -function mount () { +// 👇 将渲染操作放入 mount 函数,子应用初始化时会自动执行 +window.mount = () => { history = VueRouter.createWebHistory() router = VueRouter.createRouter({ history, @@ -146,8 +143,8 @@ function mount () { app.mount('#app') } -// 👇 将卸载操作放入 unmount 函数 -function unmount () { +// 👇 将卸载操作放入 unmount 函数,就是上面步骤2中的卸载函数 +window.unmount = () => { app.unmount() history.destroy() app = null @@ -155,29 +152,21 @@ function unmount () { history = null } -// 微前端环境下,注册mount和unmount方法 -if (window.__MICRO_APP_ENVIRONMENT__) { - window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount } -} else { - // 非微前端环境直接渲染 - mount() +// 如果不在微前端环境,则直接执行mount渲染 +if (!window.__MICRO_APP_ENVIRONMENT__) { + window.mount() } ``` -> [!NOTE] -> -> 1、mount和unmount方法都是必须的 -> -> 2、因为注册了`unmount`函数,此时上述步骤2中监听卸载事件可以省略 #### 2、设置 webpack.jsonpFunction 如果微前端正常运行,则可以忽略这一步。 如果子应用资源加载混乱导致渲染失败,可以尝试设置`jsonpFunction`来解决,因为相同的`jsonpFunction`名称会导致资源污染。 -这种情况常见于基座和子应用都是通过`create-react-app`等脚手架创建的项目。 +这种情况常见于基座和子应用都是通过`create-react-app`脚手架创建的react项目,vue项目中并不常见。 **解决方式:修改子应用的webpack配置** diff --git a/docs/1.x/zh-cn/keep-alive.md b/docs/1.x/zh-cn/keep-alive.md index 8f1fb3b20..49ae27409 100644 --- a/docs/1.x/zh-cn/keep-alive.md +++ b/docs/1.x/zh-cn/keep-alive.md @@ -1,6 +1,4 @@ -*0.6.0及以上版本支持* - -在应用之间切换时,你有时会想保留这些应用的状态,以便恢复用户的操作行为和提升重复渲染的性能,此时开启keep-alive模式可以达到这样的效果。 +在应用之间切换时,我们有时会想保留这些应用的状态,以便恢复用户的操作行为和提升重复渲染的性能,此时开启keep-alive模式可以达到这样的效果。 开启keep-alive后,应用卸载时不会销毁,而是推入后台运行。 @@ -29,13 +27,13 @@ keep-alive模式与普通模式最大的不同是生命周期,因为它不会 子应用渲染出错时触发,只有会导致渲染终止的错误才会触发此生命周期。 #### 5. afterhidden -子应用卸载时触发。 +子应用推入后台时触发。 #### 6. beforeshow -子应用再次渲染之前触发`(初始化时不执行)`。 +子应用推入前台之前触发`(初始化时不执行)`。 #### 7. aftershow -子应用再次渲染之后触发`(初始化时不执行)`。 +子应用推入前台之后触发`(初始化时不执行)`。 #### 监听生命周期 @@ -59,9 +57,9 @@ import jsxCustomEvent from '@micro-zoe/micro-app/polyfill/jsx-custom-event' onCreated={() => console.log('micro-app元素被创建')} onBeforemount={() => console.log('即将被渲染,只在初始化时执行一次')} onMounted={() => console.log('已经渲染完成,只在初始化时执行一次')} - onAfterhidden={() => console.log('已卸载')} - onBeforeshow={() => console.log('即将重新渲染,初始化时不执行')} - onAftershow={() => console.log('已经重新渲染,初始化时不执行')} + onAfterhidden={() => console.log('已推入后台')} + onBeforeshow={() => console.log('即将推入前台,初始化时不执行')} + onAftershow={() => console.log('已经推入前台,初始化时不执行')} onError={() => console.log('渲染出错')} /> ``` @@ -96,13 +94,13 @@ export default { console.log('已经渲染完成,只在初始化时执行一次'), }, afterhidden () { - console.log('已卸载'), + console.log('已推入后台'), }, beforeshow () { - console.log('即将重新渲染,初始化时不执行'), + console.log('即将推入前台,初始化时不执行'), }, aftershow () { - console.log('已经重新渲染,初始化时不执行'), + console.log('已经推入前台,初始化时不执行'), }, error () { console.log('渲染出错'), @@ -135,13 +133,8 @@ window.addEventListener('appstate-change', function (e) { ## 常见问题 -#### 1、再次渲染时url和页面不匹配 -keep-alive的应用在卸载时会保留页面状态,再次渲染时直接恢复,当应用再次渲染时的url与离开时不一致,则出现url和页面不匹配的问题。 - -如果这个问题对你造成了困扰,可以通过监听`appstate-change`事件,在`beforeshow`时进行修复,根据url跳转对应的页面。 - -#### 2、如何恢复页面滚动位置? +#### 1、如何恢复页面滚动位置? micro-app不会记录页面滚动位置,应用再次渲染时也不会进行恢复,需要开发者进行记录和恢复。 -#### 3、子应用内部页面切换后状态丢失 -micro-app的keep-alive是应用级别的,它只会保留当前正在活动的页面状态,以保证应用被卸载和重新渲染时的状态保留,如果想要缓存具体的页面或组件,需要使用子应用框架的能力,如:vue的keep-alive。 +#### 2、子应用内部页面切换后状态丢失 +micro-app的keep-alive是应用级别的,它只会保留当前正在活动的页面状态,如果想要缓存具体的页面或组件,需要使用子应用框架的能力,如:vue的keep-alive。 diff --git a/docs/1.x/zh-cn/questions.md b/docs/1.x/zh-cn/questions.md index 698636cbf..b870cdc6a 100644 --- a/docs/1.x/zh-cn/questions.md +++ b/docs/1.x/zh-cn/questions.md @@ -36,79 +36,22 @@ micro-app依赖于CustomElements和Proxy两个较新的API。 - PC端:除了IE浏览器,其它浏览器基本兼容。 - 移动端:ios10+、android5+ -## 4、微应用无法渲染但没有报错 -请检查路由配置是否正确,详情查看[路由](/zh-cn/route)一章,或者[下面第5条:jsonpFunction是否冲突](/zh-cn/questions?id=_5、webpack-jsonpfunction-冲突导致渲染失败) -## 5、webpack-jsonpfunction-冲突导致渲染失败 -这种情况常见于多个应用都是通过create-react-app等类似脚手架创建的项目,或一个应用多次重复渲染。 - -因为相同的jsonpFunction名称会导致资源加载混乱。 - -**解决方式:修改子应用的webpack配置** - - -#### ** webpack4 ** -```js -// webpack.config.js -module.exports = { - output: { - ... - jsonpFunction: `webpackJsonp_custom_app_name`, - globalObject: 'window', - }, -} -``` - -#### ** webpack5 ** -```js -// webpack.config.js -module.exports = { - output: { - ... - chunkLoadingGlobal: 'webpackJsonp_custom_app_name', - globalObject: 'window', - }, -} -``` - - - -## 6、开发时每次保存文件时报错 (热更新导致报错) -在一些场景下,热更新会导致保存时报错,请关闭热更新来解决这个问题,同时我们也在尝试更好的解决方案。 - -## 7、vue3的问题 -**1、样式失效** - -通过[禁用样式隔离](/zh-cn/configure?id=disablescopecss)解决。 - -**2、图片等静态资源无法正常加载** - -vue3中需要配置publicPath补全资源路径,详情请查看[publicPath](/zh-cn/static-source?id=publicpath) - - -## 8、开发环境中渲染angular子应用报错 -目前需要关闭angular的热更新来解决这个问题,同时我们也在尝试更好的解决方案。 -```bash -"scripts": { - "start": "ng serve --live-reload false", -}, -``` - -## 9、micro-app 报错 an app named xx already exists +## 4、micro-app 报错 an app named xx already exists 这是`name`名称冲突导致的,请确保每个子应用的`name`值是唯一的。 -## 10、基座应用的样式影响到子应用 +## 5、基座应用的样式影响到子应用 虽然我们将子应用的样式进行隔离,但基座应用的样式依然会影响到子应用,如果发生冲突,推荐通过约定前缀或CSS Modules方式解决。 如果你使用的是`ant-design`等组件库,一般会提供添加前缀进行样式隔离的功能。 -## 11、子应用在沙箱环境中如何获取到外部真实window? +## 6、子应用在沙箱环境中如何获取到外部真实window? 目前有3种方式在子应用中获取外部真实window - 1、new Function("return window")() 或 Function("return window")() - 2、(0, eval)('window') - 3、window.rawWindow -## 12、错误信息:xxx 未定义 +## 7、错误信息:xxx undefined **包括:** - `xxx is not defined` @@ -149,54 +92,11 @@ microApp.start({ }) ``` -## 13、子应用加载sockjs-node失败 - 这个问题常见于create-react-app创建的子应用,推荐通过插件系统来解决。 -```js -microApp.start({ - plugins: { - modules: { - '子应用name': [{ - loader(code) { - if (code.indexOf('sockjs-node') > -1) { - code = code.replace('window.location.port', '子应用端口').replace('window.location.hostname', '子应用host,如果和基座相同则不需要替换hostname') - } - return code - } - }], - } - } -}) -``` -实际情况可能更加复杂,上面只是一种解决思路。 - - -## 14、子应用请求接口失败 - - 1、请确保接口请求没有跨域问题,因为子应用被加载到基座渲染,所以请求接口是从基座发送。 - - - 2、请求的接口为相对地址,会以基座域名进行补全,导致报错。 - - 如:`fetch('/api/data')`,在请求时会自动被浏览器补全为`fetch(基座域名 + '/api/data')` - - 为了避免这个问题,子应用需要使用完整的地址:`fetch(子应用域名 + '/api/data')` - -## 15、子应用反向代理失败 - **解决方式:**子应用使用完整的地址发送请求 - - 如:`fetch('/api/data')` 改为 `fetch(子应用域名 + '/api/data')` - - 如果还是报跨域问题,则是服务端做了限制,此时需要撤除上述操作,并将子应用的代理放到基座应用中。 - -## 16、子应用多次渲染后内存越来越大 - 参考[内存优化](/zh-cn/advanced?id=_3、内存优化)一章 - -## 17、子应用之间如何跳转 - 参考[应用之间如何跳转](/zh-cn/route?id=应用之间如何跳转)一章 - -## 18、jsonp请求如何处理? +## 8、jsonp请求如何处理? 参考[ignore](/zh-cn/configure?id=ignore忽略元素) -## 19、子应用通过a标签下载文件失败 +## 9、子应用通过a标签下载文件失败 **原因:**当跨域时(基座和文件在不同域名下),无法通过a标签的download属性实现下载。 **解决方式:** diff --git a/src/create_app.ts b/src/create_app.ts index 8a9848e6a..735c1775a 100644 --- a/src/create_app.ts +++ b/src/create_app.ts @@ -17,6 +17,7 @@ import { isPromise, logError, getRootContainer, + isObject, } from './libs/utils' import dispatchLifecyclesEvent, { dispatchCustomEventToMicroApp } from './interact/lifecycles_event' import globalEnv from './libs/global_env' @@ -204,10 +205,14 @@ export default class CreateApp implements AppInterface { execScripts(this.source.scripts, this, (isFinished: boolean) => { if (!this.umdMode) { const { mount, unmount } = this.getUmdLibraryHooks() + /** + * umdHookUnmount can works in non UMD mode + * register with window.unmount + */ + this.umdHookUnmount = unmount as Func // if mount & unmount is function, the sub app is umd mode if (isFunction(mount) && isFunction(unmount)) { this.umdHookMount = mount as Func - this.umdHookUnmount = unmount as Func this.umdMode = true if (this.sandBox) this.sandBox.proxyWindow.__MICRO_APP_UMD_MODE__ = true // this.sandBox?.recordUmdSnapshot() @@ -284,7 +289,7 @@ export default class CreateApp implements AppInterface { * send an unmount event to the micro app or call umd unmount hook * before the sandbox is cleared */ - if (this.umdHookUnmount) { + if (isFunction(this.umdHookUnmount)) { try { umdHookUnmountResult = this.umdHookUnmount() } catch (e) { @@ -469,8 +474,15 @@ export default class CreateApp implements AppInterface { if (appStates.UNMOUNT !== this.state) { const global = (this.sandBox?.proxyWindow ?? globalEnv.rawWindow) as any this.libraryName = getRootContainer(this.container!).getAttribute('library') || `micro-app-${this.name}` - // do not use isObject - return typeof global[this.libraryName] === 'object' ? global[this.libraryName] : {} + + if (isObject(global[this.libraryName])) { + return global[this.libraryName] + } + + return { + mount: this.sandBox?.proxyWindow.mount, + unmount: this.sandBox?.proxyWindow.unmount, + } } return {} diff --git a/src/libs/global_env.ts b/src/libs/global_env.ts index 669b12158..1400d74e3 100644 --- a/src/libs/global_env.ts +++ b/src/libs/global_env.ts @@ -1,3 +1,4 @@ +import type { Func } from '@micro-app/types' import { isSupportModuleScript, isBrowser, getCurrentAppName, assign } from './utils' import { rejectMicroAppStyle } from '../source/patch' @@ -20,6 +21,8 @@ declare global { __MICRO_APP_ENVIRONMENT__?: boolean __MICRO_APP_UMD_MODE__?: boolean __MICRO_APP_BASE_APPLICATION__?: boolean + mount: Func + unmount: Func } interface Node {