代码沿用webpack学习之1.基础配置
每次要编译代码时,手动运行 npm run build 就会变得很麻烦。
webpack 中有几个不同的方式,可以在代码发生变化后自动编译代码:
webpack 的 --watch 命令可以监听(watch) 依赖图中的所有文件以进行更改。如果其中一个文件被更新,代码将被重新编译,所以你不必手动运行整个构建。
添加一个用于启动 webpack 的观察模式的 npm script 脚本:
// package.json
{
"scripts": {
"build": "webpack",
"watch": "webpack --watch",
// ...
},
// ...
}
运行 npm run watch 后,发现终端还在保持运行。
在浏览器查看到 module2.js中的报错。
然后删除 抛错 代码并保存:
// src/module2.js
export var message = 'this is module2';
class Person{
show() {
// throw new Error('this is a mistake') // 将这行注释
console.log('My name is Jack');
}
}
let p = new Person;
p.show();
发现终端自动重新打包了一次。
刷新页面,代码更新,错误被去掉了。
使用 --progress 命令 百分比查看编译打包进度。
webpack --watch --progress
1.自动编译,需手动刷新页面才能查看效果。
2.一个文件变更,重新打包整个项目。
webpack-dev-server 可以实现:
1.提供一个简单的 web 服务器,以本地站点方式访问。默认端口8080,访问地址:http://localhost:8080
2.监听变更,自动编译。
3.监听变更,实时 重新加载(live reloading)。
需要安装:
npm install --save-dev webpack-dev-server
配置webpack-dev-server
devServer.contentBase 告诉服务器从哪里提供打包之外的文件,打包的文件存储在内存映射到这个目录下。
类似配置站点根目录:告诉开发服务器(dev server),在哪里查找文件。
index.html作为手动创建的静态文件,需要指定服务器去dist下访问它。
// webpack.config.js
// ...
module.exports = {
devServer: {
contentBase: : path.resolve(__dirname, './dist') // 推荐使用绝对路径
},
// ...
}
// package.json
{
"scripts": {
"build": "webpack",
"watch": "webpack --watch --progress",
"start": "webpack-dev-server --open --progress",
// ...
},
// ...
}
webpack-dev-server 命令用于运行,–open命令用于开始运行后自动打开浏览器访问默认地址:http://localhost:8080
运行脚本 npm run start 查看效果同 watch 一样,并实现了自动刷新页面。
1.重新编译后,自动加载页面(优化了watch的第一个缺点)
2.编译的代码存储在内存中,而不是生成bundle到output目录。(优化了watch的第二个缺点)
可以删除打包后的文件,重新运行webpack-dev-server,依然可以正常访问。
注:示例中dist/index.html是手动创建的,不是编译的文件。若使用html-webpack-plugin生成,也将被存入内存。
1.重新编译,以刷新页面方式加载更新的内容,无法保留页面状态。
2.封装好的express
在node_modules/webpack-dev-server/lib/server中可以看到,webpack-dev-server是使用express搭建的服务器,然后内部使用 webpack-dev-middleware 和 WebSocket 实现实时重新加载(LiveReload)。
webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。
现在单独使用 webpack-dev-middleware 和 express 来实现 LiveReload,以提供更多自定义配置的空间。
安装
npm install -D webpack-dev-middleware express
配置 express 服务,在根目录下创建server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const app = express();
const config = require('./webpack.config.js');
const compiler = webpack(config); // 编译器
// 告诉express使用webpack-dev-middleware
app.use(webpackDevMiddleware(compiler));
// 告诉express访问静态资源的路径,类似配置webpack-dev-server的devServer.contentBase
app.use(express.static('dist'))
// 在8080端口运行文件
app.listen(8080, function () {
console.log('Example app listening on port 8080!\n');
});
添加一个 npm script,以使我们更方便地运行服务:
// package.json
{
"scripts": {
"server": "node server.js",
// ...
},
// ...
执行 npm run server ,手动在浏览器访问 http://localhost:8080
修改模块中的代码保存,手动刷新页面查看效果
至此,已经实现了下面几个功能:
1.搭建了一个简单的服务器。
2.修改模块,实时编译。
3.编译文件存储在内存,未写入磁盘
未实现的功能:
1.实时重载
2.运行服务,自动打开浏览器(可使用open模块实现,本学习不使用)
使用模块热替换实现实时重载,与webpack-dev-server的区别是,HMR以模块为单位重载,保留页面的状态。
实际效果,比如:
一个页面包含label和input表单,input中输入内容,这时修改模块令label的文本内容改变webpack-dev-server会刷新页面,input被清空。
而使用HMR,label实现更新,且页面未刷新,input未清空。
安装 webpack-hot-middleware
npm install --save-dev webpack-hot-middleware
webpack.config.js中启用HMR。
在入口前面添加 webpack-hot-middleware/client,它连接到服务器,在包重新构建时接收通知,然后相应地更新客户端的包。
// webpack.config.js 启用HMR
// ...
var webpack = require('webpack');
const hotMiddlewareScript = 'webpack-hot-middleware/client?quiet=true'; // quiet禁用console日志
module.exports = {
// ...
// devServer: {
// contentBase: path.resolve(__dirname, './dist')
// }, // 不用webpack-dev-server,可以注释掉这段
entry: {
main: [hotMiddlewareScript , './src/index.js'],
util: [hotMiddlewareScript , './src/util.js']
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 使用webpack的插件接口开启HMR
// ...
]
}
告诉express在同一个编译器中使用webpack-hot-middleware
// server.js
// ...
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware')
// ...
// 告诉express使用webpack-dev-middleware并使用webpack.config.js
app.use(webpackDevMiddleware(compiler));
app.use(webpackHotMiddleware(compiler));
// ...
现在项目中有个两个入口模块,只修改util.js令其实现热重载,html添加内容查看效果
// src/util.js
window.onload = e => import('./module3.js').then(module => {
let show = module.default;
show();
});
// 修改button文本查看页面变更
var btn = document.getElementById('btn');
btn.innerText = '输出';
if (module.hot) {
// 接受模块的更新,并更新页面中的内容
module.hot.accept()
}
// dist/index.html
// 添加内容
<input type="text" />
<button id="btn"></button>
// src/index.js
// 此文件未改动,在此展示做对比
import './css/style.css';
import data from './module1';
var div = document.createElement('div');
div.innerHTML = `${data.title}${data.info}`;
document.body.appendChild(div);
运行 npm run server 浏览器访问dist/index.html,在input中输入内容
修改util.js中的button文本并保存,看到页面未刷新,input中内容依然保留,而button的文本实现了更新。
修改index.js中的innerHTML,页面未刷新,内容也没有变化。
查看控制台Console,发现有一条信息:
Ignored an update to unaccepted module 18 -> 14
忽略对未接受模块18->14的更新
原因是,在模块index.js中未用module.hot.accept接受此模块的更新。
尽管如此,模块index.js也完成了重新编译,手动刷新页面可以看到变化。
也可以通过 HMR客户端配置 reload,对accept未接受更新的模块,执行页面重载。
// webpack.config.js
const hotMiddlewareScript = 'webpack-hot-middleware/client?quiet=true&reload=true';
如果已经通过 HotModuleReplacementPlugin 启用了模块热替换(Hot Module Replacement),则它的接口将被暴露在 module.hot 属性下面。
accep 接口 接受模块的更新,完成页面同步更新,并可以定义一个回调函数响应模块的更新。
accep(callback) 接受将当前模块(accept所在js)的更新
accep(
dependencies, // 指定接受哪些模块的更新,可以是一个字符串或字符串数组,例:'./module3.js'
callback // 用于在模块更新后触发的函数
)
修改style.css文件发现出发了重新编译,未触发热替换。
原因是loader未配置,如果未使用css分离,直接用style-loader,可以实现hmr。
不过当前代码,使用了MiniCssExtractPlugin预处理,所以需要手动配置开启hmr:
// webpack.config.js
// ...
module: {
rules: [
{
test: /\.css$/, // 根据正则匹配.css结尾的文件
use: [ // 配置loader,倒序使用
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: true // 一般在开发环境开启hmr process.env.NODE_ENV === 'development'
}
},
{ loader: 'css-loader' }
]
}
]
},
// ...
在index.js中使用accept,使其实现HMR。
// src/index.js
import './css/style.css';
import data from './module1';
// 调用位置随意
if (module.hot) {
module.hot.accept()
}
var div = document.createElement('div');
div.innerHTML = `${data.title}${data.info}`;
document.body.appendChild(div);
修改index.js中的innerHTML,页面内容更新,但是之前的内容依然保留。
所以模块被热更新,之前的仍然可能会在客户端留下痕迹,例如:操作dom、事件绑定、计时器等。
这些痕迹可以在accept的回调中处理掉,当然,最方便的就是手动刷新页面。
开发 | webpack
express
webpack-dev-middleware
webpack-hot-middleware