<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
嗨害嗨,好久不見,我是海怪。
有一陣子沒寫文章了,今天來更一期關於 qiankun 找不到生命週期的問題。
剛開始給專案接入 qiankun 的時候,時不時就會報
Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry:
開發的時候一切正常,只有在打包釋出後才會報這個 Bug,讓人非常惱火。相信有不少同學也遇到過這個問題,今天就來分享一下這個問題的思考和解決方案吧。
首先,我們要知道為什麼 qiankun 載入微應用時要找生命週期勾點。
早在 qiankun 出來前,已經有一個微前端框架 single-spa 了。
它的思想是:無論 React、Vue 還是 Angular,專案打包最終的產物都是 JS。如果在 合適的時機 以 某種執行方式 去執行微應用的 JS 程式碼,大概就能實現 主-微 結構的微前端開發了。
這裡有兩個關鍵詞:合適的時機 和 執行方式。對於前者,single-spa 參考了單頁應用(Single Page Application)的思路,也希望用生命週期來管理微應用的 bootstrap, mount, update, unmount。而對於後者,則需要開發者自己實現執行微應用 JS 的方式。
總的來說,開發者需要在微應用的入口檔案 main.js
裡寫好生命週期實現:
export async function bootstrap() { // 啟動微應用 } export async function mount() { // 載入微應用 } export async function unmount() { // 解除安裝微應用 } export async function update() { // 更新微應用 }
single-spa 會自動劫持和監聽網頁地址 URL 的變化,在命中路由規則後,執行這些生命週期勾點,從而實現微應用的載入、解除安裝和更新。
但這就有一個嚴重的問題了:一般我們專案的入口檔案就只有:
React.render(<App/>, document.querySelector('#root'))
這要如何和主應用互動呢?而且裡面的樣式、全域性變數隔離又要怎麼實現呢?Webpack 又該如何改造呢?然而,single-spa 只提供了生命週期的排程,並沒有解決這一系列問題。
既然前人解決不了,後人則可以基於原有框架繼續優化,這就是 qiankun。
qiankun 和 single-spa 最大的不同是:qiankun 是 HTML 入口。它的原理如圖所示:
可以看到 qiankun 自己實現了一套通過 HTML 地址載入微應用的機制,但對於 “要在什麼時候執行 JS” 依然用了 single-spa 的生命週期排程能力。
這就是為什麼微應用的入口檔案 main.js
依然需要提供 single-spa 的生命週期回撥。
現在我們來聊聊如何找入口的問題。
對於一個簡單的 SPA 專案來說,一個 <div id="app"></div>
+ 一個 main.js
就夠了,入口很好找。
但真實專案往往會做分包拆包、自動注入 <script>
指令碼等操作,使得最終存取的 HTML 會有多個 <script>
標籤:
<script> // 初始化 XX SDK </script> <body> ... </body> <script src="你真實的入口 main.js"></script> <script src="ant-design.js"></script> <script> // 打包後自動注入的靜態資源 retry 邏輯 </script> <script> // 公司程式碼閘道器自動注入的 JS 邏輯 </script>
對於這樣複雜的情況,qiankun 提供了 2 種定位入口的方式:
entry
屬性的 <script entry src="main.js"></script>
<script>
作為入口第一種方法是最穩妥的,可以使用 html-webpack-inject-attributes-plugin 這個 Webpack 外掛,在打包的時候就給入口 main.js
新增 entry
屬性:
plugins = [ new HtmlWebpackPlugin(), new htmlWebpackInjectAttributesPlugin({ entry: "true", }) ]
不推薦大家使用最後一種方法來確定入口,這種方式很不可靠。 因為微應用 HTML 有可能在一些公司代理、閘道器層中被攔截,自動注入一些指令碼。
這樣最終拿到 HTML 裡最後的一個 <script>
就不是原先的入口 main.js
檔案了:
<script src="你真實的入口 main.js"></script> <script> // 自動注入的閘道器層的代理邏輯 </script>
上面兩種找入口方式並不能 100% 覆蓋所有情況,比如我就遇到過這樣的場景:
entry
屬性<script>
,無法將最後一個 JS 作為入口這下 qiankun 徹底找不到我的入口了。你總不能說:手寫一個 JS 指令碼,然後每次打包後用正則去 replace
HTML,以此來新增 entry
屬性吧???
當然不行!
曾經我在 qiankun 的檔案裡看到過這段設定:
module.exports = { webpack: (config) => { config.output.library = `microApp`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; config.output.globalObject = 'window'; return config; }, ... };
檔案裡說這是一個兜底找入口的邏輯:
但檔案沒有說這裡的細節,下面就來一起研究一下。
libraryTarget
指定打包成 umd 格式,也即最終模組會相容 CommonJS 和 AMD 等多種格式來進行匯出,最終 main.js
會是這樣:
(function webpackUniversalModuleDefinition(root, factory) { // CommonJS 匯出 if (typeof exports === 'object' && typeof module === 'object') module.exports = factory(require('lodash')); // AMD 匯出 else if (typeof define === 'function' && define.amd) define(['lodash'], factory); // 另一種匯出 else if (typeof exports === 'object') exports['microApp'] = factory(require('lodash')); // 關鍵點 else root['microApp'] = factory(root['_']); })(this, function (__WEBPACK_EXTERNAL_MODULE_1__) { // 入口檔案的內容 // ... return { bootstrap() {}, mount() {}, // ... } });
直接看最後一種匯出方式 root['microApp'] = factory(root['_'])
。Webpack 設定的 globalObject
和 library
正好對應了裡面的 root
以及 'microApp'
。
而且上面的函數 factory
則是入口檔案的執行函數,理論上當執行 factory()
後會返回模組的輸出。
最終的效果是:Webpack 會把入口檔案的輸出內容掛在到 globalObject[library]
/window['microApp']
上:
window['microApp'] = { // main.js 所 export 的內容 bootstrap() {}, mount() {}, unmount() {}, update() {}, // ... }
把入口的內容掛載到 window
上有什麼好處呢?我們來稍微看點原始碼:
// 發 Http 請求獲取 HTML, JS 執行器 const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts); // 執行微應用的 JS,但這裡不一定有入口 const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox); // 獲取入口匯出的生命週期 const { bootstrap, mount, unmount, update } = getLifecyclesFromExports( scriptExports, appName, global, sandboxContainer?.instance?.latestSetProp, );
上面的程式碼很簡單,就是獲取微應用 HTML 和 JS,試圖從裡面獲取生命週期,所以下面我們來看看 getLifecyclesFromExports
做了什麼:
function getLifecyclesFromExports( scriptExports: LifeCycles<any>, appName: string, global: WindowProxy, globalLatestSetProp?: PropertyKey | null, ) { // 如果在獲取微應用的 JS 時可以鎖定入口檔案,那麼直接返回 if (validateExportLifecycle(scriptExports)) { return scriptExports; } // 不用看 if (globalLatestSetProp) { const lifecycles = (<any>global)[globalLatestSetProp]; if (validateExportLifecycle(lifecycles)) { return lifecycles; } } // 獲取 globalObject[library] 裡的內容 const globalVariableExports = (global as any)[appName]; // 判斷 globalObject[library] 裡的內容是否為生命週期 // 如果是合法生命週期,那麼直接返回 if (validateExportLifecycle(globalVariableExports)) { return globalVariableExports; } throw new QiankunError(`You need to export lifecycle functions in ${appName} entry`); }
從上面可以看到,在 getLifecyclesFromExports
最後會試圖從 windowProxy[微應用名]
中拿匯出的生命週期。
這也是為什麼兜底找入口操作需要微應用設定 Webpack,同時主應用指定的微應用名要和 library
名要一樣。
注意:qiankun 會使用 JS 沙箱來隔離微應用的環境,所以這裡的 globalObject
並不是 window
而是微應用對應的沙箱物件 windowProxy
。
在微應用裡寫 console.log(window['microApp'])
或在主應用裡輸入 console.log(window.proxy['microApp'])
即可看到微應用匯出的生命週期:
因此,在主應用中註冊微應用的時候,微應用 name
最好要和 Webpack 的 output.library
一致,這樣才能命中 qiankun 的兜底邏輯。
最後總結一下,qiankun 要找入口是因為要從中拿到生命週期回撥,把它們給 single-spa 做排程。
qiankun 支援 2 種找入口的方式:
entry
屬性的 <script>
,找到就把這個 JS 作為入口如果這兩種方法都無法幫你正確定位入口,那麼你需要:
library
, libraryTarget
以及 globalObject
,把入口匯出的內容掛載到 window
上window[library]
找微應用的生命週期回撥,找到後依然能正常載入name
和 Webpack 的 output.library
設為一致,這樣才能命中第二步的邏輯最後還要注意的是,上面說到的 window
並不是全域性物件,而是 qiankun 提供的 JS 沙箱物件 windowProxy
。
以上就是qiankun 找不到入口問題徹底解決的詳細內容,更多關於qiankun 找不到入口的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45