四、DevServer
在通过 Loaders 处理完静态资源以及 Plugins 快捷打包后,我们基本就能愉快地打包文件了。
这时,又遇到了新的问题,什么问题呢?那就是,我们只有在打包完之后,运行代码才能看到打包的结果。
在开发过程中,我们希望的是自动打包,让我们边写代码边看到修改代码后的效果,而不是每次都手动打包。
官方提供了三种方式:
- webpack's Watch Mode (监听文件变动,变动则重新打包,输出目录中可以看到新的打包文件。)
- webpack-dev-server (开启本地开发服务器,默认端口 8080,编译后的文件存在内存中,而非本地。)
- webpack-dev-middleware (将 webpack 作为 Node.js 的中间件)
这些方式都是不错的开发工具,大部分情况下,用第二种就好。
注意:这些工具仅对开发环境有益,在生产环境中请避免这样的使用!!!
1. watch (监听模式)
You can instruct webpack to "watch" all files within your dependency graph for changes. If one of these files is updated, the code will be recompiled so you don't have to run the full build manually.
在 package.json 中设置脚本。
package.json
{ "scripts": { "watch": "webpack --watch", // 监听打包 "bundle": "webpack" // 普通打包 }, }
在终端中运行npm run watch
,你会发现 webpack 是如何编译你的代码的,在打包完毕后它并不会消失,脚本会持续观察你的文件变化。当你对文件作出修改后,它会自动重新编译发生变化的模块。(也就是说你改了文件内容,它就帮你自动打包。)
2. webpack-dev-server (本地开发服务器)
The
webpack-dev-server
provides you with a rudimentary web server and the ability to use live reloading.
安装:
npm install --save-dev webpack-dev-server
package.json
{ "scripts": { "start": "webpack serve", // 开启本地服务器 "watch": "webpack --watch", // 监听打包 "bundle": "webpack" // 普通打包 }, }
webpack.config.js
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { // ... mode: 'development', devtool: 'eval-cheap-module-source-map', devServer: { // contentBase: path.join(__dirname, 'dist'), // 指定被访问html页面所在目录的路径 static: path.join(__dirname, 'dist'), // 注意:Webpack5 中已用 static 替代 contentBase open: true, // 开启服务器时,自动打开页面 compress: true, // 开启 gzip 压缩 port: 9000, // 自定义端口号 publicPath: '/' // 服务器访问静态资源的默认路径,优先级高于 output.publicPath }, // ... }
注意:
- 在开发环境中,mode、devtool、devServer这三个配置是非常重要的!
- webpack-dev-server 在编译后不会在输出目录写入任何文件。相反,它会将打包的文件存在内存中,就好像它们被安装在服务器根路径上的真实文件一样。如果希望在其他路径上找到打包的文件,可以通过使用 devServer 中的 publicPath 选项更改此设置。
3. webpack-dev-middleware (中间件)— 此部分可略过
webpack-dev-middleware
is a wrapper that will emit files processed by webpack to a server. This is used inwebpack-dev-server
internally, however it's available as a separate package to allow more custom setups if desired.
安装:
npm install --save-dev express webpack-dev-middleware
package.json
{ "scripts": { "server": "node server.js", // 运行 node 服务器 "start": "webpack serve", // 开启本地服务器 "watch": "webpack --watch", // 监听打包 "bundle": "webpack" // 普通打包 }, }
webpack.config.js
module.exports = { // ... output: { // ... publicPath: '/' } }
在根目录下添加一个 server.js
const express = require('express'); const webpack = require('webpack'); const webpackMiddleware = require('webpack-dev-middleware'); const config = require('./webpack.config') const compiler = webpack(config); // 打包编译器 // Tell express to use the webpack-dev-middleware and use the webpack.config.js // configuration file as a base. const app = express(); app.use(webpackMiddleware(compiler, { publicPath: config.output.publicPath, })); app.listen(3000, () => { console.log('Server listening on port 3000'); });
运行 npm run server,即可看到效果,服务器在 3000 端口运行:
以上,我们实现了自动打包。
五、Hot Module Replacement(HMR) - 热模块替换(热更新)
运行 npm run start
,此时,我们尝试对文件进行修改,然后回到页面,你会发现终端内 webpack 帮我们重新编译了代码,然后它会自动刷新,刷新后的页面被重置,之前在页面上的操作不见了,又要重新开始。
我们想要的效果是,当文件修改重新编译后,页面不要全部刷新,只是响应发生变化的那一部分。这时候就要用到 HMR,热模块替换。
注意:HMR 相当于 dev Server 的辅助,同样只用在开发环境,不要用在生产环境中!!!
1. HMR 之前
现在看一下在设置 HMR 之前的情况:
index.js
import './assets/styles/reset.css' import './assets/styles/global.scss' import { log } from './assets/js/log.js' const root = document.getElementById('root'); // 1.生成一个按钮 const btn = document.createElement('button'); btn.textContent = 'Add Item'; btn.classList.add('btn'); root.appendChild(btn); // 2.给按钮添加事件,向 root 上追加 div 元素 btn.addEventListener('click', () => { const item = document.createElement('div'); item.textContent = 'Item ' + (root.children.length); item.classList.add('item'); root.appendChild(item); }); log('hello', 'world!');
./assets/styles/global.scss
// 自定义变量 $color: #ff4200; $fs: 14px; $ls: 1.2; // 自定义mixin @mixin size ($w, $h: $w) { width: $w; height: $h; } body { font-size: $fs; background-color: #eaeaea; .btn { @include size(100px, 50px); background-color: $color; border: 1px solid #000; color: #fff; text-align: center; padding: 10px; margin: 10px; &:hover { background-color: #ff4200; } } .item { @include size(100px, 50px); background-color: #ff4200; border: 1px solid #000; color: #fff; text-align: center; padding: 10px; margin: 10px; &:hover { background-color: #ff4200; } } .item:nth-of-type(2n) { background-color: blueviolet } }
./assets/js/log.js
const log = (...args) => { console.log(...args); } export { log };
效果:点击 Add Item 按钮时,下方出现 item。
现在,改变样式文件,让偶数次创建的 item 换成黄绿色。
global.scss
// ... .item:nth-of-type(2n) { // background-color: blueviolet; background-color: yellowgreen; }
注意,当我们改完,按下保存的那一刻,页面刷新了,之前的 item 自然也都不见了,我们需要重新点 btn 生成出 item,才能看到修改完的样式效果。
之后,让我们改一下 log.js,
const log = (...args) => { console.log(...args, 1); } export { log };
同上,保存后页面全局刷新。
2. HMR 之后
- 在 devServer 配置后,添加 hot 和 hotOnly,意思是开启 HMR
- 在 plugins 配置后,添加 HMR 插件。(它是 webpack 内置的,记得要在最上面引入一下 webpack)
webpack.config.js
const webpack = require('webpack'); module.exports = { // ... devServer: { contentBase: path.join(__dirname, 'dist'), // 指定被访问html页面所在目录的路径 // ... hot: true, // 开启热更新 hotOnly: true, // 强制热更新,不会刷新页面 }, plugins: [ // ... new webpack.HotModuleReplacementPlugin() ], }
在设置好后,重新执行npm run start
。
重复上面的样式修改,你会发现,页面并不会全部刷新,但修改的样式已经作用上去了。
然而,对 log.js 尝试修改后,并没有任何的变化。
对于 js 文件来说,需要设置一些东西。
在 index.js 中添加如下代码:
if (module.hot) { module.hot.accept('./assets/js/log.js', (arr) => { log('hello', 'world!'); }) }
意思是,如果 webpack 开启了热更新(也就是热替代),那么,第一个参数是接受的发生更新的文件,第二个是当文件更新后触发的回调函数。
如上,我们接收了 log.js 的变化,当变化时,就会执行log('hello', 'world!')
。
此时,重新执行npm run start
,就可以看到效果了。
小结
以上,是本篇的所有内容。
- 为了以模板为支撑更有效率地输出打包文件,我们需要 HtmlWebpackPlugin;
- 为了快速定位代码错误的位置,我们需要 source map;
- 为了更好地模拟真实环境进行开发,我们需要 devServer(WDS);
- 为了实时局部更新修改的内容而非全局更新,我们需要 Hot Module Replacement(HMR)!
添加我的微信:enjoy_Mr_cat,共同成长,卷卷群里等你 🤪。
以上,感谢您的阅读~