微前端大賞二-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