1.前言
为了在日常中的开发效率更高、更容易调试代码,我们一般都会配置一套开发环境下的专用配置。而这其中最为核心的就是devServer。下边我们通过例子来一步步了解。
本章例子完整代码已放在github
为防版本差别,先放出该例的版本依赖:
{
"name": "dev-server-demo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "npm run dev",
"dev": "webpack-dev-server --progress --config webpack/webpack.dev.js",
"build": "webpack --config webpack/webpack.prod.js"
},
"devDependencies": {
"clean-webpack-plugin": "^3.0.0",
"copy-webpack-plugin": "5.0.0",
"css-loader": "^5.0.1",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^4.5.0",
"ip": "^1.1.5",
"mini-css-extract-plugin": "1.1.2",
"portfinder": "^1.0.28",
"style-loader": "^2.0.0",
"webpack": "4.29.0",
"webpack-cli": "3.2.1",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^5.4.0"
}
}2.webpack-dev-server
webpack在开发环境下有专门的依赖来运行打包,即webpack-dev-server。安装之后,配置scripts。
{
"scripts": {
"dev": "webpack-dev-server --inline --progress -config webpack/webpack.dev.js"
}
}其中,--inline是将项目以inline模式在网页中打开,可省略,默认inline模式。而--progress是在控制台显示打包进程,也可省略,无伤大雅,省略了控制台反而清爽。总而言之,这俩项不重要。
执行yarn dev,此时项目已可打开,默认地址http://localhost:8080。更改项目中的代码,也会发现页面会重新刷新,此时的更新模式为live reload,即实时重载页面。
如果想要更改开发环境的某些配置,可以在devServer下进行更改。
3.地址相关
3-1.devServer.host
主机名。默认为localhost。也可设置成127.0.0.1或localIp。推荐设置成0.0.0.0。
TIP
设置成本机ip后,可以方便处于同一局域网下的用户访问我们的开发环境地址。找寻本机ip的方法:
windows系统,在终端执行ipconfig。
macos系统,在终端执行ifconfig。
3-2.devServer.port
端口号。默认为8080。一般为了防止我们设置的端口已被其他服务占用,可使用portfinder这个库,来找寻其他可用端口。
3-3.devServer.open
是否自动打开浏览器。默认为false。
3-4.devServer.useLocalIp
自动打开浏览器时,是否默认使用本机ip。默认为false。注意:当host为localIp或者0.0.0.0时,能正常打开。
3-5.ip与portfinder
为了防止端口占用,可以使用portfinder库。
在webpack配置文件中,获取localIp的话,可以使用ip库。
// 支持返回一个promise resolve的是devConfig
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = devConfig.devServer.port
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// 找到可使用的port后 对devServer重新设置
devConfig.devServer.port = port
const httpType = devConfig.devServer.https ? 'https' : 'http'
devConfig.plugins.push(
new FriendlyErrorsPlugin({
compilationSuccessInfo: {
messages: [
`Your application is running here: ${ httpType }://${ devConfig.devServer.host }:${ port } ${ httpType }://127.0.0.1:${ port } ${ httpType }://${ ip.address() }:${ port }`
]
}
})
)
resolve(devConfig)
}
})
})4.资源相关
4-1.devServer.publicPath
开发服务器的访问前缀。默认为''。
没有设置值时,会默认取output.publicPath。但两者并没有优先级关系,二者针对的场景和作用并不同。
TIP
devServer.publicPath会改变开发环境下的访问地址前缀。
- 当设置为
/prefix/时 浏览器地址会变为127.0.0.1:8080/prefix/,会自动找/prefix/下的index.html。找不到时,index.html的寻找路径以contentBase为主。另外注意webpack-dev-server本身依赖的是一个虚拟的dist目录。
output.publicPath会改变静态资源的引用前缀 (使用html-webpack-plugin时,在dist/index.html中一看便知)
开发环境下的devServer.publicPath与output.publicPath最好保持一致。否则可能会造成资源404。
4-2.devServer.contentBase
设置index.html的寻找路径。默认为当前工作目录,即path.resolve(__dirname, '../')。
寻找index.html时,devServer.publicPath的优先级大于devServer.contentBase。
4-3.devServer.watchContentBase
监听index.html的改变。
在live reload模式下,修改index.html时,页面并不会自动刷新。设置该选项后,保证contentBase下的index.html修改时页面更新,依赖于live reload。
关闭live reload的话,失效。
5.热更新相关
5-1.devServer.hot
是否开启热更新hmr。
虽然live reload的模式,已经可以使页面自动刷新。但由于页面的刷新,页面的数据与状态必然会被初始化,这样的话,很不利于开发调试。于是我们可以使用hot模式来代替live reload。
TIP
在chrome控制台辨别live reload与hot模式的方式:
live reload: 页面会完全重新刷新,所有资源重新加载,请求重新发送。
hot: 页面部分更新,依赖update.json等类似文件进行更新。
有一些文章中在开启hot选项后,依然会在开发环境的配置文件中,添加启用webpack.HotMuduleReplacementPlugin插件。但是官网上已经表明,在设置hot为true后,会自动启用该插件,无需再添加了。
其实这块坑还是蛮多的。我在实际测试中发现,即使开启hot为true,页面还是会以live reload的方式更新。排查了一番后,整理出以下的正确步骤:
- 设置
devServer.hot为true - 设置
devServer.liveReload为false。这个属性看下节 - 在
entry文件中设置module.hot.accept。用它来监听具体文件的更新。譬如:
if (module.hot) {
module.hot.accept('./hot.js', function() {
console.log('现在在更新 hot 模块了~')
div.innerHTML = hot()
})
}- 在以上步骤之后,改变
entry文件时,发现是live reload模式,所以再设置devServer.hotOnly为true。有些缺陷,具体见下下节
看到这里,可能会有疑问,为什么实际项目当中并没有以上的代码。那是因为已经有loader帮我们做好了这部分。不必我们写很多个module.hot.accept函数来监听文件改变。譬如vue-loader以及react-hot-loader。
在上面的步骤后,js文件的热更新应该已经可以正常运行了。而css文件的hmr,需要额外注意一些地方。
- 如果
css使用的是style-loader,那么在修改css后,页面是可以正常热更新的。因为style-loader已经内置了module.hot.accept函数。 - 如果
css使用的是mini-css-extract-plugin的loader,那么热更新需要额外配置。
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
// 默认支持HMR功能 无需额外设置
// 'style-loader',
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: true
}
},
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
// 开发环境不能使用hash,否则mini-css-extract-plugin的hmr会失效。可以根据环境动态设置。
filename: 'css/[name].css'
})
]
}WARNING
开发环境下不要使用chunkhash以及contenthash。否则hmr会报错。issues
最好连hash也不要用,mini-css-extract-plugin的loader不支持热更新文件名为[name].[hash].css的css文件。需要设置为[name].css。issues
5-2.devServer.liveReload
测试的时候发现,需要将此属性设置为false(虽然官网并无此属性配置)。否则HMR不会生效,一直采用live reload的方式。
如果只是单纯设置了hot选项为true。在更新文件时,依然会发现是live reload模式,因为live reload模式依然开启。见下图:

5-3.devServer.hotOnly
只使用hot模式,即便是无法热更新。
该配置项即便hot模式失败,也不会去使用live reload模式。
当我配置该项后,在更新main.js时,往往会导致页面有这样的警告。个人认为模块热更新不支持main.js文件。

5-4.devServer.inline
可设置dev-server的两种不同模式,默认为true。应用程序启用内联模式(inline mode)。
这意味着一段处理实时重载的脚本被插入到你的包(bundle)中,并且构建消息将会出现在浏览器控制台。
也可以使用 iframe 模式,它在通知栏下面使用 <iframe> 标签,包含了关于构建的消息。
6.代理相关
6-1.devServer.proxy
该配置项主要用来解决前端在开发过程中的跨域。它可以将本地对远程发起的请求做一层代理。
假设我本地开发地址为127.0.0.1:8080,向远程服务器http://jsgoshu.cn/blog/发起请求,必然存在跨域的问题。那么webpack利用webpack-dev-server启了个服务代理,将请求映射到127.0.0.1:8080/blog/。这样的话,开发环境的跨域就已经被解决了。
proxy有两种形式,分别对应单个代理路径与多个代理路径:
- 对象
proxy: {
// 单个代理路径
'/blog': {
target: 'http://www.jsgoshu.cn',
// 重写路径
pathRewrite: { '^/blog': '/project' }
}
},- 数组
proxy: [
// 多个代理路径
{
context: ['/blog', '/project'],
// 以 /blog 和 /project开头的请求都会被代理到 http://www.jsgoshu.cn
target: 'http://www.jsgoshu.cn'
}
],6-2.devServer.headers
自定义代理服务器的响应头。
headers: {
"x-response-header": "dev-server-demo"
}7.信息相关
7-1.devServer.quiet
净化终端信息,清除掉无用冗余的打包信息。
(本身是个坑比属性 连报错也会隐藏 害的我好久才排查到chunkhash在HMR下报错 🙃 )
最好搭配friendly-errors-webpack-plugin使用。
7-2.devServer.overlay
将errors满屏显示在浏览器中。