webpack dev server(WDS)

在开始学习了解本地服务器webpack dev server之前,我们有必要先了解下我们自动编译的几种方式。

没有使用自动化编译,我们是怎样编译打包代码的呢?
在之前的学习中,我们都是手动运行npm run build 进行编译。但是每次修改完代码,我们都需要手动运行npm run build。如果现在是在开发阶段,那对于我们的开发是非常不方便的。

我们可以一起看看webpack官网提出的实现代码发生变化就自动编译的解决方案:

每次要编译代码时,手动运行 npm run build 就会变得很麻烦。 webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:

  1. webpack’s Watch Mode
  2. webpack-dev-server
  3. webpack-dev-middleware

多数场景中,你可能需要使用 webpack-dev-server,但是不妨探讨一下以上的所有选项。

方案一:watch+live server

Webpack为我们提供了watch模式。
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;这样我们就不用每次都手动运行npm run build了。

当配置了watch后,我们执行完npm run build后,终端是不会结束的,它会一直保持运行状态,当watch监听到代码文件变化后,就会立刻进行编译。

Watch怎么使用:
方式一:

  1. //package.json
  2. "scripts": {
  3. "build": "webpack --watch",
  4. },

方式二:也可以在webpack.config.js文件中开启watch模式。

  1. //webpack.config.js
  2. module.exports = {
  3. //开启watch模式:监听代码变化
  4. watch: true,
  5. mode: "development",
  6. entry: "./src/index.js",
  7. output: {
  8. filename: "bundle.js",
  9. path: path.resolve(__dirname, "./build")
  10. },
  11. }

watch只是帮我们实现了自动化编译,我们的浏览器会自动刷新吗?

浏览器不会帮我们自动刷新,但是live serve可以。
当前我们是在vscode运行代码,那vscode的插件live serve会开启一个本地服务器保存编译好的代码,浏览器请求服务器上的代码,我们就可以把编译好的文件运行到浏览器上。

我们现在完成了自动化编译,文件代码更新了,live server也会帮助我们在浏览器刷新整个页面重新加载代码。

watch+live server缺点:
现在已经实现了watch监听代码变化、live server插件提供本地服务化。但是效率并不算很高。

  1. 部分代码发生改变,会对所有代码重新编译;
  2. 编译成功后重新打包js文件(操作了文件、效率慢);
  3. Live server是vscode的插件,而其他编辑器可能不支持也可能使用各自的插件;
  4. Live server也会整个页面进行刷新。

方案二:webpack-dev-server

webpack-dev-server很强大,既能完成自动编译,而且还能替代live server,在浏览器上也实现实时重新加载。

安装:

  1. npm install --save-dev webpack-dev-server

使用:

  1. //package.json
  2. "scripts": {
  3. "serve": "webpack serve"
  4. },

npm run serve,根据我们配置,cli就会找到webpack-dev-server插件帮我们实现自动编译等一系列事情。

Webpack-dev-server都做了什么事?

  1. WDS帮我们在代码更新后自动编译,并且把编译好的文件都保存在express本地服务器上;
  2. WDS帮我们搭建本地服务器(本质是使用express开启本地服务),我们的代码会跑在8080端口;(替代live server)

(Webpack-dev-server编译后,并不会输出任何文件,而是将编译好的文件保存在内存里。事实上webpack-dev-server使用了一个库叫memfs(webpack官方提供),将所有的出口文件都保存到了内存里。这样就解决了每次自动编译都得进行文件操作导致效率低的问题。)

模块热替换(HMR)

什么是HMR?
hot module replacement,模块热替换。前面提到的,当代码发生改变,整个页面都会刷新。而HMR能帮助我们只刷新修改了代码的对应模块的代码。

好处:

  1. 不用重新加载整个页面,状态就不会丢失;
  2. 只更新需要变化的内容,节省开发时间;(速度很快)

使用:
Webpack-dev-server已经支持HMR,我们只需要在使用了webpack-dev-server的基础上,开启一下HMR就可以了。(默认使用live reloading;)

  1. //webpack.config.js
  2. devServer: {
  3. hot: true,
  4. }

但是我们还需要去指定哪些模块发生更新时,想要进行HMR。否则还是会刷新整个页面。
来到某个模块下的源代码,进行判断。如果当前模块支持,使用accept方法声明当前模块要使用hot。

  1. if (module.hot) {
  2. //accept接受两个参数 需要HMR的模块路径与更新完后的回调函数
  3. module.hot.accept("./math.js", () => {
  4. console.log("math模块发生了更新~");
  5. });
  6. }

缺点:
每写一个模块,都得写一遍当前模块要支持HRM,那岂不是工作量特别大。
但是vue框架、react框架,我们在用它们写代码时,并不用去手动声明,就可以实现每个模块都支持HRM。框架是怎么做到的呢?

框架中的HRM

当我们使用React或者vue,脚手架已经帮我们配置好了很多东西。其中就包括HMR。而且我们也不用手动指定哪些模块需要热更新。

比如说vue中所有的vue文件都是一个模块,每个vue文件已经实现了HMR热更新了。

框架是怎么实现每个模块都支持使用HMR,而且我们还无需对每个模块都手动指定要使用HMR?

React框架实现HMR

首先我们得让我们的项目支持HMR,根据上边学习到的使用webpack-dev-server并配置HMR。我们的项目就支持HMR了。但是还有一个自动帮我们实现每个模块都可以使用热更新,而无需手动指定,这个要怎么实现呢?

安装:
需要安装两个插件:
react-refresh-webpack-plugin(使我们的webpack支持react-refresh)、react-refresh(使我们的babel支持react组件的热替换

  1. npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh

使用:

  1. //webpack.config.js
  2. const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
  3. module: {
  4. rules: [
  5. {
  6. test: /\.jsx?$/i,
  7. use: "babel-loader",
  8. },
  9. ],
  10. },
  11. devServer: {
  12. hot: true,
  13. },
  14. plugins: [
  15. new ReactRefreshWebpackPlugin(),
  16. ],
  1. //babel.config.js
  2. module.exports = {
  3. presets: [
  4. ["@babel/preset-env"],
  5. ["@babel/preset-react"],
  6. ],
  7. plugins: [
  8. ["react-refresh/babel"]
  9. ]
  10. }

给每个模块都绑定上HRM需要借助Bable。通过以上配置,当我们的react组件jsx文件发生改变,babel就重新工作,根据babel的配置文件里的react-refresh给每个jsx文件都绑定上了HRM。

Vue中配置HRM

配置vue文件都支持HRM之前,我们需要先让webpack能够对vue文件进行编译。

安装:
要想webpack支持编译vue,需要借用vue-loader、vue-template-compiler帮我们完成。

Vue-template-compiler可以帮助我们对vue文件的template模板进行编译,然后由vue-loader完成webpack的编译打包。但是我们一个vue文件,还有css代码,所以还需要使用到style-loader与css-loader(css的处理在之前学习有提到,这里不过多赘述)。

  1. npm install vue-loader vue-template-compiler -D

使用:

  1. const VueLoaderPlugin = require("vue-loader/lib/plugin");
  2. module: {
  3. rules: [
  4. {
  5. test: /\.vue$/i,
  6. use: "vue-loader",
  7. },
  8. {
  9. test: /\.css/i,
  10. use: ["style-loader", "css-loader"],
  11. },
  12. ],
  13. },
  14. plugins: [
  15. new VueLoaderPlugin(),
  16. ],

以上配置,我们就实现了webpack编译vue文件了。

Vue中配置HMR
其实我们也已经实现了每个vue文件都自动绑定了HMR。啊?我们啥也不用做就实现啦?
那是因为刚才使用到的vue-loader,它已经帮我们搞好了。就很nice!

WDS、HMR原理

我们接下来探讨下WDS、HRM的原理,然后解决这些问题:
WDS原理?WDS都做了什么?HMR的原理?HMR是如何实现只更新一个模块的?

Webpack-dev-server实际上创建了两个server:提供静态资源的express server(进行http请求短连接)、HMR server(进行socket长连接)

  1. express server: webpack编译打包后的bundle.js等静态资源,会保存到express server这个服务器里,浏览器发起http://locallhost:8080请求,请求到express server上的静态资源,浏览器下载执行代码。(http请求为短连接)
  2. Socket server:首先会建立服务器与客户端的长连接。当某个模块发生变化,就会生成两个文件,json文件(记录哪里发生变化等简单信息)与js文件(发生变化的具体代码),这两个文件保存在HMR server里。然后当模块发生更新,服务器就会主动发送json、js文件给客户端。就可以完成更新。(本质上是通过websocket实时通信)

bca2b0b6ef9fd957def0c3f0fe254201.png

补充:这里补充点网络方面的知识。

短连接:像上边提到的http请求,就是短连接。发送请求(客户端)=> 建立连接 => 响应请求(服务端)=> 断开连接。 很明显地可以看出,短连接的话,服务端只能在客户端发送请求时才能返回数据,服务端不能主动发送数据。
长连接:连接->传输数据->保持连接 -> 传输数据-> ………..->一方关闭连接。长连接指建立SOCKET连接后不管是否使用都保持TCP连接。本质上依然是客户端主动发起-服务端应答的模式,是没法做到服务端主动发送通知给客户端的
Websocket:WebSocket 是类似 TCP 长连接的通讯模式。只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。
参考出处:https://blog.csdn.net/eleanoryss/article/details/109600154

devServer的其它配置

我们在开发中,可能需要配置到一些只在开发时才用到的配置。去提高我们的开发效率。就可以在devServer里进行配置。

我们使用webpack-dev-server开启本地服务器,devServer就是对本地服务器进行配置。

  1. //webpack.config.js
  2. // devServer为开发过程中, webpack-dev-server开启一个本地服务的相关配置
  3. devServer: {
  4. //hot:打开HRM
  5. hot: true,
  6. //hotOnly:默认false,当项目报错解决后刷新一次页面。设置为true不刷新页面
  7. hotOnly: true,
  8. //host:配置项目的host,默认为locallhost
  9. host: "0.0.0.0",
  10. //:port:配置端口号,默认8080
  11. port: 7777,
  12. //open:是否打开浏览器,默认false
  13. open: true,
  14. //compress:是否为静态文件开启gzip,默认false
  15. compress: true,
  16. //publicPath:访问服务器资源路径后的拼接,默认为/,直接访问http://locallhost就可以访问到服务器静态资源
  17. //修改为/abc,需要访问http://locallhost/abc才可以访问服务器静态资源
  18. //官方推荐这里的publicPath应与output的publicpath相同
  19. publicPath: "/abc",
  20. /*告诉服务器哪里提供资源,默认/。 比如index.html文件里引入public文件下的js文件,代码应该这样写:<script src="/main.js">就可以找到js文件。
  21. 如果没有配置contentBase,<script src="./public/main.js">也无法找到*/
  22. contentBase: path.resolve(__dirname, "./public"),
  23. // watchContentBase:实现contentBase加载进来的文件是否自动热更新。默认false不热更新
  24. watchContentBase: true,
  25. // publicPath: "/abc",
  26. //我们在开发中,可以用proxy解决跨域问题。部署的跨域问题还是需要用别的方法与服务器共同解决
  27. proxy: {
  28. /*配置了代理,让我们的本地服务器http://locallhost:8080去帮我们进行域名为
  29. http://locallhost:8888的api请求*/
  30. "/api": {
  31. //表示代理到的目标地址,比如/api/moment就会被代理到http://localhost:8888/api/moment
  32. target: "http://localhost:8888",
  33. //我们要的是http://localhost:8888/moment,需要将/api删掉
  34. pathRewrite: {
  35. "^/api": "",
  36. },
  37. //默认不接受https,设置为false,就可以支持https的服务器
  38. secure: false,
  39. //是否改变代理请求的headers中的host属性。默认false,代理请求的headers的host属性不是服务端的host
  40. changeOrigin: true,
  41. },
  42. },
  43. // historyApiFallback: 默认为false,当设置为true时,如果刷新后返回404错误,就直接跳转到index.html页面
  44. //rewrites:也可以根据正则,跳转到想要的页面
  45. historyApiFallback: {
  46. rewrites: [{ from: /abc/, to: "/index.html" }],
  47. },
  48. },

可查阅webpack官方文档查看所有相关配置:https://www.webpackjs.com/configuration/dev-server/

参考出处:

https://blog.csdn.net/eleanoryss/article/details/109600154
https://www.webpackjs.com/configuration/dev-server/