Skip to content

1.前言

为了在日常中的开发效率更高、更容易调试代码,我们一般都会配置一套开发环境下的专用配置。而这其中最为核心的就是devServer。下边我们通过例子来一步步了解。

本章例子完整代码已放在github

为防版本差别,先放出该例的版本依赖:

json
{
  "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

json
{
  "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.1localIp。推荐设置成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。注意:当hostlocalIp或者0.0.0.0时,能正常打开。

3-5.ip与portfinder

为了防止端口占用,可以使用portfinder库。

webpack配置文件中,获取localIp的话,可以使用ip库。

js
// 支持返回一个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.publicPathoutput.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 reloadhot模式的方式:

live reload: 页面会完全重新刷新,所有资源重新加载,请求重新发送。

hot: 页面部分更新,依赖update.json等类似文件进行更新。

有一些文章中在开启hot选项后,依然会在开发环境的配置文件中,添加启用webpack.HotMuduleReplacementPlugin插件。但是官网上已经表明,在设置hottrue后,会自动启用该插件,无需再添加了。

其实这块坑还是蛮多的。我在实际测试中发现,即使开启hottrue,页面还是会以live reload的方式更新。排查了一番后,整理出以下的正确步骤:

  1. 设置devServer.hottrue
  2. 设置devServer.liveReloadfalse这个属性看下节
  3. entry文件中设置module.hot.accept。用它来监听具体文件的更新。譬如:
js
  if (module.hot) {
    module.hot.accept('./hot.js', function() {
      console.log('现在在更新 hot 模块了~')
      div.innerHTML = hot()
    })
  }
  1. 在以上步骤之后,改变entry文件时,发现是live reload模式,所以再设置devServer.hotOnlytrue有些缺陷,具体见下下节

看到这里,可能会有疑问,为什么实际项目当中并没有以上的代码。那是因为已经有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-pluginloader,那么热更新需要额外配置。
js
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-pluginloader不支持热更新文件名为[name].[hash].csscss文件。需要设置为[name].cssissues

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有两种形式,分别对应单个代理路径与多个代理路径:

  1. 对象
js
proxy: {
  // 单个代理路径
  '/blog': {
    target: 'http://www.jsgoshu.cn',
    // 重写路径 
    pathRewrite: { '^/blog': '/project' }
  }
 },
  1. 数组
js
proxy: [
   // 多个代理路径
   {
    context: ['/blog', '/project'],
    // 以 /blog 和 /project开头的请求都会被代理到 http://www.jsgoshu.cn
    target: 'http://www.jsgoshu.cn'
   }
 ],

6-2.devServer.headers

自定义代理服务器的响应头

js
headers: {
  "x-response-header": "dev-server-demo"
}

7.信息相关

7-1.devServer.quiet

净化终端信息,清除掉无用冗余的打包信息。

(本身是个坑比属性 连报错也会隐藏 害的我好久才排查到chunkhashHMR下报错 🙃 )

最好搭配friendly-errors-webpack-plugin使用。

7-2.devServer.overlay

errors满屏显示在浏览器中。