關於微前端
的大概念你們應該聽過太多了, 這裏我就大白話闡述一下, 好比咱們新建三個vue工程
a、b、c, a
負責導航模塊, b
負責列表頁面, c
負責詳情頁面, 而後咱們能夠經過微前端
技術把他們組合在一塊兒造成一個完整項目
。css
本篇文章不會講述很深刻的細節操做, 但會講述項目搭建到項目上線的全環節, 若是你這些都會了那麼其餘的問題就不是太大阻礙了。html
必定要明確一點, 微前端
在不少場景都是不適用的, 千萬不要強行使用這門技術, 在本篇文章裏我會一點點的闡述什麼場景不適用以及爲何不適用。
前端
qiankun.js
簡簡簡簡介 qiankun.js
是當前最出色的一款微前端
實現庫, 他幫咱們實現了css隔離
、js隔離
、項目關聯
等功能, 文章的後面都會有所涉及的如今就讓咱們開始實戰吧。vue
一主二附
一共三個vue項目
, 第一個container
項目負責導航模塊, 第二個web1
第三個web2
, container
項目裏面有個subapp
文件夾, 裏面存放着web1 & web2
兩個項目, 這樣之後咱們能夠隨便添加web3,web4....
都放在subapp
文件夾便可。
node
qiankun
配置項目加載規則 在咱們的容器項目container
裏面安裝qiankun
以下命令:webpack
$ yarn add qiankun # 或者 npm i qiankun -S
打開container
項目的App.vue
文件咱們把導航重定義一下:
/w1
與/w2
路由地址分別激活web1
工程與web2
工程。nginx
<div id="nav"> <router-link to="/">Home</router-link> | <router-link to="/w1">web1</router-link> | <router-link to="/w2">web2</router-link> | </div>
咱們新增一個id爲"box"的元素, 接下來咱們引入的web1
工程就會插入到這個元素中。web
<div id="box"></div> <router-view />
把Home.vue
頁面代碼改掉:vue-router
<template> <div class="home">我是`container`工程</div> </template> <script> export default { name: "Home", }; </script> <style> .home { font-size: 23px; } </style>
此時的頁面是這個樣子的:
vue-cli
打開container
項目的main.js
文件寫入配置。
import { registerMicroApps, start } from 'qiankun'; registerMicroApps([ { name: 'vueApp2', entry: '//localhost:8083', container: '#box', activeRule: '/w2', }, { name: 'vueApp1', entry: '//localhost:8082', container: '#box', activeRule: '/w1', }, ]); start();
參數解析:
name
: 微應用的名稱,微應用之間必須確保惟一, 方便後期區分項目來源。entry
: 微應用的入口也就是當知足條件的時候, 我要激活的目標微應用的地址(也能夠是其餘形式好比html
片斷, 但本篇主要講url地址這種形式)。container
: 激活微應用的時候咱們要把這個目標微應用放在哪裏, 上面代碼的意思就是把激活的微應用放在id爲'box'
的元素裏面。activeRule
:微應用的激活規則(有不少種寫法甚至是函數形式), 上面代碼就是當路由地址爲/w1
時激活。子項目
的main.js
以配置web1
項目爲例, web2
與其相似, 在main.js
中導出本身的生命週期函數。
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; let instance = null; function render() { instance = new Vue({ router, render: h => h(App) }).$mount('#web1') // 框架會拿到完整的dom結構, 因此index.html裏面的id也要改一下 } /** * bootstrap 只會在微應用初始化的時候調用一次,下次微應用從新進入時會直接調用 mount 鉤子,不會再重複觸發 bootstrap。 * 一般咱們能夠在這裏作一些全局變量的初始化,好比不會在 unmount 階段被銷燬的應用級別的緩存等。 */ export async function bootstrap() { console.log('bootstrap'); } /** * 應用每次進入都會調用 mount 方法,一般咱們在這裏觸發應用的渲染方法 */ export async function mount() { render() } /** * 應用每次 切出/卸載 會調用的方法,一般在這裏咱們會卸載微應用的應用實例 */ export async function unmount() { instance.$destroy() }
把web1 >public >index.html
中的div元素id從app
改成web1
, 由於要多個項目合成一個項目, 因此id最好仍是不要重複。
<!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"> </head> <body> <div id="web1"></div> </body> </html>
web1
的vue.config.js
module.exports = { devServer: { port: 8082, // web2裏面改爲8083 }, }
如今咱們要分別進入container
, web1
與web2
裏面運行yarn serve
命令, 可是這樣運行命令真的好麻煩, 接下來我就介紹一種更工程化的寫法。
npm-run-all
npm-run-all
是用來經過執行一條語句來達到執行多條語句的效果的插件。
$ npm install npm-run-all --save-dev # or $ yarn add npm-run-all --dev
改裝咱們的container
工程中的package.json
文件。
"scripts": { "serve": "npm-run-all --parallel serve:*", "serve:box": "vue-cli-service serve", "serve:web1": "cd subapp/web1 && yarn serve", "serve:web2": "cd subapp/web2 && yarn serve", "build": "npm-run-all --parallel build:*", "build:box": "vue-cli-service build", "build:web1": "cd subapp/web1 && yarn build", "build:web2": "cd subapp/web2 && yarn build" },
我解釋一下:
運行: yarn serve
系統會執行scripts
裏面全部的頭部爲serve:
的命令, 因此就會實現一個命令運行三個項目, 這裏順手把build
命令也寫了。
其餘擴展玩法:
子項目
居然跨域 運行起來會發現報錯了:
須要在web1
與web2
兩個項目vue.config.js
里加上以下配置就不報錯了:
devServer: { port: 8082, // 因爲會產生跨域, 因此加上 headers: { 'Access-Control-Allow-Origin': "*" } },
之因此會有這種跨域的報錯是由於qiankun
內部使用fetch
請求的資源, 當前畢竟是啓動了三個不一樣的node服務, 外部html頁面請求其資源仍是會跨域的, 因此須要設置容許全部源。
web1
與web2
設置一下樣式, 結果以下: 咱們有時候須要單獨開發web1
, 此時咱們並不依賴container
項目, 那麼咱們就要把main.js
改裝一下:
import Vue from "vue"; import App from "./App.vue"; import router from "./router"; Vue.config.productionTip = false; let instance = null; function render() { instance = new Vue({ router, render: h => h(App) }).$mount('#web1') } if (window.__POWERED_BY_QIANKUN__) { window.__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; } if (!window.__POWERED_BY_QIANKUN__) { render() } export async function bootstrap() { console.log('bootstrap'); } export async function mount() { render() } export async function unmount() { instance.$destroy() }
逐句解釋:
window.__POWERED_BY_QIANKUN__
: 當前環境是否爲qiankun.js
提供。window.__webpack_public_path__
: 等同於 output.publicPath
配置選項, 可是他是動態的。window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__
:qiankun.js
注入的公共路徑。判斷當前環境爲單獨開發的環境就直接執行render
方法, 若是是qiankun
的容器內, 那麼須要設置publicPath
, 由於qiankun
須要把每一個子應用都區分開, 而後引入容器項目內, 這樣咱們就能夠單獨開發web1
項目了。
子應用
路由跳轉與vue-router
的異步組件小bug 在配置router
的時候咱們常常會將頁面寫成異步加載:
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
在web2
項目中的home頁面, 我增長一個按鈕跳到about
頁面:
<template> <div class="home"> <button @click="jump">點擊跳轉到about頁面</button> </div> </template> <script> export default { methods: { jump() { // this.$router.push("/web2/about"); window.history.pushState(null, null, "/w2/about"); }, }, }; </script>
上述代碼不能夠直接用this.$router.push
, 這樣會與qiankun.js
的路由分配衝突, 官網上說會出現404
這種狀況, 因此建議咱們直接用 window.history.pushState
。
可是這中寫法在當前版本qiankun.js
裏面可能會有以下錯誤:
這是因爲動態設置的publicPath
並不能知足加載異步組件chunk
, 須要咱們以下配置一番:(web2->vue.config.js
)
publicPath: `//localhost: 8083`
就能夠正常加載這個頁面了:
about
頁面。前面幾條說的都是開發相關的設置, 這裏咱們要開始介紹打包的配置了, 這裏會介紹原理與作法, 不會作的很細因此具體的項目開發仍是要好好的封裝一番。
我這裏先把nginx
簡單配置一下, 讓這個包能用。
location /ccqk/web1 { alias /web/ccqk/web1; index index.html index.htm; try_files $uri $uri/ /index.html; } location /ccqk/web2 { alias /web/ccqk/web2; index index.html index.htm; try_files $uri $uri/ /index.html; } location /ccqk { alias /web/ccqk/container; index index.html index.htm; try_files $uri $uri/ /index.html; }
因爲我以前有項目在服務器上爲了方便區分, 隨便寫了個ccqk
前綴, 那麼如今目標很明確了, 我須要打一個叫ccqk
的文件夾, 裏面有三個包container
、web1
、web2
。
container -> vue.config.js
module.exports = { outputDir: './ccqk/container', publicPath: process.env.NODE_ENV === "production" ? `/ccqk` : '/', };
web1 -> vue.config.js
const packageName = require('./package.json').name; const port = 8082 module.exports = { outputDir: '../../ccqk/web1', publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web1' : `//localhost:${port}`, devServer: { port, headers: { 'Access-Control-Allow-Origin': "*" } }, configureWebpack: { // 須要以包的形式打包, 掛載window上 output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, }, chainWebpack: config => { config.plugin("html").tap(args => { args[0].minify = false; return args; }); } };
web2 -> vue.config.json
const packageName = require('./package.json').name; const port = 8083 module.exports = { outputDir: '../../ccqk/web2', publicPath: process.env.NODE_ENV === "production" ? '/ccqk/web2' : `//localhost:${port}`, devServer: { port, headers: { 'Access-Control-Allow-Origin': "*" } }, configureWebpack: { output: { library: `${packageName}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${packageName}`, }, }, chainWebpack: config => { config.plugin("html").tap(args => { args[0].minify = false; return args; }); } };
知識點注意解釋:
output.library
: 配置導出庫的名稱, 若是libraryTarget
設置爲'var'那麼主應用能夠直接用window訪問到。output.libraryTarget
:這裏設置爲umd
意思是在 AMD 或 CommonJS 的 require 以後可訪問。output.jsonpFunction
:webpack用來異步加載chunk的JSONP 函數。chainWebpack
: 用來修改webpack
的配置, 配置不進行壓縮。web2 -> router ->index.js
const router = new VueRouter({ mode: "history", base: process.env.NODE_ENV === "development" ? '/w2' : '/ccqk/w2', routes, });
這裏的隔離並非完美的, 想要了解更詳細的內容能夠看看個人往期文章帶你走進-\>影子元素(Shadow DOM)&瀏覽器原生組件開發(Web Components API ), 看完你就會徹底理解爲啥不完美。
在多應用場景下,每一個微應用的沙箱都是相互隔離的,也就是說每一個微應用對全局的影響都會侷限在微應用本身的做用域內。好比 A 應用在 window 上新增了個屬性 test,這個屬性只能在 A 應用本身的做用域經過 window.test 獲取到,主應用或者其餘微應用都沒法拿到這個變量。
我這裏就不秀源碼不扯大概念, 直接來乾貨原理, qiankun
會在子應用
激活的時候爲其賦予一個代理後的window
對象, 用戶操做這個window
對象的每一步都會被記錄下來, 方便在卸載子應用
時還原全局window
對象, 你要問如何替換的window
對象, 其實它是用with
與evel
來實現的替換, 而且好比jq
在執行前爲了提升效率都會把window對象傳入函數裏使用, 那麼這裏直接傳入代理window
就都ok了, 電腦越寫越卡就不扯太多了。
因此其實使用了微前端
技術方案是要付出必定的成本的, 代碼速度確定是有所下降。
只有最適合的組織模式, 沒有絕對的模式, 好比一個團隊想要試試微前端, 那麼其實若是你是個移動端的商城項目, 沒什麼必要使用微前端, 若是是個小中型的後臺系統, 也不是很推薦, 除非大家是一個長期維護而且模塊繁多, 或者是你想在這個項目的基礎上另啓一個項目作, 那麼微前端
將是一把神器。
此次就是這樣, 但願與你一塊兒進步。