webpack dev server(WDS)
在开始学习了解本地服务器webpack dev server之前,我们有必要先了解下我们自动编译的几种方式。
没有使用自动化编译,我们是怎样编译打包代码的呢?
在之前的学习中,我们都是手动运行npm run build 进行编译。但是每次修改完代码,我们都需要手动运行npm run build。如果现在是在开发阶段,那对于我们的开发是非常不方便的。
我们可以一起看看webpack官网提出的实现代码发生变化就自动编译的解决方案:
每次要编译代码时,手动运行
npm run build
就会变得很麻烦。 webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:
- webpack’s Watch Mode
- webpack-dev-server
- webpack-dev-middleware
多数场景中,你可能需要使用
webpack-dev-server
,但是不妨探讨一下以上的所有选项。
方案一:watch+live server
Webpack为我们提供了watch模式。
在该模式下,webpack依赖图中的所有文件,只要有一个发生了更新,那么代码将被重新编译;这样我们就不用每次都手动运行npm run build了。
当配置了watch后,我们执行完npm run build后,终端是不会结束的,它会一直保持运行状态,当watch监听到代码文件变化后,就会立刻进行编译。
Watch怎么使用:
方式一:
//package.json
"scripts": {
"build": "webpack --watch",
},
方式二:也可以在webpack.config.js文件中开启watch模式。
//webpack.config.js
module.exports = {
//开启watch模式:监听代码变化
watch: true,
mode: "development",
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./build")
},
}
watch只是帮我们实现了自动化编译,我们的浏览器会自动刷新吗?
浏览器不会帮我们自动刷新,但是live serve可以。
当前我们是在vscode运行代码,那vscode的插件live serve会开启一个本地服务器保存编译好的代码,浏览器请求服务器上的代码,我们就可以把编译好的文件运行到浏览器上。
我们现在完成了自动化编译,文件代码更新了,live server也会帮助我们在浏览器刷新整个页面重新加载代码。
watch+live server缺点:
现在已经实现了watch监听代码变化、live server插件提供本地服务化。但是效率并不算很高。
- 部分代码发生改变,会对所有代码重新编译;
- 编译成功后重新打包js文件(操作了文件、效率慢);
- Live server是vscode的插件,而其他编辑器可能不支持也可能使用各自的插件;
- Live server也会整个页面进行刷新。
方案二:webpack-dev-server
webpack-dev-server很强大,既能完成自动编译,而且还能替代live server,在浏览器上也实现实时重新加载。
安装:
npm install --save-dev webpack-dev-server
使用:
//package.json
"scripts": {
"serve": "webpack serve"
},
npm run serve,根据我们配置,cli就会找到webpack-dev-server插件帮我们实现自动编译等一系列事情。
Webpack-dev-server都做了什么事?
- WDS帮我们在代码更新后自动编译,并且把编译好的文件都保存在express本地服务器上;
- WDS帮我们搭建本地服务器(本质是使用express开启本地服务),我们的代码会跑在8080端口;(替代live server)
(Webpack-dev-server编译后,并不会输出任何文件,而是将编译好的文件保存在内存里。事实上webpack-dev-server使用了一个库叫memfs(webpack官方提供),将所有的出口文件都保存到了内存里。这样就解决了每次自动编译都得进行文件操作导致效率低的问题。)
模块热替换(HMR)
什么是HMR?
hot module replacement,模块热替换。前面提到的,当代码发生改变,整个页面都会刷新。而HMR能帮助我们只刷新修改了代码的对应模块的代码。
好处:
- 不用重新加载整个页面,状态就不会丢失;
- 只更新需要变化的内容,节省开发时间;(速度很快)
使用:
Webpack-dev-server已经支持HMR,我们只需要在使用了webpack-dev-server的基础上,开启一下HMR就可以了。(默认使用live reloading;)
//webpack.config.js
devServer: {
hot: true,
}
但是我们还需要去指定哪些模块发生更新时,想要进行HMR。否则还是会刷新整个页面。
来到某个模块下的源代码,进行判断。如果当前模块支持,使用accept方法声明当前模块要使用hot。
if (module.hot) {
//accept接受两个参数 需要HMR的模块路径与更新完后的回调函数
module.hot.accept("./math.js", () => {
console.log("math模块发生了更新~");
});
}
缺点:
每写一个模块,都得写一遍当前模块要支持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组件的热替换)
npm install -D @pmmmwh/react-refresh-webpack-plugin react-refresh
使用:
//webpack.config.js
const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
module: {
rules: [
{
test: /\.jsx?$/i,
use: "babel-loader",
},
],
},
devServer: {
hot: true,
},
plugins: [
new ReactRefreshWebpackPlugin(),
],
//babel.config.js
module.exports = {
presets: [
["@babel/preset-env"],
["@babel/preset-react"],
],
plugins: [
["react-refresh/babel"]
]
}
给每个模块都绑定上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的处理在之前学习有提到,这里不过多赘述)。
npm install vue-loader vue-template-compiler -D
使用:
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module: {
rules: [
{
test: /\.vue$/i,
use: "vue-loader",
},
{
test: /\.css/i,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [
new VueLoaderPlugin(),
],
以上配置,我们就实现了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长连接)
- express server: webpack编译打包后的bundle.js等静态资源,会保存到express server这个服务器里,浏览器发起http://locallhost:8080请求,请求到express server上的静态资源,浏览器下载执行代码。(http请求为短连接)
- Socket server:首先会建立服务器与客户端的长连接。当某个模块发生变化,就会生成两个文件,json文件(记录哪里发生变化等简单信息)与js文件(发生变化的具体代码),这两个文件保存在HMR server里。然后当模块发生更新,服务器就会主动发送json、js文件给客户端。就可以完成更新。(本质上是通过websocket实时通信)
补充:这里补充点网络方面的知识。
短连接:像上边提到的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就是对本地服务器进行配置。
//webpack.config.js
// devServer为开发过程中, webpack-dev-server开启一个本地服务的相关配置
devServer: {
//hot:打开HRM
hot: true,
//hotOnly:默认false,当项目报错解决后刷新一次页面。设置为true不刷新页面
hotOnly: true,
//host:配置项目的host,默认为locallhost
host: "0.0.0.0",
//:port:配置端口号,默认8080
port: 7777,
//open:是否打开浏览器,默认false
open: true,
//compress:是否为静态文件开启gzip,默认false
compress: true,
//publicPath:访问服务器资源路径后的拼接,默认为/,直接访问http://locallhost就可以访问到服务器静态资源
//修改为/abc,需要访问http://locallhost/abc才可以访问服务器静态资源
//官方推荐这里的publicPath应与output的publicpath相同
publicPath: "/abc",
/*告诉服务器哪里提供资源,默认/。 比如index.html文件里引入public文件下的js文件,代码应该这样写:<script src="/main.js">就可以找到js文件。
如果没有配置contentBase,<script src="./public/main.js">也无法找到*/
contentBase: path.resolve(__dirname, "./public"),
// watchContentBase:实现contentBase加载进来的文件是否自动热更新。默认false不热更新
watchContentBase: true,
// publicPath: "/abc",
//我们在开发中,可以用proxy解决跨域问题。部署的跨域问题还是需要用别的方法与服务器共同解决
proxy: {
/*配置了代理,让我们的本地服务器http://locallhost:8080去帮我们进行域名为
http://locallhost:8888的api请求*/
"/api": {
//表示代理到的目标地址,比如/api/moment就会被代理到http://localhost:8888/api/moment
target: "http://localhost:8888",
//我们要的是http://localhost:8888/moment,需要将/api删掉
pathRewrite: {
"^/api": "",
},
//默认不接受https,设置为false,就可以支持https的服务器
secure: false,
//是否改变代理请求的headers中的host属性。默认false,代理请求的headers的host属性不是服务端的host
changeOrigin: true,
},
},
// historyApiFallback: 默认为false,当设置为true时,如果刷新后返回404错误,就直接跳转到index.html页面
//rewrites:也可以根据正则,跳转到想要的页面
historyApiFallback: {
rewrites: [{ from: /abc/, to: "/index.html" }],
},
},
可查阅webpack官方文档查看所有相关配置:https://www.webpackjs.com/configuration/dev-server/
参考出处:
https://blog.csdn.net/eleanoryss/article/details/109600154
https://www.webpackjs.com/configuration/dev-server/