想象一下,在 webpack5 还没出来前,前端使用第三方组件库,例如使用 dayjs 日期处理库,都是通过 npm i dayjs -s 安装 dayjs 模块到项目里,然后就可以通过 require 或者 import 来使用。整个过程也不是很麻烦,但是如果某一天 dayjs 这个库修复了一个重大 bug,我们得重新通过 npm 来安装最新版本,然后重启项目,那么有没有一个更简单的方案呢?

模块联邦

为了减少对 npm 包的安装频率,还有避免本地安装 node_modules 包,webpack 开发团队想出了一个解决方案,能否将第三方库放到网络上单独部署,如果项目需要使用再从该库的地址上直接拉取下来使用,这样就不需要通过 npm 来安装,更新指定依赖包,也方便实时保持库的新版本。感觉有点类似以前通过 cdn 引入第三方 js 库的感觉

模块联邦还可以实现微前端服务

什么是微前端

微前端是一个技术栈无关的多应用集成思想,将一个大应用拆分成多个子应用来开发,每个子应用可以使用自己的技术栈(vue,angularjs,reactjs,jquery)进行独立开发,部署。最后通过主应用基座来集成各个子应用。具体细节这里不展开讲

模块联邦如何实现模块共享

webpack 提供了一个 ModuleFederationPlugin 插件,能够实现模块的发布和引入, ModuleFederationPlugin 插件常用的配置有以下几个

  • name : 必须,唯一的库名,使用者通过 [name]/[exposes]使用
  • filename :必须,暴露的文件名,在使用者通过 remotes 来引入
  • library :可选, umd 的名称,类似于 jQuery 的$,lodash 的_
  • exposes : 可选,配置暴露指定的模块,供其他人使用
  • remotes : 可选,表示使用其他远程的模块
  • shared :可选, 配置共享库,例如 shared: ['react', 'react-dom'] 意思是宿主和引用依赖的模块共享配置的库,如果 host 有该库则不需要再次下载,直接使用 host 已有的库

接下来一步一步实现一个模块联邦功能

模块的开发和部署

通过 CRA(create react app)来创建一个应用。我们创建一个 common-component 项目

然后配置 ModuleFederationPlugin ,在 config-overrides.js 文件里面新增以下配置。关于 cra 如何自定义 webpack 配置可以查看这篇文章

module.exports = override(
(config) => {
config.output.publicPath = 'http://localhost:3000/';
return config;
},
addWebpackPlugin(
new ModuleFederationPlugin({
name: 'ccomponent',
filename: 'remoteEntry.js',
exposes: {
'./button': './src/button/index.jsx',
},
})
)
);

注意这里的 publicPath 是必须的,获取组件的时候会从当前 host 下获取 button 这个组件,通过 ModuleFederationPlugin 的 exposes 选项暴露 button 组件。

我们在 src 新建一个 button 组件

// src/button/index.jsx
import React from 'react';
import PropTypes from 'prop-types';
const Button = (props) => {
const { children } = props;
return <button>{children}</button>;
};
Button.propTypes = {
children: PropTypes.node.isRequired,
};
export default Button;

通过 npm run start 启动,这时候可以访问到 http://localhost:3000/remoteEntry.js

模块的使用

我们再通过 CRA 创建一个名称 app 的项目,同样的在 config-overrides.js 里面引入 ModuleFederationPlugin

module.exports = override(
addWebpackPlugin(
new ModuleFederationPlugin({
name: 'app',
remotes: {
ccomponent: 'ccomponent@http://localhost:3000/remoteEntry.js',
},
})
)
);

cccomponent 是上面 common-component 项目里面配置的 name

然后在 app 里面使用 common-component 的 button 组件,这里使用 React.lazy 来异步加载该组件

const Button = React.lazy(() => import('ccomponent/button'));
function App() {
return (
<div className="App">
<React.Suspense fallback="Loading Component">
<Button>hello</Button>
</React.Suspense>
</div>
);
}

到此为止,就可以在 app 项目里面看到 button 了,666 ~

总结

webpack 提供对模块联邦会使以后模块使用更加方便,快捷,一切工具都是为了方便开发,给 webpack 的贡献者点个赞!