微前端实践--webpack5模块联邦
webpack5推出一個(gè)非常令人驚艷的功能叫module federation,中文叫模塊聯(lián)邦,它提供了一套在不同項(xiàng)目構(gòu)建之間的調(diào)度、運(yùn)行機(jī)制。它很像微前端,但又不限于此。本文結(jié)合案例介紹一下該特性的基本應(yīng)用和原理。
類似微前端
微前端的概念相信大家都不陌生,其本質(zhì)是服務(wù)的拆分與隔離,最大程度地減少服務(wù)之間的沖突與碰撞。webpack的模塊聯(lián)邦做的事情與此差不多。
不過,webpack模塊聯(lián)邦有更多的優(yōu)點(diǎn):
- 基于webpack生態(tài),學(xué)習(xí)成本、實(shí)施成本低。畢竟大多數(shù)項(xiàng)目都在webpack
- 天生的工程化,npm各種包任你發(fā)揮
- 相關(guān)概念脈絡(luò)清晰易懂
- 配置簡單易上手,官方也提供了基于各種框架的版本
那么,如果你之前有過在前端實(shí)踐微服務(wù)的念頭,又由于這樣那樣的原因沒有實(shí)施,現(xiàn)在,你的機(jī)會來了!
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-1Ee2RO9E-1621275790600)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6d1d375b08441fa9e384d56259a4699~tplv-k3u1fbpfcp-zoom-1.image)]
三個(gè)概念
首先,要理解三個(gè)重要的概念:
- webpack構(gòu)建。一個(gè)獨(dú)立項(xiàng)目通過webpack打包編譯而產(chǎn)生資源包。
- remote。一個(gè)暴露模塊供其他webpakc構(gòu)建消費(fèi)的webpack構(gòu)建。
- host。一個(gè)消費(fèi)其他remote模塊的webpack構(gòu)建。
一言以蔽之,一個(gè)webpack構(gòu)建可以是remote–即服務(wù)的提供方,也可以是host–即服務(wù)的消費(fèi)方,也可以同時(shí)扮演服務(wù)提供者和服務(wù)消費(fèi)者,完全看項(xiàng)目的架構(gòu)。
host與remote兩個(gè)角色的依賴關(guān)系可用下圖表示:
需要指出的是,任何一個(gè)webpack構(gòu)建既可以作為host消費(fèi)方,也可以作為remote提供方,區(qū)別在于職責(zé)和webpack配置的不同。
案例實(shí)操
項(xiàng)目依賴關(guān)系介紹
一共有三個(gè)微應(yīng)用:lib-app、component-app、main-app,角色分別是:
- lib-appas remote,暴露了兩個(gè)模塊react和react-dom
- component-app as remote and host,依賴lib-app,暴露了一些組件供main-app消費(fèi)
- 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"}})],//...省略 }編譯后的結(jié)果如下:
除去生成的map文件,有四個(gè)文件:main.js、remoteEntry.js、...react_index.js、...react-dom_index.js;
- 第一個(gè)是本項(xiàng)目的入口文件(該項(xiàng)目只是暴露接口,所以該文件為空)
- 第二個(gè)是遠(yuǎn)程入口文件,其他webpack構(gòu)建使用、訪問本項(xiàng)目暴露的模塊時(shí),須通過它來加載
- 第三個(gè)和第四個(gè)是暴露的模塊,供其他項(xiàng)目消費(fèi)
component-app的配置
依賴lib-app,暴露三個(gè)模塊組件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"}}),] }三個(gè)暴露的組件:
//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"}}/> }構(gòu)建結(jié)果基本跟上一個(gè)類似。
需要說明的是,為了保證暴露的組件可以正常工作,需要在本地做測試,main.js 是測試的入口函數(shù)。該子項(xiàng)目下運(yùn)行npm run start打開瀏覽器:localhost:3001可以看到組件正常工作:
并且打開控制臺網(wǎng)絡(luò),react、react-dom模塊已經(jīng)從本項(xiàng)目中分離:
main-app的配置
main-app依賴兩個(gè)項(xiàng)目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",})]//省略... };由于需要等待基礎(chǔ)模塊加載完畢,所以需要配置懶加載入口bootstrap.js.
- webpack打包入口文件
- bootstrap.js
- 根組件App.jsx
運(yùn)行并打開瀏覽器http://localhost:3002:
查看控制臺,資源進(jìn)行了很好的分離:
基本原理
這一節(jié),我們從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的所有依賴,返回一個(gè)promise.等一切依賴就緒,再獲取./bootstrap.js模塊并執(zhí)行
這里是__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對象并依次執(zhí)行對象里的成員函數(shù),此時(shí)該對象有兩個(gè)成員:
{remotes:(chunkId, promises) => {//查找chunkId bootstrap_js對應(yīng)的所有遠(yuǎn)程模塊并加載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)=>{//負(fù)責(zé)加載chunkId對應(yīng)的本地模塊} }綜上,bootstrap_js對應(yīng)了兩個(gè)promises:
- 一個(gè)負(fù)責(zé)遠(yuǎn)程依賴加載
- 另一個(gè)負(fù)責(zé)本地加載
等到所有依賴模塊加載完準(zhǔn)備就緒,才會require模塊并執(zhí)行。
當(dāng)然,細(xì)節(jié)遠(yuǎn)不止此。源碼里還有一些比較有趣的模塊,如__webpack_require__.l負(fù)責(zé)以script標(biāo)簽的方式加載腳本、webpackJsonpCallback負(fù)責(zé)更新本地模塊的promsie狀態(tài)、__webpack_require__.f.j里遠(yuǎn)程模塊的層級調(diào)用等,
囿于篇幅有限,無法作做過多深入介紹,有興趣的朋友,歡迎留言討論!
最后
本文所涉及的案例已經(jīng)托管到Github。
如有任何疑惑,歡迎留言討論,如果本文對你有所幫助,可以點(diǎn)贊轉(zhuǎn)發(fā)給更多的人哦。
總結(jié)
以上是生活随笔為你收集整理的微前端实践--webpack5模块联邦的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机类智能问答助手论文,写论文不用愁,
- 下一篇: HTML与CSS基础笔试和期末题库