Github 仓库地址:https://github.com/Evelynzzz/react-webpack-boilerplate
版本:Webpack 4.39.1
相关依赖:
- MiniCssExtractPlugin:0.8.0
- style-loader:1.0.0
- less-loader:5.0.0
- postcss-loader:3.0.0
- css-loader:3.2.0
判断是开发模式还是生产模式
在配置 Webpack 时,需要区分用于开发模式还是生产模式。比如我们只需要在生产模式时压缩 CSS;而在开发模式的时候,我们又希望生成 Sourcemap 便于调试,以及样式热更新。那么,怎么在 webpack.config.js 中判断开发、生产模式呢?
我通常会定义三个 webpack 配置文件:
- webpack.config.base.js:通用的配置,比如入口,出口,插件,loader等。以下两个配置文件会引入此配置,再修改添加其他配置。
- webapck.config.dev.js:开发模式下,启动 webpack-dev-server。
- webapck.config.prod.js:生产模式下,编译打包。
然后在 package.json 中分别配置了 start
和 build
脚本:
"scripts": {
"start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --open",
"build": "cross-env NODE_ENV=production webpack --config webpack.config.prod.js --progress --colors -p"
}
注意命令中通过 定义了变量NODE_ENV
,因此在webpack.config.base.js 中可以通过 process.env.NODE_ENV
获取它的值,从而判断时生产模式还是开发模式。
const devMode = process.env.NODE_ENV === 'development'; // 是否是开发模式
接下来进入正题。
提取 CSS 到单独的文件中
在 Webpack 4 之前,我们使用 extract-text-webpack-plugin 插件来提取项目中引入的样式文件,打包到一个单独的文件中。从 Webpack 4 开始,这个插件就过时了,需要使用 MiniCssExtractPlugin。
This plugin extracts CSS into separate files. It creates a CSS file per JS file which contains CSS. It supports On-Demand-Loading of CSS and SourceMaps.
此插件为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件,并支持 CSS 和 SourceMap 的按需加载。
注意:这里说的每个包含 CSS 的 JS 文件,并不是说组件对应的 JS 文件,而是打包之后的 JS 文件!接下来会详细说明。
情景一
先举一个基础配置的例子。 webpack.config.js:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader','postcss-loader' // postcss-loader 可选
],
},{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader, 'css-loader','postcss-loader','less-loader' // postcss-loader 可选
],
}
],
},
};
基于以上配置,如果入口 app.js 中引用了 Root,Root 引入了 Topics。而 Root.js 中引用样式 main.css,Topics.js 中引用了 topics.css。
// 入口文件 app.js
import Root from './components/Root'
// Root.js
import '../styles/main.less'
import Topics from './Topics'
// Topics.js
import "../styles/topics.less"
这种情况下,Topics 会和 Root 同属一个 chunk,所以会一起都打包到 app.js 中, 结果就是 main.less 和 topics.less 会被提取到一个文件中:app.css。而不是生成两个 css 文件。
Asset Size Chunks Chunk Names
app.css 332 bytes 1 [emitted] app
app.js 283 KiB 1 [emitted] [big] app
情景二
但是,如果 Root.js 中并没有直接引入 Topics 组件,而是配置了代码分割 ,比如模块的动态引入,那么结果就不一样了:
Asset Size Chunks Chunk Names
app.css 260 bytes 1 [emitted] app
app.js 281 KiB 1 [emitted] [big] app
topics.bundle.js 2.55 KiB 4 [emitted] topics
topics.css 72 bytes 4 [emitted] topics
因为这个时候有两个 chunk,对应了两个 JS 文件,所以会提取这两个 JS 文件中的 CSS 生成对应的文件。这才是“为每个包含 CSS 的 JS 文件创建一个单独的 CSS 文件”的真正含义。
情景三
但是,如果分割了 chunk,还是只希望只生成一个 CSS 文件怎么办呢?也是可以做到的。但需要借助 Webpack 的配置 optimization.splitChunks.cacheGroups
。
optimization.splitChunks 是干什么的呢?在 Webpack 4 以前,我们使用 CommonsChunkPlugin
来提取重复引入的第三方依赖,比如把 React 和 Jquery 单独提取到一个文件中。而从 Webpack 4 开始,CommonsChunkPlugin
被 optimization.splitChunks
替代了。从命名也能看出来,它是用来拆分 chunk 的。怎么在这里需要用到这个配置呢?先来看看配置怎么写的:
optimization: {
splitChunks: {
cacheGroups: {
// Extracting all CSS/less in a single file
styles: {
name: 'styles',
test: /\.(c|le)ss$/,
chunks: 'all',
enforce: true,
},
}
}
},
打包结果:
Asset Size Chunks Chunk Names
app.js 281 KiB 2 [emitted] [big] app
styles.bundle.js 402 bytes 0 [emitted] styles
styles.css 332 bytes 0 [emitted] styles
topics.bundle.js 2.38 KiB 5 [emitted] topics
可以看出,样式确实都被提取到一个 styles.css 文件中了。但与此同时多了一个 style.bundle.js 文件,这就是 optimization.splitChunks.cacheGroups
的效果。具体原理就不在此深究,感兴趣的话可以研究一下。
MiniCssExtractPlugin vs. style-loader
首先这两个插件用途完全不同:MiniCssExtractPlugin 提取 JS 中引入的 CSS 打包到单独文件中,然后通过标签 <link>
添加到头部;style-loader 则是通过 <style>
标签直接将 CSS 插入到 DOM 中。
通常,基本的 CSS 配置都是类似这样的。先 style-loader,然后 css-loader。
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader', 'css-loader'
],
},
],
}
但后来由于想要提取 CSS 到单独的文件里,就需要用上 MiniCssExtractPlugin。那么问题来了,如下的配置可行吗?
{
test: /\.css$/,
use: [
'style-loader', MiniCssExtractPlugin.loader, 'css-loader','postcss-loader'
],
}
生产模式
根据 MiniCssExtractPlugin 文档 中说到的,此插件适用于没有style-loader
的生产模式中,以及需要 HMR 的开发模式。
This plugin should be used only on
production
builds withoutstyle-loader
in the loaders chain, especially if you want to have HMR indevelopment
.
也就是说,在生产模式中,以上的配置同时使用了style-loader
和 MiniCssExtractPlugin 是不合适的(试了一下,style-loader
不会起作用)。
我们只能取其一。也可以如下两者结合,开发模式中使用 style-loader,生产模式中使用 MiniCssExtractPlugin。各取所需,毕竟这两者的作用还是很不同。
{
test: /\.css$/,
use: [
devMode?'style-loader':MiniCssExtractPlugin.loader,'css-loader','postcss-loader'
]
}
样式文件热更新(HMR)
从上面引用的那句话也可以看出,在开发模式中, 我们可以用 MiniCssExtractPlugin 实现样式的 HMR(Hot Module Replacement,模块热更新)。
样式文件的 HMR 是指什么呢?如果没有配置 HMR,开发模式下,修改 CSS 源文件的时候,页面并不会自动刷新加载修改后的样式。需要手动刷新页面,才会加载变化。而 HMR 实现了被修改模块的热更新,使得变化即时显示在页面上,不再需要刷新整个页面。
但其实 style-loader
也实现了 HMR 接口,如 Wepack 文档的 In a Module 中说到的:
HMR is an opt-in feature that only affects modules containing HMR code. One example would be patching styling through the
style-loader
. In order for patching to work, thestyle-loader
implements the HMR interface; when it receives an update through HMR, it replaces the old styles with the new ones.
因此开发环境下,这两个插件都是可以热更新 CSS 的,只是 MiniCssExtractPlugin 的配置可能更丰富一些。比如说:style-loader 只热更新 JS 中引入的样式,如果 index.html 中通过 <link>
引入了服务器中的一个CSS 文件:
<link rel="stylesheet" href="/vendors/test.css">
<!-- 通过配置 copy-webpack-plugin 在打包时把 html/vendors/test.css 拷贝到服务器根目录中,因此可以这么链接 -->
如果开发模式下,修改 test.css 的源码,style-loader 不会热更新变化 CSS,而是需要刷新整个页面,但 MiniCssExtractPlugin 则会自动重新加载所有的样式。可能还有其他区别,在此不详细说明了。
MiniCssExtractPlugin 插件可以这么配置 Less 文件的 HMR:
const devMode = process.env.NODE_ENV === 'development'; // 是否是开发模式
//......
module.exports = {
//......
module: {
rules:[
{
test: /\.less$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// 只在开发模式中启用热更新
hmr: devMode,
// 如果模块热更新不起作用,重新加载全部样式
reloadAll: true,
},
},
'css-loader','postcss-loader','less-loader'
]
},
// ......
]
}
}
参考阅读
- MiniCssExtractPlugin 文档: https://webpack.js.org/plugins/mini-css-extract-plugin/
- Webpack 文档之Loading CSS: https://webpack.js.org/guides/asset-management/#loading-css
- style-loader 文档:https://webpack.js.org/loaders/style-loader/
- Webpack 文档之Hot Module Replacement: https://webpack.js.org/concepts/hot-module-replacement/
网友评论