Webpack 自动刷新与代理配置实战:WDS、Nodemon 及底层原理解析

作者:测试鸭

时间:2026-10-22

浏览:368

本文详解如何利用 webpack-dev-server 实现前端开发的自动刷新,解决手动编译与发布的繁琐流程。通过配置 Nodemon 监控配置文件变更,并结合代理功能转发 API 请求,提升开发效率。同时深入剖析 WDS 基于 Chokidar 和 SockJS 的底层通信机制,帮助开发者理解自动刷新的实现原理。

文章目录

Webpack 自动刷新配置与代理实战

前端开发过程中,开发者需要频繁修改 JavaScript 代码和样式,并根据浏览器中的实时效果进行不断调整。此外,开发目录与本地发布目录通常不一致,每次修改后都需要执行发布操作。更复杂的是,部分页面效果无法通过直接双击 HTML 文件查看,往往需要在本地搭建 Nginx 站点进行观察,确认本地无误后再部署至测试环境。若依靠手工刷新浏览器、手动发布以及启动站点,将耗费大量精力。而这三项工作,Webpack 均能提供自动化支持。

webpack-dev-server

Webpack 通过 webpack-dev-server(简称 WDS)实现自动刷新功能。WDS 是一个运行在内存中的开发服务器,底层基于 Express 框架。启动后,它能够实时检测文件变更并自动重新编译代码。

WDS 安装与启动方式

1. 安装


npm install webpack-dev-server --save-dev

首先通过 npm 将其安装至开发目录。安装完成后,可在 node_modules/bin 目录下找到相关执行文件。

2. NPM 启动

接着修改 package.json 文件(基于上一节配置):


"scripts": {
  "start": "webpack-dev-server --env development",
  "build": "webpack --env production"
 }

此时,即可通过 npm run start 或 npm start 命令启动服务。

Webpack 自动刷新配置与代理实战

启动后,控制台会显示 Project is running at http://localhost:8080。打开该页面即可访问。

Webpack 自动刷新配置与代理实战

这表明 WDS 已自动创建了一个站点。当我们修改 component.js 时,命令行中会出现编译日志,页面也会随之自动刷新。

Webpack 自动刷新配置与代理实战

3. 直接启动

官网介绍可以直接通过以下命令启动 WDS。


webpack-dev-server --env development

但可能会遇到提示 webpack-dev-server --env development 不是内部命令的错误。此类问题通常由环境变量配置引起,只需将开发环境的 bin 目录添加至系统环境变量即可。例如,若目录为 'E:\Html5\node_modules\.bin',则将其追加到环境变量路径中。

C:\Users\Administrator.9BBOFZPACSCXLG2\AppData\Roaming\npm;C:\Program Files (x86)\Microsoft VS Code\bin;E:\Html5\node_modules\.bin

Webpack 自动刷新配置与代理实战

4.8080端口占用

若默认的 8080 端口被占用,WDS 会自动切换至其他可用端口。例如,若已有 Nginx 占用了该端口。


server{
   listen    8080;
   location / {
      root  E:/Html5/build;
      index index.html index.htm;
    }
  }

再次启动 WDS:

Webpack 自动刷新配置与代理实战

此时端口已切换至 8081。用户也可以手动指定端口:


 devServer:{
  //...
  port: 9000
}

使用 Nodemon 监控配置文件

Nodemon 自动启动

WDS 仅监视开发文件,若 webpack.config.js 发生变更,并不会触发 WDS 的自动重启。因此,我们需要借助 Nodemon 来实现这一功能。


npm install nodemon --save-dev

首先在开发目录安装 Nodemon,然后修改 package.json:


 "scripts": {
  "start": "nodemon --watch webpack.config.js --exec \"webpack-dev-server --env development\"",
  "build": "webpack --env production"
 },

此举旨在让 Nodemon 监视 webpack.config.js,一旦文件发生变化,便自动启动 WDS。

Webpack 自动刷新配置与代理实战

这样,开发者即可专注于代码编写,无需手动干预启动流程。

WDS 代理配置与底层原理

代理

然而存在一个疑问:WDS 站点的替代性问题。由于我们自行部署的 Nginx 配置了部分 API 代理,若直接挂载在 WDS 默认站点上,将无法访问这些接口。换言之,是否可以为 WDS 配置刷新路径?如果文件改变,能否刷新指定地址,或者配置代理?既然 WDS 本身是一个 HTTP 服务器,理应具备代理功能。经搜索,果然找到相关示例:https://github.com/webpack/webpack-dev-server/tree/master/examples/proxy-advanced


module.exports = {
  context: __dirname,
  entry: "./app.js",
  devServer: {
    proxy: {
      "/api": {
        target: "http://jsonplaceholder.typicode.com/",
        changeOrigin: true,
        pathRewrite: {
          "^/api": ""
        },
        bypass: function(req) {
          if(req.url === "/api/nope") {
            return "/bypass.html";
          }
        }
      }
    }
  }
}

即将 api 这个字段替换成 http://jsonplaceholder.typicode.com/,并将其从原地址中删掉,这样就可以自己实现代理了。皆大欢喜!WDS 是通过 http-proxy-middleware 来实现代理。更多参考:http://webpack.github.io/docs/webpack-dev-server.html;https://github.com/chimurai/http-proxy-middleware#options

但是,这种刷新是如何实现的呢?因为页面上没有嵌入什么别的 js,去翻原码 web-dev-server/server.js 中有这么一段:


Server.prototype._watch = function(path) {
  const watcher = chokidar.watch(path).on("change", function() {
    this.sockWrite(this.sockets, "content-changed");
  }.bind(this))

  this.contentBaseWatchers.push(watcher);
}

用 chokidar 来监视文件变化,server的内部维护的有一个socket集合:


Server.prototype.sockWrite = function(sockets, type, data) {
  sockets.forEach(function(sock) {
    sock.write(JSON.stringify({
      type: type,
      data: data
    }));
  });
}

sock 是一个 sockjs 对象。https://github.com/sockjs/sockjs-client,从 http://localhost:8080/webpack-dev-server/ 页面来看,sockjs 是用来通信记录日志的。


var onSocketMsg = {
  hot: function() {
    hot = true;
    log("info", "[WDS] Hot Module Replacement enabled.");
  },
  invalid: function() {
    log("info", "[WDS] App updated. Recompiling...");
    sendMsg("Invalid");
  },
  hash: function(hash) {
    currentHash = hash;
  },
...
}

我们在看 app.js,其中有一个 OnSocketMsg 对象。


var onSocketMsg = {
  hot: function() {
    hot = true;
    log("info", "[WDS] Hot Module Replacement enabled.");
  },
  invalid: function() {
    log("info", "[WDS] App updated. Recompiling...");
    sendMsg("Invalid");
  },
  hash: function(hash) {
    currentHash = hash;
  },
  "still-ok": function() {
    log("info", "[WDS] Nothing changed.")
    if(useWarningOverlay || useErrorOverlay) overlay.clear();
    sendMsg("StillOk");
  },
  "log-level": function(level) {
    logLevel = level;
  },
  "overlay": function(overlay) {
    if(typeof document !== "undefined") {
      if(typeof(overlay) === "boolean") {
        useWarningOverlay = overlay;
        useErrorOverlay = overlay;
      } else if(overlay) {
        useWarningOverlay = overlay.warnings;
        useErrorOverlay = overlay.errors;
      }
    }
  },
  ok: function() {
    sendMsg("Ok");
    if(useWarningOverlay || useErrorOverlay) overlay.clear();
    if(initial) return initial = false;
    reloadApp();
  },
  "content-changed": function() {
    log("info", "[WDS] Content base changed. Reloading...")
    self.location.reload();
  },
  warnings: function(warnings) {
    log("info", "[WDS] Warnings while compiling.");
    var strippedWarnings = warnings.map(function(warning) {
      return stripAnsi(warning);
    });
    sendMsg("Warnings", strippedWarnings);
    for(var i = 0; i < strippedWarnings.length; i++)
      console.warn(strippedWarnings[i]);
    if(useWarningOverlay) overlay.showMessage(warnings);

    if(initial) return initial = false;
    reloadApp();
  },
  errors: function(errors) {
    log("info", "[WDS] Errors while compiling. Reload prevented.");
    var strippedErrors = errors.map(function(error) {
      return stripAnsi(error);
    });
    sendMsg("Errors", strippedErrors);
    for(var i = 0; i < strippedErrors.length; i++)
      console.error(strippedErrors[i]);
    if(useErrorOverlay) overlay.showMessage(errors);
  },
  close: function() {
    log("error", "[WDS] Disconnected!");
    sendMsg("Close");
  }
};

ok 的时候触发一个 reloadApp


function reloadApp() {
  if(hot) {
    log("info", "[WDS] App hot update...");
    var hotEmitter = __webpack_require__("./node_modules/webpack/hot/emitter.js");
    hotEmitter.emit("webpackHotUpdate", currentHash);
    if(typeof self !== "undefined") {
      // broadcast update to window
      self.postMessage("webpackHotUpdate" + currentHash, "*");
    }
  } else {
    log("info", "[WDS] App updated. Reloading...");
    self.location.reload();
  }
}

也就是说 WDS 先检测文件是否变化,然后通过 sockjs 通知到客户端,这样就实现了刷新。之前 WebSocket 的第三方只用过 socket.io,看起来 sockjs 也蛮好用的。不必外带一个 js,在主 js 里面就可以写了。

小结:效率提高的一方面是将一些机械的重复性流程或动作自动化起来。WDS 和 nodemon 就是两个为你干活的小弟。

demo:webpack-ch2_jb51.rar

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章