webpack学习之2.自动编译、实时重载LiveReload、热替换HMR

代码沿用webpack学习之1.基础配置

每次要编译代码时,手动运行 npm run build 就会变得很麻烦。

webpack 中有几个不同的方式,可以在代码发生变化后自动编译代码:

  1. webpack’s Watch Mode 观察模式
  2. webpack-dev-server
  3. webpack-dev-middleware

1.使用观察模式 watch

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.一个文件变更,重新打包整个项目。

2.使用webpack-dev-server

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 一样,并实现了自动刷新页面

对比watch优点

1.重新编译后,自动加载页面(优化了watch的第一个缺点)
2.编译的代码存储在内存中,而不是生成bundle到output目录。(优化了watch的第二个缺点)
可以删除打包后的文件,重新运行webpack-dev-server,依然可以正常访问。
注:示例中dist/index.html是手动创建的,不是编译的文件。若使用html-webpack-plugin生成,也将被存入内存。

缺点

1.重新编译,以刷新页面方式加载更新的内容,无法保留页面状态。
2.封装好的express

3.使用webpack-dev-middleware

node_modules/webpack-dev-server/lib/server中可以看到,webpack-dev-server是使用express搭建的服务器,然后内部使用 webpack-dev-middlewareWebSocket 实现实时重新加载(LiveReload)。

webpack-dev-middleware 是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。

简单搭建

现在单独使用 webpack-dev-middlewareexpress 来实现 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模块实现,本学习不使用)

模块热替换HMR (hot module replacement)

使用模块热替换实现实时重载,与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));

// ...

添加html内容查看效果

现在项目中有个两个入口模块,只修改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';

module.hot.accept

如果已经通过 HotModuleReplacementPlugin 启用了模块热替换(Hot Module Replacement),则它的接口将被暴露在 module.hot 属性下面。

accep 接口 接受模块的更新,完成页面同步更新,并可以定义一个回调函数响应模块的更新。

accep(callback) 接受将当前模块(accept所在js)的更新
accep(
  dependencies, // 指定接受哪些模块的更新,可以是一个字符串或字符串数组,例:'./module3.js'
  callback // 用于在模块更新后触发的函数
)

css未更新

修改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

你可能感兴趣的:(webpack)