Webpack5模块联邦源码探究

简介: 虽然webpack5已经发布了一段时间了,但一直都没有研究过,最近正好在做微前端相关的调研,恰好看到了webpack5的模块联邦与微前端的相关方案,便想着探究下模块联邦的相关源码。

前端 | Webpack5模块联邦源码探究.png

前言

虽然webpack5已经发布了一段时间了,但一直都没有研究过,最近正好在做微前端相关的调研,恰好看到了webpack5的模块联邦与微前端的相关方案,便想着探究下模块联邦的相关源码。(ps:关于微前端,稍微说一句,个人觉得在选取微前端方案的时候可有结合现有资源以及形态进行相关取舍,从共享能力、隔离机制、数据方案、路由鉴权等不同维度综合考量,个人使用最小的迁移成本,渐进式的过渡,才是最优的选择。)

目录结构

  • 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
  • sharing

    • 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进行模块的消费和提供

Module

Webpack的module整合了不同的模块,抹平了不同的差异,模块联邦正是基于webpack的模块实现的依赖共享及传递

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的核心是实现容器的模块的加载与导出,从而在模块外侧进行一层的包装为了对模块进行传递与依赖分析

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核心是为了实现模块的通信与传递,通过调用反馈的机制实现模块间的传递

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依赖的过滤,从而对共有依赖只进行一遍请求

总结

webpack5的模块联邦是在通过自定义Container容器来实现对每个不同module的处理,Container Reference作为host去调度容器,各个容器以异步方式exposed modules;对于共享部分,对于provider提供的请求内容,每个module都有一个对应的runtime机制,其在分析完模块之间的调用关系及依赖关系之后,才会调用consumer中的运行时进行加载,而且shared的代码无需自己手动打包。webapck5的模块联邦可以实现微前端应用的模块间的相互调用,并且其共享与隔离平衡也把控的较好,对于想研究模块联邦实现微前端的同学可以参考这篇文章【第2154期】EMP微前端解决方案,随着webpack5的推广及各大脚手架的跟进,相信webpack5的模块联邦方案会是未来微前端方案的主流。

参考

相关文章
|
2月前
|
缓存 前端开发 JavaScript
深入了解Webpack:模块打包的革命
【10月更文挑战第11天】深入了解Webpack:模块打包的革命
|
7月前
|
存储 API
使用Webpack的module.hot API来定义模块的热替换
使用Webpack的`module.hot` API实现模块热替换,简单示例展示如何在`myModule`变化时执行回调。`module.hot.accept`接收模块路径和回调函数,当模块或其依赖变更时触发回调,用于执行更新逻辑。可通过`module.hot.data`保存和恢复状态以实现热替换时保持应用程序的状态。
|
2月前
|
缓存 前端开发 JavaScript
Webpack技术深度解析:模块打包与性能优化
【10月更文挑战第13天】Webpack技术深度解析:模块打包与性能优化
|
3月前
|
前端开发 开发者
在前端开发中,webpack 作为一个强大的模块打包工具,为我们提供了丰富的功能和扩展性
【9月更文挑战第1天】在前端开发中,Webpack 作为强大的模块打包工具,提供了丰富的功能和扩展性。本文重点介绍 DefinePlugin 插件,详细探讨其原理、功能及实际应用。DefinePlugin 可在编译过程中动态定义全局变量,适用于环境变量配置、动态加载资源、接口地址配置等场景,有助于提升代码质量和开发效率。通过具体配置示例和注意事项,帮助开发者更好地利用此插件优化项目。
88 13
|
4月前
|
缓存 前端开发 JavaScript
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
219 1
|
4月前
|
前端开发 开发者
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。
在前端开发中,webpack 作为模块打包工具,其 DefinePlugin 插件可在编译时动态定义全局变量,支持环境变量定义、配置参数动态化及条件编译等功能。本文阐述 DefinePlugin 的原理、用法及案例,包括安装配置、具体示例(如动态加载资源、配置接口地址)和注意事项,帮助开发者更好地利用此插件优化项目。
128 0
|
6月前
|
前端开发 JavaScript 架构师
Webpack模块联邦:微前端架构的新选择
Webpack的模块联邦是Webpack 5引入的革命性特性,革新了微前端架构。它允许独立的Web应用在运行时动态共享代码,无需传统打包过程。基本概念包括容器应用(负责加载协调)和远程应用(独立应用,可暴露模块)。实现步骤涉及容器和远程应用的`ModuleFederationPlugin`配置,以及在应用间导入和使用远程模块。模块联邦的优势在于独立开发、按需加载、版本管理和易于维护。通过实战案例展示了如何构建微前端应用,包括创建容器和远程应用,以及消费远程组件。高级用法涉及动态加载、路由集成、状态管理和错误处理。
116 3
|
6月前
|
缓存 前端开发 JavaScript
Webpack作为模块打包器,为前端项目提供了高度灵活和可配置的构建流程
【6月更文挑战第12天】本文探讨了优化TypeScript与Webpack构建性能的策略。理解Webpack的解析、构建和生成阶段是关键。优化包括:调整tsconfig.json(如关闭不必要的类型检查)和webpack.config.js选项,启用Webpack缓存,实现增量构建,代码拆分和懒加载。这些方法能提升构建速度,提高开发效率。
65 3
|
7月前
|
前端开发 JavaScript 开发者
深入了解Webpack:前端模块打包工具
深入了解Webpack:前端模块打包工具
111 1
|
前端开发 JavaScript 开发者
webpack模块打包器
Webpack是一种前端资源构建工具,可以将多个文件和模块打包成一个或多个bundle。它具有高度的可配置性,支持各种类型的文件和插件,可以自定义打包过程和结果。Webpack的核心概念包括入口、出口和模式,可以分别用于指示打包的起点、输出位置和优化级别。Webpack还具有自动化构建过程,通过Tapable机制组织多个处理流程,并允许插件监听特定事件来参与整个构建过程。总之,Webpack是一个功能强大的前端资源构建工具,提供了高度可配置的选项和插件机制,方便开发者进行自定义和扩展。