[探索]Webpack DevServer和HMR原理
写作不易,未经作者允许禁止以任何形式转载!
如果觉得文章不错,欢迎关注、点赞和分享!
Webpack-Dev-Server
为什么要搭建本地服务器
-
目前开发的代码,为了运行需要有两个操作
npm run build
编译- 通过 live-server 或者直接通过浏览器打开 html 文件,查看效果
-
为了完成自动编译,webpack 提供了几种可选的方式
- Webpack watch mode
- Webpack-dev-server
- Webpack-dev-middleware
Webpack Watch Mode
-
webpack 提供了 watch 模式
- 在该模式下,webpack 依赖图中所有文件,只要有一个发生了更新,那么代码将被重新编译。
- 不需要手动
npm run build
-
如何开启?
- 方式一:在导出的配置中,添加 watch:true
module.exports = { |
- 方式二:在启动 webapck 的命令中,添加–watch 标识
npm script:{ |
Webpack Dev Server
-
上面的方式可以监听到文件的变化,但是没有自动刷新浏览器的功能
- webpack-dev-server 可以实现
-
安装
npm install --save webpack-dev-server
-
修改 npm script,同时可在配置文件中 devServer 属性下配置 devServer
script:{ |
- webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中
- 事实上 webpck-dev-server 使用了一个叫 memfs 的库。
Webpack Dev Middleware
-
webpack-dev-middleware 是一个封装器,它可以把 webpack 处理过的文件发送到一个 server
- webpack-dev-server 在内部使用了它,然而它也可以作为一个单独的 package 来使用,以便根据需求进行更多自定义配置
- 搭配一个服务器来使用它,比如 express.
npm install --save express webpack-dev-middleware
-
编写 Server.js
const express = require('express'); |
- Node Server.js 即可运行起一个服务,并监听文件更改和刷新浏览器。
PublicPath
-
Output 中有两个很重要的属性:path 和 publicPath
- path:用于指定文件的输出路径,是一个聚堆路径
- publicPath:默认是一个空字符串,它为我们项目中的资源制定一个公共的路径 publicPath
-
这个 publicPath 很不容易理解,其实就是给我们打包的资源,给它一个路径
- 资源的路径 = output.publicePath + 打包资源的路径(比如"js/[name].bundle.js")
-
常用的值
- ./ :本地环境下可以使用这个相对路径
- / :服务器部署时使用,服务器地址 + /js/[name].bundle.js
-
devServer 的 publicPath、output 的 publicPath 和[webpackDevMiddleware 的 publicPath]需一致
ContentBase
-
devServer 中 contentBase 对于我们直接访问打包后的资源其实并没有太大的作用,它的主要作用是如果我们打包后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容:
- 比如在 index.html 中,我们需要依赖一个 abc.js 文件,这个文件我们存放在 public 文件中;
- 在 index.html 中,我们应该如何去引入这个文件?
- 比如代码是这样的:;
- 这样打包后浏览器无法通过相对路径去找到这个文件夹;
- 所以代码是这样:;
- 如何让它去查找到这个文件的存在? 设置 contentBase 即可;
-
当然在 devServer 中还有一个可以监听 contentBase 发生变化后重新编译的一个属性:watchContentBase。
Proxy 代理
proxy 是我们开发中常用的一个配置选项,它的目的设置代理来解决跨域访问的问题
- 设置
- target:标识的是代理到的目标地址,比如/api/moment 会被代理到 http://localhost:8888/api/moment
- pathRewrite:默认情况下,我们的/api 也会被写入到 URL 中,如果希望删除,可以使用
- secure:默认情况下不接受转发到 https 的服务器,如果希望支持,设置为 false
- changeOrigin:表示是否更新代理后请求 headers 中的 host 地址
- historyApiFallback:解决 SPA 页面在路由跳转后,进行页面刷新返回 404 的错误
- boolean 值:默认是 false,如果设置为 true,刷新的时候,返回 404 错误时,会自动返回 index.html 的内容
- object 值:可以配置 rewrites 属性
- 可以配置 from 来匹配路径,决定要跳到哪个页面,详情查阅官方文档。
Other Config
-
hotOnly
- 默认情况下当代码编译失败修复后会刷新页面,不希望刷新设置 hotOnly:true
-
host 主机地址
- 默认值是 localhost
- 如果其他 PC 也可以访问可设置 0.0.0.0
-
localhost 和 0.0.0.0 的区别
- localhost 本质上是一个域名会被解析为 127.0.0.1
- 127.0.0.1 是一个会换地址,表达得意思是主机自己发出去的包,直接被自己接受
- 正常的数据库包经常 应用层 -> 传输层 -> 网络层 -> 数据链路层 -> 物理层
- 而回环地址,在网联络层直接就被获取
- 监听 127.0.0.1 时,同个网段下的主机中,通过 ip 地址是不能访问的。
- 0.0.0.0:监听 IPV4 上所有的地址,再根据端口找到不同的应用程序。
- 监听 0.0.0.0 时,在同一个网段下的主机中,通过 IP 地址是可以访问的。
-
Port
- 设置监听的端口,默认为 8080
-
open 是否打开浏览器
- 默认为 false,true 会打开浏览器
- 也可以设置类似于 Google Chrome 等值
-
compress 是否为静态文件开启 gzip compression
- 默认是是 false,可以设置为 true
配置示例
devServer: { |
Hot Module Replacement
-
什么是 HMR?
- HMR 全称 Hot Module Replacement,翻译为模块热替换
- 模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面。
-
HMR 通过如下几种方式,来提高开发的速度。
- 不重新加载整个页面,这样可以保留某些应用程序的状态不丢失;
- 只需更新需要变化的内容,节省开发时间
- 修改了 css、js 源代码,会立即在浏览器更新,相当于直接在浏览器的 devtools 中直接修改样式。
-
如何使用 HMR?
- 默认情况下,webpack-dev-server 已经支持 HMR,只需要开启即可。
- 在不开启 HMR 的情况下,修改了源代码后,整个页面会自动刷新,使用的是 live reloading。
-
如何开启
- 修改 webpack.config.js
module.exports = { |
- 更新后还是刷新整个浏览器,因为需要定义使用 HMR 的模块。
if (module.hot) { |
框架的 HMR
有一个问题:在开发其他项目时,我们是否需要经常手动去写入 module.hot.accpet 相关的 API 呢?
-
比如开发 Vue、React 项目,我们修改了组件,希望进行热更新,这个时候应该如何去操作?
-
社区已经针对这些有很成熟的解决方案了:
- 比如 vue 开发中,我们使用 vue-loader,此 loader 支持 vue 组件的 HMR,提供开箱即用的体验;
- 比如 react 开发中,有 React Hot Loader,实时调整 react 组件(目前 React 官方已经弃用了,改成使用 react- refresh);
Vue 的 HMR
-
Vue 的加载需要 vue-loader,而 vue-loader 加载的默认会进行 HMR 处理
-
安装加载 Vue 所需依赖
npm install vue-loader vue-template-compiler
-
配置 Webpack.config.js
const VueLoaderPlugin = require("vue-loader/lib/plugin") |
React 的 HMR
-
在之前,React 是借助 React Hot Loader 来实现 HMR,目前已经改成使用 react-refesh 来实现了
-
安装相关依赖
npm install @pmmmwh/react-refresh-webpack-plugin react-refresh
-
webpack.config.js
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin") |
- babel.config.js
module.exports = { |
HMR 的原理
-
那么 HMR 的原理是什么呢?如何可以做到只更新一个模块中的内容?
- webpack-dev-server 会创建两个服务:提供静态资源的服务(express)和 Socket(net.Socket)
- Express Server 负责直接提供静态资源服务(打包后的资源直接被浏览器请求和解析)
-
HMR Socket Server 是一个 socket 长连接
- 长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端)
- 当服务期间听到对应模块发上变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk)
- 通过长连接,可以直接将这两个文件主动发送给客户端。
- 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新。
[探索]Webpack DevServer 和 HMR 原理 (juejin.cn)
掘金:前端 LeBron
知乎:前端 LeBron
持续分享技术博文,关注微信公众号 👇🏻