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机制,加载这两个文件,并且针对修改的模块进行更新。

03-05 17:08