目前,每次修改源代码之后,都需要手动运行 webpack
命令重新进行编译打包,并且手动刷新浏览器,才能看到最新的效果。Webpack 中有三种方案来解决这个问题。
watch 模式:
Webapck 提供了 watch 模式。在该模式下,Webapck 会监听依赖图中所有文件的源代码,一旦发现源代码发生变化,Webapck 就会自动重新进行编译打包。但是,仍需要手动刷新浏览器,才能看到最新的效果。
开启 watch 模式有两种方式:
- 在
webpack.config.js
配置文件中,添加watch:true
。module.exports = { watch: true }
- 在启动 Webpack 的脚本命令中,添加
--watch
参数。
DevServer:
DevServer 会启动一个本地的服务器,并且会监听源代码的变化并自动重新进行编译打包,不再需要每次修改原代码之后都手动运行命名进行编译打包了;而且会自动刷新浏览器,不再需要手动刷新浏览器了。
-
安装
webpack-dev-server
:npm install webpack-dev-server --save-dev
。 -
运行
webpack serve
命令将会开启webpack-dev-server
服务。当监听到源代码发生变化,将会自动重新进行编译打包,并自动刷新浏览器。webpack-dev-server
是通过 express 开启一个本地服务的,默认使用Live Reloading
实时重载。Webpack5 之前的命令是
webpack-dev-server
;但是 Webpack5 之后的命令是webpack serve
,webpack-cli
检测到有 serve 这个参数,将会自动启动webpack-dev-server
的服务。
配置 DevServer:
可以在 Webpack 配置文件中配置 DevServer。
module.exports = {
devServer: {
host: 0000, // 主机,默认是 localhost。
port: 8888, // 端口号,默认是 8080
open: true, // 是否自动打开浏览器,默认是 false
compress: true, // 是否对打包的文件进行 gzip 压缩
}
}
localhost 和
0.0.0.0
的区别:
localhost 其实是一个域名,会被解析为127.0.0.1
的 IP 地址。127.0.0.1
是一个回环地址,也就是说自己主机发出去的包,直接被自己接收。在同一个局域网下的主机,通过 IP 地址是不能访问的。正常的数据包是会经过
应用层 --> 传输层 --> 网络层 --> 数据链路层 --> 物理层
;而回环地址在网络层就被获取到了,是不会经过数据链路层和物理层的。
0.0.0.0
是监听 IPV4 上的所有地址,再根据端口号找到不同的应用程序。在同一个局域网下的主机,通过 IP 地址是能访问的。
HMR:
HMR:Hot Module Replacement,模块热替换。是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面。HMR 只更新需要变化的内容,而不会重新加载整个页面,这样可以保留应用程序的状态不丢失。
在普通项目中使用 HMR:
-
新建
src/index.js
和src/js/utils.js
,并编写代码。// src/index.js import './js/utils' console.log('index.js')
// src/js/utils.js console.log(1)
-
安装
webpack-dev-server
:npm install webpack-dev-server --save-dev
。 -
在
webpack.config.js
配置文件中进行配置。webpack-dev-server
内部已经集成了 HMR,如果想要开启 HMR,需要手动配置。module.exports = { devServer: { // 配置 webpack-dev-server 开启 HMR hot: true, } }
-
必须指定哪些模块发生更新时,进行 HMR。
如果不指定,当模块发生更新时,将会自动刷新整个页面。
// 在 src/index.js 文件中指定当 utils 模块发生更新时,进行 HMR if(module.hot) { // 第一个参数是一个字符串或数组,用来指定哪些模块发生变化时要使用 HMR;第二个参数是一个函数,用来指定当模块更新时可以进行的操作 module.hot.accept('./js/utils') }
-
运行
webpack server
开启 HMR。
-
修改
src/js/utils.js
文件中的代码。// src/js/utils.js console.log(2)
-
会发现,浏览器保留了之前的状态,只更新了 utils 模块的内容。
在框架中使用 HMR:
在 React 中使用 HMR:
在之前,React 是借助于 react-hot-loader
来实现 HMR 的,但是目前官方已经弃用了这个 loader,改为使用 reatc-refresh
。
如果是通过
create-react-app
脚手架创建的 React 项目,默认就已经配置好了react-refresh
。
如果是开发者手动搭建的 React 项目,则需要手动配置react-refresh
。
- 安装
react
和react-dom
用来编写 React 代码:npm install react react-dom
。 - 安装 Babel、
babel-loader
、@babel/preset-react
用来转换 React 代码:npm install @babel/core babel-loader @babel/preset-react --save-dev
。 - 安装
html-webpack-plugin
用来自动生成打包后的index.html
文件:npm install html-webpack-plugin --save-dev
。 - 安装 React 中实现模块热替换的库和插件:
npm install react-refresh @pmmmwh/react-refresh-webpack-plugin --save-dev
。react-refresh
:实现模块热替换功能。
@pmmmwh/react-refresh-webpack-plugin
: 在 Webpack 中能够使用模块热替换功能需要的插件。 - 新建
src/js/App.jsx
并编写代码。// src/js/App.jsx import React from 'react' export default class App extends React.Component { state = { title: 'Hello World', } render() { return ( <div>{this.state.title}</div> ) } }
- 新建
src/index.js
和src/index.html
文件并编写代码。// src/index.js import React from 'react' import ReactDOM from 'react-dom' import App from './js/App' console.log('index.js') ReactDOM.render(<App/>, document.getElementById('app'))
// src/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>
- 在
webpack.config.js
配置文件进行配置。const HtmlWebpackPlugin = require('html-webpack-plugin') // 1. 导入插件 const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin') module.exports = { mode: 'development', module: { rules: [ { test: /\.jsx?$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react'], // 3. react-refresh 专门为 babel 提供的插件 "plugins": ["react-refresh/babel"] } } }, ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), // 使用插件 new ReactRefreshWebpackPlugin() ] }
- 安装
webpack-dev-server
用来启动一个本地服务:npm install webpack-dev-server --save-dev
。 - 运行
webpack serve
命令将会开启本地服务。
- 修改
src/js/App.jsx
文件中的代码。import React from 'react' export default class App extends React.Component { state = { title: 'Hello React', } render() { return ( <div>{this.state.title}</div> ) } }
- 会发现,浏览器保留了之前的状态,只更新了
App.jsx
的内容。
在 Vue 中使用 HMR:
在 Vue 中,vue-loader
在加载组件时,默认就会对其进行 HMR 的处理。
- 安装 Vue 用来编写 Vue 代码:
npm install vue
。 - 安装
vue-loader
用来处理 Vue 文件,安装vue-template-compiler
用来对 Vue 文件中的 template 模板进行编译:npm install vue-loader vue-template-compiler --save-dev
。 - 安装
html-webpack-plugin
用来自动生成打包后的index.html
文件:npm install html-webpack-plugin --save-dev
。 - 新建
src/js/App.vue
文件并编写代码。// src/js/App.vue <template> <div>{{title}}</div> </template> <script> export default { data() { return { title: 'Hello Vue1111' } } } </script> <style scoped></style>
- 新建
src/index.js
和src/index.html
文件并编写代码。// src/index.js import Vue from 'vue' import App from './js/App.vue' console.log('index.js') new Vue({ render: h => h(App) }).$mount('#app')
// src/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div id="app"></div> </body> </html>
- 在
webpack.config.js
配置文件中进行配置。// webpack.config.js // VueLoaderPlugin 插件在安装 vue-loader 时会被默认安装 const VueLoaderPlugin = require('vue-loader/lib/plugin') module.exports = { module: { rules: [ { test: /\.vue$/, use: 'vue-loader', } ] }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), // 使用 vue-loader 必须同时使用 VueLoaderPlugin 插件,才能对 Vue 文件正确处理 new VueLoaderPlugin() ] }
- 安装
webpack-dev-server
用来启动一个本地服务:npm install webpack-dev-server --save-dev
。 - 运行
webpack serve
命令将会开启本地服务。
- 修改
src/App.vue
文件中的代码代码。<template> <div>Hello Vue</div> </template>
- 会发现,浏览器保留了之前的状态,只更新了
App.vue
的内容。
HMR 的原理:
webpack-dev-server
会创建两个服务:
- express server:提供静态资源的服务,通过 Node 中 express 来实现。当浏览器第一次访问网址时,就是通过 express 返回 Webpack 打包后的静态资源,浏览器对下载下来的 HTML、CSS、JS 等资源进行解析和执行。
- HMR server:Socket 服务,通过 Web Socket 来实现。当服务器监听到有文件发生变化时,会生成
.json
(manifest)和.js
(update chunk)两个文件;通过 Socket 长连接,将这两个文件主动发送给浏览器;浏览器接收到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且只针对修改的模块进行更新。Socket 长连接在建立连接后,双方可以互相通信。也就是浏览器可以主动发送消息给服务器,服务器也可以主动发送消息给浏览器。
watch 模式、DevServer 和 HMR 的对比:
- 编译打包:
- watch 模式可以监听源代码的变化并自动重新进行编译打包,不再需要每次修改原代码之后都手动运行命新进行编译打包了。但是即使只更改了一个文件的一个地方,也会对所有的源代码都重新进行编译;并且每次编译成功后,都会生成新的本地文件,文件操作是很耗费性能的。
使用 watch 可以看到项目中有打包输出的文件。
- DevServer 可以监听源代码的变化并自动重新进行编译打包,不再需要每次修改原代码之后都手动运行命名进行编译打包了。但是即使只更改了一个文件的一个地方,也会对所有的源代码都重新进行编译;不过每次编译成功后,不会生成新的本地文件,而只是保留在内存中,相比 watch 模式来说效率更高。
使用 DevServer 可以看到项目中并没有有打包输出的文件。
- HMR 可以监听源代码的变化并自动重新进行编译打包,不再需要每次修改原代码之后都手动运行命名进行编译打包了;并且它只会重新编译发生了变化的模块,而不会重新编译整个应用程序;而且每次编译成功后,不会生成新的本地文件,而只是保留在内存中。
使用 HMR 可以看到项目中并没有有打包输出的文件。
- watch 模式可以监听源代码的变化并自动重新进行编译打包,不再需要每次修改原代码之后都手动运行命新进行编译打包了。但是即使只更改了一个文件的一个地方,也会对所有的源代码都重新进行编译;并且每次编译成功后,都会生成新的本地文件,文件操作是很耗费性能的。
- 在浏览器中渲染:
- watch 模式没有自动刷新浏览器的功能,需要手动刷新浏览器才能看到编译打包后最新的效果。
- DevServer 虽然会自动刷新浏览器,但是会刷新整个页面。
- HMR 会自动刷新浏览器,但是不会重新加载整个页面,只会更新发生了变化的模块,没有发生变化的模块的状态就可以被保留下来。