Single-Spa微前端實踐

背景

在前端,每每由一個前端團隊建立並維護一個 Web 應用程序,使用 REST API 從後端服務獲取數據。這種方式若是作得好的話,它可以提供優秀的用戶體驗。但主要的缺點是單頁面應用(SPA)不能很好地擴展和部署。中後臺應用因爲其應用生命週期長 (動輒 3+ 年) ,因爲參與的人員、團隊的增多、變遷,等特色從一個普通應用演變成一個巨石應用 ( Frontend Monolith ) 後,隨之而來的應用不可維護的問題。這類問題在企業級 Web 應用中尤爲常見。javascript

在一個大公司裏,單前端團隊可能成爲一個發展瓶頸。前端

什麼是微前端架構?

首先,必須先了解什麼是微前端架構。 微前端架構是一種相似於微服務的架構,它將微服務的理念應用於瀏覽器端,即將 Web 應用由單一的單體應用轉變爲多個小型前端應用聚合爲一的應用。vue

Single spa什麼?

single spa是一個javascript庫,容許許多小應用程序在一個頁面應用程序中共存。這個https://single-spa.surge.sh/ 網站是一個演示應用程序,展現了什麼單一應用。 spa的理念是讓獨立的、獨立的應用程序組成一個完整的頁面。單spa並無長期依賴於單個框架和每一個特性,而是幫助您在開發新框架時採用它們。 簡單來講就是一個萬能的粘合劑,使用這個庫可讓你的應用可使用多個不一樣的技術棧(vue、react、angular等等)進行同步開發,最後使用一個公用的路由便可實現完美切換。 固然了,也可使用同樣的技術棧,分不一樣的團隊進行開發,只須要最後使用這個庫將其整合在一塊兒,設置不用的路由名稱便可。java

如何開始一個Single spa 項建立項目parent

第一步、新建項目

修改app.vue 文件 添加node

<div id=「vue」></div> 容器react

引入安裝包文件webpack

npm install single-spa --save –dios

Main.js 引入配置文件git

import './single-spa-config.js’github

  1. 建立子項目vue-child

引入裝包文件

npm install single-spa-vue --save -d

第二步、 父組件single-spa-config 文件

首先了解一下singleSpa 主要的API

  • registerApplication 定義:

registerApplication is the most important api your root config will use. Use this function to register any application within single-spa. --- 註冊子項目的方法

appName: 子項目名稱

applicationOrLoadingFn: 子項目註冊函數,用戶須要返回 single-spa 的生命週期對象。後面咱們會介紹single-spa的生命週期機制

activityFn: 回調函數入參 location 對象,能夠寫自定義匹配路由加載規則。 調用方法

singleSpa.registerApplication('appName’,
 () => System.import('appName’), 
location => location.pathname.startsWith('appName’)
)


複製代碼
  • singleSpa.start:啓動函數

修改父容器的single-spa-config 文件

// single-spa-config.js
  import * as singleSpa from 'single-spa'; //導入single-spa
  import axios from 'axios’
  
  /*runScript:一個promise同步方法。能夠代替建立一個script標籤,而後加載服務*/
  const runScript = async (url) => {
  return new Promise((resolve, reject) => {
  const script = document.createElement('script');
  script.src = url;
  script.onload = resolve;
  script.onerror = reject;
  const firstScript = document.getElementsByTagName('script')[0];
  firstScript.parentNode.insertBefore(script, firstScript);
  });
  };
  singleSpa.registerApplication( //註冊微前端服務
      'singleDemo', 
  async () => {
  await runScript('http://127.0.0.1:3000/js/chunk-vendors.js');
  await runScript('http://127.0.0.1:3000/js/app.js'); 
  return window.singleVue;
  },
  location => location.pathname.startsWith('/vue-antd') // 配置微前端模塊前綴
  );
  singleSpa.start(); // 啓動

複製代碼

第三步、註冊子組件

修改父項目的main.js

import Vue from 'vue'
  import App from './App.vue'
  import singleSpaVue from "single-spa-vue"
  
  const vueOptions = {
  el: "#vue",
  render: h => h(App),
  }
  // new Vue().$mount('#app')
  // singleSpaVue包裝一個vue微前端服務對象
  const vueLifecycles = singleSpaVue({
  Vue,
  appOptions: vueOptions
  });
  // 導出生命週期對象
  export const bootstrap = vueLifecycles.bootstrap; // 啓動時
  export const mount = vueLifecycles.mount; // 掛載時
  export const unmount = vueLifecycles.unmount; // 卸載時
  export default vueLifecycles;

複製代碼

single-spa生命週期

生命週期函數共有4個:bootstrap、mount、unmount、update。 生命週期能夠傳入 返回Promise的函數也能夠傳入 返回Promise函數的數組。

export default { 
    // app啓動 
    Bootstrap: [() => Promise.resolve()],
     // app掛載 
    Mount: [() => Promise.resolve()],
     // app卸載 
    Unmount: [() => Promise.resolve()], 
    // service更新,只有service纔可用
     update: [() => Promise.resolve()]
  }

複製代碼

第五步、自動加載 bundle和chunk.vendor

在上面父項目加載子項目的代碼中,咱們能夠看到。咱們要註冊一個子服務,須要一次性加載2個JS文件。若是須要加載的JS更多,甚至生產環境的 bundle 有惟一hash, 那咱們還能寫死文件名和列表嗎? 咱們的實現思路,就是讓子項目使用 stats-webpack-plugin 插件,每次打包後都輸出一個 只包含重要信息的manifest.json文件。父項目先ajax 請求 這個json文件,從中讀取出須要加載的js目錄,而後同步加載。

子項目添加vue.config.js ,並進行如下修改,生成manifest.json 文件

const StatsPlugin = require('stats-webpack-plugin');
  module.exports = {
  ….
    output: {
    library: "singleVue", // 導出名稱
    libraryTarget: "window", //掛載目標
    },
    /**** 添加開頭 ****/
    plugins: [
    new StatsPlugin('manifest.json', {
    chunkModules: false,
    entrypoints: true,
    source: false,
    chunks: false,
    modules: false,
    assets: false,
    children: false,
    exclude: [/node_modules/]
    })
    ]
    /**** 添加結尾 ****/
  ….
  };
  

複製代碼

固然,父項目中的單runScript已經沒法支持使用了,寫個getManifest方法,處理一下。

const getManifest = (url, bundle) => new Promise(async (resolve) => {
    const { data } = await axios.get(url);
    // eslint-disable-next-line no-console
    const { entrypoints, publicPath } = data;
    const assets = entrypoints[bundle].assets;
    for (let i = 0; i < assets.length; i++) {
    await runScript(publicPath + assets[i]).then(() => {
    if (i === assets.length - 1) {
    	resolve()
    }
    })
    }
  });

複製代碼

咱們首先ajax到 manifest.json 文件,解構出裏面的 entrypoints publicPath字段,遍歷出真實的js路徑,而後按照順序加載。

async () => {
  let singleVue = null;
  await getManifest('http://127.0.0.1:3000/manifest.json', 'app').then(() => {
  	singleVue = window.singleVue;
  });
  return singleVue; 
},

複製代碼

第六步、添加另外一個新項目

Vue-child 引入 ant UI Vue-child-two 引入 element

一樣可使用多個不一樣的技術棧(vue、react、angular等等)進行同步開發

Single spa 優勢

  • 敏捷性 - 獨立開發和更快的部署週期: 開發團隊能夠選擇本身的技術並及時更新技術棧。 一旦完成其中一項就能夠部署,而沒必要等待全部事情完畢。
  • 下降錯誤和迴歸問題的風險,相互之間的依賴性急劇降低。
  • 更簡單快捷的測試,每個小的變化沒必要再觸碰整個應用程序。
  • 更快交付客戶價值,有助於持續集成、持續部署以及持續交付。

參考文章

single-spa 文檔

微前端 single-spa

這多是你見過最完善的微前端解決方案!

single-spa微前端

Single-Spa + Vue Cli 微前端落地指南 (項目隔離遠程加載,自動引入)

項目完整地址:github.com/zhaiyy/sing…

相關文章
相關標籤/搜索