其實方便點可使用 qiankun 的微前端方案javascript
依賴版本:html
"single-spa": "^5.5.2",
"single-spa-vue": "^1.8.2",
複製代碼
啓動由 system.js
接管,配置 webpack
下 out.libraryTarget
爲 system
前端
html
入口中經過 importmap
,設置當前應用、子應用 名稱+地址
vue
通常用法(DOM
節點一直存在的狀況下):registerApplication
註冊子應用,經過 system.js
引入,設置渲染路由 activeWhen
,傳遞給子應用的參數 customProps
java
使用 Parcel
用法(DOM
節點不是一直存在的狀況下):主應用也須要包裹 singleSpaVue
/singleSpaReact
等,而後 registerApplication
本身,在某個組件(A)內使用由 main.js/ts
在 bootstraps
/mount
時導出的 mountParcel
,在某組件(A)掛載後,手動將子應用(當作組件用)掛載到這個組件的某個 DOM
節點(見1.3)webpack
啓動方式由 single-spa-vue
接管,能夠判斷 window.singleSpaNavigate
爲 false
單獨啓動git
配置在主應用的掛載點,appOptions
下的 el
設置,默認掛載到 body
下github
導出一些生命週期事件,至少以下三個:bootstrap
/mount
/unmount
,能夠在 mount
下接收主應用傳遞的參數web
異步組件須要使用:(否則主應用使用子應用會報錯)vue-cli
systemjs-webpack-interop
設置 setPublicPath
;webpack
配置:config.output.jsonpFunction = 'wpJsonpFlightsWidget';
下載 single-spa
yarn add single-spa
複製代碼
HTML
入口system.js
的包最後下載下來放項目裏,防止引用的 cdn
有時候抽風
systemjs-importmap
也能夠經過配置文件自動生成,這樣也好區分開發環境跟生成環境不一樣的入口,注意打包後子應用的入口的跨域問題
webpack
自動插入 HTML
// systemJs-Importmap.js
const isEnvDev = process.env.NODE_ENV === 'development';
// systemjs-importmap 的配置,經過webpack給html用
module.exports = [
{
name: 'root-config',
entry: './js/app.js',
},
{
name: '@vue-mf/calendar',
entry: isEnvDev
? '//localhost:2333/js/app.js'
: 'https://zero9527.github.io/vue-calendar/js/app.js',
},
];
// vue.config.js
chainWebpack: config => {
config.plugin('html').tap(args => {
const importMap = { imports: {} };
systemJsImportmap.forEach(item => (importMap.imports[item.name] = item.entry));
args[0].systemJsImportmap = JSON.stringify(importMap, null, 2);
return args;
});
},
// public\index.html
<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap"></script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script>
System.import('root-config');
</script>
複製代碼
public/index.html
下手動添加<meta name="importmap-type" content="systemjs-importmap" />
<script type="systemjs-importmap"> { "imports": { "root-config": "//localhost:666/js/app.js", "@vue-mf/calendar": "//localhost:2333/js/app.js" } } </script>
<script src="./libs/systemjs/system.min.js"></script>
<script src="./libs/systemjs/extras/amd.min.js"></script>
<script src="./libs/systemjs/extras/named-exports.min.js"></script>
<script src="./libs/systemjs/extras/named-register.min.js"></script>
<script src="./libs/systemjs/extras/use-default.min.js"></script>
<script> System.import('root-config'); </script>
複製代碼
JSON
,注意格式!這裏配置當前應用的配置 名稱:地址
,與子應用的 名稱:地址
<script type="systemjs-importmap"> { "imports": { "root-config": "//localhost:666/js/app.js", "@vue-mf/calendar": "//localhost:2333/js/app.js" } } </script>
複製代碼
@vue-mf/calendar
,在 registerApplication
時,對應 app:
import('@vue-mf/calendar')
的名稱,如registerApplication({
name: '@vue-mf/calendar',
app: () => (window as any).System.import('@vue-mf/calendar'),
activeWhen: '',
customProps: {
root: 'json-util',
},
});
複製代碼
systemJS
接管<script> System.import('root-config'); </script>
複製代碼
webpack
配置去掉文件 hash
,方便引入文件名
// vue.config.js
module.exports = {
outputDir: 'docs',
publicPath: './',
filenameHashing: false,
productionSourceMap: false,
configureWebpack: config => {
config.output.libraryTarget = 'system';
config.devServer = {
port: 666,
headers: {
'Access-Control-Allow-Origin': '*',
},
disableHostCheck: true,
historyApiFallback: true,
};
},
};
複製代碼
// src\single-spa-config.ts
import { registerApplication, start } from 'single-spa';
registerApplication({
name: '@vue-mf/calendar',
app: () => (window as any).System.import('@vue-mf/calendar'),
activeWhen: '',
customProps: {
root: 'json-util',
},
});
start();
複製代碼
main.ts
中引入其實在哪引入均可以,確保 DOM
節點存在就能夠,若是時動態建立的,首次加載能夠,可是恢復狀態後,會提示找不到 DOM
節點
// src\main.ts
import './single-spa-config';
複製代碼
翻譯過來叫:包裹,能夠在主應用將一個子應用當作組件,手動掛載、卸載使用,不限框架,
webpack 5
有一個Module Federation
也是能夠跨項目使用組件的
把子應用當作一個組件使用,放在主應用的某個組件(A)下面時,DOM
節點不是一直存在的狀況
主應用:使用 singleSpaVue
/singleSpaReact
包裹,而後 registerApplication
本身,在某個組件(A)內使用由 main.js/ts
在 bootstraps
/mount
時導出的 mountParcel
,在某組件(A)掛載後,手動將子應用(當作組件用)掛載到這個組件的某個 DOM
節點
子應用:不須要在主應用 registerAppliaction
註冊,而是手動在某個組件(A)內手動掛載到某個 DOM
節點
// src\main.ts
//...
// **************** 主應用通常寫法 ****************
// // 子應用 registerAppliaction 註冊
// new Vue({
// router,
// render: (h: any) => h(App),
// }).$mount('#json-util');
// **************** 主應用使用 Parcel 寫法 ****************
// 主應用使用 Parcel 掛載子應用(某組件下)的時候的寫法
// 須要把當前應用當作子應用,而後 registerAppliaction 調用
const singleSpa = singleSpaVue({
Vue,
appOptions: {
el: '#json-util',
render: (h: any) => h(App),
router,
},
});
// eslint-disable-next-line
export let mountParcel: any;
export const bootstrap = (props: any) => {
mountParcel = props.mountParcel;
return singleSpa.bootstrap(props);
};
export const { mount, unmount } = singleSpa;
複製代碼
import { registerApplication, start } from 'single-spa';
// 改成 Parcel 手動掛載子應用了,須要導出 mountParcel,已經用 singleVue 包裹了,因此要用 registerApplication 啓動
registerApplication({
name: 'root-config',
app: () => (window as any).System.import('root-config'),
activeWhen: () => true,
});
registerApplication({
name: '@vue-mf/calendar',
app: () => (window as any).System.import('@vue-mf/calendar'),
activeWhen: location => {
return location.href.includes('/sub-app');
},
customProps: {
root: 'json-util',
},
});
// 改成 Parcel 手動掛載了,全部這個要去掉
// registerApplication({
// name: '@vue-mf/clock',
// app: () => (window as any).System.import('@vue-mf/clock'),
// activeWhen: location => {
// return location.href.includes('/sub-app');
// },
// customProps: {
// root: 'json-util',
// },
// });
start();
複製代碼
某個組件(A)在 mount
以後手動將子應用掛載到某個 DOM
節點
使用了 composition-api
import { mountParcel } from '@/main';
const parcel = ref<any>(null);
const mountClockParcel = () => {
const routePath = ctx.root.$route.path;
const domElement = document.getElementById('app-clock');
if (routePath === '/sub-app' && domElement) {
const parcelConfig = (window as any).System.import('@vue-mf/clock');
parcel.value = mountParcel(parcelConfig, { domElement });
} else if (parcel.value) {
parcel.value.unmount();
}
};
onMounted(() => {
mountClockParcel();
});
watch(
() => ctx.root.$route.path,
() => {
mountClockParcel();
},
);
複製代碼
例子:vue-calendar
single-spa-vue
yarn add single-spa-vue
複製代碼
vue-cli-plugin-single-spa
解決這個問題
single-spa.min.js?25a2:2 single-spa minified message #37: See single-spa.js.org/error/?code…
yarn add -D vue-cli-plugin-single-spa
複製代碼
main.js/ts
注意:
appOptions
下,el
能夠給當前應用配置在主應用的掛載DOM
節點,這個節點須要提早設置好;不提供el
的話默認掛載在body
下
// 其餘的代碼省略
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
// ...
// ============= 非 single-spa 單獨啓動 =============
if (!(window as any).singleSpaNavigate) {
new Vue({
render: (h: any) => h(App),
}).$mount('#app-calendar');
}
// ============= single-spa 模式啓動 =============
const vueLifeCycles = singleSpaVue({
Vue,
appOptions: {
// el:掛載的dom節點,在主項目須要有;沒有el的話會添加到body下
el: '#app-calendar',
render: (h: any) => h(App),
},
});
export function bootstrap(props: object) {
return vueLifeCycles.bootstrap(props);
}
export function mount(props: object) {
console.log('mount: ', props);
return vueLifeCycles.mount(props);
}
export function unmount(props: object) {
return vueLifeCycles.unmount(props);
}
複製代碼
子項目使用異步組件 import()
時,單獨跑起來沒問題!!!可是在主應用裏面會報錯,改成正常引入 import from
就沒事。。。
子應用使用異步組件,在主應用報錯
Uncaught TypeError: application '@vue-mf/calendar' died in status BOOTSTRAPPING: Object(...) is not a function
複製代碼
<template>
<div id="app-calendar">
<div class="title">Vue-Calendar</div>
<Calendar />
</div>
</template>
<script lang="ts"> // 正常 import Calendar from '@/components/Calendar/index.vue'; // single-spa在主應用加載:不行 // const Calendar = () => import(@/components/Calendar/index.vue); // single-spa在主應用加載:不行 // import AsyncComponent from '@/components/AsyncComponent/index'; // single-spa 下使用異步組件,在主應用加載有問題 // const Calendar = AsyncComponent(() => // import( // /* webpackPrefetch: true */ // /* webpackChunkName: 'calendar' */ // '@/components/Calendar/index.vue' // ), // ); export default { name: 'App', components: { Calendar, }, }; </script>
複製代碼
子項目添加以下設置
// src\set-public-path.ts
import { setPublicPath } from 'systemjs-webpack-interop';
if ((window as any).singleSpaNavigate) {
setPublicPath('@vue-mf/calendar', 2);
}
複製代碼
// vue.config.js
config.output.jsonpFunction = 'wpJsonpFlightsWidget';
複製代碼
single-spa.js.org/docs/recomm…