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
满屏显示在浏览器中。