配置完有关 CSS loader 后,还有一个问题,我们不想将 CSS 都插入到 style 标签中,如果 CSS 样式代码很多,会导致生成的 HTML 文件很大,我们希望使用 <link>
标签引入打包后的 CSS 文件(将 CSS 单独提取出来),这时候就要使用一个插件:mini-css-extract-plugin
。
下载:yarn add mini-css-extract-plugin -D
。
配置:
// webpack.config.dev.js
let MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ....
plugins: [
new MiniCssExtractPlugin({
// 抽离的样式叫什么名字(会生成在 css 文件夹下)
filename: "css/main.css",
// chunkFilename 是异步加载的 CSS 资源名 id 默认从数字 0 开始
chunkFilename: 'static/css/[id].css'
})
],
module: {
rules: [
{
test: /\.(sc|sa)ss$/,
use: [
{ // 拆分 css
loader: MiniCssExtractPlugin.loader,
options: {
// 生成使用 ES6 模块语法的 JS 模块
// 使用ES模块是有益的,例如在模块 tree shaking 的情况下
esModule: true,
hmr: isDev, // 是开发环境时,开启热更替
reloadAll: true // 当热更替无法正常工作时,重新加载
}
},{
loader: "css-loader",
options: { // 启用 CSS Module
modules: true
},
"postcss-loader", "sass-loader"
]
}
]
}
}
压缩 js 代码
当指定了 mode: production
后 ,webpack 会自动压缩代码。当然也可以自己来指定:
module.exports = {
optimization: {
// 指定该项后,代码会被压缩
minimiza: true,
}
}
webpack4 中集成了 terser-webpack-plugin
来压缩 JavaScript 代码。也可以自定义该插件的配置。
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimize: true,
minimizer: [new TerserPlugin({
// 插件的作用范围,默认值是 /\.m?js(\?.*)?$/i
test: /\.jsx?$/,
// 不包含哪些文件或目录,也可以是一个字符串数组或正则表达式数组,表示设置多个路径
exclude: /\/excludes/,
// 开启多进程压缩,默认是 false
parallel: true,
// 是否生成 source-map,这需要 devtool 的配置。默认是 false
sourceMap: true,
// 是否开启缓存,默认是 false,如果传入字符串,缓存路径就是该字符串
cache: false,
// 针对哪些文件或目录有效,也可以是一个字符串数组或正则表达式数组,表示设置多个路径
include: /\/src/
})],
},
};
关于 terser-webpck-plugin 的更多配置可以参考官方 GitHub:terser-webpack-plugin
压缩 CSS
mini-css-extract-plugin 这个插件可以将样式提取出来,要压缩 CSS 文件,可以使用 optimize-css-assets-webpack-plugin
这个插件,该插件支持 webpack4.0 及以上版本。
配置如下:
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({ /* ... */ }),
new OptimizeCssAssetsPlugin({
// 生效范围,只压缩匹配到的资源,默认是 /\.css$/g
assetNameRegExp: /\.optimize\.css$/g,
// 压缩处理器,默认为 cssnano
cssProcessor: require('cssnano'),
// 压缩处理器的配置,默认值是 {}
cssProcessorPluginOptions: {
preset: ['default', { discardComments: { removeAll: true } }],
},
// 是否展示 log
canPrint: true
})
]
}
}
需要注意的是,使用 OptimizeCssAssetsPlugin 插件压缩 CSS 文件后,JS 文件压缩可能就会失效。这时候就需要使用 JS 压缩插件:UglifyJsPlugin
。
下载:yarn add uglify-js-plugin -D
。
在 optimization
的 minimizer
配置项中配置:
{
minimizer: [
new UglifyJsPlugin({
// 是否需要缓存(是)
cache: true,
// 是否是并发打包(是)
parallel: true,
// 是否生成源码映射(是)
sourceMap: true
}),
new OptimizeCssAssetsPlugin({
// ...
})
]
}
代码分片
在 js 文件中,常常会引入第三方模块,比如 React、Vue等。而且一个或多个文件可能都要引入这些模块,导致一个 js 文件会很大。我们可以使用插件给第三方的模块和业务中不常更新的模块创建一个入口。这里就要再添加一个配置项 —— optimization.SplitChunks
。webpack 会根据你选择的 mode 来执行不同的优化,不过这些优化也可以手动配置和重写。优化配置大部分都在 optimization
这个配置项中。
默认情况下,optimization 的配置是这样的:
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
配置内容很多,简单介绍一下。
chunks
:他有三个选项:async
(默认)、initial
、all
。async
只提取异步chunk
,initial
则只针对入口 chunk 生效,而 all 表示两种模式都开启。minChunks
:表示该模块被 n 个入口同时引用才会进行提取,默认是一次引用就提取。比如在写 React 程序时,React 模块会被经常引入,这时候就有必要进行提取一些,当然也可以设置成Infinity
表示所有模块都不会被提取;name
:字段默认是 true,表示SplitChunks
可以根据cacheGroups
和作用范围自动为新生成的chunk
命名,并以automaticNameDelimiter
的值的形式进行分隔。如:vendors~a~b~c.js 的意思就是cacheGroups
为vendors
并且该 chunk 是由 a、b、c 三个入口 chunk 所产生的;minSize
:表示抽取出来的文件在压缩前的最小值,当大于 minSize 时才拆分代码;maxSize
:表示抽取出来的文件在压缩前的最大值,默认为 0,表示不限制最大大小;maxAsyncRequests
:按需加载的最大并行请求数;maxInitialRequests
:一个入口最大并行请求数;cacheGroupts
:可以理解为分离 chunks 时的规则。默认情况下有两种规则 —— vendors 和 default。vendors 用于提取所有 node_modules 中符合条件的模块(第三方模块),default 则作用于被多次引用的模块(我们自己写的公共模块)。其中的配置项:test
表示路径匹配,模块所在路径;priority
权重,设置打包 chunks 的优先级,默认 vendors 优先级更高,会先查找 node_modules 下的模块,如果 vendors 和 default 中有重名的模块时,priority
就派上用场了;minChunks
与上面的 minChunks 功能相同,但只适用于该分组;reuseExistingChunk
是否重用该 chunk;
我们也可以重新配置:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: { // 第三方模块
test: /[\\/]node_modules[\\/]/,
name: 'vendors', // chunk 名称
priority: 10,
minSize: 3000,
minChunks: 1
},
common: { // 公共模块
name: 'common',
priority: 1,
minSize: 3000,
minChunks: 2
}
}
}
}
webpack 中的 chunk
在 webpack 中有三个生成 chunk 的地方,分别是:
entry
,入口配置项,当值是对象时,对象的键就是 chunk 的名字;splitChunks
,代码分片时,由name
指定分割出的 chunks 的名称;- 异步加载的模块,异步加载的模块也会生成 chunk;
在 html-webpack-plugin 插件中,有一个 chunks
配置项,用来指定在 HTML 文档中加载哪些 chunk,如果不手动指定,它会把所有的 chunk 都加载进来。对于多页应用来说,可能有些 chunk 在这个页面是用不到的,这就要手动配置哪些 chunk 应用到当前文档里。
chunk、bundle 和 module 的区别
module
是各个源码文件,webpack 中一切皆模块;chunk
多模块合并成的块,是内存中的概念。可以由 entry、import()、splitChunk 等产生;bundle
每一个 chunk 在编译打包之后都会产出一个文件,bundle 就是最终输出的文件;
动态链接库
当使用 React 库时,需要引入 React-dom,这两个库文件很大,每次打包会浪费很长时间。而且没必要这么一次次的重复打包(第三方库又不会被修改)。因此,我们希望把 React、React-dom 两个库单独打包出来,以后再用,直接引用打包好的文件即可。这样可以的减少打包时间和打包的文件大小。
使用 dllPlugin 可以做到。首先,需要再创建一个专门打包 react、react-dom 的 webpack 配置文件。并写入以下内容:
// webpack.config.react.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'production',
entry: {
react: ['react','react-dom'],
},
output: {
filename: '_dll_[name].js',
// 打包输出路径
path: path.resolve(__dirnamem,'../build/dll'),
// 将打包好的模块给个名字(导出的变量的名字)
library: '_dll_[name]',
},
plugins: [
new webpack.DllPlugin({
// name 属性值规定与 library 属性值相同
name: '_dll_[name]',
// path 表示产生一个清单,这个清单可以找到打包的文件
path: path.resolve(__dirname,'../build/dll','manifest.json'),
}),
]
}
运行 webpack.config.react.js 文件进行打包:
npx webpack --config webpack.config.react.js
然后来到 HTML 文件,引入打包好的文件:
<script src="/dll/_dll_react.js"></script>
打包后,还需要来到之前 webpack.config.dev.js 配置文件中,让 webpack 知道,在打包之前应该先找到打包好的 _dll_react.js 文件,这样就避免了再次打包 react、react-dom 文件。这时就需要对该文件进行配置:
{
plugins: [
// 引用动态链接库
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname,'../build/dll','manifest.json')
}),
]
}