webpack 里涉及了多种形式的刷新,其中包括文件监听,修改内容时,页面的整体刷新,还有修改组件时,只针对组件的局部刷新。从最基本的 webpack -w,到 webpack-dev-serverhtml-webpack-plugin 的结合,再到 Hot Module Replacement,它们都有其对应的应用场景。

注:本文的内容是对上一篇文章【webpack】的补充与提高,所以,项目结构和文件是相同的。

webpack –watch

webpack 给我们提供了 webpack --watch 这么一个命令,它的简写是 webpack -w。当在 cli 中输入 webpack -w,webpack会开启监听模式,观察项目文件的变化。

这样,你在每次修改完文件后,就无需再运行 webpack -p 去重新编译文件,只需要刷新浏览器,便能看到修改的效果。

所以,这个命令的优点是修改后免编译,缺点是修改完文件后,还是得手动去刷新浏览器。

webpack-dev-server

理想情况下,我们是希望修改完文件后,浏览器会自动刷新,即插件具备livereload的功能。通过文章前面的描述,可以采用 webpack-dev-server 并且设置 inline: true 属性。

当修改 entry 里设置的 JavaScript 文件时,浏览器里的页面也跟着刷新。

但修改 html、css 文件却发现浏览器却没跟着刷新,也就是说,这个插件不会监控 html、css 文件。

所以,你还需要借助 html-webpack-plugin 插件。这样,无论是修改 html文件、js文件,还是 css文件,或其他资源,页面都能自动刷新了。

模块热替换 HMR(Hot Module Replacement)

随着web技术在移动端的发展,单页面应用变得越来越火热。这种应用一般只有一个页面,它由许多组件和模块组成,然后通过ajax结合路由方式去加载不同状态的模块。

一个复杂的单页面应用,通常包含了很多组件,而组件里面又包含很多状态。用户的操作,是从一个组件的状态,到另外一个状态,或者到下一个组件。

但这种单页面应用,在开发时也带来一些问题。

比如,一个申请贷款的项目。第一步是 “填写个人信息”,操作完后进入第二个界面 “绑定并设置银行卡”,继续操作完后进入第三个界面 “选择贷款方式”…等等后续界面的操作。当我们在修改第三个组件 “选择贷款方式” 时,页面会重新刷新,所有之前填写的信息被重置,各种状态被丢失,界面也回到第一个组件,即 “填写个人信息”。要查看修改的效果,还得完成前面界面的操作,直到第三个界面。

又比如,假如某个页面的结构是一个tab切换,里面每一个tab项都是一个组件,默认是显示第一个tab项。但我们在开发第二个tab项时,每次修改和变更,页面又重新刷新到第一个tab项,要查看修改后的效果,还得手动切换到第二个tab项。

这无疑给我们的开发调试带来了一定的阻碍。

模块热替换的概念

依照webpack官网的解释,可以理解为:

模块热替换功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载页面。这使得你可以在独立模块变更后,无需刷新整个页面,就可以更新这些模块,极大地加速了开发时间。

你可以把 HMR 看成是 LiveReload 的一种替换方式。

注意:无论是前面提到的刷新插件、还是HMR,它们都是应用在开发环境中,辅助我们更好的开发项目,与生产环境无关!

模块热替换-react

要实现 模块热替换 的功能,可以通过很多方式。由于涉及到组件化开发,目前最流行的采用 webpack + react 的开发模式,如果你采用的也是 react 开发,那么,你可以使用通过 react-hot-loader 加载器来实现 模块热替换,去刷新 react 组件。

首先安装它:

> npm install --save-dev react-hot-loader@next

然后在 webpack.config.js 中设置:

// webpack.config.js
...
var webpack = require('webpack');
...
entry: [
'react-hot-loader/patch', // 开启 React 代码的模块热替换(HMR)

'webpack-dev-server/client?http://localhost:8080', // 为 webpack-dev-server 的环境打包代码,然后连接到指定服务器域名与端口

'webpack/hot/only-dev-server', // 为热替换(HMR)打包好代码,only- 意味着只有成功更新运行代码才会执行热替换(HMR)

path.resolve(__dirname, './src/res/js/main.js')
],
output: {
path: path.resolve(__dirname, './dist/res/'),
filename: 'js/[name].js',
publicPath: '/'
},

devServer: {
inline: true,
historyApiFallback: true,
hot: true
},
....

plugins: [
...
new webpack.HotModuleReplacementPlugin(), // 开启全局的模块热替换(HMR)
new webpack.NamedModulesPlugin() // 当模块热替换(HMR)时在浏览器控制台输出对用户更友好的模块名字信息
]
...

上面的代码中,entry 的值为一个数组,除了原来的 path.resolve(__dirname, './src/res/js/main.js'), 它还增加了 'react-hot-loader/patch''webpack-dev-server/client?http://localhost:8080'、以及 'webpack/hot/only-dev-server' 这三项。

另外,还在 devServer 中开启了 hot 属性,plugins 新增了关于 HMR 的设置。

最后,修改 main.js

// main.js
import React,{Component} from 'react';
import {render} from 'react-dom';

import Navbar from './navbar';
import Heading from './heading';

import { AppContainer } from 'react-hot-loader';

class HelloReact extends Component {

constructor() {
super();
this.state = {
num: 0,
tabIndex: 0
};
}

componentDidMount() {
var _this = this;

setInterval(() => {
var num = _this.state.num;

num += 1;

_this.setState({
num: num
});
}, 1000);
}

switch(index) {
this.setState({
tabIndex: index
});
}

render(){
return (
<div>
<h3 style={{color: 'red'}}>{this.state.num}</h3>
<div className="switch">
<button className="" onClick={() => this.switch(0)}>Navbar</button>
<button className="" onClick={() => this.switch(1)}>Heading</button>
</div>
{
this.state.tabIndex === 0
?
<Navbar />
:
<Heading />
}

</div>
)
}
};

render(<AppContainer><HelloReact /></AppContainer>, document.getElementById('hello-react'));

// 组件热刷新
if (module.hot) {
module.hot.accept(['./navbar', './heading'], () => {
render(<AppContainer><HelloReact /></AppContainer>, document.getElementById('hello-react'));
});
}

在上述代码中,父组件包含了两个子组件 <Navbar /><Heading />(它们的代码,在此不赘述),父组件设置了一个定时器计数,并且,用户可以切点击 NavbarHeading 按钮来切换这两个子组件的显示隐藏。

这样,我们无论是修改 <Navbar /> 组件,还是修改 <Heading /> 组件,都只更新它们对应的视图,而不会刷新整个页面,父组件里的定时计数也不会被重置。