Webpack5模塊聯邦源碼探究

image

前言

雖然webpack5已經發布了一段時間了,但一直都沒有研究過,最近正好在作微前端相關的調研,剛好看到了webpack5的模塊聯邦與微前端的相關方案,便想着探究下模塊聯邦的相關源碼。(ps:關於微前端,稍微說一句,我的以爲在選取微前端方案的時候可有結合現有資源以及形態進行相關取捨,從共享能力、隔離機制、數據方案、路由鑑權等不一樣維度綜合考量,我的使用最小的遷移成本,漸進式的過渡,纔是最優的選擇。)javascript

目錄結構

圖片

  • container前端

    • ModuleFederationPlugin.js (核心,重點分析)
    • options.js (用戶輸入的option)
    • ContainerEntryDependency.js
    • ContainerEntryModule.js
    • ContainerEntryModuleFactory.js
    • ContainerExposedDependency.js
    • ContainerPlugin.js (核心,重點分析)
    • ContainerReferencePlugin.js (核心,重點分析)
    • FallbackDependency.js
    • FallbackItemDependency.js
    • FallbackModule.js
    • FallbackModuleFactory.js
    • RemoteModule.js
    • RemoteRuntimeModule.js
    • RemoteToExternalDependency.js
  • sharingjava

    • SharePlugin.js (核心,重點分析)
    • ShareRuntimeModule.js
    • utils.js
    • resolveMatchedConfigs.js
    • ConsumeSharedFallbackDependency.js
    • ConsumeSharedModule.js
    • ConsumeSharedPlugin.js
    • ConsumeSharedRuntimeModule.js
    • ProvideForSharedDependency.js
    • ProvideSharedModule.js
    • ProvideSharedModuleFactory.js
    • ProvideSharedPlugin.js
  • Module.js (webpack的module)
  • ModuleGraph.js (module圖的依賴)

源碼解析

總體webpack5的模塊聯邦 Module Federation是基於ModuleFedreationPlugin.js的,其最後是以webapck插件的形式接入webpack中,其內部主要設計ContainerPlugin用於解析Container的配置信息,ContainerReferencePlugin用於兩個或多個不一樣Container的調用關係的判斷,SharePlugin是共享機制的實現,經過ProviderModule和ConsumerModule進行模塊的消費和提供webpack

Module

Webpack的module整合了不一樣的模塊,抹平了不一樣的差別,模塊聯邦正是基於webpack的模塊實現的依賴共享及傳遞git

class Module extends DependenciesBlock {
    constructor(type, context = null, layer = null) {
        super();
        // 模塊的類型
        this.type = type;
        // 模塊的上下文
        this.context = context;
        // 層數
        this.layer = layer;
        this.needId = true;
        // 模塊的id
        this.debugId = debugId++;
    }
    // webpack6中將被移除
    get id() {}
    set id(value) {}
    // 模塊的hash,Module圖中依賴關係的惟一斷定
    get hash() {}
    get renderedHash() {}
    // 獲取文件
    get profile() {}
    set profile(value) {}
    // 模塊的入口順序值 webpack模塊加載的穿針引線機制
    get index() {}
    set index(value) {}
    // 模塊的出口信息值 webpack模塊加載的穿針引線機制
    get index2() {}
    set index2(value) {}
    // 圖的深度
    get depth() {}
    set depth(value) {}
    // chunk相關
    addChunk(chunk) {}
    removeChunk(chunk) {}
    isInChunk(chunk) {}
    getChunks() {}
    getNumberOfChunks() {}
    get chunksIterable() {}
    // 序列化和反序列化上下文
    serialize(context) {}
    deserialize(context) {}
}

ContainerPlugin

圖片

class ContainerPlugin {
    constructor(options) {}

    apply(compiler) {
        compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, callback) => {
            const dep = new ContainerEntryDependency(name, exposes, shareScope);
            dep.loc = { name };
            compilation.addEntry(
                compilation.options.context,
                dep,
                {
                    name,
                    filename,
                    library
                },
                error => {
                    if (error) return callback(error);
                    callback();
                }
            );
        });

        compiler.hooks.thisCompilation.tap(
            PLUGIN_NAME,
            (compilation, { normalModuleFactory }) => {
                compilation.dependencyFactories.set(
                    ContainerEntryDependency,
                    new ContainerEntryModuleFactory()
                );

                compilation.dependencyFactories.set(
                    ContainerExposedDependency,
                    normalModuleFactory
                );
            }
        );
    }
}

ContainerPlugin的核心是實現容器的模塊的加載與導出,從而在模塊外側進行一層的包裝爲了對模塊進行傳遞與依賴分析github

ContainerReferencePlugin

圖片

class ContainerReferencePlugin {
    constructor(options) {}

    apply(compiler) {
        const { _remotes: remotes, _remoteType: remoteType } = this;

        const remoteExternals = {};

        new ExternalsPlugin(remoteType, remoteExternals).apply(compiler);

        compiler.hooks.compilation.tap(
            "ContainerReferencePlugin",
            (compilation, { normalModuleFactory }) => {
                compilation.dependencyFactories.set(
                    RemoteToExternalDependency,
                    normalModuleFactory
                );

                compilation.dependencyFactories.set(
                    FallbackItemDependency,
                    normalModuleFactory
                );

                compilation.dependencyFactories.set(
                    FallbackDependency,
                    new FallbackModuleFactory()
                );

                normalModuleFactory.hooks.factorize.tap(
                    "ContainerReferencePlugin",
                    data => {
                        if (!data.request.includes("!")) {
                            for (const [key, config] of remotes) {
                                if (
                                    data.request.startsWith(`${key}`) &&
                                    (data.request.length === key.length ||
                                        data.request.charCodeAt(key.length) === slashCode)
                                ) {
                                    return new RemoteModule(
                                        data.request,
                                        config.external.map((external, i) =>
                                            external.startsWith("internal ")
                                                ? external.slice(9)
                                                : `webpack/container/reference/${key}${
                                                        i ? `/fallback-${i}` : ""
                                                  }`
                                        ),
                                        `.${data.request.slice(key.length)}`,
                                        config.shareScope
                                    );
                                }
                            }
                        }
                    }
                );

                compilation.hooks.runtimeRequirementInTree
                    .for(RuntimeGlobals.ensureChunkHandlers)
                    .tap("ContainerReferencePlugin", (chunk, set) => {
                        set.add(RuntimeGlobals.module);
                        set.add(RuntimeGlobals.moduleFactoriesAddOnly);
                        set.add(RuntimeGlobals.hasOwnProperty);
                        set.add(RuntimeGlobals.initializeSharing);
                        set.add(RuntimeGlobals.shareScopeMap);
                        compilation.addRuntimeModule(chunk, new RemoteRuntimeModule());
                    });
            }
        );
    }
}

ContainerReferencePlugin核心是爲了實現模塊的通訊與傳遞,經過調用反饋的機制實現模塊間的傳遞web

sharing

圖片

class SharePlugin {
    constructor(options) {
        const sharedOptions = parseOptions(
            options.shared,
            (item, key) => {
                if (typeof item !== "string")
                    throw new Error("Unexpected array in shared");
                /** @type {SharedConfig} */
                const config =
                    item === key || !isRequiredVersion(item)
                        ? {
                                import: item
                          }
                        : {
                                import: key,
                                requiredVersion: item
                          };
                return config;
            },
            item => item
        );

        const consumes = sharedOptions.map(([key, options]) => ({
            [key]: {
                import: options.import,
                shareKey: options.shareKey || key,
                shareScope: options.shareScope,
                requiredVersion: options.requiredVersion,
                strictVersion: options.strictVersion,
                singleton: options.singleton,
                packageName: options.packageName,
                eager: options.eager
            }
        }));

        const provides = sharedOptions
            .filter(([, options]) => options.import !== false)
            .map(([key, options]) => ({
                [options.import || key]: {
                    shareKey: options.shareKey || key,
                    shareScope: options.shareScope,
                    version: options.version,
                    eager: options.eager
                }
            }));
        this._shareScope = options.shareScope;
        this._consumes = consumes;
        this._provides = provides;
    }

    apply(compiler) {
        new ConsumeSharedPlugin({
            shareScope: this._shareScope,
            consumes: this._consumes
        }).apply(compiler);
        new ProvideSharedPlugin({
            shareScope: this._shareScope,
            provides: this._provides
        }).apply(compiler);
    }
}

sharing的整個模塊都在實現共享的功能,其利用Provider進行提供,Consumer進行消費的機制,將共享的數據隔離在特定的shareScope中,經過resolveMatchedConfigs實現了對provider依賴及consumer依賴的過濾,從而對共有依賴只進行一遍請求app

總結

圖片

圖片

webpack5的模塊聯邦是在經過自定義Container容器來實現對每一個不一樣module的處理,Container Reference做爲host去調度容器,各個容器以異步方式exposed modules;對於共享部分,對於provider提供的請求內容,每一個module都有一個對應的runtime機制,其在分析完模塊之間的調用關係及依賴關係以後,纔會調用consumer中的運行時進行加載,並且shared的代碼無需本身手動打包。webapck5的模塊聯邦能夠實現微前端應用的模塊間的相互調用,而且其共享與隔離平衡也把控的較好,對於想研究模塊聯邦實現微前端的同窗能夠參考這篇文章【第2154期】EMP微前端解決方案,隨着webpack5的推廣及各大腳手架的跟進,相信webpack5的模塊聯邦方案會是將來微前端方案的主流。異步

參考

相關文章
相關標籤/搜索