本文主要介绍基于single-spa的微前端项目搭建
一、涉及技术及框架
single-spa:连接主项目与子应用的桥梁
systemjs:浏览器端异步加载模块
single-spa-vue:vue子应用连接主应用插件
二、主应用工程搭建
- vue-cli搭建vue项目
vue create root-web
- 改造root项目使用systemjs加载子应用,以下是index.html的改造
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- <link rel="preload" href="https://cdnjs.cloudflare.com/ajax/libs/single-spa/4.3.7/system/single-spa.min.js" as="script" crossorigin="anonymous" />
<link rel="preload" href="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js" as="script" crossorigin="anonymous" /> -->
<script type="systemjs-importmap">
{
"imports": {
"pro1": "http://localhost:1003/pro1/js/app.js",
"app1": "http://localhost:1003/app1/js/app.js",
"single-spa": "http://lib.baomitu.com/single-spa/5.9.0/system/single-spa.min.js",
"vue": "http://lib.baomitu.com/vue/2.6.12/vue.min.js",
"vue-router": "http://lib.baomitu.com/vue-router/2.8.1/vue-router.min.js",
"element-ui": "http://lib.baomitu.com/element-ui/2.13.2/index.js",
"jquery": "http://lib.baomitu.com/jquery/1.12.4/jquery.min.js",
"vuex": "http://lib.baomitu.com/vuex/2.5.0/vuex.min.js",
"axios": "http://lib.baomitu.com/axios/0.21.1/axios.min.js"
}
}
</script>
</head>
<script crossorigin="anonymous" integrity="sha512-e77b00UHTqTXXo8ZEHATvvSIm7CETY1K+4wJyXHD6NHeoyvyOYePB4lkX5KDBFilzzbPHLvaQTJSrnwG8To3tQ==" src="http://lib.baomitu.com/systemjs/6.8.3/system.js"></script>
<!-- <script src="../src/system.js"></script> -->
<script crossorigin="anonymous" integrity="sha512-XRrZRDEJK7FeoBd7ICImm9S3UNG87nFAUm4td9v2uedBr16byMk9FK4AZ4N608aVHHSKPlhdSWTy/b717KJ54Q==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/named-register.min.js"></script>
<script crossorigin="anonymous" integrity="sha512-fRfysEnVm90UgqFUsB+UlgnHK5vvSnsl64hxHaJJl4wE+P1sTwDUuQGmohtg/CSK60mY5A4elesW6UWQdLQsAg==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/amd.min.js"></script>
<script crossorigin="anonymous" integrity="sha512-mf4/y3IroNfkl20Brygdc+tb4a38MrEmU+VTSyMaqlsZhfVHCdHniA//HEgETGlJptSSCPv72Dy6kXB+OHiGAQ==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/named-exports.min.js"></script>
<script crossorigin="anonymous" integrity="sha512-Ahpc6VqzcdPS1xTuBGdnw+l64Iz6KNXAy/4/1bBzAv6nTl+NK7mnYHMDtI8wAzd2PTV9GVNURwKf87y36eWZ+g==" src="http://lib.baomitu.com/systemjs/6.8.3/extras/use-default.min.js"></script>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
<!-- <import-map-overrides-full></import-map-overrides-full> -->
</html>
- 引入systemjs包。用systemjs
module.exports = {
devServer: {
open: true,
contentBase: "./dist",
historyApiFallback: true,
watchOptions: { aggregateTimeout: 300, poll: 1000 },
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
proxy: {
"/pro1": {
target: "http://localhost:9000",
pathRewrite: { "^/pro1": "" },
},
"/app1": {
target: "http://localhost:9081",
pathRewrite: { "^/app1": "" },
},
},
},
};
- 改造main.js
import App from "./App.vue";
import routes from "./routers/router";
const SystemJS = window.System;
Promise.all([
SystemJS.import("single-spa"),
SystemJS.import("vue-router"),
SystemJS.import("axios"),
SystemJS.import("vue"),
]).then(function(modules) {
var singleSpa = modules[0];
var Vue = modules[3];
var VueRouter = modules[1];
var axios = modules[2];
Vue.use(VueRouter);
Vue.config.productionTip = false;
singleSpa.registerApplication(
"pro1",
() => SystemJS.import("pro1"),
(location) => {
return location.hash.startsWith("#/pro1");
}
);
singleSpa.registerApplication(
"app1",
() => SystemJS.import("app1"),
(location) => location.hash.startsWith("#/app1")
);
singleSpa.start();
const router = new VueRouter({
routes,
});
new Vue({
render: (h) => h(App),
router,
}).$mount("#app");
});
使用system加载模块。singleSpa.registerApplication注册子应用,当路由匹配app1时会到远程加载子应用的代码。主应用加载公共资源vue、vue-router后子应用可以不打包这些。实现共享。
三、子应用改造
- 同样适用vue-cli创建项目
vue create spa-a
npm install single-spa-vue --save
npm install systemjs-webpack-interop --save //用于实现异步路由时主应用可加载代码分割块
- 改造main.js
import './set-public-path'
import Vue from 'vue';
import App from './App.vue';
import router from './routers/router';
import singleSpaVue from 'single-spa-vue';
import VueRouter from 'vue-router'
Vue.config.productionTip = false;
Vue.use(VueRouter)
const vueLifecycles = singleSpaVue({
Vue,
appOptions: {
render: (h) => h(App),
router
},
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
暴露3个方法bootstrap 、mount 、unmount 使主应用和子应用实现通信
set-public-path主要用于子应用适用异步路由时主应用能加载到代码chunk
import { setPublicPath } from 'systemjs-webpack-interop'
setPublicPath('pro1', 2) //子应用的前缀。与主应用注册时的名称相同
子应用的vue.config.js配置
// Temporary until we can use https://github.com/webpack/webpack-dev-server/pull/2143
module.exports = {
chainWebpack: config => {
config.devServer.set('inline', false)
config.devServer.set('hot', true)
// Vue CLI 4 output filename is js/[chunkName].js, different from Vue CLI 3
// More Detail: https://github.com/vuejs/vue-cli/blob/master/packages/%40vue/cli-service/lib/config/app.js#L29
if (process.env.NODE_ENV !== 'production') {
config.output.filename(`js/[name].js`)
.libraryTarget('umd') // 打包方式 必须配置。这样systemjs才能正常加载子应用
.end()
}
config.externals(['vue', 'vue-router']) //不打包vue等公共资源
config
.optimization.splitChunks({ //去掉默认的chunk-vendors
cacheGroups: {
common: {
name: `chunk-common`,
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true
}
}
})
},
filenameHashing: false
}