vue項目'微前端'qiankun.js的實戰攻略

vue項目'微前端'qiankun.js的實戰攻略

本篇介紹

     關於微前端的大概念你們應該聽過太多了, 這裏我就大白話闡述一下, 好比咱們新建三個vue工程a、b、c, a負責導航模塊, b負責列表頁面, c負責詳情頁面, 而後咱們能夠經過微前端技術把他們組合在一塊兒造成一個完整項目css

    本篇文章不會講述很深刻的細節操做, 但會講述項目搭建到項目上線的全環節, 若是你這些都會了那麼其餘的問題就不是太大阻礙了。html

     必定要明確一點, 微前端在不少場景都是不適用的, 千萬不要強行使用這門技術, 在本篇文章裏我會一點點的闡述什麼場景不適用以及爲何不適用。
    前端

1. 微前端qiankun.js簡簡簡簡介

     qiankun.js是當前最出色的一款微前端實現庫, 他幫咱們實現了css隔離js隔離項目關聯等功能, 文章的後面都會有所涉及的如今就讓咱們開始實戰吧。vue

2. 本次的項目結構一主二附

     一共三個vue項目, 第一個container項目負責導航模塊, 第二個web1第三個web2, container項目裏面有個subapp文件夾, 裏面存放着web1 & web2兩個項目, 這樣之後咱們能夠隨便添加web3,web4....都放在subapp文件夾便可。
image.pngnode

3. 安裝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>

此時的頁面是這個樣子的:
image.pngvue-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();

參數解析:

  1. name: 微應用的名稱,微應用之間必須確保惟一, 方便後期區分項目來源。
  2. entry: 微應用的入口也就是當知足條件的時候, 我要激活的目標微應用的地址(也能夠是其餘形式好比html片斷, 但本篇主要講url地址這種形式)。
  3. container: 激活微應用的時候咱們要把這個目標微應用放在哪裏, 上面代碼的意思就是把激活的微應用放在id爲'box'的元素裏面。
  4. activeRule:微應用的激活規則(有不少種寫法甚至是函數形式), 上面代碼就是當路由地址爲/w1時激活。

4. 配置子項目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>

web1vue.config.js

module.exports = {
  devServer: {
        port: 8082, // web2裏面改爲8083
    },
}

     如今咱們要分別進入container, web1web2裏面運行yarn serve命令, 可是這樣運行命令真的好麻煩, 接下來我就介紹一種更工程化的寫法。

5. 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命令也寫了。

其餘擴展玩法:

  1. serial: 多個命令按排列順序執行,例如:npm-run-all --serial clean lint build:**
  2. continue-on-error: 是否忽略錯誤,添加此參數 npm-run-all 會自動退出出錯的命令,繼續運行正常的
  3. race: 添加此參數以後,只要有一個命令運行出錯,那麼 npm-run-all 就會結束掉所有的命令
上述準備工做都作完了, 咱們能夠啓動項目試試了。

6. 請求子項目居然跨域

     運行起來會發現報錯了:
image.png

須要在web1web2兩個項目vue.config.js里加上以下配置就不報錯了:

devServer: {
        port: 8082,
        // 因爲會產生跨域, 因此加上
        headers: {
            'Access-Control-Allow-Origin': "*"
        }
    },

之因此會有這種跨域的報錯是由於qiankun內部使用fetch請求的資源, 當前畢竟是啓動了三個不一樣的node服務, 外部html頁面請求其資源仍是會跨域的, 因此須要設置容許全部源。

咱們爲web1web2設置一下樣式, 結果以下:

image.png
image.png
image.png

  • 但這些僅僅是個開始而已, 由於各類問題立刻紛至沓來。

7. 區分在是否在主應用內

     咱們有時候須要單獨開發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()
}

逐句解釋:

  1. window.__POWERED_BY_QIANKUN__: 當前環境是否爲qiankun.js提供。
  2. window.__webpack_public_path__: 等同於 output.publicPath 配置選項, 可是他是動態的。
  3. window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__:qiankun.js注入的公共路徑。

判斷當前環境爲單獨開發的環境就直接執行render方法, 若是是qiankun的容器內, 那麼須要設置publicPath, 由於qiankun須要把每一個子應用都區分開, 而後引入容器項目內, 這樣咱們就能夠單獨開發web1項目了。

8. 子應用路由跳轉與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裏面可能會有以下錯誤:
image.png

     這是因爲動態設置的publicPath並不能知足加載異步組件chunk, 須要咱們以下配置一番:(web2->vue.config.js)

publicPath: `//localhost: 8083`

就能夠正常加載這個頁面了:
image.png

而且此時直接刷新當前url也還能夠正確顯示about頁面。

9. 區分開發與打包

     前面幾條說的都是開發相關的設置, 這裏咱們要開始介紹打包的配置了, 這裏會介紹原理與作法, 不會作的很細因此具體的項目開發仍是要好好的封裝一番。

我這裏先把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的文件夾, 裏面有三個包containerweb1web2

第一步: 確立打包路徑
  • 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;
        });
    }
    };

    知識點注意解釋:

  1. output.library: 配置導出庫的名稱, 若是libraryTarget設置爲'var'那麼主應用能夠直接用window訪問到。
  2. output.libraryTarget:這裏設置爲umd意思是在 AMD 或 CommonJS 的 require 以後可訪問。
  3. output.jsonpFunction:webpack用來異步加載chunk的JSONP 函數。
  4. chainWebpack: 用來修改webpack的配置, 配置不進行壓縮。
第二步: 配置路由路徑
  • web2 -> router ->index.js

    const router = new VueRouter({
    mode: "history",
    base: process.env.NODE_ENV === "development" ? '/w2' : '/ccqk/w2',
    routes,
    });

10. css隔離

image.png
     這裏的隔離並非完美的, 想要了解更詳細的內容能夠看看個人往期文章帶你走進-\>影子元素(Shadow DOM)&瀏覽器原生組件開發(Web Components API ), 看完你就會徹底理解爲啥不完美。

11. js隔離

     在多應用場景下,每一個微應用的沙箱都是相互隔離的,也就是說每一個微應用對全局的影響都會侷限在微應用本身的做用域內。好比 A 應用在 window 上新增了個屬性 test,這個屬性只能在 A 應用本身的做用域經過 window.test 獲取到,主應用或者其餘微應用都沒法拿到這個變量。

     我這裏就不秀源碼不扯大概念, 直接來乾貨原理, qiankun會在子應用激活的時候爲其賦予一個代理後的window對象, 用戶操做這個window對象的每一步都會被記錄下來, 方便在卸載子應用時還原全局window對象, 你要問如何替換的window對象, 其實它是用withevel來實現的替換, 而且好比jq在執行前爲了提升效率都會把window對象傳入函數裏使用, 那麼這裏直接傳入代理window就都ok了, 電腦越寫越卡就不扯太多了。

     因此其實使用了微前端技術方案是要付出必定的成本的, 代碼速度確定是有所下降。

12. 康威定律

  • 第必定律 組織溝通方式會經過系統設計表達出來。
  • 第二定律 時間再多一件事情也不可能作的完美,但總有時間作完一件事情。
  • 第三定律 線型系統和線型組織架構間有潛在的異質同態特性。
  • 第四定律 大的系統組織老是比小系統更傾向於分解。

     只有最適合的組織模式, 沒有絕對的模式, 好比一個團隊想要試試微前端, 那麼其實若是你是個移動端的商城項目, 沒什麼必要使用微前端, 若是是個小中型的後臺系統, 也不是很推薦, 除非大家是一個長期維護而且模塊繁多, 或者是你想在這個項目的基礎上另啓一個項目作, 那麼微前端將是一把神器。

end.

此次就是這樣, 但願與你一塊兒進步。

相關文章
相關標籤/搜索