持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
共享模块的处理
- npm模式模块共享 npm包带来了变革式的依赖好处,同时也带来了升级麻烦,再npm包中的做共享模块都是通过开发一个npm包的功能,最后发布在npm仓库中,每个项目npm i安装
致命缺点: 子包更新后,使用子包的各个项目如何更新,各个项目需要重新编译打包上线。
- lib包(umd,esmodule)形式,在html中想要axios的功能,直接script中引用axios的cdn lib包后,就能使用方法了
缺点:
- 各个lib包容易依赖冲突
- 编译的时候lib包容易重复很多内容,不易优化
- 模块联邦
这个功能比较完美的解决上面几种共享模式下的问题,既可以做到打包发布模块供给后,消费者能够实时保持同步,也可以进行代码构建时候的优化,他可以在一个应用中直接导出使用另外一个应用的模块,相当于两个应用能够互相依赖,却能分别不将对方打包进去。
webpack实现模块联邦
webpack在5版本提供了这个功能,使用时插件配置ModuleFederationPlugin
实操例子:一个项目APP1有一个button1,另一个项目App2有一个button2,现在想实现App1不但有button1,也想有button2。但是又不想将两个button完全拆离成功共享组件,两个button更新互相也能更新
APP1项目入口:
入口主要是引用了一个本地的button1,和import
button2,这里的import导入方式,不是导入的package.json的npm包,是后面的打包编译配置的形式
import LocalButton from './Button';
import React from 'react';
const RemoteButton = React.lazy(() => import('app2/Button'));
const App = () => (
<div>
<h1>Bi-Directional</h1>
<h2>App 1</h2>
<LocalButton />
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
export default App;
wenpack配置参数简介
- name 必须,当前应用的名字,全局唯一ID,通过 name/{expose} 的方式使用
- library 可选,打包方式,与 name 保持一致即可
- filename 可选,打包后的文件名,对应上面的 remoteEntry.js
- remotes 可选,表示当前应用是一个 Host,可以引用 Remote 中 expose 的模块
- exposes 可选,表示当前应用是一个 Remote,exposes 内的模块可以被其他的 Host 引用,引用方式为 import(name/{expose})
- shared 可选,依赖的包(下面包含了 shared 中包含的配置项)
- 如果配置了这个属性。webpack在加载的时候会先判断本地应用是否存在对应的包,如果不存在,则加载远程应用的依赖包。
- 以 app2 来说,因为它是一个远程应用,配置了
["react", "react-dom"]
,而它被 app1 所消费,所以 webpack 会先查找 app1 是否存在这两个包,如果不存在就使用 app2 自带包。 app1里面同样申明了这两个参数,因为 app1 是本地应用,所以会直接用 app1 的依赖。 - shared 配置项指示 remote 应用的输出内容和 host 应用可以共用哪些依赖。 shared 要想生效,则 host 应用和 remote 应用的 shared 配置的依赖要一致。
- import 共享依赖的实际的 package name,如果未指定,默认为用户自定义的共享依赖名,即 react-shared。如果是这样的话,webpack 打包是会抛出异常的,因为实际上并没有 react-shared 这个包。
- singleton 是否开启单例模式,true 则开启。如何启用单例模式,那么 remote 应用组件和 host 应用共享的依赖只加载一次,且与版本无关。 如果版本不一致,会给出警告。不开启单例模式下,如果 remote 应用和 host 应用共享依赖的版本不一致,remote 应用和 host 应用需要分别各自加载依赖。
- requiredVersion 指定共享依赖的版本,默认值为当前应用的依赖版本。- 如果 requiredVersion 与实际应用的依赖的版本不一致,会给出警告。
- strictVersion 是否需要严格的版本控制。单例模式下,如果 strictVersion 与实际应用的依赖的版本不一致,会抛出异常。默认值为 false。
- shareKey 共享依赖的别名, 默认值值 shared 配置项的 key 值。
- shareScope 当前共享依赖的作用域名称,默认为 default。
- eager 共享依赖在打包过程中是否被分离为 async chunk。eager 为 false, 共享依赖被单独分离为 async chunk; eager 为 true, 共享依赖会打包到 main、remoteEntry,不会被分离。默认值为 false,如果设置为 true, 共享依赖其实是没有意义的。
- shareScope 所用共享依赖的作用域名称,默认为 default。如果 shareScope 和 share["xxx"].shareScope 同时存在,share["xxx"].shareScope 的优先级更高。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
const deps = require('./package.json').dependencies;
module.exports = {
entry: './src/index',
mode: 'development',
devServer: {
port: 3001,
},
output: {
publicPath: 'http://localhost:3001/',
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: ['@babel/preset-react'],
},
},
],
},
plugins: [
new ModuleFederationPlugin({
name: 'app1',
filename: 'remoteEntry.js',
remotes: {
app2: 'app2@http://localhost:3002/remoteEntry.js',
},
exposes: {
'./Button': './src/Button',
},
// sharing code based on the installed version, to allow for multiple vendors with different versions
shared: [
{
...deps,
react: {
// eager: true,
singleton: true,
requiredVersion: deps.react,
},
'react-dom': {
// eager: true,
singleton: true,
requiredVersion: deps['react-dom'],
},
},
],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
};
效果:
APP2项目入口
import LocalButton from './Button';
import React from 'react';
const RemoteButton = React.lazy(() => import('app1/Button'));
const App = () => (
<div>
<h1>Bi-Directional</h1>
<h2>App 2</h2>
<LocalButton />
<React.Suspense fallback="Loading Button">
<RemoteButton />
</React.Suspense>
</div>
);
export default App;
webpack关键配置
plugins: [
new ModuleFederationPlugin({
name: 'app2',
filename: 'remoteEntry.js',
remotes: {
app1: ['app1@http://localhost:3001/remoteEntry.js'],
},
exposes: {
'./Button': './src/Button',
},
shared: [
{
...deps,
react: {
// eager: true,
singleton: true,
requiredVersion: deps.react,
},
'react-dom': {
// eager: true,
singleton: true,
requiredVersion: deps['react-dom'],
},
},
],
}),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
效果: