在前面的文章里,我们一直在用 npm run build 打包,然后手动打开 dist/index.html 看效果。但真实开发中,改一行代码就要重新构建、再手动刷新浏览器,效率极低。**开发服务器(dev server)**就是为了解决这个问题而存在的。
一、开发服务器是什么
开发服务器本质上是一个跑在本地的 HTTP 服务,它接管了「编译 + 提供页面」这两件事:
- 它在构建时启动,把 webpack 的打包结果放到内存里(而不是写磁盘)。
- 它对外提供一个本地地址(如
http://localhost:8080),你直接用浏览器访问就能看到页面。 - 它监听源码变化,自动重新编译,并通过热更新或自动刷新把最新结果推给浏览器。
换句话说,它把「改代码 → 打包 → 刷新」这一串手动操作全部自动化了。
二、为什么需要它
| 手动打包 | 使用 dev server |
|---|---|
每次改动都要重新 build | 改动后自动重新编译 |
| 产物写到磁盘,频繁 IO | 产物存内存,读写更快 |
| 需要手动刷新浏览器 | 自动刷新 / 热更新 |
用 file:// 打开,受同源策略限制 | 本地 HTTP 服务,行为接近线上 |
| 跨域接口无法直接请求 | 支持代理转发解决跨域 |
| 出错要点开 devtools 找 | 错误可直接覆盖在页面上 |
其中「内存编译」很关键:dev server 使用 memory-fs,产物不落盘,所以速度远快于一次次写 dist。
三、安装与基本配置
webpack 5 中,dev server 由 webpack-dev-server 提供,通过 webpack-cli 的 serve 命令启动:
npm install webpack-dev-server --save-dev
在 webpack.config.js 中配置:
module.exports = {
mode: 'development',
devServer: {
static: './dist',
port: 8080,
open: true,
},
};
并在 package.json 添加脚本:
{
"scripts": {
"dev": "webpack serve --mode development",
"build": "webpack"
}
}
之后运行:
npm run dev
浏览器会自动打开 http://localhost:8080,修改源码保存后页面会自动更新。
四、常用配置项
module.exports = {
devServer: {
// 静态资源目录(即 output.path)
static: {
directory: path.resolve(__dirname, 'dist'),
},
port: 8080, // 端口
host: '0.0.0.0', // 允许局域网访问
open: true, // 启动后自动打开浏览器
hot: true, // 开启热更新(HMR)
compress: true, // 开启 gzip 压缩
historyApiFallback: true, // SPA 路由回退
client: {
overlay: true, // 编译错误覆盖到页面上
},
},
};
几个重点:
- static:指定静态文件服务目录。如果用了
HtmlWebpackPlugin,通常指向output.path。 - hot:开启模块热替换(详见下一节)。
- client.overlay:编译报错时在页面上显示红色遮罩,比翻控制台快得多。
五、热更新(HMR)
热更新(Hot Module Replacement)让修改模块时无需刷新整个页面,只替换改动的部分,从而保留页面状态(比如表单内容、组件内部状态)。
配置只需开启:
devServer: {
hot: true,
}
配合 HtmlWebpackPlugin 时,HMR 是开箱即用的。若要让某个模块在更新后执行自定义逻辑,可以监听 module.hot:
if (module.hot) {
module.hot.accept('./utils.js', () => {
console.log('utils.js 已更新');
});
}
webpack 5 中,CSS 的 HMR 也是自动生效的,改样式无需刷新页面。若只想用 HMR、拒绝「HMR 失败时整页刷新」的降级行为,可设为 hot: 'only'。
六、代理:解决跨域
开发时前端页面和接口通常不同源,浏览器会拦截跨域请求。dev server 的 proxy 把请求转发到后端服务器,从而绕过跨域限制:
devServer: {
proxy: [
{
context: ['/api'],
target: 'http://localhost:3000',
changeOrigin: true,
pathRewrite: { '^/api': '' },
},
],
}
这样前端请求 /api/user,实际会被转发到 http://localhost:3000/user。
注意 webpack 5 中
proxy推荐写成数组形式,每一项用context指定要匹配的路径。
七、单页应用路由回退
使用 React Router、Vue Router 的 history 模式时,刷新 /user/123 会向服务器请求该路径,而服务器上并不存在这个文件,导致 404。historyApiFallback 会把这类请求都回退到 index.html:
devServer: {
historyApiFallback: true,
}
八、与生产构建的区别
| 开发服务器 | 生产构建 | |
|---|---|---|
| 产物位置 | 内存 | 磁盘 dist/ |
| 模式 | development | production |
| 优化 | 无(构建快) | Tree Shaking、压缩 |
| 用途 | 本地开发调试 | 部署上线 |
所以 dev server 只用于开发,部署时依然要跑 npm run build。可以把两者配置拆成 webpack.dev.js 和 webpack.prod.js,再用 webpack-merge 合并公共部分。
九、完整配置示例
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].js',
},
module: {
rules: [
{ test: /\.css$/i, use: ['style-loader', 'css-loader'] },
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
],
devServer: {
static: path.resolve(__dirname, 'dist'),
port: 8080,
open: true,
hot: true,
compress: true,
historyApiFallback: true,
client: { overlay: true },
proxy: [
{
context: ['/api'],
target: 'http://localhost:3000',
changeOrigin: true,
},
],
},
devtool: 'eval-cheap-module-source-map', // 便于调试
};
小结
开发服务器的价值,在于把开发阶段重复的「编译、刷新、跨域调试」全部自动化:
| 能力 | 对应配置 |
|---|---|
| 本地 HTTP 服务 | static / port / host |
| 自动刷新与热更新 | hot / client |
| 解决跨域 | proxy |
| SPA 路由回退 | historyApiFallback |
| 错误可视化 | client.overlay |
| 便于调试 | devtool |
搭建它只需要 webpack-dev-server + 一段 devServer 配置 + 一个 webpack serve 脚本。掌握这些,开发体验会有质的提升。