欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

qiankun 找不到入口問(wèn)題徹底解決

 更新時(shí)間:2022年08月30日 14:56:02   作者:寫(xiě)代碼的海怪  
這篇文章主要為大家介紹了qiankun 找不到入口問(wèn)題徹底解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

嗨害嗨,好久不見(jiàn),我是海怪。

有一陣子沒(méi)寫(xiě)文章了,今天來(lái)更一期關(guān)于 qiankun 找不到生命周期的問(wèn)題。

剛開(kāi)始給項(xiàng)目接入 qiankun 的時(shí)候,時(shí)不時(shí)就會(huì)報(bào)

Application died in status LOADING_SOURCE_CODE: You need to export the functional lifecycles in xxx entry:

開(kāi)發(fā)的時(shí)候一切正常,只有在打包發(fā)布后才會(huì)報(bào)這個(gè) Bug,讓人非常惱火。相信有不少同學(xué)也遇到過(guò)這個(gè)問(wèn)題,今天就來(lái)分享一下這個(gè)問(wèn)題的思考和解決方案吧。

為什么要找生命周期

首先,我們要知道為什么 qiankun 加載微應(yīng)用時(shí)要找生命周期鉤子。

早在 qiankun 出來(lái)前,已經(jīng)有一個(gè)微前端框架 single-spa 了。

它的思想是:無(wú)論 React、Vue 還是 Angular,項(xiàng)目打包最終的產(chǎn)物都是 JS。如果在 合適的時(shí)機(jī) 以 某種執(zhí)行方式 去執(zhí)行微應(yīng)用的 JS 代碼,大概就能實(shí)現(xiàn) 主-微 結(jié)構(gòu)的微前端開(kāi)發(fā)了。

這里有兩個(gè)關(guān)鍵詞:合適的時(shí)機(jī) 和 執(zhí)行方式。對(duì)于前者,single-spa 參考了單頁(yè)應(yīng)用(Single Page Application)的思路,也希望用生命周期來(lái)管理微應(yīng)用的 bootstrap, mount, update, unmount。而對(duì)于后者,則需要開(kāi)發(fā)者自己實(shí)現(xiàn)執(zhí)行微應(yīng)用 JS 的方式。

總的來(lái)說(shuō),開(kāi)發(fā)者需要在微應(yīng)用的入口文件 main.js 里寫(xiě)好生命周期實(shí)現(xiàn):

export async function bootstrap() {
  // 啟動(dòng)微應(yīng)用
}
export async function mount() {
  // 加載微應(yīng)用
}
export async function unmount() {
  // 卸載微應(yīng)用
}
export async function update() {
  // 更新微應(yīng)用
}

single-spa 會(huì)自動(dòng)劫持和監(jiān)聽(tīng)網(wǎng)頁(yè)地址 URL 的變化,在命中路由規(guī)則后,執(zhí)行這些生命周期鉤子,從而實(shí)現(xiàn)微應(yīng)用的加載、卸載和更新。

但這就有一個(gè)嚴(yán)重的問(wèn)題了:一般我們項(xiàng)目的入口文件就只有:

React.render(<App/>, document.querySelector('#root'))

這要如何和主應(yīng)用交互呢?而且里面的樣式、全局變量隔離又要怎么實(shí)現(xiàn)呢?Webpack 又該如何改造呢?然而,single-spa 只提供了生命周期的調(diào)度,并沒(méi)有解決這一系列問(wèn)題。

既然前人解決不了,后人則可以基于原有框架繼續(xù)優(yōu)化,這就是 qiankun。

qiankun 和 single-spa 最大的不同是:qiankun 是 HTML 入口。它的原理如圖所示:

可以看到 qiankun 自己實(shí)現(xiàn)了一套通過(guò) HTML 地址加載微應(yīng)用的機(jī)制,但對(duì)于 “要在什么時(shí)候執(zhí)行 JS” 依然用了 single-spa 的生命周期調(diào)度能力。

這就是為什么微應(yīng)用的入口文件 main.js 依然需要提供 single-spa 的生命周期回調(diào)。

如何找入口

現(xiàn)在我們來(lái)聊聊如何找入口的問(wèn)題。

對(duì)于一個(gè)簡(jiǎn)單的 SPA 項(xiàng)目來(lái)說(shuō),一個(gè) <div id="app"></div> + 一個(gè) main.js 就夠了,入口很好找。

但真實(shí)項(xiàng)目往往會(huì)做分包拆包、自動(dòng)注入 <script> 腳本等操作,使得最終訪問(wèn)的 HTML 會(huì)有多個(gè) <script> 標(biāo)簽:

<script>
  // 初始化 XX SDK
</script>
<body>
  ...
</body>
<script src="你真實(shí)的入口 main.js"></script>
<script src="ant-design.js"></script>
<script>
  // 打包后自動(dòng)注入的靜態(tài)資源 retry 邏輯
</script>
<script>
  // 公司代碼網(wǎng)關(guān)自動(dòng)注入的 JS 邏輯
</script>

對(duì)于這樣復(fù)雜的情況,qiankun 提供了 2 種定位入口的方式:

  • 找 帶有 entry 屬性的 <script entry src="main.js"></script>
  • 如果找不到,那么把 最后一個(gè) <script> 作為入口

第一種方法是最穩(wěn)妥的,可以使用 html-webpack-inject-attributes-plugin 這個(gè) Webpack 插件,在打包的時(shí)候就給入口 main.js 添加 entry 屬性:

plugins = [
    new HtmlWebpackPlugin(),
    new htmlWebpackInjectAttributesPlugin({
        entry: "true",
    })
]

不推薦大家使用最后一種方法來(lái)確定入口,這種方式很不可靠。 因?yàn)槲?yīng)用 HTML 有可能在一些公司代理、網(wǎng)關(guān)層中被攔截,自動(dòng)注入一些腳本。

這樣最終拿到 HTML 里最后的一個(gè) <script> 就不是原先的入口 main.js 文件了:

<script src="你真實(shí)的入口 main.js"></script>
<script>
  // 自動(dòng)注入的網(wǎng)關(guān)層的代理邏輯
</script>

兜底找入口

上面兩種找入口方式并不能 100% 覆蓋所有情況,比如我就遇到過(guò)這樣的場(chǎng)景:

  • 腳手架封裝得太黑盒,導(dǎo)致添加插件不生效,無(wú)法在打包時(shí)注入 entry 屬性
  • 測(cè)試環(huán)境中,代理工具會(huì)自動(dòng)往 HTML 插入 <script>,無(wú)法將最后一個(gè) JS 作為入口

這下 qiankun 徹底找不到我的入口了。你總不能說(shuō):手寫(xiě)一個(gè) JS 腳本,然后每次打包后用正則去 replace HTML,以此來(lái)添加 entry 屬性吧???

當(dāng)然不行!

曾經(jīng)我在 qiankun 的文檔里看到過(guò)這段配置:

module.exports = {
  webpack: (config) => {
    config.output.library = `microApp`;
    config.output.libraryTarget = 'umd';
    config.output.jsonpFunction = `webpackJsonp_${name}`;
    config.output.globalObject = 'window';
    return config;
  },
  ...
};

文檔里說(shuō)這是一個(gè)兜底找入口的邏輯:

但文檔沒(méi)有說(shuō)這里的細(xì)節(jié),下面就來(lái)一起研究一下。

微應(yīng)用的 Webpack 配置

libraryTarget 指定打包成 umd 格式,也即最終模塊會(huì)兼容 CommonJS 和 AMD 等多種格式來(lái)進(jìn)行導(dǎo)出,最終 main.js 會(huì)是這樣:

(function webpackUniversalModuleDefinition(root, factory) {
  // CommonJS 導(dǎo)出
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory(require('lodash'));
  // AMD 導(dǎo)出
  else if (typeof define === 'function' && define.amd)
    define(['lodash'], factory);
  // 另一種導(dǎo)出
  else if (typeof exports === 'object')
    exports['microApp'] = factory(require('lodash'));
  // 關(guān)鍵點(diǎn)
  else root['microApp'] = factory(root['_']);
})(this, function (__WEBPACK_EXTERNAL_MODULE_1__) {
  // 入口文件的內(nèi)容
  // ...
  return {
    bootstrap() {},
    mount() {},
    // ...
  }
});

直接看最后一種導(dǎo)出方式 root['microApp'] = factory(root['_'])。Webpack 配置的 globalObjectlibrary 正好對(duì)應(yīng)了里面的 root 以及 'microApp'。

而且上面的函數(shù) factory 則是入口文件的執(zhí)行函數(shù),理論上當(dāng)執(zhí)行 factory() 后會(huì)返回模塊的輸出。

最終的效果是:Webpack 會(huì)把入口文件的輸出內(nèi)容掛在到 globalObject[library]/window['microApp'] 上:

window['microApp'] = {
  // main.js 所 export 的內(nèi)容
  bootstrap() {},
  mount() {},
  unmount() {},
  update() {},
  // ...
}

主應(yīng)用的兜底邏輯

把入口的內(nèi)容掛載到 window 上有什么好處呢?我們來(lái)稍微看點(diǎn)源碼:

// 發(fā) Http 請(qǐng)求獲取 HTML, JS 執(zhí)行器
const { template, execScripts, assetPublicPath } = await importEntry(entry, importEntryOpts);
// 執(zhí)行微應(yīng)用的 JS,但這里不一定有入口
const scriptExports: any = await execScripts(global, sandbox && !useLooseSandbox);
// 獲取入口導(dǎo)出的生命周期
const { bootstrap, mount, unmount, update } = getLifecyclesFromExports(
  scriptExports,
  appName,
  global,
  sandboxContainer?.instance?.latestSetProp,
);

上面的代碼很簡(jiǎn)單,就是獲取微應(yīng)用 HTML 和 JS,試圖從里面獲取生命周期,所以下面我們來(lái)看看 getLifecyclesFromExports 做了什么:

function getLifecyclesFromExports(
  scriptExports: LifeCycles<any>,
  appName: string,
  global: WindowProxy,
  globalLatestSetProp?: PropertyKey | null,
) {
  // 如果在獲取微應(yīng)用的 JS 時(shí)可以鎖定入口文件,那么直接返回
  if (validateExportLifecycle(scriptExports)) {
    return scriptExports;
  }
  // 不用看
  if (globalLatestSetProp) {
    const lifecycles = (<any>global)[globalLatestSetProp];
    if (validateExportLifecycle(lifecycles)) {
      return lifecycles;
    }
  }
  // 獲取 globalObject[library] 里的內(nèi)容
  const globalVariableExports = (global as any)[appName];
  // 判斷 globalObject[library] 里的內(nèi)容是否為生命周期
  // 如果是合法生命周期,那么直接返回
  if (validateExportLifecycle(globalVariableExports)) {
    return globalVariableExports;
  }
  throw new QiankunError(`You need to export lifecycle functions in ${appName} entry`);
}

從上面可以看到,在 getLifecyclesFromExports 最后會(huì)試圖從 windowProxy[微應(yīng)用名] 中拿導(dǎo)出的生命周期。

這也是為什么兜底找入口操作需要微應(yīng)用配置 Webpack,同時(shí)主應(yīng)用指定的微應(yīng)用名要和 library 名要一樣。

注意:qiankun 會(huì)使用 JS 沙箱來(lái)隔離微應(yīng)用的環(huán)境,所以這里的 globalObject 并不是 window 而是微應(yīng)用對(duì)應(yīng)的沙箱對(duì)象 windowProxy。

在微應(yīng)用里寫(xiě) console.log(window['microApp']) 或在主應(yīng)用里輸入 console.log(window.proxy['microApp']) 即可看到微應(yīng)用導(dǎo)出的生命周期:

因此,在主應(yīng)用中注冊(cè)微應(yīng)用的時(shí)候,微應(yīng)用 name 最好要和 Webpack 的 output.library 一致,這樣才能命中 qiankun 的兜底邏輯。

總結(jié)

最后總結(jié)一下,qiankun 要找入口是因?yàn)橐獜闹心玫缴芷诨卣{(diào),把它們給 single-spa 做調(diào)度。

qiankun 支持 2 種找入口的方式:

  • 正則匹配 帶有 entry 屬性的 <script> ,找到就把這個(gè) JS 作為入口
  • 當(dāng)找不到時(shí),默認(rèn)把 最后一個(gè) JS 作為入口

如果這兩種方法都無(wú)法幫你正確定位入口,那么你需要:

  • 在微應(yīng)用配置 library, libraryTarget 以及 globalObject,把入口導(dǎo)出的內(nèi)容掛載到 window
  • 加載微應(yīng)用時(shí),主應(yīng)用會(huì)試著從 window[library] 找微應(yīng)用的生命周期回調(diào),找到后依然能正常加載
  • 在主應(yīng)用注冊(cè)微應(yīng)用時(shí),要把微應(yīng)用的 name 和 Webpack 的 output.library 設(shè)為一致,這樣才能命中第二步的邏輯

最后還要注意的是,上面說(shuō)到的 window 并不是全局對(duì)象,而是 qiankun 提供的 JS 沙箱對(duì)象 windowProxy。

以上就是qiankun 找不到入口問(wèn)題徹底解決的詳細(xì)內(nèi)容,更多關(guān)于qiankun 找不到入口的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論