1. 程式人生 > >微前端大賞二-singlespa實踐

微前端大賞二-singlespa實踐

微前端大賞二-singlespa實踐

  • 微前端大賞二-singlespa實踐
      • 介紹singleSpa
      • singleSpa核心邏輯
    • 搭建環境
      • vue main
      • react child
      • 生命週期
    • 結論
    • 參考文章

 

介紹singleSpa

singleSpa是一個javascript庫
它可以讓很多小頁面、小的元件、不同架構的前端元件在一個頁面應用程式中共存。

這裡有一個演示: (https://single-spa.surge.sh/)

這個庫可以讓你的應用可以使用多個不同的技術棧(vue、react、angular等等),這樣我們就可以做同步開發,最後再使用一個公用的路由即可實現路由完美切換。 也可以使用一樣的技術棧,分不同的團隊進行開發,只需要最後使用這個庫把它們整合在一起,設定不用的路由名稱就可以了。

優點:

  • 敏捷
    獨立開發和更快的部署週期: 開發團隊可以選擇自己的技術並及時更新技術棧。 一旦完成其中一項就可以部署,而不必等待所有事情完畢。

  • 風險下降
    降低錯誤和迴歸問題的風險,相互之間的依賴性急劇下降。

  • 更小單元
    更簡單快捷的測試,每一個小的變化不必再觸碰整個應用程式。

  • 持續交付
    更快交付客戶價值,有助於持續整合、持續部署以及持續交付。

缺點:

  • 配置複雜
    singlespa相對來說配置複雜,當然我們還有更簡單一點的qiankun,也可以基於singlespa封裝一套更適合自己的框架。
  • 一定的資源浪費
    由於核心邏輯還是在於請求manifest,拿到js檔案後執行渲染,這個過程不可避免會產生一些冗餘,對於C端的應用來說,這個問題比較致命,當然,對於B端來說,這個是可以接受的,在可控制的範圍之內

 

singleSpa核心邏輯

幾張圖可以解決singleSpa的核心邏輯

第一張圖,很顯然,第一步,在我們的webpack應用裡生成一個manifest.json檔案,這個檔案內容差不多如下:

 

{
  "files": {
    "static/js/0.chunk.js": "/static/js/0.chunk.js",
    "static/js/0.chunk.js.map": "/static/js/0.chunk.js.map",
    "static/js/1.chunk.js": "/static/js/1.chunk.js",
    "static/js/1.chunk.js.map": "/static/js/1.chunk.js.map",
    "main.js": "/static/js/main.chunk.js",
    "main.js.map": "/static/js/main.chunk.js.map",
    "runtime-main.js": "/static/js/bundle.js",
    "runtime-main.js.map": "/static/js/bundle.js.map",
    "index.html": "/index.html",
    "static/media/logo.svg": "/static/media/logo.103b5fa1.svg"
  },
  "entrypoints": [
    "static/js/bundle.js",
    "static/js/0.chunk.js",
    "static/js/main.chunk.js"
  ]
}

 

關鍵點在 entrypoints 這個屬性,我們可以通過manifest拿到專案的依賴表並可以使用script標籤動態加載出來,這個時候我們就可以實現動態載入不同的微前端應用了。

 

 

第二張圖,我畫出了更加具體的,singlespa在渲染過程中的核心邏輯
1、 首先我們有 main(主app) child(子app),主app只有一個,子app可以有多個
2、 其次,主app上一般我們可以在index.html裡面,寫多幾個空間,也就是多幾個div
例如:

 

<div id=”react-app”></div>
<div id=”vue-app”></div>

 

3、然後,在我們的child上,我們要用webpack外掛,生成一個帶有所有需要載入的依賴檔案的manifest.json
4、主應用去載入這個manifest.json,獲取到具體的js,使用script標籤把它放到主應用上,進行渲染

至此我們就可以完全搞清楚,為什麼singlespa這麼神奇了,接下來讓我們搭建一個簡易版的singlespa

 

搭建環境

 

vue main

由於我們需要使用webpack配置,而最新版本的vue-cli預設只有babel,我們用這個步驟來安裝一個vue版本的main
1、裝包

 

 npm install @vue/cli @vue/cli-init  -g

 

2、建立一個專案

 

vue init webpack demo-single

 

3、cd demo-single

4、裝包

 

npm i single-spa single-spa-vue axios --save

 

5、在src目錄建立一個singlespa配置檔案 single-spa-config.js

 

    // 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);
        });
    };

    const getManifest = (url, bundle) => new Promise(async (resolve) => {
        const { data } = await axios.get(url);
        // eslint-disable-next-line no-console
        const { entrypoints } = data;

        for (let i = 0; i < entrypoints.length; i++) {
            await runScript('http://127.0.0.1:3000/' + entrypoints[i]).then(() => {
                if (i === entrypoints.length - 1) {
                    resolve()
                }
            })
        }
    });

    singleSpa.registerApplication( //註冊微前端服務
        'singleDemoVue', async () => {
            let singleVue = null;
            await getManifest('http://127.0.0.1:3000/asset-manifest.json').then(() => {
                singleVue = window.singleReact;
            });
            return singleVue;    
        },
        location => location.pathname.startsWith('/react') // 配置字首
    );

    singleSpa.start(); // 啟動

 

注: 可以看到,runScript就是個建立script標籤的方法,getManifest是一個簡單的獲取manifest並建立script的方法

6、在main.js裡引入這個檔案

 

import './single-spa-config'

7、執行

npm run dev

 

最終得到這樣一個工程

 

 

這樣我們就完成了一個入口的配置,當然它還很簡單,更復雜的操作我們應該放在具體的工程上去做

 

react child

上面的程式碼可以看到,我們register了一個react應用 http://127.0.0.1:3000/asset-manifest.json 並且訪問了它的manifest檔案,現在我們需要建立一個react子應用,也是直接通過幾個步驟來完成,我們使用create-react-app來快速搭建:

1、裝包

 

npm install create-react-app -g

 

2、建立

 

npx create-react-app my-app

 

3、建立完成後,注意我們需要對webpack做一點修改,預設create-react-app會有一個git本地分支,讓我們先提交到本地倉庫一下

 

git status
git add ./
git commit -m ttt

 

4、拿到webpack配置檔案,create-react-app預設隱藏了webpack配置檔案

 

yarn eject 或 npm run eject

 

5、修改webpack檔案
修改 /config/webpack.config.js 在output增加:

 

output: {
    ... 這裡忽略了原有的
    library: 'singleReact',
    libraryTarget: 'window'
}

 

修改 /scripts/start.js檔案,在const devServer = new ...這個地方,增加一個header的設定:

 

const devServer = new WebpackDevServer(compiler, {
      ...serverConfig,
      // 這裡上增加的header設定
      headers: {
        'Access-Control-Allow-Origin': '*',
      }
  
    });

 

6、修改src/index.js
一個是要把root改為動態渲染,一個是註冊生命週期

 

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import singleSpaReact, {SingleSpaContext} from 'single-spa-react';

const rootComponent = () => {
  ReactDOM.render(
      <React.StrictMode>
        <App />
      </React.StrictMode>
    ,
    document.getElementById('react-root')
  );
}

// ReactDOM.render(
//   ,
//   document.getElementById('root')
// );


const reactLifecycles = singleSpaReact({
  React,
  ReactDOM,
  rootComponent,
  errorBoundary(err, info, props) {
    // https://reactjs.org/docs/error-boundaries.html
    console.error(err)
    return (
      <div>This renders when a catastrophic error occurs</div>
    );
  },
});
export const bootstrap = reactLifecycles.bootstrap;
export const mount = reactLifecycles.mount;
export const unmount = reactLifecycles.unmount;


// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

7、執行
npm run start

8、在main的vue那裡,訪問/react 你會看到下面有一個react渲染和vue的一起出現,大功告成

 

生命週期

生命週期函式共有4個:bootstrap、mount、unmount、update。 生命週期可以傳入 返回Promise的函式也可以傳入 返回Promise函式的陣列。
引用一個大佬完整的說明, 非常的詳細:
https://github.com/YataoZhang/my-single-spa/issues/4

 

結論

single spa可以給我們提供一整套方案,去搭建一套微前端整合框架,但它並不是一個開箱即用的封裝,它有很多的坑等著我們去踩。
一般情況下,我們選擇使用qiankun,它的封裝程度更好,api更加友好一些。待積攢足夠多的使用經驗,可以考慮自研一套自己的微前端框架,增加整體的前端研發效率。
下節我將給大家帶來qiankun對singlespa的封裝,在具體應用中的實踐。待完結框架篇後,我們可以再深入探究singlespa的實現原理以及各種概念。

 

參考文章

single-spa 文件: https://single-spa.js.org/docs/getting-started-overview/

微前端 single-spa: https://juejin.cn/post/6844903896884707342

這可能是你見過最完善的微前端解決方案!: https://www.infoq.cn/article/o6GxRD9iHQOplKICiDDU

single-spa微前端: http://www.soulapp.tech/2019/09/25/single-spa微前端/

Single-Spa + Vue Cli 微前端落地指南 (專案隔離遠端載入,自動引入) : https://juejin.cn/post/6844904025565954055