上一篇文章:从0实现一个single-spa的前端微服务(中)中咱们已经实现了single-spa
+ systemJS
的前端微服务以及完善的开发和打包配置,今天主要讲一下这个方案存在的细节问题,以及qiankun
框架的一些研究对比。javascript
single-spa
的三个生命周期函数bootstrap
、 mount
、 unmount
分别表示初始化、加载时、卸载时。css
bootstrap
、mount
和unmount
函数是必需的,可是unload
是可选的。Promise
。promise
解析后再调用下一个。咱们知道,子系统卸载以后,其引入的css
并不会被删掉,因此在子系统卸载时删掉这些css
,是一种解决css
污染的办法,可是不太好记录子系统引入了哪些css
。html
咱们能够借助换肤的思路来解决css
污染,首先css-scoped
解决95%的样式污染,而后就是全局样式可能会形成污染,咱们只须要将全局样式用一个id/class
包裹着就能够了,这样这些全局样式仅在这个id/class
范围内生效。前端
具体作法就是:在子系统加载时(mount
)给<body>
加一个特殊的id/class
,而后在子系统卸载时(unmount
)删掉这个id/class
。而子系统的全局样式都仅在这个id/class
范围内生效,若是子系统独立运行,只须要在子系统的入口文件index.html
里面给<body>
手动加上这个id/class
便可。vue
代码以下:java
async function mount(props){
//给body加class,以解决全局样式污染
document.body.classList.add('app-vue-history')
}
async function unmount(props){
//去掉body的class
document.body.classList.remove('app-vue-history')
}
复制代码
固然了,你写的全局样式也在这个class
下面:node
.app-vue-history{
h1{
color: red
}
}
复制代码
暂时没有很好的办法解决,可是能够靠编码规范来约束:页面销毁以前清除本身页面上的定时器/全局事件,必要的时候,全局变量也应该销毁。webpack
这是一个比较常见的需求,相似还有某个系统须要插入一段特殊的js/css
,而其余系统不须要,解决办法任然是在子系统加载时(mount
)插入须要的js/css
,在子系统卸载时(unmount
)删掉。git
const headEle = document.querySelector('head');
let linkEle = null ;
// 由于新插入的icon会覆盖旧的,因此旧的不用删除,若是须要删除,能够在unmount时再插入进来
async function mount(props){
linkEle = document.createElement("link");
linkEle.setAttribute('rel','icon');
linkEle.setAttribute('href','https://gold-cdn.xitu.io/favicons/favicon.ico');
headEle.appendChild(linkEle);
}
async function unmount(props){
headEle.removeChild(linkEle);
linkEle = null;
}
复制代码
注意:上面例子中是修改icon标签,不影响页面的加载。若是某个子系统须要在页面加载以前加载某个js(例如配置文件),须要将加载 js 的函数写成 promise,而且将这个周期函数放到 single-spa-vue 返回的周期前面。 github
系统之间通讯通常有两种方式:自定义事件和本地存储。若是是两个系统相互跳转,能够用URL
传数据。
通常来讲,不会同时存在A、B两个子系统,常见的数据共享就是登录信息,登录信息通常使用本地存储记录。另一个常见的场景就是子系统修改了用户信息,主系统须要从新请求用户信息,这个时候通常用自定义事件通讯,自定义事件具体如何操做,能够看上一篇文章的例子。
另外,single-spa
的注册函数registerApplication
,第四个参数能够传递数据给子系统,但传递的数据必须是一个对象。
注册子系统的时候:
singleSpa.registerApplication(
'appVueHistory',
() => System.import('appVueHistory'),
location => location.pathname.startsWith('/app-vue-history/'),
{ authToken: "d83jD63UdZ6RS6f70D0" }
)
复制代码
子系统(appVueHistory
)接收数据:
export function mount(props) {
//官方文档写的是props.customProps.authToken,实际上发现是props.authToken
console.log(props.authToken);
return vueLifecycles.mount(props);
}
复制代码
关于子系统的生命周期函数:
bootstrap
,mount
,unmount
均包含参数props
props
是一个对象,包含name
,singleSpa
,mountParcel
,customProps
。不一样的版本可能略有差别customProps
就是注册的时候传递过来的参数查看single-spa-vue
源码能够发现,在unmount
生命周期,它将vue
实例destroy
(销毁了)而且清空了DOM
。因此实现keep-alive
的关键在于子系统的unmount
周期中不销毁vue
实例而且不清空DOM
,采用display:none
来隐藏子系统。而在mount
周期,先判断子系统是否存在,若是存在,则去掉其display:none
便可。
咱们须要修改single-spa-vue
的部分源代码:
function mount(opts, mountedInstances, props) {
let instance = mountedInstances[props.name];
return Promise.resolve().then(() => {
//先判断是否已加载,若是是,则直接将其显示出来
if(!instance){
//这里面都是其源码,生成DOM并实例化vue的部分
instance = {};
const appOptions = { ...opts.appOptions };
if (props.domElement && !appOptions.el) {
appOptions.el = props.domElement;
}
let domEl;
if (appOptions.el) {
if (typeof appOptions.el === "string") {
domEl = document.querySelector(appOptions.el);
if (!domEl) {
throw Error(
`If appOptions.el is provided to single-spa-vue, the dom element must exist in the dom. Was provided as ${appOptions.el}`
);
}
} else {
domEl = appOptions.el;
}
} else {
const htmlId = `single-spa-application:${props.name}`;
// CSS.escape 的文档(需考虑兼容性)
// https://developer.mozilla.org/zh-CN/docs/Web/API/CSS/escape
appOptions.el = `#${CSS.escape(htmlId)}`;
domEl = document.getElementById(htmlId);
if (!domEl) {
domEl = document.createElement("div");
domEl.id = htmlId;
document.body.appendChild(domEl);
}
}
appOptions.el = appOptions.el + " .single-spa-container";
// single-spa-vue@>=2 always REPLACES the `el` instead of appending to it.
// We want domEl to stick around and not be replaced. So we tell Vue to mount
// into a container div inside of the main domEl
if (!domEl.querySelector(".single-spa-container")) {
const singleSpaContainer = document.createElement("div");
singleSpaContainer.className = "single-spa-container";
domEl.appendChild(singleSpaContainer);
}
instance.domEl = domEl;
if (!appOptions.render && !appOptions.template && opts.rootComponent) {
appOptions.render = h => h(opts.rootComponent);
}
if (!appOptions.data) {
appOptions.data = {};
}
appOptions.data = { ...appOptions.data, ...props };
instance.vueInstance = new opts.Vue(appOptions);
if (instance.vueInstance.bind) {
instance.vueInstance = instance.vueInstance.bind(instance.vueInstance);
}
mountedInstances[props.name] = instance;
}else{
instance.vueInstance.$el.style.display = "block";
}
return instance.vueInstance;
});
}
function unmount(opts, mountedInstances, props) {
return Promise.resolve().then(() => {
const instance = mountedInstances[props.name];
instance.vueInstance.$el.style.display = "none";
});
}
复制代码
而子系统内部页面则和正常vue
系统同样使用<keep-alive>
标签来实现缓存。
vue-router
路由配置的时候可使用按需加载(代码以下),按需加载以后路由文件就会单独打包成一个js
和css
。
path: "/about",
name: "about",
component: () => import( "../views/About.vue")
复制代码
而 vue-cli3
生成的模板打包后的index.html
中是有使用prefetch
和preload
来实现路由文件的预请求的:
<link href=/js/about.js rel=prefetch>
<link href=/js/app.js rel=preload as=script>
复制代码
prefetch
预请求就是:浏览器网络空闲的时候请求并缓存文件
systemJs
只能拿到入口文件,其余的路由文件是按需加载的,没法实现预请求。可是若是你没有使用路由的按需加载,则全部路由文件都打包到一个文件(app.js
),则能够实现预请求。
上述完整demo
文件地址:github.com/gongshun/si…
qiankun
是蚂蚁金服开源的基于single-spa
的一个前端微服务框架。
咱们知道全部的全局的方法(alert
,setTimeout
,isNaN
等)、全局的变/常量(NaN
,Infinity
,var
声明的全局变量等)和全局对象(Array
,String
,Date
等)都属于window
对象,而能致使js
污染的也就是这些全局的方法和对象。
因此qiankun
解决js
污染的办法是:在子系统加载以前对window
对象作一个快照(拷贝),而后在子系统卸载的时候恢复这个快照,便可以保证每次子系统运行的时候都是一个全新的window
对象环境。
那么如何监测window
对象的变化呢,直接将window
对象进行一下深拷贝,而后深度对比各个属性显然可行性不高,qiankun
框架采用的是ES6
新特性,proxy
代理方法。
具体代码以下(源代码是ts
版的,我简化修改了一些):
// 沙箱期间新增的全局变量
const addedPropsMapInSandbox = new Map();
// 沙箱期间更新的全局变量
const modifiedPropsOriginalValueMapInSandbox = new Map();
// 持续记录更新的(新增和修改的)全局变量的 map,用于在任意时刻作 snapshot
const currentUpdatedPropsValueMap = new Map();
const boundValueSymbol = Symbol('bound value');
const rawWindow = window;
const fakeWindow = Object.create(null);
const sandbox = new Proxy(fakeWindow, {
set(target, propKey, value) {
if (!rawWindow.hasOwnProperty(propKey)) {
addedPropsMapInSandbox.set(propKey, value);
} else if (!modifiedPropsOriginalValueMapInSandbox.has(propKey)) {
// 若是当前 window 对象存在该属性,且 record map 中未记录过,则记录该属性初始值
const originalValue = rawWindow[propKey];
modifiedPropsOriginalValueMapInSandbox.set(propKey, originalValue);
}
currentUpdatedPropsValueMap.set(propKey, value);
// 必须从新设置 window 对象保证下次 get 时能拿到已更新的数据
rawWindow[propKey] = value;
// 在 strict-mode 下,Proxy 的 handler.set 返回 false 会抛出 TypeError,
// 在沙箱卸载的状况下应该忽略错误
return true;
},
get(target, propKey) {
if (propKey === 'top' || propKey === 'window' || propKey === 'self') {
return sandbox;
}
const value = rawWindow[propKey];
// isConstructablev :监测函数是不是构造函数
if (typeof value === 'function' && !isConstructable(value)) {
if (value[boundValueSymbol]) {
return value[boundValueSymbol];
}
const boundValue = value.bind(rawWindow);
Object.keys(value).forEach(key => (boundValue[key] = value[key]));
Object.defineProperty(value, boundValueSymbol,
{ enumerable: false, value: boundValue }
)
return boundValue;
}
return value;
},
has(target, propKey) {
return propKey in rawWindow;
},
});
复制代码
大体原理就是记录window
对象在子系统运行期间新增、修改和删除的属性和方法,而后会在子系统卸载的时候复原这些操做。
这样处理以后,全局变量能够直接复原,可是事件监听和定时器须要特殊处理:用addEventListener
添加的事件,须要用removeEventListener
方法来移除,定时器也须要特殊函数才能清除。因此它重写了事件绑定/解绑和定时器相关函数。
重写定时器(setInterval
)部分代码以下:
const rawWindowInterval = window.setInterval;
const hijack = function () {
const timerIds = [];
window.setInterval = (...args) => {
const intervalId = rawWindowInterval(...args);
intervalIds.push(intervalId);
return intervalId;
};
return function free() {
window.setInterval = rawWindowInterval;
intervalIds.forEach(id => {
window.clearInterval(id);
});
};
}
复制代码
小细节:切换子系统不能立马清除子系统的延时定时器,好比说子系统有一个message
提示,3秒钟后自动关闭,若是你立马清除掉了,就会一直存在了。那么延迟多久再清除子系统的定时器合适呢?5s?7s?10s?彷佛都不太理想,做者最终决定不清除setTimeout
,毕竟使用了一次以后就没用了,影响不大。
因为qiankun在js沙箱功能中使用了proxy新特性,因此它的兼容性和vue3同样,不支持IE11及如下版本的IE。不过做者说能够尝试禁用沙箱功能来提升兼容性,可是不保证都能运行。去掉了js沙箱功能,就变得索然无味了。
function
关键字直接声明一个全局函数,这个函数属于window
对象,可是没法被delete
:
function a(){}
Object.getOwnPropertyDescriptor(window, "a")
//控制台打印以下信息
/*{ value: ƒ a(), writable: true, enumerable: true, configurable: false }*/
delete window.a // 返回false,表示删除失败
复制代码
configurable:当且仅当指定对象的属性描述能够被改变或者属性可被删除时,为true
既然没法被delete
,那么qiankun
的js
沙箱是如何作的呢,它是怎样消除子系统的全局函数的影响的呢?
声明全局函数有两种办法,一种是function
关键字在全局环境下声明,另外一种是以变量的形式添加:window.a = () => {}
。咱们知道function
声明的全局函数是没法删除的,而变量的形式是能够删除的,qiankun
直接避免了function
关键字声明的全局函数。
首先,咱们编写在.vue
文件或者main.js
文件中function
声明的函数都不是全局函数,它只属于当前模块的。只有index.html
中直接写的全局函数,或者不被打包文件里面的函数是全局的。
在index.html
中编写的全局函数,会被处理成局部函数。 源代码:
<script> function b(){} //测试全局变量污染 console.log('window.b',window.b) </script>
复制代码
qiankun处理后:
(function(window){;
function b(){}
//测试全局变量污染
console.log('window.b',window.b)
}).bind(window.proxy)(window.proxy);
复制代码
那他是如何实现的呢?首先用正则匹配到index.html
里面的外链js
和内联js
,而后外链js
请求到内容字符串后存储到一个对象中,内联js
直接用正则匹配到内容也记录到这个对象中:
const fetchScript = scriptUrl => scriptCache[scriptUrl] ||
(scriptCache[scriptUrl] = fetch(scriptUrl).then(response => response.text()));
复制代码
而后运行的时候,采用eval
函数:
//内联js
eval(`;(function(window){;${inlineScript}\n}).bind(window.proxy)(window.proxy);`)
//外链js
eval(`;(function(window){;${downloadedScriptText}\n}).bind(window.proxy)(window.proxy);`))
复制代码
同时,他还会考虑到外链js
的async
属性,即考虑到js
文件的前后执行顺序,不得不说,这个做者真的是细节满满。
它解决css
污染的办法是:在子系统卸载的时候,将子系统引入css
使用的<link>
、<style>
标签移除掉。移除的办法是重写<head>
标签的appendChild
方法,办法相似定时器的重写。
子系统加载时,会将所须要的js/css
文件插入到<head>
标签,而重写的appendChild
方法会记录所插入的标签,而后子系统卸载的时候,会移除这些标签。
解决子系统预请求的的根本在于,咱们须要知道子系统有哪些js/css
须要加载,而借助systemJs
加载子系统,只知道子系统的入口文件(app.js
)。qiankun
不只支持app.js
做为入口文件,还支持index.html
做为入口文件,它会用正则匹配出index.html
里面的js/css
标签,而后实现预请求。
网络很差和移动端访问的时候,qiankun
不会进行预请求,移动端大可能是使用数据流量,预请求则会浪费用户流量,判断代码以下:
const isMobile =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const isSlowNetwork = navigator.connection
? navigator.connection.saveData || /(2|3)g/.test(navigator.connection.effectiveType)
: false;
复制代码
请求js/css
文件它采用的是fetch
请求,若是浏览器不支持,还须要polyfill
。
如下代码就是它请求js
并进行缓存:
const defaultFetch = window.fetch.bind(window);
//scripts是用正则匹配到的script标签
function getExternalScripts(scripts, fetch = defaultFetch) {
return Promise.all(scripts.map(script => {
if (script.startsWith('<')) {
// 内联js代码块
return getInlineCode(script);
} else {
// 外链js
return scriptCache[script] ||
(scriptCache[script] = fetch(script).then(response => response.text()));
}
}));
}
复制代码
qiankun
的源码中已经给出了使用示例,使用起来也很是简单好用。接下来我演示下如何从0开始用qianklun
框架实现微前端,内容改编自官方使用示例。PS:基于qiankun1
版本
vue-cli3
生成一个全新的vue
项目,注意路由使用history
模式。qiankun
框架:npm i qiankun -S
app.vue
,使其成为菜单和子项目的容器。其中两个数据,loading
就是加载的状态,而content
则是子系统生成的HTML
片断(子系统独立运行时,这个HTML
片断会被插入到#app
里面的)<template>
<div id="app">
<header>
<router-link to="/app-vue-hash/">app-vue-hash</router-link>
<router-link to="/app-vue-history/">app-vue-history</router-link>
</header>
<div v-if="loading" class="loading">loading</div>
<div class="appContainer" v-html="content">content</div>
</div>
</template>
<script>
export default {
props: {
loading: {
type: Boolean,
default: false
},
content: {
type: String,
default: ''
},
},
}
</script>
复制代码
main.js
,注册子项目,子项目入口文件采用index.html
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { registerMicroApps, start } from 'qiankun';
Vue.config.productionTip = false
let app = null;
function render({ appContent, loading }) {
if (!app) {
app = new Vue({
el: '#container',
router,
data() {
return {
content: appContent,
loading,
};
},
render(h){
return h(App, {
props: {
content: this.content,
loading: this.loading,
},
})
}
});
} else {
app.content = appContent;
app.loading = loading;
}
}
function initApp() {
render({ appContent: '', loading: false });
}
initApp();
function genActiveRule(routerPrefix) {
return location => location.pathname.startsWith(routerPrefix);
}
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:80',
render,
activeRule: genActiveRule('/app-vue-hash')
},
{
name: 'app-vue-history',
entry: 'http://localhost:1314',
render,
activeRule: genActiveRule('/app-vue-history')
},
]);
start();
复制代码
注意:主项目中的index.html
模板里面的<div id="app"></div>
须要改成<div id="container"></div>
vue-cli3
生成一个全新的vue
项目,注意路由使用hash
模式。src
目录新增文件public-path.js
,注意用于修改子项目的publicPath
。if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
复制代码
main.js
,配合主项目导出single-spa
须要的三个生命周期。注意:路由实例化须要在main.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;
let router = null;
let instance = null;
function render() {
router = new VueRouter({
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appVueHash');// index.html 里面的 id 须要改为 appVueHash,不然子项目没法独立运行
}
if (!window.__POWERED_BY_QIANKUN__) {//全局变量来判断环境
render();
}
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main framework', props);
render();
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
复制代码
vue.config.js
,主要是容许跨域、关闭热更新、去掉文件的hash
值、以及打包成umd
格式const path = require('path');
const { name } = require('./package');
function resolve(dir) {
return path.join(__dirname, dir);
}
const port = 7101; // dev port
module.exports = {
filenameHashing: true,
devServer: {
hot: true,
disableHostCheck: true,
port,
overlay: {
warnings: false,
errors: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
// 自定义webpack配置
configureWebpack: {
output: {
// 把子应用打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
复制代码
history
模式的vue
项目与hash
模式只有一个地方不一样,其余的如出一辙。
即main.js
里面路由实例化的时候须要加入条件判断,注入路由前缀
function render() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/app-vue-history' : '/',
mode: 'history',
routes,
});
instance = new Vue({
router,
store,
render: h => h(App),
}).$mount('#appVueHistory');
}
复制代码
自定义事件能够传递数据,可是彷佛不太完美,数据不具有“双向传递性”。若是想在父子项目都能修改这个数据,而且都能响应,咱们须要实现一个顶级vuex
。
具体思路:
Vuex
,而后在子项目注册时候传递给子项目mounted
生命周期拿到主项目的Vuex
,而后注册到全局去:new Vue
的时候,在data
中声明,这样子项目的任何一个组件均可以经过this.$root
访问到这个Vuex
。大体代码以下,
主项目main.js
:
import store from './store';
registerMicroApps([
{
name: 'app-vue-hash',
entry: 'http://localhost:7101',
render,
activeRule: genActiveRule('/app-vue-hash'),
props: { data : store }
},
{
name: 'app-vue-history',
entry: 'http://localhost:1314',
render,
activeRule: genActiveRule('/app-vue-history'),
props: { data : store }
},
]);
复制代码
子项目的main.js
:
function render(parentStore) {
router = new VueRouter({
routes,
});
instance = new Vue({
router,
store,
data(){
return {
store: parentStore,
}
},
render: h => h(App),
}).$mount('#appVueHash');
}
export async function mount(props) {
render(props.data);
}
复制代码
子项目的Home.vue
中使用:
<template>
<div class="home">
<span @click="changeParentState">主项目的数据:{{ commonData.parent }},点击变为2</span>
</div>
</template>
<script> export default { computed: { commonData(){ return this.$root.store.state.commonData; } }, methods: { changeParentState(){ this.$root.store.commit('setCommonData', { parent: 2 }); } }, } </script>
复制代码
js
沙箱和预请求,在start
函数中配置便可start({
prefetch: false, //默认是true,可选'all'
jsSandbox: false, //默认是true
})
复制代码
registerMicroApps
也能够传递数据给子项目,而且能够设置全局的生命周期函数// 其中app对象的props属性就是传递给子项目的数据,默认是空对象
registerMicroApps(
[
{
name: 'app-vue-hash',
entry: 'http://localhost:80',
render, activeRule:
genActiveRule('/app-vue-hash') ,
props: { data : 'message' }
},
{
name: 'app-vue-history',
entry: 'http://localhost:1314',
render,
activeRule: genActiveRule('/app-vue-history')
},
],
{
beforeLoad: [
app => { console.log('before load', app); },
],
beforeMount: [
app => { console.log('before mount', app); },
],
afterUnmount: [
app => { console.log('after unload', app); },
],
},
);
复制代码
qiankun
的官方文档:qiankun.umijs.org/zh/api/#reg…
上述demo
的完整代码github.com/gongshun/qi…
js
沙箱并不能解决全部的js
污染,例如我给<body>
添加了一个点击事件,js
沙箱并不能消除它的影响,因此说,还得靠代码规范和本身自觉。
抛开兼容性,我以为qiankun
真的太好用了,无需对子项目作过多的修改,开箱即用。也不须要对子项目的开发部署作任何额外的操做。
qiankun
框架使用index.html
做为子项目的入口,会将里面的style/link/script
标签以及注释代码解析并插入,可是他没有考虑meta
和title
标签,若是切换系统,其中meta
标签有变化,则不会解析并插入,固然了,meta
标签不影响页面展现,这样的场景并很少。而切换系统,修改页面的title
,则须要经过全局钩子函数来实现。
qiankun
框架很差实现keep-alive
需求,由于解决css/js
污染的办法就是删除子系统插入的标签和劫持window
对象,卸载时还原成子系统加载前的样子,这与keep-alive
相悖:keep-alive
要求保留这些,仅仅是样式上的隐藏。
微前端中子项目的入口文件常见的有两种方式:JS entry
和 HTML entry
纯single-spa
采用的是JS entry
,而qiankun
既支持JS entry
,又支持HTML entry
。
JS entry
的要求比较苛刻:
(1)将css
打包到js
里面
(2)去掉chunk-vendors.js
,
(3)去掉文件名的hash
值
(4)将入口文件(app.js
)放置到index.html
目录,其余文件不变,缘由是要截取app.js
的路径做为publicPath
APP entry | 优势 | 缺点 |
---|---|---|
JS entry | 能够复用公共依赖(vue,vuex,vue-router等) | 须要各类打包配置配合,没法实现预加载 |
HTML entry | 简单方便,能够预加载 | 多一层请求,须要先请求到HTML文件,再用正则匹配到其中的js和css,没法复用公共依赖(vue,vuex,vue-router等) |
我以为能够将入口文件改成二者配合,使用一个对象来配置:
{
publicPath: 'http://www.baidu.com',
entry: [
"app.3249afbe.js"
"chunk-vendors.75fba470.js",
],
preload: [
"about.3149afve.js",
"test.71fba472.js",
]
}
复制代码
这样既能够实现预加载,又能够复用公共依赖,而且不用修改太多的打包配置。难点在于如何将子系统须要的js
文件写到配置文件里面去,有两个思路:方法1:写一个node
服务,按期(或者子系统有更新时)去请求子系统的index.html
文件,而后正则匹配到里面的js
。方法2:子系统打包时,webpack
会将生成的js/css
文件的请求插入到index.html
中(HtmlWebpackPlugin
),那么是否也能够将这些js
文件的名称发送到服务器记录,可是有些静态js
文件不是打包生成的就须要手动配置。
最后,有什么问题或者错误欢迎指出,互相成长,感谢!