dev-server与HMR提高开发效率

前言

在上一篇博客中,我们配置好了最基础的功能,它能处理js、css、html基础的文件,并且能够分区域打包,形成一个较为规范的dist目录,当然,仅仅实现这些功能是不够的,我们这章节主要来介绍以下如何更方便我们开发调试。

why dev-server

我们在之前的配置中,每次修改js或者css后,都需要build一次才能看到新的页面,这当然是不可取的,如果你用过create-react-app 或者 vue-cli生成的项目,你就会在知道package.json中有一个script启动脚本npm run dev,这就能让我们边改代码,边调试,效率非常高。

具体配置

分清dev与prod环境

我们在设置package.json的script脚本时,会带上环境设置:

1
2
3
4
5
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"dev": "webpack-dev-server --mode development"
},

为什么要这么设置呢,因为在webpack配置当中,我们要区分开dev和prod环境,来做不同的处理,比如我们之前使用到的mini-css-extract-plugin,他在prod环境下是能够很方便的将css分离出来,但是如果在dev环境下使用HMR(热更新),这个时候mini-css-extract-plugin就会和HMR有冲突,因为mini-css-extract-plugin并不支持HMR,所以,我们在配置HMR之前,先设置好在dev中不使用mini-css-extract-plugin,用style-loader替代便能完美地进行热更新啦~

切换css的loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
return {
...
plugins: [
...
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css",
chunkFilename: "css/[id].[hash].css",
})
],
module: {
rules: [
{
test: /\.css/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
}
}

这里,我们设置了一个变量devMode,它表示是否处于development的环境下,如果是,则使用style-loader,这样在dev环境下便不会和HMR冲突,如果是在prod环境下,则使用MiniCssExtractPlugin.loader来分离出css

引入webpack-dev-server

在使用之前,我们先npm i webpack-dev-server -D,安装完成后,我们在webpack.config.js中配置devServer选项:

1
2
3
4
5
6
7
8
9
10
11
module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
return {
...
devServer: {
compress: true,
port: 9000,
open: true
}
}
}
  • compress 表示服务是否启动Gzip压缩,这里我们开启以加快加载速度
  • port 表示服务启动的端口号,注意开发的时候不要被其他的服务占用了,最好设置一个少见的端口号
  • open 表示是否自动打开网页

配置完成之后,启动dev:

run dev```,便会自动开启一个9000端口的网页,当你改动css或者js的时候,网页会自动刷新,这样一个dev环境就初步搭建好了,但是你会发现,当你改变js的时候,整个页面都全部刷新了,控制台中的console也全部没了,这对我们开发其实并不是特别友好的,我们希望改动代码后,能够实时看到更新结果,并且页面不要自动刷新,这对我们开发是非常有帮助的,比如一些需要点击打开的弹窗,当你改变其样式后,页面自动刷新,这时候你又要手动打开这个弹窗,来查看更新结果,这显然是不优雅的,于是我们开始下一步:HMR
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

### 启动HMR

启动HMR其实并不是非常复杂的行为,但是又一个坑困扰我许久,在最后我将会指出,我们继续更改webpack配置:

修改devServer选项,添加hot属性;

``` javascript
module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
return {
...
devServer: {
compress: true,
port: 9000,
hot: true,
open: true
}
}
}

增加HMR的插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const webpack = require('webpack')
module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
return {
entry: './src/index.js',
output: {
...
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
],
module: {
...
},
devServer: {
compress: true,
port: 9000,
hot: true,
open: true
}
}
}

这样,我们似乎就已经配置完毕了,但是当我们运行dev命令后,更改css后,页面不刷新便能看到更新结果,但是更改js文件后,还是会刷新页面,当初非常疑惑,之后后面谷歌才发现,需要在入口文件加入一段js代码:

1
2
3
4
5
6
7
// index.js
...

if (module.hot) {
// 实现热更新
module.hot.accept();
}

这是webpack的HMR对js文件的特殊处理,我们再次修改js文件时,发现已经不再需要刷新页面,便能实时查看更新结果。

last

最后,我们来看完整的webpack.config.js配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = (env, argv) => {
const devMode = argv.mode === 'development'
return {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css",
chunkFilename: "css/[id].[hash].css",
}),
new webpack.HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.css/,
use: [
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /\.(png|jpg|gif|jpeg|svg)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1024,
name: '[name].[hash:6].[ext]',
outputPath: 'images/',
publicPath: '../images'
}
}
]
}
]
},
devServer: {
compress: true,
port: 9000,
hot: true,
open: true
}
}
}

通过我们的倒腾,这下总算是有了真正开发的模样,dev-server提高了我们开发效率,配合HMR,我们能够实现一些很方便的调试,当然,距离配置出完美地工程还有很远,请关注我的博客后续更新~

本章节的代码已经上传到Github,传送门webpack-study,请自行切换到chapter-02分支。

In doing we learn. :)