配置文件的分离
一个项目可以根据不同的环境,比如生产环境和开发环境,进行不同的配置,有两种实现方式
方案一:所有webpack配置都在同一个文件中,根据执行时传入的环境变量判断环境
// 执行build和serve指令接收不同的环境变量
"scripts": {
"build": "webpack --config ./config/webpack.common.js --env production",
"serve": "webpack serve --config ./config/webpack.common.js --env development"
},
// 这里通过判断传入的 env 值来判断环境
const path = require('path');
module.exports = function (env) {
const isProduction = env.production;
return {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, '../build'),
},
};
};
方案二:针对不同的环境创建不同的配置文件,提取公共配置在common文件中
// 执行build和serve指令调用不同的文件
"scripts": {
"build": "webpack --config ./config/webpack.prod.js",
"serve": "webpack serve --config ./config/webpack.dev.js",
},
代码分离
代码分离(Code Splitting)是webpack一个非常重要的特性:
- 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件
- 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载就会影响首页的加载速度
- 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能
webpack中常用的代码分离有三种:
- 入口起点:使用entry配置手动分离代码
- 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码
- 动态导入:通过模块的内联函数调用来分离代码
第一种
入口手动分离代码
上面的代码虽然在同一个文件中,但是代码逻辑毫无关联,所以我们手动将它分离为两个文件
在webpack的config中设置两个文件的入口,再通过[name]的方式命名打包后的文件
最终打包的结果如下
这种方法一看就很扯,项目大起来根本分离代码就能把人累死,所以根本没人用Entry Dependencies(入口依赖)
假如我们的index.js和main.js都依赖两个库:lodash、dayjs
- 如果我们单纯的进行入口分离,那么打包后的两个bundle都会有一份相同的lodash和dayjs
- 事实上我们可以对他们进行共享
假如我们的两个文件都引入了lodash和dayjs这两个库
我们可以如下操作实现
引入webpack原生的一个plugin插件
const TerserPlugin = require('terser-webpack-plugin');
然后在entry中如下配置
entry: {
main: { import: './src/main.js', dependOn: ['lodash', 'dayjs'] },
index: { import: './src/index.js', dependOn: ['lodash', 'dayjs'] },
lodash: 'lodash',
dayjs: 'dayjs',
},
最终打包效果如下
还可以用这种方法简化
entry: {
main: { import: './src/main.js', dependOn: 'shared' },
index: { import: './src/index.js', dependOn: 'shared' },
shared: ['lodash', 'dayjs'],
},
第二种:SplitChunks
另外一种分包的模式是splitChunk,它是使用SplitChunksPlugin来实现的:
- 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件
- 只需要提供SplitChunksPlugin相关的配置信息即可
Webpack提供了SplitChunksPlugin默认的配置,我们以可以手动来修改它的配置:
- 比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all
具体配置:
entry: {
main: './src/main.js',
index: './src/index.js',
// main: { import: './src/main.js', dependOn: 'shared' },
// index: { import: './src/index.js', dependOn: 'shared' },
// shared: ['lodash', 'dayjs'],
},
首先,entry中不需要额外设置
optimization: {
...
splitChunks: {
// async异步导入
// initial同步代码
// all异步同步都导入
chunks: 'all',
},
},
然后,在optimization中添加splitChunks对象,对象中属性值chunks设置为all表示不论是异步代码还是同步代码全部做分包处理,即不同模块加载的相同模块只打包一次,两个模块分别引入,而不是分别打包到两个文件中
minSize
最小尺寸,如果拆分出来一个包,那么拆分出来的这个包的大小最小为minSize
splitChunks: {
chunks: 'all',
// 最小尺寸:如果拆分出来一个,那么拆分出来的这个包的大小最小为minSize
minSize: 20000
},
maxSize
将大于maxSize的包拆分成不小于minSize的包
splitChunks: {
chunks: 'all',
// 将大于maxSize的包,拆分成不小于minSize的包
maxSize: 30000,
},
minChunks
表示引入的包,至少被导入了几次
splitChunks: {
chunks: 'all',
// minChunks只拆分被打包minChunks及以上次数的文件
minChunks: 2,
},
第三种:动态导入
另外一个代码拆分的方式是动态导入,webpack提供了两种实现动态导入的方式:
- 第一种,使用ECMAScript中的import()语法来完成,也是目前推荐的方式
- 第二种,使用webpack一流的require.ensure,目前已经不推荐使用
// 只要是异步导入的代码,webpack都会进行代码分离
import('./foo').then((res) => {
console.log(res);
});
魔法注释
magic comments,可以给动态引入的包打包后的文件命名
输出文件的命名规则
output: {
...
chunkFilename: '[name].chunk.js',
},
在要使用magic comments的文件中这样注释
// magic comments
import(/* webpackChunkName:"foo" */ './foo').then((res) => {
console.log(res);
});
import(/* webpackChunkName:"foo_02" */ './foo_02').then((res) => {
console.log(res);
});
这里的 / xxx / 内容会被上面的 chunkFilename 中的 name 替换掉
最终创建的文件如下图