[TOC] >[success] # 学习前额外知识补充 1. `localhost`:本质上是一个域名,通常情况下会被解析成**127.0.0.1** 2. `127.0.0.1`:**回环地址(Loop Back Address)**,表达的意思其实是我们主机自己发出去的包,直接被自己接收 * 正常的数据库包经常 应用层 - 传输层 - 网络层 - 数据链路层 - 物理层 * 而回环地址,是在网络层直接就被获取到了,是不会经常数据链路层和物理层的 * 比如我们监听 127.0.0.1时,在同一个网段下的主机中,通过ip地址是不能访问的 3. `0.0.0.0`:监听IPV4上所有的地址,再根据端口找到不同的应用程序 * 比如我们监听 0.0.0.0时,在同一个网段下的主机中,**通过ip地址是可以访问**的; >[success] # webpack -- 开发环境运行构建 1. 在开过程希望可以做到,当**文件发生变化时**,可以自动的完成 **编译** 和 **展示**,而不是每次通过`npm run build`编译相关的代码后在通过,过`live server`或者直接通过浏览器,打开index.html代码每次刷新来查看改变 2. webpack 完成自动编译检测提供的方法 * `webpack watch mode` * `webpack-dev-server` >[info] ## webpack -- watch 1 .`Webpack-cli`中的 watch工作模式,这种模式下项目中的文件会被监视,一旦当这些文件发生变化,就会自动**重新运行打包任务**,但需要手动刷新浏览器,并且配合一些`http`服务,例如使用`http-server` 2. **原理**轮询判断文件的最后编辑时间是否发生变化,一开始有个文件的修改时间,先存储起来这个修改时间,下次再有修改就会和上次修改时间比对,发现不一致的时候不会立即告诉监听者,而是把文件修改缓存起来,等待一段时间,等待期间内如果有其他发生变化,会把变化列表一起构建,并生成到`bundle`文件夹 3. 开启监听模式两种方式 * 启动webpack 命令时 带上--watch 参数 * 在配置webpack.config.js 中设置watch:true >[danger] ##### 配置代码 ~~~ const HtmlWebpackPlugin = require('html-webpack-plugin') /** @type {import('webpack').Configuration} */ const config = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', }, watch: true, // 默认false 不开启 //只有开启监听模式时,watchOptions才有意义 watchOptions: { //默认为空,不监听的文件或者文件夹,支持正则匹配 ignored: /node_modules/, //监听到变化发生后会等300ms再去执行,默认300ms aggregateTimeout: 300, //判断文件是否发生变化是通过不停询问系统指定文件有没有变化实现的,ms为单位下面意思是 每秒检查一次变动 poll: 1000, }, plugins: [ new HtmlWebpackPlugin({ title: 'aaa', }), ], } module.exports = config ~~~ >[danger] ##### 如何解决使用watch 浏览器自动刷新问题 1. 如果我们希望编译过后自动刷新浏览器 可以利用`live server` 这类工具,当文件发生改变后这类工具本身就会帮我们刷新页面类似的还有` npm install browser-sync --dev`,使用指令` npm browser-sync dist --files` 2. 这类的**缺点**也狠明显,这个过程中Webpack会不断地将文件写入磁盘,然后这类工具再从磁盘中读取出来这个过程当中一次就会多出两部磁盘读写操作,**过程中涉及大量磁盘读写操作,必然会导致效率低下** >[info] ## webpack-dev-server (推荐) 1. `webpack-dev-server`使用了一个库叫`memfs`(memory-fs webpack自己写的),是 Webpack 官方推出的一个开发工具它提供一个开发服务器,并且集成了自动编译和自动刷新浏览器等一系列功能,安装完成过后,这个模块为我们提供了一个叫作` webpack-dev-server` 的 **CLI 程序** 是**开发时候官方提供一个本地自启服务** 2. `webpack-dev-server`为了提高工作效率,并没有将打包后的结果写入到磁盘。它是将打包结果暂时存放在内存当中,而内部的`http-server`也正是将内存中的这些**文件读出来发送给浏览器** 3. 安装 `npm install webpack-dev-server -D` 4. 使用 执行`webpack-dev-server` >[danger] ##### 使用 ~~~ // 开启热更新 devServer: { hot: true, }, ~~~ ~~~ const HtmlWebpackPlugin = require('html-webpack-plugin') /** @type {import('webpack').Configuration} */ const config = { mode: 'development', entry: './src/index.js', output: { filename: 'bundle.js', }, // 开启模块热更新 devServer: { hot: true, }, plugins: [ new HtmlWebpackPlugin({ title: 'aaa', }), ], } module.exports = config ~~~ * **更多配置** 1. `webpack-dev-serve`配合的插件 `new webpack.HotModuleReplacementPlugin()`,但配置时候**hot: true 会自动引入这个 plugin** 2. 对**devServer中contentBase**'字段说明,某些资源没有通过`webpack`打包但也想被`webpack-dev-server`调用,例如`copy-webpack-plugin`插件将一些不需要参加构建的静态文件复制到指定文件夹,如果开发阶段也使用`copy-webpack-plugin`这类插件,因为在开发过程中,我们会频繁重复执行打包任务,假设这个目录下需要拷贝的文件比较多,如果每次都需要执行这个插件,那打包过程开销就会比较大,每次构建的速度也就自然会降低这类插件往往只在**上线前的那一次打包中使用**,在开发过程更多推荐这种情况使用`contentBase` 作为配置 3. `proxy`由于开发服务器的缘故,我们还将应用运行在localhost的一个端口上面,当我们访问线上环境的,如果接口没有开启'CORS'跨域,就会产生跨域问题,解决开发阶段这种问题的产生可以使用 ~~~ // plugins:[ // new webpack.HotModuleReplacementPlugin(), // hot: true 会自动引入这个 plugin // ], devServer: { // 配置devServer contentBase: './public', // 额外指明静态资源目录 proxy: { // 添加代理服务配置 '/api': { // 需要被代理的请求路径前缀 // http://localhost:8080/api/users -> https://api.github.com/api/users target: 'https://api.github.com', // 代理目标 // ----------------------注意下面pathRewrite 和上面是两种情况一般二选一--------------------------------------- // 但是我们需要请求的接口地址没有/api // http://localhost:8080/api/users -> https://api.github.com/users pathRewrite: { // 代理路径重写 '^/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true // 以实际代理请求的主机名请求 } open: true, // 自动打开浏览器 port: 3000, // 设置启动时候的运行端口 contentBase: './dist', // 指定托管的根目录 hot: true // 启用热更新 // hotOnly: true // 代码报错会显示错误信息不回进行热更新,hot 遇到报错会进行上一次的更新导致没有错误信息 } ~~~ 其他字段 **`compress`是否为静态文件开启gzip compression默认值是false,可以设置为true** ![](https://img.kancloud.cn/4f/54/4f5476a0277d2d5b976c732b32f76b0e_896x310.png) **`port`设置监听的端口,默认情况下是8080** **`open`是否打开浏览器** >[danger] ##### 热更新 1. 名词解释浏览器的热更新,指的是我们在本地开发的同时打开浏览器进行预览,当代码文件发生变化时,浏览器自动更新页面内容的技术。这里的自动更新,表现上又分为**自动刷新整个页面**,以及**页面整体无刷新而只更新页面的部分内容** >[danger] ##### 模块热更新 -- 开启HMR 1. **HMR**的全称是`Hot Module Replacement`,翻译为模块热替换,热更新最好的方式就是这种模块热更新,因为可以做到应用程序运行过程中,**替换、添加、删除模块**,而**无需重新刷新整个页面** 2. 默认情况下,`webpack-dev-server`已经支持HMR,我们只需要开启即可(默认已经开启),在不开启HMR的情况下,当我们修改了源代码之后,整个页面会自动刷新,使用的是live reloading 产生问题 **一旦页面整体刷新,页面中任何之前的操作状态都会丢失**,**可以解决**应用运行过程中实时替换某个模块,应用运行状态不受影响,解决自动刷新导致页面状态丢失等。热替换只将修改的模块实时替换至应用中 ~~~ devServer: { hot: true, }, ~~~ 3. **是否开启了热更新意味着所有模块都可以享受热更新?** * **当对css 进行更改的时候发现开启热更新不在是整个页面进行刷新**,似乎满足了我们期望的**只对更改的局部** * **js替换时 出现页面刷新** 原因,因为 样式文件经过`Loader`处理,自动处理了样式文件的**模块热更新**,CSS文件仅需要替换文件就可以覆盖样式;而**JS无规律**,导出类型以及引用杂乱,因此没有通用的模块替换方案 4. **Vue/React**等脚手架项目JS照样可以**模块热更新**,使用框架下的开发,每种文件都是有规律的,且脚手架创建的项目内部都集成了**HMR**方案。例如们使用`vue-loader`,此loader支持vue组件的HMR,提供开箱即用的体验,`React Hot Loader`,实时调整react组件(目前React官方已经弃用了,改成使用**react-refresh**) * `style-loader` 举例子 ~~~ //为了清晰期间,我们将模块名称注释以及与热更新无关的逻辑省略,并将 css 内容模块路径赋值为变量 //cssContentPath 以便多处引用,实际代码可从示例运行时中查看 var cssContentPath = "./node_modules/css-loader/dist/cjs.js!./src/style.css" var api = __webpack_require__("./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js"); var content = __webpack_require__(cssContentPath); ... var update = api(content, options); ... module.hot.accept( cssContentPath, function(){ content = __webpack_require__(cssContentPath); ... update(content); } ) module.hot.dispose(function() { update(); }); // update(content) 是将新的样式内容更新到原 style 标签中,而 update() 则是移除注入的 style 标签, //'module.hot' 是由'模块热替换插件(HotModuleReplacementPlugin)' 提供的'API' ~~~ >[danger] ##### 自己开发js 模块进行模块热更新 1. JS 代码中的热替换利用`模块热替换插件(HotModuleReplacementPlugin)`提供的API ~~~ // ./text.js export const text = 'Hello World' // ./index2.js import {text} from './text.js' const div = document.createElement('div') document.body.appendChild(div) function render() { div.innerHTML = text; } render() if (module.hot) { module.hot.accept('./text.js', function() { render() }) } ~~~ >[danger] ##### 问答 ~~~ 1. 热更新会产生新的文件么? 答:不输出文件,而是放在内存中。不输出出文件指的是那些入口打包后的那些'js' 文件 2.HotModuleReplacementPlugin 这个热更新插件: 这里面的热更新有最核心的是 'HMR' Server 和 'HMR' runtime。'HMR Server' 是服务端,用来将变化的 js 模块 通过 websocket 的消息通知给浏览器端。'HMR Runtime'是浏览器端,用于接受 'HMR Server' 传递的模块数据, 浏览器端可以看到 .hot-update.json 的文件过来。(HMR 是HotModuleReplacementPlugin 缩写) 3.HotModuleReplacementPlugin是做什么用的? 答:webpack 构建出来的 bundle.js 本身是不具备热更新的能力的,HotModuleReplacementPlugin 的作用就是将 HMR runtime 注入到 bundle.js,使得bundle.js可以和HMR server建立websocket的通信连接 4.webpack-dev-server和hot-module-replacement-plugin之间的关系? 答:'webpack-dev-server(WDS)'的功能提供 bundle server的能力,就是生成的 bundle.js 文件可以通过 localhost://xxx 的方式去访 问,另外 WDS 也提供 livereload(浏览器的自动刷新)。 'hot-module-replacement-plugin' 的作用是提供 HMR 的 runtime,并且将 runtime 注入到 bundle.js 代码里面去。 一旦磁盘里面的文件修改,那么 HMR server 会将有修改的 js module 信息发送给 HMR runtime,然后 HMR runtime 去局部更新页面的代码。因此这种方式可以不用刷新浏览器。 简单理解:hot-module-replacement-plugin 包给 webpack-dev-server 提供了热更新的能力。 5.热更新分析原理如图,来自极客时间 最开始运行时候 走的是1-》2-》A -》B 更改内容时候热更新 1-》2-》3-》4 ~~~ ![](https://img.kancloud.cn/62/3d/623d74ba05da8a7d113a8300cd167ea8_1185x548.png)