上一篇文章:從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
文件不是打包生成的就須要手動配置。
最後,有什麼問題或者錯誤歡迎指出,互相成長,感謝!