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 = {
entry: "./src/index.js",
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "build"),
},
watch:true,
}
- 方式二:在启动webapck的命令中,添加--watch标识
npm script:{
"watch": "webpack --watch"
}
# npm run watch
Webpack Dev Server
上面的方式可以监听到文件的变化,但是没有自动刷新浏览器的功能
- webpack-dev-server可以实现
安装
npm install --save webpack-dev-server
- 修改npm script,同时可在配置文件中devServer属性下配置devServer
script:{
"serve":"webpack serve"
}
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")
const webpack = require("webpack")
const webpackDevMiddleware = require("webpack-dev-middleware")
const app = express()
const config = require("./webpack.config")
const compiler = webpack(config)
app.use(webpackDevMiddleware(compiler,{
publicPath:config.output.publicPath
}),()=>{
console.log("这里是回调函数")
})
app.listen(3000,()=>{
console.log("Server running")
})
- 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中,我们应该如何去引入这个文件?
- 比如代码是这样的:<script src="./public/abc.js"></script>;
- 这样打包后浏览器无法通过相对路径去找到这个文件夹;
- 所以代码是这样:<script src="/abc.js"></script>;
- 如何让它去查找到这个文件的存在? 设置contentBase即可;
- 当然在devServer中还有一个可以监听contentBase发生变化后重新编译的一个属性:watchContentBase。
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: true,
hostOnly:true,
host:"0.0.0.0",
port:8080,
open:true,
compress:true,
proxy:{
"/api":{
target:"http://localhost:8888",
pathRewrite:{
"^/api":""
},
secure:false,
changeOrigin:true
}
}
}
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 = {
entry: "./src/index.js",
output: {
filename: "js/bundle.js",
path: path.resolve(__dirname, "build"),
},
watch:true,
mode: "development",
devServer:{
hot:true
},
}
- 更新后还是刷新整个浏览器,因为需要定义使用HMR的模块。
if(module.hot){
module.hot.accept("./App.vue",()=>{
console.log("vue更新了")
})
}
框架的HMR
- 比如开发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")
module: {
rules: [
{
test: /\.vue$/,
use: ["vue-loader"]
},
]
},
plugins:[new VueLoaderPlugin()]
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")
plugins: [
new ReactRefreshWebpackPlugin(),
],
- babel.config.js
module.exports = {
presets: [
["@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3.8
}],
["@babel/preset-react"],
["@babel/preset-typescript"]
],
plugins: [
['react-refresh/babel']
]
}
HMR的原理
那么HMR的原理是什么呢?如何可以做到只更新一个模块中的内容?
- webpack-dev-server会创建两个服务:提供静态资源的服务(express)和Socket(net.Socket)
- Express Server负责直接提供静态资源服务(打包后的资源直接被浏览器请求和解析)
HMR Socket Server是一个socket长连接
- 长连接有一个最好的好处是建立连接后双方可以通信(服务器可以直接发送文件到客户端)
- 当服务期间听到对应模块发上变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
- 通过长连接,可以直接将这两个文件主动发送给客户端。
- 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新。