From afc3fdaa7e0661187ddcac9c03f86b9ccdcc3ba1 Mon Sep 17 00:00:00 2001
From: bailicangdu <1264889788@qq.com>
Date: Fri, 15 Oct 2021 15:37:19 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=8A=A8=E6=80=81mod?=
=?UTF-8?q?ule=E5=88=9B=E5=BB=BA=E6=96=B9=E5=BC=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/zh-cn/advanced.md | 202 ++++++++++--------
examples/children/angular11/src/main.ts | 36 +++-
.../children/react16/config/webpack.config.js | 4 -
examples/children/react16/public/index.html | 3 +
examples/children/react16/src/index.js | 13 +-
.../react16/src/pages/inline/inline.js | 1 -
.../children/react17/config/webpack.config.js | 2 +-
examples/children/vue2/src/main.js | 14 +-
examples/children/vue2/vue.config.js | 2 -
examples/children/vue3/src/main.js | 49 ++++-
.../src/pages/multiple/multiple.js | 2 -
.../main-react16/src/pages/react16/react16.js | 1 -
examples/main-react16/src/pages/vite/vite.js | 1 -
examples/main-react16/src/pages/vue2/vue2.js | 3 -
examples/main-vue2/src/pages/multiple.vue | 2 -
.../main-vue3-vite/src/pages/multiple.vue | 2 -
src/__tests__/micro_app.test.ts | 2 +-
src/source/scripts.ts | 89 ++++----
18 files changed, 257 insertions(+), 171 deletions(-)
diff --git a/docs/zh-cn/advanced.md b/docs/zh-cn/advanced.md
index 77ecbda18..f8f211b5a 100644
--- a/docs/zh-cn/advanced.md
+++ b/docs/zh-cn/advanced.md
@@ -147,8 +147,7 @@ microApp.start({
## 3、内存优化
虽然我们在卸载子应用时对变量和事件进行了清除,但仍有一些变量无法回收。如果子应用渲染和卸载非常频繁,建议通过下面方式进行内存优化。
-### 将子应用修改为umd格式
-#### 步骤1:在子应用入口文件导出相应的生命周期钩子
+#### 在window上注册mount和unmount方法
@@ -156,142 +155,161 @@ microApp.start({
```js
// index.js
...
-// 将渲染和卸载的操作移动到mount和unmount函数中
-// ReactDOM.render(, document.getElementById("root"))
-
-// window.addEventListener('unmount', function () {
-// ReactDOM.unmountComponentAtNode(document.getElementById('root'))
-// })
-
-// 应用每次渲染时都会执行 mount 方法,在此处可以执行初始化相关操作(必传)
+// 👇 将渲染操作放入 mount 函数
export function mount () {
ReactDOM.render(, document.getElementById("root"))
}
-// 应用每次卸载时都会执行 unmount 方法,在此处可以执行卸载相关操作(必传)
+// 👇 将卸载操作放入 unmount 函数
export function unmount () {
- // 卸载应用
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
-// 非微前端环境直接运行
-if (!window.__MICRO_APP_ENVIRONMENT__) {
- mount()
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount();
}
```
-#### ** Vue **
+#### ** Vue2 **
```js
// main.js
...
-// 将渲染和卸载的操作移动到mount和unmount函数中
-// const app = new Vue({
-// router,
-// render: h => h(App),
-// }).$mount('#app')
-
-// window.addEventListener('unmount', function () {
-// app.$destroy()
-// })
-
-let app
-// 应用每次渲染时都会执行 mount 方法,在此处可以执行初始化相关操作(必传)
-export function mount () {
- app = new Vue({
- router,
- render: h => h(App),
- }).$mount('#app')
+let app = null
+// 👇 将渲染操作放入 mount 函数
+function mount () {
+ app = new Vue(...).$mount('#app')
}
-// 应用每次卸载时都会执行 unmount 方法,在此处可以执行卸载相关操作(必传)
-export function unmount () {
- // 卸载应用
+// 👇 将卸载操作放入 unmount 函数
+function unmount () {
app.$destroy()
+ app = null
}
-// 非微前端环境直接运行
-if (!window.__MICRO_APP_ENVIRONMENT__) {
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
mount()
}
```
-
-#### 步骤2:修改子应用的webpack配置
+#### ** Vue3 **
+```js
+// main.js
+...
+let app = null
+// 👇 将渲染操作放入 mount 函数
+function mount () {
+ app = createApp(App)
+ app.use(router)
+ app.mount('#app')
+}
-
+// 👇 将卸载操作放入 unmount 函数
+function unmount () {
+ app.unmount()
+ app = null
+}
-#### ** webpack4 **
-```js
-// webpack.config.js
-module.exports = {
- ...
- output: {
- library: 'micro-app-子应用的name', // 子应用的name就是中name属性的值
- libraryTarget: 'umd',
- jsonpFunction: `webpackJsonp_${packageName}`,
- },
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount()
}
```
-#### ** webpack5 **
+#### ** Angular **
+以`angular11`为例。
+
```js
-// webpack.config.js
-module.exports = {
- ...
- output: {
- library: {
- name: `micro-app-子应用的name`, // 子应用的name就是中name属性的值
- type: 'umd',
- },
- },
- devServer: {
- ...
- // injectClient: false, 当`webpack-dev-server`版本为3.x,需设置injectClient
+// 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
}
}
-```
-
-
-通常`library`的值固定为`micro-app-子应用的name`,但也可以自定义。
-自定义的值需要在``标签中通过`library`属性指定。
+let app = null;
+// 👇 将渲染操作放入 mount 函数
+function mount () {
+ platformBrowserDynamic().bootstrapModule(AppModule)
+ .then((ngModuleRef: any) => {
+ app = ngModuleRef
+ })
+ .catch(err => console.error(err))
+}
-
+// 👇 将卸载操作放入 unmount 函数
+function unmount () {
+ app?.destroy();
+ app = null;
+}
-#### ** webpack4 **
-```js
-// webpack.config.js
-module.exports = {
- ...
- output: {
- library: '自定义的library名称', 👈
- libraryTarget: 'umd',
- jsonpFunction: `webpackJsonp_${packageName}`,
- },
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount();
}
```
-#### ** webpack5 **
+
+#### ** Vite **
+因为vite作为子应用时关闭了沙箱,导致`__MICRO_APP_ENVIRONMENT__`和`__MICRO_APP_NAME__`两个变量失效,所以需要自行判断是否微前端环境以及手动填写应用name值。
+
```js
-// webpack.config.js
-module.exports = {
- ...
- output: {
- library: {
- name: `自定义的library名称`, 👈
- type: 'umd',
- },
- },
+// main.js
+...
+let app = null
+// 👇 将渲染操作放入 mount 函数
+function mount () {
+ app = createApp(App)
+ app.use(router)
+ app.mount('#app')
+}
+
+// 👇 将卸载操作放入 unmount 函数
+function unmount () {
+ app.unmount()
+ app = null
+}
+
+// 微前端环境下,注册mount和unmount方法
+if (我在微前端环境) {
+ // 应用的name值,即 元素的name属性值
+ window[`micro-app-${应用的name值}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount()
}
```
+通常注册的形式为`window['micro-app-${window.__MICRO_APP_NAME__}'] = {...}`,但也支持自定义名称,`window['自定义的名称'] = {...}`
+
+自定义的值需要在``标签中通过`library`属性指定。
+
```html
-
```
+
+在沙箱关闭时`__MICRO_APP_NAME__`变量失效(如:vite子应用),此时可以使用自定义名称的方式进行注册,也可以通过 `window['micro-app-${应用的name值}']`的方式注册。
diff --git a/examples/children/angular11/src/main.ts b/examples/children/angular11/src/main.ts
index c7b673cf4..bdc10cf6e 100644
--- a/examples/children/angular11/src/main.ts
+++ b/examples/children/angular11/src/main.ts
@@ -8,5 +8,37 @@ if (environment.production) {
enableProdMode();
}
-platformBrowserDynamic().bootstrapModule(AppModule)
- .catch(err => console.error(err));
+declare global {
+ interface Window {
+ microApp: any
+ __MICRO_APP_NAME__: string
+ __MICRO_APP_ENVIRONMENT__: string
+ }
+}
+
+let app = null;
+// 将渲染操作放入 mount 函数
+function mount () {
+ platformBrowserDynamic().bootstrapModule(AppModule)
+ .then((ngModuleRef: any) => {
+ app = ngModuleRef
+ })
+ .catch(err => console.error(err))
+
+ console.log('微应用child-angular11渲染了');
+}
+
+// 将卸载操作放入 unmount 函数
+function unmount () {
+ app?.destroy();
+ app = null;
+ console.log('微应用child-angular11卸载了');
+}
+
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount();
+}
diff --git a/examples/children/react16/config/webpack.config.js b/examples/children/react16/config/webpack.config.js
index c13129e2a..07f4beef2 100644
--- a/examples/children/react16/config/webpack.config.js
+++ b/examples/children/react16/config/webpack.config.js
@@ -227,10 +227,6 @@ module.exports = function (webpackEnv) {
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
// globalObject: 'this', // micro-app注释
-
- library: `micro-app-react16`,
- libraryTarget: 'umd',
- jsonpFunction: `webpackJsonp_${appPackageJson.name}`,
},
optimization: {
minimize: isEnvProduction,
diff --git a/examples/children/react16/public/index.html b/examples/children/react16/public/index.html
index 19b57017d..4a40ec524 100644
--- a/examples/children/react16/public/index.html
+++ b/examples/children/react16/public/index.html
@@ -16,6 +16,9 @@
color: red;
}
+
diff --git a/examples/children/react16/src/index.js b/examples/children/react16/src/index.js
index e05592a2b..aa43a4059 100644
--- a/examples/children/react16/src/index.js
+++ b/examples/children/react16/src/index.js
@@ -58,7 +58,7 @@ window.microApp?.addGlobalDataListener(handleGlobalData);
// })
-export function mount () {
+function mount () {
ReactDOM.render(
@@ -68,15 +68,18 @@ export function mount () {
console.timeEnd("react16");
}
-export function unmount () {
+function unmount () {
console.log("微应用react16卸载了 -- 来自umd-unmount");
// 卸载应用
ReactDOM.unmountComponentAtNode(document.getElementById("root"));
}
-// 非微前端环境直接运行
-if (!window.__MICRO_APP_ENVIRONMENT__) {
- mount()
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount();
}
// document.addEventListener('click', function () {
diff --git a/examples/children/react16/src/pages/inline/inline.js b/examples/children/react16/src/pages/inline/inline.js
index 3b927b0b9..b66a3000f 100644
--- a/examples/children/react16/src/pages/inline/inline.js
+++ b/examples/children/react16/src/pages/inline/inline.js
@@ -36,7 +36,6 @@ function Vue2 () {
// inline
// disableScopecss
// disableSandbox
- library='micro-app-vue2'
/>
)
diff --git a/examples/children/react17/config/webpack.config.js b/examples/children/react17/config/webpack.config.js
index 3e53bd871..9c0cc6e40 100644
--- a/examples/children/react17/config/webpack.config.js
+++ b/examples/children/react17/config/webpack.config.js
@@ -225,7 +225,7 @@ module.exports = function (webpackEnv) {
// jsonpFunction: `webpackJsonp${appPackageJson.name}`,
// this defaults to 'window', but by setting it to 'this' then
// module chunks which are built will work in web workers as well.
- // globalObject: 'this', // micro-app 注释
+ // globalObject: 'window', // micro-app 注释
},
optimization: {
minimize: isEnvProduction,
diff --git a/examples/children/vue2/src/main.js b/examples/children/vue2/src/main.js
index 3a0558693..3275643ec 100644
--- a/examples/children/vue2/src/main.js
+++ b/examples/children/vue2/src/main.js
@@ -18,7 +18,7 @@ const router = new VueRouter({
routes,
})
-let app
+let app = null
// app = new Vue({
// router,
@@ -32,7 +32,7 @@ let app
// app.$destroy()
// })
-export function mount () {
+function mount () {
console.log("微应用vue2渲染了 -- 来自umd-mount");
app = new Vue({
router,
@@ -41,13 +41,17 @@ export function mount () {
console.timeEnd('vue2')
}
-export function unmount () {
+function unmount () {
console.log("微应用vue2卸载了 -- 来自umd-unmount");
// 卸载应用
app.$destroy()
+ app = null
}
-// 非微前端环境直接运行
-if (!window.__MICRO_APP_ENVIRONMENT__) {
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
mount()
}
diff --git a/examples/children/vue2/vue.config.js b/examples/children/vue2/vue.config.js
index 85f4ea624..f1d6d5fb5 100644
--- a/examples/children/vue2/vue.config.js
+++ b/examples/children/vue2/vue.config.js
@@ -20,8 +20,6 @@ module.exports = {
configureWebpack: {
output: {
jsonpFunction: `webpackJsonp-chile-vue2`,
- library: `micro-app-vue2`,
- libraryTarget: 'umd',
}
},
}
diff --git a/examples/children/vue3/src/main.js b/examples/children/vue3/src/main.js
index 729727433..12f4851d7 100644
--- a/examples/children/vue3/src/main.js
+++ b/examples/children/vue3/src/main.js
@@ -2,19 +2,46 @@ import './public-path'
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'
-import routes from './router'
+import router from './router'
import App from './App.vue'
-const app = createApp(App)
-app.use(ElementPlus)
-app.use(routes)
-app.mount('#app')
+// const app = createApp(App)
+// app.use(ElementPlus)
+// app.use(router)
+// app.mount('#app')
-console.log('微应用vue3渲染了')
+// console.log('微应用vue3渲染了')
-// 监听卸载
-window.addEventListener('unmount', function () {
- console.log('微应用vue3卸载了')
- // 卸载应用
+// // 监听卸载
+// window.addEventListener('unmount', function () {
+// console.log('微应用vue3卸载了')
+// // 卸载应用
+// app.unmount()
+// })
+
+let app = null
+// 将渲染操作放入 mount 函数
+function mount () {
+ app = createApp(App)
+ app.use(ElementPlus)
+ app.use(router)
+ app.mount('#app')
+
+ console.log('微应用child-vue3渲染了')
+}
+
+// 将卸载操作放入 unmount 函数
+function unmount () {
app.unmount()
-})
+ app = null
+ console.log('微应用child-vue3卸载了')
+}
+
+// 微前端环境下,注册mount和unmount方法
+if (window.__MICRO_APP_ENVIRONMENT__) {
+ // @ts-ignore
+ window[`micro-app-${window.__MICRO_APP_NAME__}`] = { mount, unmount }
+} else {
+ // 非微前端环境直接渲染
+ mount()
+}
diff --git a/examples/main-react16/src/pages/multiple/multiple.js b/examples/main-react16/src/pages/multiple/multiple.js
index fce572ddc..1d9d8cbcc 100644
--- a/examples/main-react16/src/pages/multiple/multiple.js
+++ b/examples/main-react16/src/pages/multiple/multiple.js
@@ -47,7 +47,6 @@ function Vue3 () {
// destory
// inline
// scopecss='false'
- library='micro-app-react16'
>
diff --git a/examples/main-react16/src/pages/react16/react16.js b/examples/main-react16/src/pages/react16/react16.js
index f26673346..0eb916b27 100644
--- a/examples/main-react16/src/pages/react16/react16.js
+++ b/examples/main-react16/src/pages/react16/react16.js
@@ -171,7 +171,6 @@ export default class App extends React.Component {
name='modal-app1'
url={this.state.url}
baseRoute='/micro-app/demo/react16'
- library='micro-app-react16'
// disableSandbox
// macro
/>
diff --git a/examples/main-react16/src/pages/vite/vite.js b/examples/main-react16/src/pages/vite/vite.js
index bfd4e1709..c25e9d0ae 100644
--- a/examples/main-react16/src/pages/vite/vite.js
+++ b/examples/main-react16/src/pages/vite/vite.js
@@ -36,7 +36,6 @@ function vite () {
// destory
inline
disableSandbox
-
>
diff --git a/examples/main-react16/src/pages/vue2/vue2.js b/examples/main-react16/src/pages/vue2/vue2.js
index 4d09aac66..f0202319b 100644
--- a/examples/main-react16/src/pages/vue2/vue2.js
+++ b/examples/main-react16/src/pages/vue2/vue2.js
@@ -47,6 +47,3 @@ function Vue2 () {
}
export default Vue2
-// 664 649 637 663 656 650 676 平均 656
-// 751 660 683 706 695 687 689 平均 695
-// 685 671 695 663 695 720 709 平均 691
diff --git a/examples/main-vue2/src/pages/multiple.vue b/examples/main-vue2/src/pages/multiple.vue
index 1fb75c7b4..3768e2889 100644
--- a/examples/main-vue2/src/pages/multiple.vue
+++ b/examples/main-vue2/src/pages/multiple.vue
@@ -8,7 +8,6 @@
url='http://localhost:3001/micro-app/react16/'
baseRoute='/multiple'
:data='data'
- library='micro-app-react16'
>
diff --git a/examples/main-vue3-vite/src/pages/multiple.vue b/examples/main-vue3-vite/src/pages/multiple.vue
index bbb0b5ab2..c4ca3ad6d 100644
--- a/examples/main-vue3-vite/src/pages/multiple.vue
+++ b/examples/main-vue3-vite/src/pages/multiple.vue
@@ -8,7 +8,6 @@
url='http://localhost:3001/micro-app/react16/'
baseRoute='/multiple'
:data='data'
- library='micro-app-react16'
>
diff --git a/src/__tests__/micro_app.test.ts b/src/__tests__/micro_app.test.ts
index 5fbf4486a..669b2d64d 100644
--- a/src/__tests__/micro_app.test.ts
+++ b/src/__tests__/micro_app.test.ts
@@ -33,7 +33,7 @@ test('log error message if customElements is not supported in this environment',
})
microApp.start()
- expect(console.error).toBeCalledWith('[micro-app] customElements is not supported in this environment')
+ expect(console.error).toBeCalledWith('[micro-app] micro-app is not supported in this environment')
window.customElements = rawcustomElements
})
diff --git a/src/source/scripts.ts b/src/source/scripts.ts
index 63d051748..4f9c9b5e6 100644
--- a/src/source/scripts.ts
+++ b/src/source/scripts.ts
@@ -198,6 +198,39 @@ export function execScripts (
}
}
+/**
+ * run code
+ * @param url script address
+ * @param code js code
+ * @param app app
+ * @param module type='module' of script
+ * @param isDynamic dynamically created script
+ * @param callback callback from execScripts for first exec
+ */
+export function runScript (
+ url: string,
+ code: string,
+ app: AppInterface,
+ module: boolean,
+ isDynamic: boolean,
+ callback?: Func,
+): any {
+ try {
+ code = bindScope(url, code, app)
+ if (app.inline) {
+ const scriptElement = pureCreateElement('script')
+ setInlinScriptContent(url, code, module, scriptElement, callback)
+ if (isDynamic) return scriptElement
+ app.container?.querySelector('micro-app-body')!.appendChild(scriptElement)
+ } else {
+ Function(code)()
+ if (isDynamic) return document.createComment('dynamic script extract by micro-app')
+ }
+ } catch (e) {
+ console.error('[micro-app from runScript]', e)
+ }
+}
+
/**
* Get dynamically created remote script
* @param url script address
@@ -232,17 +265,16 @@ export function runDynamicScript (
replaceElement = document.createComment(`dynamic script with src='${url}' extract by micro-app`)
}
- fetchSource(url, app.name).then((data: string) => {
- info.code = data
+ fetchSource(url, app.name).then((code: string) => {
+ info.code = code
app.source.scripts.set(url, info)
- if (info.isGlobal) globalScripts.set(url, data)
+ if (info.isGlobal) globalScripts.set(url, code)
try {
- data = bindScope(url, data, app)
+ code = bindScope(url, code, app)
if (app.inline) {
- if (info.module) (replaceElement as HTMLScriptElement).setAttribute('type', 'module')
- replaceElement.textContent = data
+ setInlinScriptContent(url, code, info.module, replaceElement as HTMLScriptElement)
} else {
- Function(data)()
+ Function(code)()
}
} catch (e) {
console.error('[micro-app from runDynamicScript]', e, url)
@@ -257,44 +289,29 @@ export function runDynamicScript (
}
/**
- * run code
+ * common handle for inline script
* @param url script address
* @param code js code
- * @param app app
* @param module type='module' of script
- * @param isDynamic dynamically created script
+ * @param scriptElement target script element
* @param callback callback from execScripts for first exec
*/
-export function runScript (
+function setInlinScriptContent (
url: string,
code: string,
- app: AppInterface,
module: boolean,
- isDynamic: boolean,
+ scriptElement: HTMLScriptElement,
callback?: Func,
-): any {
- try {
- code = bindScope(url, code, app)
- if (app.inline) {
- const script = pureCreateElement('script')
- if (module) {
- // module script is async, transform it to a blob for subsequent operations
- const blob = new Blob([code], { type: 'text/javascript;charset=utf-8' })
- script.src = URL.createObjectURL(blob)
- script.setAttribute('type', 'module')
- script.setAttribute('originSrc', url)
- callback && (script.onload = callback)
- } else {
- script.textContent = code
- }
- if (isDynamic) return script
- app.container?.querySelector('micro-app-body')!.appendChild(script)
- } else {
- Function(code)()
- if (isDynamic) return document.createComment('dynamic script extract by micro-app')
- }
- } catch (e) {
- console.error('[micro-app from runScript]', e)
+): void {
+ if (module) {
+ // module script is async, transform it to a blob for subsequent operations
+ const blob = new Blob([code], { type: 'text/javascript;charset=utf-8' })
+ scriptElement.src = URL.createObjectURL(blob)
+ scriptElement.setAttribute('type', 'module')
+ scriptElement.setAttribute('originSrc', url)
+ callback && (scriptElement.onload = callback)
+ } else {
+ scriptElement.textContent = code
}
}