三大应用场景调研,Webpack 新功能 Module Federation 深入解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Federated Modules 是一个令人激动的功能,它可能会改变未来几年的前端打包方式,作者深入分析了 Module Federation 的原理及其应用场景,希望能对大家有所启发。

作者:云谦

image.png

导读:Federated Modules 是一个令人激动的功能,它可能会改变未来几年的前端打包方式,作者深入分析了 Module Federation 的原理及其应用场景,希望能对大家有所启发。

WHAT(Module Federation 是什么?)

Module Federation [ˌfedəˈreɪʃn] 使 JavaScript 应用得以在客户端或服务器上动态运行另一个 bundle 的代码。

这其中的关键点是:

  • 动态,包含两个含义:

    • 按需,可以把一个包拆开来加载其中一部分;
    • 运行时,跑在浏览器而非 node 编译时;
  • 另一个 bundle 的代码,之前应用之间做共享是在文件级或 npm 包级 export 成员,现在可以在应用级 export 成员属性。

image.png

一些相关的概念:

  • Remote,被 Host 消费的 Webpack 构建;
  • Host,消费其他 Remote 的 Webpack 构建;

一个应用可以是 Host,也可以是 Remote,也可以同时是 Host 和 Remote。

image.png

HOW(它的原理是什么?)

通过回答 Module Federation 如何运转?Host 如何消费 Remote?以及 Remote 如何优先使用 Host shared 的依赖?这三个问题,我们分析一下 Module Federation 的原理。

ModuleFederationPlugin

整体是通过ModuleFederationPlugin这个插件串联起来的。

配置示例:

new ModuleFederationPlugin({
 name: "app-1",
 library: { type: "var", name: "app_1" },
 filename: "remoteEntry.js",
 remotes: {
    app_02: 'app_02',
    app_03: 'app_03',  
},
  exposes: {
    antd: './src/antd',
    button: './src/button',  
},
  shared: ['react', 'react-dom'],
}),

配置属性:

  • name,必须,唯一 ID,作为输出的模块名,使用的时通过 ${name}/${expose} 的方式使用;
  • library,必须,其中这里的 name 为作为 umd 的 name;
  • remotes,可选,表示作为 Host 时,去消费哪些 Remote;
  • exposes,可选,表示作为 Remote 时,export 哪些属性被消费;
  • shared,可选,优先用 Host 的依赖,如果 Host 没有,再用自己的;

image.png

产物:

image.png

  • main.js,应用主文件;
  • remoteEntry.js,作为 remote 时被引的文件;
  • 一堆异步加载的文件,main.js 或 remoteEntry.js 里可能加载的文件;

所以比如下面如图示例的应用集群:

image.png

加载方式应该这样:

<script src="C/remoteEntry.js"></script>
<script src="B/remoteEntry.js"></script>
<script src="A/main.js"></script

C/remoteEntry.js 和 B/remoteEntry 的顺序没有要求,只要在 A/main.js 之前就好了。

A 如何消费 B ?

可以通过代码示例来进行理解。

B 源码:

// src/react.js
export * from 'react';

// webpack.config.js
...
exposes: {
  react: './src/react',
},

A 源码:

// 异步加载 B 的 react 模块
const React = await import('B/react');

B 构建产物:

// windows 变量
let B;

const moduleMap = {  
    'react': () => {
      return Promise.all([e('a'), e('b'), e('c')]),  
   },
};

B = {  
   get(moduleId) {
    return moduleMap(moduleId);  
   }
}

A 构建产物:

const modules = {  
    'B': () => {    
      return B;  
   }
};

// 异步获取模块 export 内容
function e(moduleId) {  
// 1. 取 shared 的模块
  
// 2. 取 remote 的模块
 const idToExternalAndNameMapping = {
    'B/react': ['B', 'react'],  
 };
 // 从 module B 里取 react
  const data = idToExternalAndNameMapping[moduleId];
  __webpack_require__(data[0]).get(data[1]);
  
// 3. 取当前项目的异步模块
}

// 初始化
e('B/react');

这其中的原理:

  • 多个 Bundler 之间通过全局变量串联;
  • Remote 会 export get 方法获取他的子模块,子模块的获取通过 Promise 以按需的方式引入;

A 如何让 B 用 A shared 的库?

再看如下两个代码示例。

B 构建产物:

let B;
__webpack_require__.Overrides = {};

function e(moduleId) {  
   // 1. 取 shared 的模块
  // 当前项目的 shared 模块列表
  const fallbackMapping = {};
  // 先从 Overrides 里取,再从当前项目里取
  push_require_try(__webpack_require__.Overrides[moduleId] || fallbackMapping[moduleId]);
  
  // 2. 取 remote 的模块  
  // 3. 取当前项目的异步模块
}

B = {  
   override(override) {
    Object.assign(__webpack_require__.Overrides, override);  
   }
}

A 构建产物:

B.override(Object.assign({
  'react': () => {
    // A 的 react 内容
  },
}, __webpack_require__.Overrides));

原理分析:

  • Remote(B)export override 方法,Host(A) 会调用其关联 Remote 的 override 方法,把 shared 的依赖写进去;
  • Remote(B) 获取模块时会优先从 override 里取,没有再从当前 Bundle 的模块索引里取;

这样,B 里面在 require react 时,就会用 A 的 react 模块。

WHY(它的应用场景有哪些?)

Module Federation 可以用在哪里?

微前端

image.png

如上图,这是去年画的一张微前端的图,其中最下面的 “公共依赖加载” 一直是没有非常优雅的方案。

image.png

方法一:让每个子应用都分开打包,主应用不管,这样不会有问题,但问题就是尺寸大,而且大了不是一点点。

方法二:主应用包含 antd 和 react,子应用如果版本一致不打包 react 和 antd,版本不一致就自己打一份,但有几个问题:

  1. antd 和 react 是通过 umd 的方式同步载入的,主应用初始化会比较慢;
  2. 主应用升级了 antd 的时候,所有子应用可能需要一起升级,这个成本就很大了。

方法三:利用 Module Federation 的 shared 能力,子应用的依赖如果和主应用匹配,那么,能解决方法二里的第一个问题,但第二个问题依旧解不了。

方法四:利用 Module Federation 的 remotes 能力,再提一个应用专门提供库被消费,看起来前面的问题都能解。

image.png

有没有感觉技术又轮回到了 seajs + spmjs 的时代。

应用集群

微前端是应用集群的解法之一,但不是唯一方案。

现状是,通过 npm 共享组件。

image.png

基于 Module Federation,除通过 npm 共享依赖,还可以有运行时的依赖、组件、页面甚至应用的直接共享。

image.png

这样一来,灵活性就非常大了,可以在应用的各个层面做共享。A 应用引用 B 整个应用,也可以应用 B 的的页面和组件,还可以提一个库应用,做 npm 依赖的运行时共享。

编译提速,应用秒开

我们大部分场景不是微前端或应用集群,Module Federation 还可以帮助我们干什么?

现在项目组织和文件依赖通常是这样:

image.png

现状是:

  • 全部打成一个包;
  • 打包时间较慢,据统计,内部云编译平台的平均编译时间在 100s 以上;

期望的是:

  • node_modules 下的提前打包好,通过 runtime 的方式引;
  • 本地调试和编译时只打项目文件;
  • 快,根据项目复杂度可提升到 1s - 7s 之内;

为什么不是其他的编译速度优化方案?

image.png

举一个对比的例子,比如 external,我们之前还有做过自动的 external 方案,虽然他也可能显著提速,但有以下问题:

  • 以空间换时间,依赖包全量引用导致 npm,用在生产上会牺牲部分产品体验,需权衡;
  • 不是所有的依赖都有 umd 包,覆盖率不够;
  • npm 可能有依赖,比如 antd 依赖 react 和 moment,那么 react 和 moment 也得 external 并且在html 里引用他们;
  • 需要手动修改 html 里的引用,维护上有成本提升。

更多参考:


image.png
关注「Alibaba F2E」
把握阿里巴巴前端新动向

相关文章
|
2月前
|
并行计算 数据挖掘 大数据
[go 面试] 并行与并发的区别及应用场景解析
[go 面试] 并行与并发的区别及应用场景解析
|
2月前
|
数据采集 人工智能 自然语言处理
全球首篇!调研近400篇文献,鹏城实验室&中大深度解析具身智能
【8月更文挑战第17天】在人工智能领域,具身智能正成为研究焦点。它强调智能体在现实世界中的感知与交互。近期,鹏城实验室与中山大学联合发布的首篇全球具身智能综述,调研近400篇文献,总结了该领域的理论和技术进展。文章探讨了具身感知、交互及仿真到现实的适应性等关键议题,并指出了面临的挑战如数据质量、模型泛化等,为通向通用人工智能铺路。论文已发表于IEEE会议记录中。
188 60
|
4天前
|
缓存 前端开发 JavaScript
Webpack技术深度解析:模块打包与性能优化
【10月更文挑战第13天】Webpack技术深度解析:模块打包与性能优化
|
14天前
|
自动驾驶 5G 网络架构
|
2月前
|
机器学习/深度学习 人工智能 PyTorch
掌握 PyTorch 张量乘法:八个关键函数与应用场景对比解析
PyTorch提供了几种张量乘法的方法,每种方法都是不同的,并且有不同的应用。我们来详细介绍每个方法,并且详细解释这些函数有什么区别:
52 4
掌握 PyTorch 张量乘法:八个关键函数与应用场景对比解析
|
2月前
|
缓存 前端开发 JavaScript
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
Webpack 模块解析:打包原理、构造形式、扣代码补参数和全局导出
75 1
|
2月前
|
机器学习/深度学习 自然语言处理 自动驾驶
【深度学习】深度学习的详细解析:涵盖定义、技术原理及应用场景
深度学习(Deep Learning)是机器学习(Machine Learning)的一个重要分支,它通过使用多层的神经网络来模拟人脑的学习过程,从而实现对数据的分析和理解。以下是关于深度学习的详细解析
153 2
|
2月前
|
开发者 图形学 C#
揭秘游戏沉浸感的秘密武器:深度解析Unity中的音频设计技巧,从背景音乐到动态音效,全面提升你的游戏氛围艺术——附实战代码示例与应用场景指导
【8月更文挑战第31天】音频设计在游戏开发中至关重要,不仅能增强沉浸感,还能传递信息,构建氛围。Unity作为跨平台游戏引擎,提供了丰富的音频处理功能,助力开发者轻松实现复杂音效。本文将探讨如何利用Unity的音频设计提升游戏氛围,并通过具体示例代码展示实现过程。例如,在恐怖游戏中,阴森的背景音乐和突然的脚步声能增加紧张感;在休闲游戏中,轻快的旋律则让玩家感到愉悦。
70 0
|
2月前
|
存储 缓存 关系型数据库
深入解析MySQL中的表类型及其应用场景
【8月更文挑战第31天】
169 0
|
2月前
|
移动开发 前端开发 JavaScript

推荐镜像

更多