Module Federation 带来的可能不仅仅是微前端

501 阅读4分钟

背景

话说这天来了一个新需求:将 A 项目的某个功能移植到 B 项目中,并且希望后续二者同步迭代。

作为一名前端开发者,首先我们首先就 pass 了 CV 大法;想到用npm发布组件,于是我们一顿操作,在两个项目中都安装了同一个组件,后续迭代功能时只需要更新公共组件然后在两个项目中同步更新组件版本即可。

过了几天发现功能反馈不错,于是要在项目 C、项目 D 也加入这个功能,

QQ20220215-0.gif

此时你顿感不妙,意识到该方案需要大量的手动操作,不行不行,得另辟蹊径。

翻阅资料时发现在收藏夹吃了两年灰的笔记:webpack5 Module Federation

Module Federation

多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。

这通常被称作微前端,但并不仅限于此。

Module Federation的所有功能都在一个叫做 ModuleFederationPlugin 的插件中实现,但仅可webpack 5 及以上使用。

Module Federation的使用非常简单:

new ModuleFederationPlugin({
  name: 'app1', // 注册到全局的应用名称
  filename: 'remoteEntry.js', // 应用用于共享的部分,默认是'remoteEntry.js'
  remotes: {
    // 当前应用需要消费的其他服务提供方
    app2: 'app2',
    app3: 'app3',
  },
  exposes: {
    // 当前应用作为服务提供方时提供的模块
    button: './src/button',
  },
  shared: ['react', 'react-dom'], // 多个应用共存时的公共依赖,主要是避免出现多个相同依赖的问题。
});

我们在基座应用服务提供方编写简单demo

demo代码.png

效果如下

preview.gif

原理

跨应用共享组件,看着很神奇,是如何做到的呢?

webpack中有一个专门的运行时用于解析入口文件提供的模块,运行时的核心内容如下:

  • modulesMap:一个Map结构key是模块路径,value是模块内容
  • require:一个方法,用来解析静态import,加入到modulesMap
    // 静态import
    import React from 'react'
    
  • require.ensure:一个方法,用来解析动态import,通过网络请求加载异步chunk文件,并把文件内容合并进modulesMap
    // 动态import
    const AsyncComponent = React.lazy(() => import('remote-app/async-component'))
    

Module Federation就是在这个基础上的扩展:

  1. 首先解析文件头部的import语句,如果是共享(shared)模块,则转化为require.ensure,通过网络请求将chunk合并到modulesMap中;接着初始化shared-scope,用来处理依赖共享的逻辑
  2. 解析到动态import语句时,如果是远端(remote)模块,则转化为require.ensure,通过网络请求将chunk合并到modulesMap中;利用shared-scope复用依赖,实现依赖的共享
  3. 解析完模块后,最后执行用户代码

模块 本质上是 JS 代码片段(这种代码片段一般称为 chunk )。因此,Module Federation 实际上是 chunk 的组合。不管是共享模块还是远端模块,都是使用require.ensure加载一些异步chunk

而在Module Federation模式下避免不了相同依赖的情况,因此多了一个shared-scope的概念,用来实现依赖共享的相关逻辑。

src=http___p0.itc.cn_images01_20210518_e97603be24d4434b856a1a9d0718d9f1.jpeg&refer=http___p0.itc.jpeg

所以Module Federation是多个应用之间chunk的直接复用

4.png

不仅仅是微前端

看着确实有点微前端的意思,但不仅仅可以作为微前端哦~

相比于qiankunModule Federation看起来更注重模块,将多个模块拼接成到应用中。

模块本质上就是js嘛,所以我们可以尽情发挥想象。

  • 远程加载组件

    毕竟本身就支持从远程地址加载组件嘛,省去了我们自己折腾的步骤

  • 组件热更新

    远程组件更新后,组件消费方无需编译即可看到效果

  • 跨版本组件库

    再也不用担心技术栈版本不一致的问题

  • 在多个应用中复用同一套资源

    有点类似中台概念,多个应用中难免有相同或相似的功能点,这就会造成大量代码重复。而常规的npm管理组件又避免不了手动同步组件版本的问题,因此Module Federation也非常适合这种场景

    诶,这不就是我们文章开头所说的场景嘛,非常适合用Module Federation

    src=http___pic2.zhimg.com_50_v2-ad7fcdbeedf8c4c669b0753ef8927fab_hd.jpg&refer=http___pic2.zhimg.jpeg

但万事万物都存在双刃剑,Module Federation的缺点也显而易见

  • 远程组件的安全性问题

    如果没有一定的安全限制和测试规范,万一被谁改坏了代码,leader再也不用担心谁的绩效打C问题

  • 版本要求过高

    Webpack5及以上,历史项目迁移需要一定成本

  • 实时查看远程模块的代码比较困难

    毕竟是远端资源,要想编译器支持实时查看远端编译前的代码还是比较困难的

写到最后

Module Federation只是一个可选项,如果正好你遇到了类似的问题,用该方案去解决也是可以的。

但也要小心这把双刃剑带来的反噬哦~

相关资料

某个基于该特性的微前端框架

demo合集

投稿来自:jarvis