微前端架構(gòu)ModuleFederationPlugin源碼解析
序言
本文是 Webpack ModuleFederationPlugin(后面簡(jiǎn)稱 MF) 源碼解析 文章中的第一篇,在此系列文章中,我將帶領(lǐng)大家抽絲剝繭、一步步地去解析 MF 源碼。當(dāng)然為了幫助大家理解,可能中間也會(huì)涉及到 Webpack 源碼中的其它實(shí)現(xiàn),我會(huì)根據(jù)情況或淺或深的一并進(jìn)行講解。因?yàn)榭?Webpack 源碼需要掌握的知識(shí)量非常大,所以為了更好理解文章中的內(nèi)容,你最好有如下 Webpack 相關(guān)的背景知識(shí):
- 對(duì) Webpack 核心的數(shù)據(jù)結(jié)構(gòu):Dependency、Module、Chunk 等有基本的認(rèn)識(shí)
- 了解 Webpack 中的插件機(jī)制,對(duì)基于 tabpable 的 Hooks 機(jī)制有一定的了解,如果寫過 Webpack 插件就更好了
- 看過 MF 的官方文檔,對(duì)其帶來的關(guān)鍵性作用有基本的認(rèn)識(shí),如果了解一些其應(yīng)用場(chǎng)景就更好了
話不多說,讓我們開始正文。
背景
先簡(jiǎn)單說一下為什么要去閱讀 MF 的源碼,我個(gè)人理解閱讀源碼有兩個(gè)原因:
一,它的實(shí)現(xiàn)非常優(yōu)秀,通過閱讀源碼能學(xué)習(xí)一些設(shè)計(jì)思想和編程技巧;
二,工作或者自己的項(xiàng)目使用到了,但是官方給的文檔不太夠,遇到問題無論最后有沒有解決,都有點(diǎn)摸不著頭腦,閱讀源碼是為了更好地了解其內(nèi)部實(shí)現(xiàn),遇到問題更容易 debug。
而我閱讀 MF 的源碼,主要是出于第二種目的,當(dāng)然我個(gè)人對(duì) Webpack 也是非常感興趣。目前我們部門 B 端的產(chǎn)品是基于 MF 實(shí)現(xiàn)的微前端架構(gòu),而我主要負(fù)責(zé) B 端的開發(fā)以及參與 B 端性能優(yōu)化專項(xiàng),今年大部分時(shí)間都是跟 MF “搏斗”。
雖然到目前為止,性能優(yōu)化已經(jīng)獲得了一些階段性的勝利,但是實(shí)際上在這個(gè)過程中,我們還是走了很多彎路。這些彎路不少是由于對(duì) MF 內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)不夠了解導(dǎo)致的,當(dāng)然除此之外,我們還需要建立一套規(guī)范的 MF 標(biāo)準(zhǔn)化開發(fā)流程。所以,閱讀 MF 源碼對(duì)于我個(gè)人來說非常有必要。
首先,我們先簡(jiǎn)單了解下 MF 相關(guān)知識(shí)。
MF 基本介紹
首先,MF 是一個(gè) Webpack 的官方插件,在 Webpack 生態(tài)中有茫茫多的插件中,好像一個(gè)插件有點(diǎn)微不足道。但是,MF 的作者稱其為 “A game-changer in JavaScript architecture”,當(dāng)然從構(gòu)建工具的角度來講,有點(diǎn)言過其實(shí),因?yàn)樗荒苡糜?Webpack 中。
但是從它帶來的 JavaScript 架構(gòu)設(shè)計(jì)上的理念:遠(yuǎn)程依賴共享(復(fù)用組件或者其它邏輯), 我覺得其實(shí)是給前端帶來新的思考視角。
以前我們復(fù)用組件或者邏輯主要的方式有:
- 抽離一個(gè) NPM 包,從維護(hù)性和復(fù)用性角度來講,是目前最常見的方式。缺點(diǎn)在于,在微前端架構(gòu)中,如果 fix 了一個(gè) NPM 包問題,那么每一個(gè)應(yīng)用都需要升級(jí)版本,重新構(gòu)建打包部署上線,多團(tuán)隊(duì)開發(fā)的時(shí)候非常低效;
- 將產(chǎn)物打包成 UMD 的格式,然后通過 CDN 的方式能一定程度解決重新構(gòu)建打包上線的問題,但是隨著復(fù)用的組件和邏輯越多,可能會(huì)引入很多多余的 chunk 問題(如果對(duì)性能有很高的要求) 。比如 A 和 B 組件同時(shí)依賴了 lodash,那么打包成 UMD 格式有多余的 lodash chunk,沒法復(fù)用。
我們來看下 MF 是怎么解決這個(gè)問題的。首先看一個(gè)簡(jiǎn)單的 MF 使用的例子,假設(shè)我們現(xiàn)在有兩個(gè)應(yīng)用 app1 和 app2:
// app1 webpack.config.js module.exports = { // 省略其它配置 plugins: [ new ModuleFederationPlugin({ name: 'app1', filename: 'remoteEntry.js', remotes: { app2: 'app2@http://localhost:3002/remoteEntry.js', }, exposes: { './input': './src/components/Input' }, shared: { 'react': { singleton: true, requiredVersion: require('./package.json').dependencies.react }, 'react-dom': { singleton: true, requiredVersion: require('./package.json').dependencies['react-dom'] }, 'lodash': { requiredVersion: require('./package.json').dependencies['lodash'], singleton: true, } } }), ] } // app1 src/components/Input.tsx import * as React from 'react' import { Input } from 'antd' export default function WrapperInput () { return ( <div> app1 input: <Input /> </div> ) } // app1 src/App.tsx import { Input } from 'antd'; import * as React from 'react'; const RemoteButton = React.lazy(() => import('app2/Button')); const App = () => ( <div> <h1>Typescript</h1> <h2>App 1</h2> <React.Suspense fallback="Loading Button"> <RemoteButton /> </React.Suspense> <div> <Input /> </div> </div> ); export default App;
app2 的部分代碼:
// app2 webpack.config.js module.exports = { // 省略其它配置 plugins: [ new ModuleFederationPlugin({ name: 'app2', filename: 'remoteEntry.js', exposes: { './Button': './src/Button', }, remotes: { app1: 'app1@http://localhost:3001/remoteEntry.js', }, }), } // app2 src/Button.tsx import * as React from 'react'; const Button = () => <button>App 3 Button</button>; export default Button; // app2 src/App.tsx import * as React from 'react'; import LocalButton from './Button'; import RemoteInput from 'app1/input'; const App = () => ( <div> <h1>Typescript</h1> <h2>App 3</h2> <LocalButton /> <React.Suspense fallback={null}> <RemoteInput /> </React.Suspense> </div> ); export default App;
最后實(shí)現(xiàn)的效果:
簡(jiǎn)單的 Webpack 配置,我們就可以實(shí)現(xiàn) app1 和 app2 兩個(gè)應(yīng)用之間的組件遠(yuǎn)程共享,從代碼看,我們知道 app1 依賴了 app2 的 Button
組件,而 app2 依賴了 app1 的 Input
組件。
當(dāng)然不止如此,MF 還可以做到:
- 依賴復(fù)用, app1 和 app2 同時(shí)依賴了 react 和 react-dom,那我們可以在雙方的 Webpack 配置中,將兩個(gè)依賴配置成
shared
,而且通過requiredVersion
指定版本; - 微前端架構(gòu),微前端架構(gòu)有很多實(shí)現(xiàn)的方式,比如 iframe、web-component 等,但是 MF 的出現(xiàn),使得實(shí)現(xiàn)一套微前端的架構(gòu)更加簡(jiǎn)單,也能非常容易解決微前端架構(gòu)中的一些組件復(fù)用問題、頻繁構(gòu)建部署上線問題;
- 支持服務(wù)端渲染,MF 的實(shí)現(xiàn)不依賴瀏覽器,同樣的代碼,只需要將 Webpack 配置中的
target
改成
node
,那么構(gòu)建的產(chǎn)物就能支持 SSR。
到這里,讀者已經(jīng)對(duì) MF 的使用和定位有了基本的印象,根據(jù) MF 帶來的全新的復(fù)用能力,我們可以做一些應(yīng)用場(chǎng)景的思考。
應(yīng)用場(chǎng)景
微前端架構(gòu)
微前端是這幾年比較火的一個(gè)前端應(yīng)用架構(gòu)方案,其中比較核心的一點(diǎn)是各子應(yīng)用之間要做到獨(dú)立開發(fā),獨(dú)立構(gòu)建部署上線。 從上一節(jié)對(duì) MF 的介紹中,我們發(fā)現(xiàn)它天然就已經(jīng)有這個(gè)優(yōu)勢(shì),因此為了設(shè)計(jì)一個(gè)基于 MF 的微前端架構(gòu),我們要解決的第一點(diǎn)是子應(yīng)用之間需要有個(gè)類似中心化的服務(wù),將其它子應(yīng)用的服務(wù)地址下發(fā)給需要消費(fèi)的子應(yīng)用;第二點(diǎn),我們要解決子應(yīng)用之間的一些通信問題,例如共享的一些用戶狀態(tài)。 當(dāng)然還有一些其它問題,例如 UI 一致性問題。
基于以上的問題,我們可以很容易想到一種非常經(jīng)典的微前端架構(gòu)方案,那就是基于一個(gè)基座服務(wù)的中心化的架構(gòu)方式。
每個(gè) APP 都是一個(gè)子應(yīng)用,這里可以有兩種方式:如果完全不需要依賴基座的狀態(tài),則可以做成一個(gè)更加通用的前端服務(wù),只作為提供方,在 MF 中也稱為 remotes 應(yīng)用。如果需要依賴主應(yīng)用的狀態(tài),或者說只導(dǎo)出路由讓基座幫忙注冊(cè),這樣就可以共享基座的所有狀態(tài),這種方式與我們現(xiàn)在 B 端的架構(gòu)方式類似。這樣的架構(gòu)方式,也能通過 MF shared 機(jī)制鎖定 UI 庫的版本,保證所有子應(yīng)用 UI 的一致性。
服務(wù)化的 library 和 components
跳出微前端架構(gòu),假設(shè)我們現(xiàn)在的場(chǎng)景是維護(hù)一個(gè)巨型前端應(yīng)用,我們發(fā)現(xiàn)隨著頁面和依賴的第三方依賴逐漸增多,那么每次開發(fā)構(gòu)建部署上線的時(shí)長也會(huì)不斷增加。雖然 Webpack v5+ 版本已經(jīng)做了很多優(yōu)化例如本地緩存,但是對(duì)于巨型應(yīng)用,我們還是發(fā)現(xiàn)構(gòu)建還是非常低效。于是,基于 MF 的能力,我們可以做這樣的一個(gè)架構(gòu)設(shè)計(jì):
我們可以將平時(shí)使用的第三方庫和組件庫,分別做成一個(gè)單獨(dú)的服務(wù),如果部門技術(shù)棧統(tǒng)一的項(xiàng)目可以通過 MF 插件遠(yuǎn)程使用這兩個(gè)服務(wù),這樣無論是開發(fā)時(shí)還是上線構(gòu)建都可以省掉這部分的構(gòu)建時(shí)間,一定程度上提高了開發(fā)效率。
MF 的使用姿勢(shì)非常靈活,你可以根據(jù)開發(fā)需要,充分挖掘更多的使用場(chǎng)景。MF 介紹的部分就到這里,下面我們正式進(jìn)入源碼解析的內(nèi)容。
ModuleFederationPlugin 源碼解析
入口源碼
MF 插件相關(guān)的源碼放在 lib/container
下,我們首先看下 lib/container/ModuleFedration.js
的代碼:
// 省略一些 import 代碼 class ModuleFederationPlugin { /** * @param {ModuleFederationPluginOptions} options options */ constructor(options) { validate(options); this._options = options; } /** * Apply the plugin * @param {Compiler} compiler the compiler instance * @returns {void} */ apply(compiler) { const { _options: options } = this; // expose 模塊編譯產(chǎn)物導(dǎo)出的類型,選項(xiàng)有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一樣的 // var 代表輸出的模塊是掛在 window 對(duì)象上 const library = options.library || { type: "var", name: options.name }; // remote 模的類型,選項(xiàng)有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一樣的 const remoteType = options.remoteType || (options.library && isValidExternalsType(options.library.type) ? /** @type {ExternalsType} */ (options.library.type) : "script"); // enabledLibraryTypes 專門存儲(chǔ) entry 需要輸出的 library 類型,然后被 EnableLibraryPlugin 插件消費(fèi), if ( library && !compiler.options.output.enabledLibraryTypes.includes(library.type) ) { compiler.options.output.enabledLibraryTypes.push(library.type); } // 在完成所有內(nèi)部插件注冊(cè)后處理 MF 插件 compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => { if ( options.exposes && (Array.isArray(options.exposes) ? options.exposes.length > 0 : Object.keys(options.exposes).length > 0) ) { // 如果有 expose 配置,則注冊(cè)一個(gè) ContainerPlugin new ContainerPlugin({ name: options.name, library, filename: options.filename, runtime: options.runtime, shareScope: options.shareScope, exposes: options.exposes }).apply(compiler); } if ( options.remotes && (Array.isArray(options.remotes) ? options.remotes.length > 0 : Object.keys(options.remotes).length > 0) ) { // 如果有 expose 配置,則初始化一個(gè) ContainerReferencePlugin new ContainerReferencePlugin({ remoteType, shareScope: options.shareScope, remotes: options.remotes }).apply(compiler); } if (options.shared) { // 如果有 shared 配置,則初始化一個(gè) SharePlugin new SharePlugin({ shared: options.shared, shareScope: options.shareScope }).apply(compiler); } }); } }
從代碼中可以看出,MF 插件入口的代碼其實(shí)不復(fù)雜,核心的代碼不到 100 行,我們首先把焦點(diǎn)放在插件初始化的 options
參數(shù)上,它的類型為 ModuleFederationPluginOptions
。
這里有個(gè)細(xì)節(jié)可以注意下,因?yàn)?Webpack 的源碼是用純 JS 寫的,為了彌補(bǔ)如像 TypeScript 的類型注釋的優(yōu)勢(shì)使得源碼更加可讀的問題,Webpack 使用了 JSDoc 配合 VS Code,在大多數(shù)場(chǎng)景下也能起到類型注釋的效果,而 Webpack 根目錄下的 declarations
目錄使用了 TS 定義了核心的一些數(shù)據(jù)類型,然后導(dǎo)出給其它 JS 文件在使用 JSDoc 時(shí)使用。
我們回到主題,我們看下ModuleFederationPluginOptions
的類型定義:
export interface ModuleFederationPluginOptions { /** * container 應(yīng)用導(dǎo)出的模塊配置,一般是一個(gè)對(duì)象 */ exposes?: Exposes; /** * 打包產(chǎn)物的文件名稱 */ filename?: string; /** * 構(gòu)建產(chǎn)物的類型,里面的 type 配置可以是 umd、commonjs、var 等類型 */ library?: LibraryOptions; /** * container 的名稱 */ name?: string; /** * 依賴的 remote 應(yīng)用 library 類型,配置的值可以是 umd、commonjs、script、var 等 */ remoteType?: ExternalsType; /** * container 應(yīng)用依賴的遠(yuǎn)程應(yīng)用 */ remotes?: Remotes; /** * 配置了該選項(xiàng),會(huì)為模塊split 一個(gè)以該名稱命名的 chunk */ runtime?: EntryRuntime; /** * 所有共享模塊的作用域名稱,默認(rèn)為 default,很少會(huì)修改 */ shareScope?: string; /** * 應(yīng)用之間需要共享的模塊 */ shared?: Shared; }
每個(gè)選項(xiàng)我都用注釋做了簡(jiǎn)單的介紹,我們重點(diǎn)關(guān)注幾個(gè)常用的配置,對(duì)于 library
、runtime
、
remoteType
等配置平時(shí)很少使用,這里先不過多介紹,后面看到相關(guān)的源碼可以再回顧。
filename
和 name
比較好理解,以上一小節(jié)的 app1 的 Webpack 配置為例,我們可以看到其配置如下:
new ModuleFederationPlugin({ name: 'app1', filename: 'remoteEntry.js', // 省略其它配置 })
如果這樣配置,app1 正好expose
了一些模塊給其它應(yīng)用消費(fèi),例如 app2,則 app2 首先要通過 app1 的
name
去找到它,也就是會(huì)在 remotes
的配置中添加 app1 的指向,這里等會(huì)介紹 remotes
選項(xiàng)時(shí)再細(xì)說。而 app2 在運(yùn)行時(shí)就會(huì)加載到 app1 的構(gòu)建產(chǎn)物 remoteEntry.js
,訪問 app2 的服務(wù),打開 network,我們可以看到其加載了 app1 的 remoteEntry.js
:
我們重點(diǎn)介紹下 exposes
、remotes
、shared
等選項(xiàng)。
在上面的 MF 插件源碼中,其核心的幾行代碼就是,在 afterPlugins hook
觸發(fā)后(完成其它所有內(nèi)置插件初始化后),根據(jù)是否有上面三個(gè)配置,來決定是否要注冊(cè) ContainerPlugin
、
ContainerReferencePlugin
、SharePlugin
等插件。所以更加核心的實(shí)現(xiàn),是分別交給了上面三個(gè)插件去完成。
Exposes
exposes
的配置是告訴 Webpack 當(dāng)前應(yīng)用導(dǎo)出給其它應(yīng)用消費(fèi)的模塊,首先我們來看下 exposes
配置的類型定義:
export type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject; export type ExposesItem = string; export type ExposesItems = ExposesItem[]; export interface ExposesObject { [k: string]: ExposesConfig | ExposesItem | ExposesItems; } export interface ExposesConfig { import: ExposesItem | ExposesItems; name?: string; }
上面的類型定義相對(duì)來說比較簡(jiǎn)單,只是套娃比較多,還是以上面 app1 的 Webpack 配置為例,據(jù)我平時(shí)了解到的,最常見的配置方式還是:
exposes: { './input': './src/components/Input' },
但是你也可以配置:
exposes: { './input': { name: 'input', import: './src/components/Input' } },
這種方式配置會(huì)有什么不一樣了?這里會(huì)留一個(gè)懸念,在看后續(xù)的源碼中,我們?cè)僭敿?xì)介紹。
Remotes
remotes
配置是告訴 Webpack 當(dāng)前應(yīng)用依賴了哪些遠(yuǎn)程應(yīng)用,我們來看下其類型定義:
export type Remotes = (RemotesItem | RemotesObject)[] | RemotesObject; export type RemotesItem = string; export type RemotesItems = RemotesItem[]; export interface RemotesObject { [k: string]: RemotesConfig | RemotesItem | RemotesItems; } export interface RemotesConfig { /** * 共享模塊需要依賴的其它模塊 */ external: RemotesItem | RemotesItems; // 共享作用域的名稱,默認(rèn)為 default shareScope?: string; }
還是以 app1 為例,我們回顧其 remote 的配置:
remotes: { app2: 'app2@http://localhost:3002/remoteEntry.js', },
告訴了 Webpack 如果需要消費(fèi) app2 導(dǎo)出的模塊,那么則需要加載 app2 服務(wù)的 remoteEntry.js
文件,所以 app1 在初始化的時(shí)候就會(huì)加載此文件,然后通過下面的方式加載 app2 導(dǎo)出的模塊:
import RemoteButton from 'app2/Button';
是不是有點(diǎn)神奇,這里面的實(shí)現(xiàn)用了什么黑魔法,簡(jiǎn)單的幾個(gè)配置,然后啟動(dòng)服務(wù),就能消費(fèi)其它遠(yuǎn)程應(yīng)用的模塊。保持耐心,后續(xù)我們將慢慢揭開其神秘的面紗。
Shared
MF 關(guān)于 shared
配置部分是我個(gè)人覺得最復(fù)雜的部分,當(dāng)然 SharedPlugin
的實(shí)現(xiàn)也是相對(duì)來說比較復(fù)雜,因?yàn)檫@里牽扯到一些需要 shared
配置延伸出的例如單例問題。 先留個(gè)懸念,稍后解釋單例問題,我們還是先看 shared
配置類型定義:
export type Shared = (SharedItem | SharedObject)[] | SharedObject; export type SharedItem = string; export interface SharedObject { [k: string]: SharedConfig | SharedItem; } export interface SharedConfig { // 配置了 eager 是告訴 webpack 該模塊是作為一個(gè) initial chunk,無論怎么樣,初始化都需要加載該模塊 eager?: boolean; // 共享模塊依賴的模塊 import?: false | SharedItem; // 共享模塊的包名 packageName?: string; // 共享模塊的版本 requiredVersion?: false | string; // 如果配置了 key,查找共享模塊的時(shí)候,會(huì)在當(dāng)前共享作用域查找配置的 key shareKey?: string; // 共享作用域 shareScope?: string; // 是否需要保持單例 singleton?: boolean; // 是否需要嚴(yán)格校驗(yàn)共享模塊的版本,只有配置了 requiredVersion 配置該選型才有效 strictVersion?: boolean; // 指定提供的模塊的版本,將會(huì)替代低版本的模塊,但是不會(huì)替代版本更好的模塊 version?: false | string; }
從 SharedConfig
類型我們就可以看到 shared
配置有很多的場(chǎng)景需要適配,每個(gè)配置我都做了簡(jiǎn)單注釋來介紹。當(dāng)然可能這個(gè)時(shí)候,不熟悉 MF 的小伙伴看到這些配置可能是懵逼的狀態(tài)。不用著急,這些配置項(xiàng),在后面更加具體的源碼使用場(chǎng)景,我會(huì)再進(jìn)行介紹,這里先留個(gè)印象。
我們還是看下 app1 的配置:
shared: { 'react': { singleton: true, requiredVersion: require('./package.json').dependencies.react }, 'react-dom': { singleton: true, requiredVersion: require('./package.json').dependencies['react-dom'] }, 'lodash': { requiredVersion: require('./package.json').dependencies['lodash'], singleton: true, } }
這里分別將 react
、react-dom
、lodash
等三方包配置成了 shared
,這樣有什么作用了?
實(shí)際上 shared
配置是告訴 Webpack 這些依賴需要共享(復(fù)用) ,因?yàn)樵?MF 的遠(yuǎn)程模塊消費(fèi)機(jī)制里面,多個(gè)應(yīng)用之間可能會(huì)依賴相同的三方包,如果沒有一個(gè)共享機(jī)制,那么一定會(huì)導(dǎo)致多余的 chunk 加載,而且還有其它需要解決的問題。
以前面的 app1 和 app2 為例,app1 本身是一個(gè) react 應(yīng)用,它依賴了 app2 的一個(gè)組件,而 app2 同樣也依賴了 react ,那么如果沒有這個(gè)共享模塊的機(jī)制,那么 app1 消費(fèi) app2 的組件可能就還需要加載 app2 的構(gòu)建的 react 依賴。而且我們知道,react 的運(yùn)行機(jī)制是在同一個(gè)JS runtime 里面,是不能同時(shí)存在兩個(gè) react 實(shí)例的,這也是
singleton
配置的由來,它的作用就是為了解決類似這樣的場(chǎng)景。
當(dāng)然,要講清楚這部分的原理,還有運(yùn)行機(jī)制,除了需要一定的 MF 使用經(jīng)驗(yàn)外,還需要對(duì)其源碼有一定的了解,我們后續(xù)在剖析 SharedPlugin
插件源碼時(shí)再詳細(xì)聊。
小結(jié)
雖然 MF 插件入口的源碼部分相對(duì)來說還是不復(fù)雜的,所以本小節(jié)我們聚焦在其配置上。實(shí)際上對(duì)于 上面提到的一些配置,例如 MF 插件的 library
、remoteType
等配置在官網(wǎng)是沒有提到的,包括 exposes
、
remotes
、shared
等配置的一些更加高級(jí)的選項(xiàng),這也是 Webpack 配置復(fù)雜然后官網(wǎng)又不完全介紹一直被人詬病的地方。
總結(jié)
本文我們從 MF 插件主入口出發(fā),分析了其插件的注冊(cè)時(shí)機(jī),并且通過閱讀這部分的源碼,我們了解到:
- 插件的配置選項(xiàng)除了常用的
exposes
、remotes
、shared
、filename
、name
等之外還有
library
、remoteType
、sharedScope
等配置項(xiàng),可以指定 exposes
和 remotes
模塊的
library
類型;
- MF 核心的源碼實(shí)現(xiàn)是通過其它三個(gè)插件
ContainerPlugin
、
ContainerReferencePlugin
、SharePlugin
等來實(shí)現(xiàn),然后根據(jù)是否傳入 exposes
、
remotes
、shared
來決定是否需要初始化各個(gè)插件;
exposes
、remotes
、shared
等選項(xiàng)有很多進(jìn)階的配置,特別是shared
配置比較復(fù)雜,從共享三方依賴、單例、版本鎖定等角度思考,就可以想象這里面的設(shè)計(jì)不簡(jiǎn)單。
后續(xù)文章
下一篇文章,我們開始逐漸進(jìn)入深水區(qū),首先深入到 CotainerPlugin
的源碼,一步步揭開其神秘的面紗。為了更好理解后續(xù)的文章,建議讀者了解一下 Webpack 構(gòu)建流程,特別是核心的構(gòu)建階段,Dependency
和Module
之間的轉(zhuǎn)換流程,更多關(guān)于微前端架構(gòu)ModuleFederationPlugin的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)中如何解決Fragment +Viewpager滑動(dòng)頁面重復(fù)加載的問題
這篇文章主要介紹了Android開發(fā)中如何解決Fragment +Viewpager滑動(dòng)頁面重復(fù)加載的問題 ,需要的朋友可以參考下2017-07-07Android Retrofit 2.0框架上傳圖片解決方案
這篇文章主要介紹了Android Retrofit 2.0框架上傳一張與多張圖片解決方案,感興趣的小伙伴們可以參考一下2016-03-03Android編程開發(fā)之打開文件的Intent及使用方法
這篇文章主要介紹了Android編程開發(fā)之打開文件的Intent及使用方法,已實(shí)例形式分析了Android打開文件Intent的相關(guān)布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10解析后臺(tái)進(jìn)程對(duì)Android性能影響的詳解
本篇文章是對(duì)Android中后臺(tái)進(jìn)程對(duì)Android性能的影響進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05android 跳轉(zhuǎn)進(jìn)市場(chǎng)的實(shí)現(xiàn)代碼
本篇文章是對(duì)android中跳轉(zhuǎn)進(jìn)市場(chǎng)的實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Android中實(shí)現(xiàn)多線程操作的幾種方式
多線程一直是一個(gè)老大難的問題,首先因?yàn)樗y以理解,其次在實(shí)際工作中我們需要面對(duì)的關(guān)于線程安全問題也并不常見,今天就來總結(jié)一下實(shí)現(xiàn)多線程的幾種方式,感興趣的可以了解一下2021-06-06