webpack5主要新特性介绍
- 资源模块;
- moduleIds & chunkIds 的优化;
- 移除Node.js的polyfill;
- 更强大的tree-shaking;
- 持久化缓存提高性能;
- Module Federation 模块联邦;
一、资源模块
资源模块是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外的loader
- raw-loader => asset/source 导出资源的源代码;
- file-loader => asset/resource 发送一个单独的文件并导出URL;
- url-loader => asset/inline 导出一个资源的data URL;
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。 具体代码实现:
module.exports = {
...
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,//不打包 不编译node_modules下面的文件
use:[
{
loader:'babel-loader',
options:{
presets:[
'@babel/preset-env',
'@babel/preset-react'
]
}
}
]
},
{
test:/\.png$/,
type:'asset/resource'//对标file-loader
},
{
test:/\.ico$/,
type:'asset/inline'//对标url-loader 模块的大小<limit base64字符串
},
{
test:/\.txt$/,
type:'asset/source'//对标raw-loader
},
{
test:/\.jpg$/,
type:'asset',//对标raw-loader
parser:{
dataUrlCondition:{
maxSize:4*1024
}
}
},
{
test:/\.css$/,
use:['style-loader','css-loader']
},
]
}
}
二、moduleIds & chunkIds 的优化
概念和选项
- module: 每一个文件其实都可以看成一个module;
- chunk: webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个chunk
- 在webpack5之前,没有从entry打包的chunk文件,都会以1、2、3....的文件命名方式输出、删除某些文件,从而可能导致缓存失效 生产模式下,默认启用这些功能chunkIds: "deterministic", moduleIds: "deterministic", 此算法采用 稳定性 的方式将短数字ID(3或4个字符)的hash值分配给modules 和 chunks; chunkId设置为deterministic,则output中的chunkFilename里的[name]会被替换成确定性短数字ID; 虽然chunkId不变(不管值是 deterministic | natural | names),但更改chunk内容,chunkhash还是会改变的。
三、移除Node.js的polyfill
Webpack4 带来了许多Node.js核心模块 polyfill, 一旦模块中使用了任何核心模块(如crypto),这些模块就会被自动启用 Webpack5 不在自动引入这些polyfill
安装
npm i crypto-js crypto-browserify stream-browserify buffer -D
使用: src/index.js
import CryptoJS from 'crypto-js';
console.log(CryptoJS.MD5('gongjin').toString());
webpack.config.js
module.exports = {
...
resolve:{
/* fallback:{
'crypto':require.resolve('crypto-browserify'),
'stream':require.resolve('stream-browserify'),
'buffer':require.resolve('buffer')
}, */
fallback:{
'crypto':false,
'stream':false,
'buffer':false
}
}
}
在webpack5中还是要使用polyfill(例如:crypto)该怎么使用呢?
npm i crypto-js crypto-browserify -D
// index.js 使用文件中
import CryptoJS from 'crypto';
console.log(CryptoJS);
console.log(CryptoJS.MD5('gongjin').toString());
// pachage.json 文件中添加
"browser": {
"crypto": "crypto-js" // crypto-js为前端使用的crypto插件,需要npm i crypto-js
},
// webpack.config.js 文件中添加配置
module.exports = {
...
resolve:{
fallback:{
'crypto':require.resolve('crypto-browserify'), // 需要使用crypto就加上这个
'stream':require.resolve('stream-browserify'), // 需要使用stream就加上这个
'buffer':require.resolve('buffer') // 需要使用buffer就加上这个
}
}
...
}
四、更强大的tree-shaking
// inner.js
import { test1 } from './module1.js';
test1();
// module1.js
import { test3 } from './module2';
export function test1(){
console.log('test1');
}
export function test2(){
console.log('test2' + test3);
}
// module2.js
export function test3(){
console.log('test3');
}
export function test4(){
console.log('test4');
}
// webpack.config.js 文件中添加配置
module.exports = {
...
mode: 'production',
optimization:{
usedExports: true, // 标记出未被导出的变量, 使用到的文件才导出
}
...
}
sideEffects(了解即可)
函数副作用只当调用函数时,除了返回函数值之外,还产生了附加的影响,例如修改全局变量;
严格的函数式语言要求函数必须无副作用;
// webpack.config.js 文件中添加配置
module.exports = {
...
mode: 'production',
optimization:{
sideEffects: true, // true: 开启副作用,就会使没有用到的地方打包进去, 默认true不会打包
}
...
}
五、持久化缓存提高性能
第一次构建是一次全量构建,它会利用磁盘模块缓存(以空间换时间),使得后续的构建从中获利。
后续构建具体流程是:读取磁盘缓存 -> 校验模块 -> 解封模块内容。
// webpack.config.js
module.exports = {
cache: {
// 将缓存类型设置为文件系统
type: 'filesystem', // 默认是memory
// 默认路径是 node_modules/.cache/webpack
// 可以自定义文件目录:.cache_webpack5
cacheDirectory: path.resolve(__dirname, '.cache_webpack5')
}
六、模块联邦
什么是模块联邦?
Module Federation是为了解决独立应用之间代码共享问题。可以在项目内动态加载其他项目的代码,同步可以共享依赖。 通过细化功能模块、组件复用、共享第三方库、runtime dependencies线上加载npm包等,可以更好的服务于多页应用、微前端等开发模式。
以前我们常用的npm包来进行一个公共组件的引用(即UMD)
动机
Module Federation的动机是为了不同开发小组间共同开发一个或者多个引用; 应用将被划分为更小的应用块,一个应用块,可以是比如头部导航或者侧边栏的前端组件,也可以是数据获取逻辑的逻辑组件 每个应用块由不同的小组开发; 应用或者应用块共享; Module Federation
使用Module Federation时,每个应用块都是一个独立的构建,这些构建都将编译为容器;
容器可以被其他应用或者其他容器应用;
一个被引用的容器被称为 Application A / Application B ,引用者被称为 Application B / Application A, Application A / Application B 暴露模块给 Application B / Application A, Application A / Application B则可以使用这些暴露的模块,这些模块也可以被称为 remote 模块; 从而实现微前端的效果;
微前端也是一种模块共享的方式,主要解决多项目并存问题,多项目并存的最大问题就是模块共享,不能有冲突。 具体代码实现:
// Application A 亦可以叫远程模块
// webpack.config.js
module.exports = {
...
plugins: [
...
new ModuleFederationPlugin({
name:'remoteVar',// remote向外暴露的全局变量名, 唯一
filename:'remoteEntry.js',//构建出来的文件名
// 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用
exposes:{
'./NewsList':'./src/NewsList'
},
remotes:{
host:'hostVar@http://localhost:8081/remoteEntry.js'
},
// 可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM 版本,从而统一版本;
shared:['react','react-dom']
})
]
};
// Application B 亦可以叫本地模块
// webpack.config.js
module.exports = {
...
plugins: [
...
new ModuleFederationPlugin({
name:'hostVar', // remote向外暴露的全局变量名, 唯一
filename:'remoteEntry.js',
remotes:{
remote:'remoteVar@http://localhost:8080/remoteEntry.js'
},
exposes:{
'./Slides':'./src/Slides'
},
shared:['react','react-dom']
})
]
};