文 掘金號 Terry豆 @每日優鮮html
著做權歸做者全部。商業轉載請聯繫本帳號得到受權,非商業轉載請註明每日優鮮大前端團隊以及原文地址。前端
我所在團隊是作toB業務的,技術棧是Vue,團隊目前有十多個典型的toB業務(菜單+內容佈局),這些業務都是服務於一個大平臺的,由於歷史緣由,每一個業務都是獨立的,都有一個html入口,因此當用戶在這個大平臺上使用這十多個業務的時候,每當切換系統時,頁面都會刷新,體驗不好;在開發層面,這十多個業務又有太多共同之處,每次修改爲本都很高。
vue
最近有一個很重要的需求X,內容是這樣的:從十多個項目中,每一個項目抽取若干功能組成一個新項目,基於現有架構的話,每當點擊來自不一樣系統的功能頁面就要刷新一次,這是不可接受的。爲了新需求X重複開發一遍這些業務功能又不現實,因此從技術角度來看,架構改造不可避免。
node
通過一番調研比對,咱們決定使用當下比較火的 SingleSpa 來完成改造(iframe方案嘗試過,不太適合咱們的場景),目前改造已完成,咱們實現瞭如下效果:react
作微前端改造以前,藍色系區域都是用公共包的方式由每一個子項目引入,因此子項目運行的時候展現的藍色系部分都是相同的,給人一種在使用同一個系統的錯覺,實際上切換系統的時候整個頁面都要從新載入。
webpack
微前端改造後,只有橘紅色區域是變化的,頁面也再也不刷新。ios
圖2展現了圖1中的tab頁籤區以及子項目展現區。信息作了馬賽克處理。
git
乍一看沒什麼特別的,但若是我說這些tab分別來自於不一樣git倉庫的獨立vue項目呢?
這就是這套微前端架構的強大之處,讓不一樣單頁vue項目能夠隨意組合成一個項目,而這些項目本身又是獨立的vue項目。
github
仔細看圖2中路由的變化,hash路由的第一級決定了要加載哪一個子項目(work、sms、tms是三個不一樣的git工程),不一樣子項目間的切換也徹底沒有刷新😁
web
爲了讓tab切換不刷新,這裏使用了keep-alive去緩存頁面,考慮到內存性能,在關閉tab頁籤時經過一些方法(主要是keep-alive的exclude屬性)去除了keep-alive緩存,同時爲了讓子項目間的tab切換也不刷新,對圖3下面提到的包裝器也進行了不小的改造。讓tab切換不刷新只是爲了提高用戶體驗,這一步不是必要的,有必定的成本。
實現一套微前端架構,能夠把其分紅四部分(參考:alili.tech/archive/110… )
因此是這麼個概念:電源(加載器)→電源適配器(包裝器)→️電器底座(主項目)→️電器(子項目)️
主項目和子項目都須要用包裝器包裝,只不過主項目的配置寫法有不一樣
加載器和包裝器須要根據本身的需求作一些二次開發
總的來講是這樣一個流程:用戶訪問index.html後,瀏覽器運行加載器的js文件,加載器去讀取圖4中的配置文件,而後註冊配置文件中配置的各個項目後,首先加載主項目(菜單等),再經過路由斷定,動態遠程加載子項目。
這裏有個vue微前端版demo,包含最基礎的效果與源碼,務必研究一下這個demo再結合以上理論來幫助理解
*遠程加載的子項目資源要在chrome的network中的xhr那一欄才能看到
在資源服務器上起一個監聽服務(我使用的是nodejs腳本+pm2守護),原有子項目的部署方式徹底不變(先後端徹底分離,資源帶hash),當監聽服務檢測到文件改動時,去子項目部署文件夾裏找它的index.html,把入口js用以下正則匹配出來,寫入apps.config.js。
// content[i]爲子項目文件夾名稱。這段代碼是nodejs腳本片斷。 const reg = new RegExp(`src="\(\\/${content[i]}\\/index\\.\\w{8}.js\)`) // 對應圖中的 /brain/index.3c4b55cf.js 複製代碼
圖4中的brain便是主項目,它的base屬性爲true,其他子項目的base屬性爲false
這裏說的的項目打包都是基於webpack。
它是實現遠程加載子項目的核心。
咱們使用的是0.21版本的:github.com/systemjs/sy…
由於要動態經過http引入外部js,又不影響在開發的時候使用import、require方法,因此找到了systemjs來作這件事。
根據systemjs文檔說明,咱們只須要把子項目打成umd格式(umd糅合了AMD和CommonJS)的包便可動態外部加載。
// 每一個子項目的webpack.config.js output: { path: xxx, publicPath: xxx, filename: '[name].[chunkhash:8].js', chunkFilename: 'js/[name].[chunkhash:8].chunk.js', libraryTarget: 'umd', // 這裏必定要寫成umd,否則打出來的包system.js沒法讀取 library: xxx, //模塊的名稱 }, 複製代碼
文檔:www.webpackjs.com/configurati…
這麼多同類型的vue項目,必定有大量的重複代碼、重複引用,因此這是一塊巨大的性能優化點,經過配置externals能夠極大減少子項目打包出來的體積。
我並無徹底按照文檔說明的方式來從CDN引入,緣由是這樣的:入口index.html只有一個,若是按文檔來作,一次引入全部CDN資源,可能子項目A用獲得這些,但子項目B用不到這些,而我只訪問了子項目B而已,這樣不就多加載了無用的資源嗎?
通過一番調研,一樣利用systemjs解決了這個問題
// 每一個子項目本身的webpack.config.js,根據使用狀況設置externals externals: { 'axios': 'axios', 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex': 'Vuex', 'iview': 'iview', 'moment': 'moment', 'echarts': 'echarts', '@mfb/pc-utils-micro':'@mfb/pc-utils-micro', // 私有公共方法包 '@mfb/pc-components-micro':'@mfb/pc-components-micro', // 私有公共組件包 // '@mfb/pc-components-micro':'@mfb/pc-components-micro-0.2.1', // 若是須要指定版本,則用這一行替換上一行 ... }, 複製代碼
// index.html 整個微前端的惟一入口 <script src="system.js"></script> <script> SystemJS.config({ map: { "Vue": "//xxx.cdn.cn/static/vue/2.5.17/vue.min.js", "vue": "//xxx.cdn.cn/static/vue/2.5.17/vue.min.js", // 由於iview前置須要vue,是小寫的,就又聲明瞭一次 "Vuex": "//xxx.cdn.cn/static/vuex/3.0.1/vuex.min.js", "VueRouter": "//xxx.cdn.cn/static/vueRouter/3.0.1/vue-router.min.js", "iview": "//xxx.cdn.cn/static/iview/3.3.2/iview.min.js", "moment": "//xxx.cdn.cn/static/moment/2.22.2/moment.min.js", "axios": "//xxx.cdn.cn/static/axios/0.15.3/axios.min.js", "echarts": "//xxx.cdn.cn/static/echarts/4.2.1/echarts.min.js", "@mfb/pc-utils-micro": "//xxx.cdn.cn/static/mfb-pc-utils-micro/mfb-pc-utils-micro-0.0.6.js", "@mfb/pc-components-micro": "//xxx.cdn.cn/static/mfb-pc-components-micro/mfb-pc-components-micro-0.0.42.js", "@mfb/pc-components-micro-0.2.1": "//xxx.cdn.cn/static/mfb-pc-components-micro/mfb-pc-components-micro-0.2.1.js" // 若是須要指定版本 } }) </script> 複製代碼
如此一來,systemjs只是在加載index.html時註冊了這些CDN地址,不會直接去加載,當子項目裏用到的時候,systemjs會接管模塊引入,systemjs會去上面註冊的map中查找匹配的模塊,就再動態去加載資源。這樣就避免了不一樣子項目在這套架構下產生的多餘加載。
按咱們的配置,webpack打包後,externals配置的模塊不會打包進bundle,會被摘出來按umd規範經過requre/define方式去加載。
看systemjs源碼會發現它從新定義了require和define方法,因此它能接管externals的外部引入過程。
我最直白的感覺是實現了項目級別的模塊化,把不一樣項目變成了一個個模塊來拼裝組合,也就是說模塊化從項目內提高到了項目自己
總結一下使用這套架構收到的好處,分爲如下幾點:
也是有不少麻煩之處,須要消耗必定成本:
不過跟收益比起來,這些成本就不算什麼了~
最後要說一下,並非全部場景都適合微前端,尤爲是項目規模小、數量少的場景不建議使用。
什麼樣的場景適合這套架構呢?通常有如下特徵:
可能你會問:爲何不一開始就把全部須要整合的功能用一個git來維護?
答:理想是美好的,誰也沒有先知能力,隨着公司業務發展亦或是組織架構的改變、人員更迭,以上場景是幾乎不可避免的;我很難想象十多個項目的好幾百個功能都在一個git裏管理起來有多困難。
可能你還會問,那我把須要整合的業務整合成到一個git倉庫呢?
答:這固然是一個解決辦法,前提是整合的成本你能接受;而且未來還有這類需求呢?每次都要手動整合業務代碼到同一個git倉庫嗎?假設全部人都只維護這個整合完的git倉庫,並行的需求線多了,上線時間會不會擁擠?一個功能產生了致命錯誤,會不會全部功能跟着出問題?
最後我想說:
咱們作這套框架的初衷是解決眼前的問題,然而發現它附帶的潛力價值卻比想象的多得多。
招前端賢士,隸屬每日優鮮前端團隊,更多小夥伴期待與你一塊兒浪。若是你覺的一技之長沒地方施展,若是你以爲想玩更潮的技術,若是你厭倦了一成不變,若是你覺的到了瓶頸沒法突破,若是你以爲幹了這麼多年前端突然迷茫了,若是你有股創業勁卻沒機會,若是若是,那是時候做出改變和嘗試了,期待你跟咱們聊聊,寫點什麼發給咱們。大前端TL郵箱:mazhuang@missfresh.cn 座標:北京