一代版本一代神,现代的前端已经不是会用一个react就能混过去的了,虽然正式工作上还是打螺丝,调包侠+切图仔,但是有些时候,新知识不可不学。 有两个概念近些年很火,一个是module federation一个是monorepo,光看名字可能觉得有点像,但是其实是两个东西。
模块联邦module federation
这是webpack在v5被投入生产,并作为v5的核心特性之一。它的出现解决了一些问题,或者说它适用于以下场景:
- 微前端架构:实现独立部署的子应用动态集成(如电商平台的首页、商品页拆分)。
- 大型应用拆分:逐步重构单体应用,降低维护成本。
- 跨团队代码共享:避免重复发布 npm 包,直接运行时复用模块。
基本上可以说他是微前端的方式。当然市面上肯定大部分工具也会跟上webpack,比如vite就通过rollup钩子实现了(vite-plugin-federation),又比如@module-federation/rollup插件,next-mf插件,Rspack(基于webpack)。 接下来看下他的主要配置
主应用webpack.config.js:
css
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
...
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'remote@http://localhost:3001/app1Entry.js', // 子应用地址
},
shared: {
react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
};
这里看到了一个shared配置singleton,指明了哪些模块应该作为单例共享,也就是单例模式,true的话父子应用共用一个实例,避免重复加载,但是当插件需要完全隔离的依赖如react环境时,可以设置成false;
remotes字段指定了远程微应用的名称和其远程入口文件URL。 当主应用需要用到子应用的玩意时,如下:
javascript
// 动态加载子应用的Button组件
const RemoteButton = React.lazy(() => import('app1/Button'));
子应用webpack配置
less
const { ModuleFederationPlugin } = require('webpack').container; module.exports = {
entry: './src/moduleOutput.js', // 必须通过moduleOutput.js间接引入
plugins: [
new ModuleFederationPlugin({
name: 'app1', // 子应用名称(全局唯一)
filename: 'app1Entry.js', // 入口文件名(默认),模块清单名
exposes: {
'./Button': './src/Button.js', // 暴露组件路径
},
shared: {
react: { singleton: true }, 'react-dom': { singleton: true }
},
}),
],
};
go
// moduleOutput.js
import('./index'); // 延迟加载业务代码
注意,这里我们看到entry不是我们平时项目脚手架自带的index.js/ts,而是通过其他文件moduleOutput.js,这个文件的存在是为了正确执行模块联邦的动态加载机制和代码执行顺序,而主要导致这样的原因是:
- 主应用加载子应用时,会先下载
app1Entry.js模块清单文件,然后在按需加载子模块,比如exposes中的Button,如果子应用直接以index.js作为entry,可能会在子应用的子模块模块被主应用加载时,子应用的依赖(如react)未准备就绪,毕竟子应用也是配置了按需加载,这就会导致运行错误 app1Entry.js这文件的作用就是延迟执行,通过动态导入(import())将子应用的业务代码(如index.js)的加载推迟到 所有共享依赖(如 React)已就绪后 。 当然,如果父子应用没有共享的模块,那么这个文件也就没必要了,另外shared的依赖中,有一个requiredVersion字段,可以让父子协商是否共享模块。
monorepo
这其实不是具体工具,而是一种思想:强关联性,同一业务线的项目,可以将项目放在同一个版本管理工具中(比如git),这么做的好处有很多,比如
- 代码共享与复用,一些公共的ts定义,和api接口层,组件能直接引用,并且所有项目共用顶层
node_modules,减少重复依赖安装(通过workspaces功能) - 统一工程化配置,比如eslint,pritter,jest和webpack等构建工具等,这会让维护成本降低。
- 统一版本管理,通过
changesets等工具自动化版本号和changeLog管理 - 版本提交的完整性,当修改底层库时,可同时更新依赖他的所有应用,这保证了提交的完整性
- 依赖关系可视化,可用preune等命令工具生成关系图,便于框架优化
- 统一CI配置,所有项目共用一套CI/CD流程 当然也不是所有业务线都要这么做,这适用于部分场景:
- 微前端架构
- 全栈项目(对我来说当然是js的全栈)
- 多应用平台,比如pc,mobile共用业务逻辑
- 大型团队协作,减少代码碎片化
- 替代npm的频繁更替 常用来实现monorepo的工具有
pnpm,lerna,turborepo,我一般使用pnpm
总结
这么一盘,好像两者也不是毫无联系,这都和微前端扯到了关系,但是两者场景并不是非常一致,且手段不同。最共同的点是,他们都是要学的东西。