关于 ModuleFederation 和前端跨工程模块化方面的实践

4,427 阅读4分钟

前端跨工程模块化,是随着前端应用越来越复杂,大部分具有一定规模的企业都会面临不同产品线开发的功能(前端业务模块)如何在不同工程中复用的问题。

就目前前端技术的发展来看,跨工程方面的技术尚处于发展期,和单体工程不同,跨工程之间的代码复用方案在前端技术领域还不够成熟。

再展开跨工程方面的问题之前,我们先来简单的回顾下前端工程化的发展历程。

根据我这些年的经验,我对前端工程化的发展大致分为如下几个阶段。

  1. 手工管理代码文件的阶段
    • 主要以文件管理为主,文件的加载合并基本都已手工为主
    • 自动化工具和单一的模块加载器开始出现,但处理单位依然以文件为主
  2. 以工程为单位对前端代码进行管理
    • 各种包管理工具开始出现,围绕代码包的管理,加载,整合,让前端有了工程概念
    • 但管理方式和处理对象以单体工程为主和 spa 类应用的兴起相符合,同时期的移动端应用也多为单体应用
  3. 多工程管理阶段
    • 这一阶段的问题变得更加复杂,前端工程需要继续保持之前单体工程化的能力,独立发布部署和维护,但构建后的代码又要集成到同一个运行环境中。

    • 目前前端工程化在大部分成规模的公司都处于这一阶段,而大部分中小企业则尚处于上一阶段,跨工程的问题开始出现但不普世。

    • 这一阶段诞生了一些新的技术,例如以容器化的各种微前端框架,微前端主要解决多个应用集成后的环境冲突问题,但相比这个问题更容易出现在大型且有一定的历史的企业,而 webpack5 的 module federation 则更聚焦大多数前端会面临的问题,即不同工程内的模块如何共享的问题。

针对上述阶段我们不过多展开,本文主要想讨论的是跨工程间模块复用的问题,目前这一领域据我所知仅有 module federation 一项。

但 mf 有一些比较致命的问题

  1. 不支持 webpack5 以前的版本
  2. 不支持其他打包工具或者无构建的纯 JS 模块

总体来说 mf 是一个很有创意的方案,但它和 webpack5 过度耦合的设计和实现影响了这一技术的发展,以及其成为标准的可能

说了这么多,现在来讨论下在跨工程模块共享技术上我们的实践,如果你也遇到同类的问题,或许可以参考我们的方案

首先我们设想的跨工程模块的使用场景和 webpack5 的 mf 类似

import { create } from '@rdeco/core'

create({
  name:"foo",
  exports:{
    send(msg, next){
      console.log(msg) // 'hello'
      next('world')
    }
  }
})

// localhost:3000/bar.js
import { foo } from 'remote://foo'

const res = await foo.send('hello')
console.log(res) // world
// babel.config.js

module.exports = {
  plugins:[['rdeco',{
    moduleMap:{
      foo:'http://localhost:3001/foo.js'
    }
  }]]
}

这是我们使用 rdeco 实现的一个可用的版本,相比 mf,这套方案更有优势

  1. 和构建工具无关,可以实现任意 webpack 版本的 mf,因为是一套运行时的方案,因此无论什么构建工具都可以支持。
  2. 基于事件驱动,不需要关心模块是否就绪的问题
  3. 不依赖 npm,rdeco 模块不是标准的 es6 模块,但这一点我认为现行的 es6 模块标准很难解决跨工程模块引用的问题。

总体而言在我们的实践过程中,这套方案足够轻量,并且独立工程输出的模块在不同工程间也非常易于调试,几乎和单体工程下的模块在开发体验上几乎是一致的。

基于这套方案,即使只有 2 - 3 个人的超小型前端团队也可以实现独立的工程架构设计,将不同类型的组件合理的划分到不同的工程中,然后很容易的在运行环境对他们进行整合

这套方案中的技术已经开源,如果你感兴趣可以自行搜索。