微前端实践--webpack5模块联邦
webpack5推出一個非常令人驚艷的功能叫module federation,中文叫模塊聯邦,它提供了一套在不同項目構建之間的調度、運行機制。它很像微前端,但又不限于此。本文結合案例介紹一下該特性的基本應用和原理。
類似微前端
微前端的概念相信大家都不陌生,其本質是服務的拆分與隔離,最大程度地減少服務之間的沖突與碰撞。webpack的模塊聯邦做的事情與此差不多。
不過,webpack模塊聯邦有更多的優點:
- 基于webpack生態,學習成本、實施成本低。畢竟大多數項目都在webpack
- 天生的工程化,npm各種包任你發揮
- 相關概念脈絡清晰易懂
- 配置簡單易上手,官方也提供了基于各種框架的版本
那么,如果你之前有過在前端實踐微服務的念頭,又由于這樣那樣的原因沒有實施,現在,你的機會來了!
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-1Ee2RO9E-1621275790600)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6d1d375b08441fa9e384d56259a4699~tplv-k3u1fbpfcp-zoom-1.image)]
三個概念
首先,要理解三個重要的概念:
- webpack構建。一個獨立項目通過webpack打包編譯而產生資源包。
- remote。一個暴露模塊供其他webpakc構建消費的webpack構建。
- host。一個消費其他remote模塊的webpack構建。
一言以蔽之,一個webpack構建可以是remote–即服務的提供方,也可以是host–即服務的消費方,也可以同時扮演服務提供者和服務消費者,完全看項目的架構。
host與remote兩個角色的依賴關系可用下圖表示:
需要指出的是,任何一個webpack構建既可以作為host消費方,也可以作為remote提供方,區別在于職責和webpack配置的不同。
案例實操
項目依賴關系介紹
一共有三個微應用:lib-app、component-app、main-app,角色分別是:
- lib-appas remote,暴露了兩個模塊react和react-dom
- component-app as remote and host,依賴lib-app,暴露了一些組件供main-app消費
- main-app as host,依賴lib-app和component-app
lib-app暴露模塊
//webpack.config.js module.exports = {//...省略plugins: [new ModuleFederationPlugin({name: "lib_app",filename: "remoteEntry.js",exposes: {"./react":"react","./react-dom":"react-dom"}})],//...省略 }編譯后的結果如下:
除去生成的map文件,有四個文件:main.js、remoteEntry.js、...react_index.js、...react-dom_index.js;
- 第一個是本項目的入口文件(該項目只是暴露接口,所以該文件為空)
- 第二個是遠程入口文件,其他webpack構建使用、訪問本項目暴露的模塊時,須通過它來加載
- 第三個和第四個是暴露的模塊,供其他項目消費
component-app的配置
依賴lib-app,暴露三個模塊組件Button、Dialog、Logo
//webpack.config.js module.exports = {//...省略plugins:[new ModuleFederationPlugin({name: "component_app",filename: "remoteEntry.js",exposes: {"./Button":"./src/Button.jsx","./Dialog":"./src/Dialog.jsx","./Logo":"./src/Logo.jsx"},remotes:{"lib-app":"lib_app@http://localhost:3000/remoteEntry.js"}}),] }三個暴露的組件:
//Button.jsx import React from 'lib-app/react'; export default function(){return <button style={{color: "#fff",backgroundColor: "#409eff",borderColor: "#409eff"}}>按鈕組件</button> } //Dialog.jsx import React from 'lib-app/react'; export default class Dialog extends React.Component {constructor(props) {super(props);}render() {if(this.props.visible){return (<div style={{position:"fixed",left:0,right:0,top:0,bottom:0,backgroundColor:"rgba(0,0,0,.3)"}}><button onClick={()=>this.props.switchVisible(false)} style={{position:"absolute",top:"10px",right:"10px"}}>X</button><div style={{ marginTop:"20%",textAlign:"center"}}><h1>What is your name ?</h1><input style={{fontSize:"18px",lineHeight:2}} type="text" /></div></div>);}else{return null;}} } // Logo.jsx import React from 'lib-app/react'; import pictureData from './MF.jpeg' export default function(){return <img src={pictureData} style={{width:"500px",borderRadius:"10px"}}/> }構建結果基本跟上一個類似。
需要說明的是,為了保證暴露的組件可以正常工作,需要在本地做測試,main.js 是測試的入口函數。該子項目下運行npm run start打開瀏覽器:localhost:3001可以看到組件正常工作:
并且打開控制臺網絡,react、react-dom模塊已經從本項目中分離:
main-app的配置
main-app依賴兩個項目lin-app、component-app。
///webpack.config.js module.exports = {//省略...plugins: [new ModuleFederationPlugin({name: "main_app",remotes:{"lib-app":"lib_app@http://localhost:3000/remoteEntry.js","component-app":"component_app@http://localhost:3001/remoteEntry.js"},}),new HtmlWebpackPlugin({template: "./public/index.html",})]//省略... };由于需要等待基礎模塊加載完畢,所以需要配置懶加載入口bootstrap.js.
- webpack打包入口文件
- bootstrap.js
- 根組件App.jsx
運行并打開瀏覽器http://localhost:3002:
查看控制臺,資源進行了很好的分離:
基本原理
這一節,我們從host的代碼著手,簡單分析這一切是如何交互、工作的。
程序從main.js里的一段代碼開始:
__webpack_require__.e("bootstrap_js").then(__webpack_require__.bind(__webpack_require__,"./bootstrap.js"))__webpack_require__.e("bootstrap_js")是加載id為bootstrap_js的chunk的所有依賴,返回一個promise.等一切依賴就緒,再獲取./bootstrap.js模塊并執行
這里是__webpack_require__.e的代碼:
__webpack_require__.e = (chunkId) => {return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {__webpack_require__.f[key](chunkId, promises);return promises;}, [])); };上面一段代碼做了一件事,遍歷__webpack_require__.f對象并依次執行對象里的成員函數,此時該對象有兩個成員:
{remotes:(chunkId, promises) => {//查找chunkId bootstrap_js對應的所有遠程模塊并加載var chunkMapping = {"bootstrap_js": ["webpack/container/remote/lib-app/react","webpack/container/remote/component-app/Button",//省略...]};var idToExternalAndNameMapping = {"webpack/container/remote/lib-app/react": ["default","./react","webpack/container/reference/lib-app"],"webpack/container/remote/component-app/Button": ["default","./Button","webpack/container/reference/component-app"],//...省略};},j:(chunkId,promises)=>{//負責加載chunkId對應的本地模塊} }綜上,bootstrap_js對應了兩個promises:
- 一個負責遠程依賴加載
- 另一個負責本地加載
等到所有依賴模塊加載完準備就緒,才會require模塊并執行。
當然,細節遠不止此。源碼里還有一些比較有趣的模塊,如__webpack_require__.l負責以script標簽的方式加載腳本、webpackJsonpCallback負責更新本地模塊的promsie狀態、__webpack_require__.f.j里遠程模塊的層級調用等,
囿于篇幅有限,無法作做過多深入介紹,有興趣的朋友,歡迎留言討論!
最后
本文所涉及的案例已經托管到Github。
如有任何疑惑,歡迎留言討論,如果本文對你有所幫助,可以點贊轉發給更多的人哦。
總結
以上是生活随笔為你收集整理的微前端实践--webpack5模块联邦的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机类智能问答助手论文,写论文不用愁,
- 下一篇: 设计模式之装饰模式详解(附应用举例实现)