lines-of-code

前言

上一章我们完成了 Webpack 的基础配置。对项目常用的资源都进行了模块化,这一章主要说明在基于 Webpack 的项目上实现模块热替换 (HMR)模块热替换表现在开发上,就是当我们修改某段 html 或者 js 时。浏览器会自动完成代码的替换切不需要刷新浏览器。别看这个事情很小,一旦习惯了这种设定,就很难回到传统的开发方式了。它对开发效率上的提升是肥肠有必要的,所以我们今天就在基于 Webpack 的项目上来实现这一特性。Webpack 里实现 HMR 有两种方式:

  1. webpack-dev-server

    自带服务器、配置简便。

  2. webpack-dev-middleware

    这是一个中间件,必须搭配 express 等其他服务器来使用。配置较为复杂,需要有 express 等 Node 服务器使用经验。

这两个我们都会讲到

HMR: Hot Module Replacement

webpack-dev-server

基于上一章的源代码进行开发,我们为其添加 webpack-dev-server 依赖 yarn add webpack-dev-server ,并且在 package.json 文件中为 scripts 属性添加一条脚本。

// package.json

{
  // 省略代码
  "scripts": {
    "dev": "webpack-dev-server --open"
  },
  // 省略代码
}

最后执行 yarn run dev ,正常情况下会自动打开浏览器并且访问项目。仔细观察,当我们改动 html 或 js 的时候会触发 Webpack 的构建过程,并且浏览器自动刷新了。这个阶段我们叫做实时重载,如果没有太多要求,这个时候也就已经够用了。但如果项目是一个 SPA 应用,那就需要更进一步的实现 HMR ,实现重新加载模块而不刷新页面。因为对于 SPA 应用来说 HMR 能更好的发挥它的作用。将 dev 脚本更改为 "dev": "webpack-dev-server --open --inline -- hot" 启用热替换功能。更改 common.css 中 box 的背景色,保存后查看浏览器状态。会发现页面在不刷新的情况下更新了 div.box 的背景色。观察控制台的内容,提示已经更新了模块。2-1

需要注意的是 index.html 并不在依赖索引中,尽管会触发 webpack 的构建过程,但 webpack-dev-server hmr 并不会重载 index.html 的内容。

源代码

webpack-dev-middleware

同样使用上一章源代码进行开发。相对于 webpack-dev-server 来说,因为需要另外配置服务器,所以webpack-dev-middleware 的使用相对复杂一点。

这里需要有点 express 基础

添加 wepack-dev-middlewareexpress依赖 yarn add wepack-dev-middleware express 。新建 build/dev-server.js 文件:

// dev-server.js

var express = require("express");
var webpackDevMiddleware = require("webpack-dev-middleware");
var webpack = require("webpack");
var webpackConfig = require("../webpack.config");

var app = express();
var compiler = webpack(webpackConfig);

app.use(webpackDevMiddleware(compiler, {
  publicPath: "/" // 大部分情况下和 `output.publicPath`相同
}));

app.listen(8080, function () {
  console.log("Listening on port 8080!");
});

以上代码的作用是启动 webpack ,并且配置 webpack-dev-middleware 中间件,最后启动 express。这仅仅是创建了一个基于 webpack 的开发服务器,实现 HMR 我们需要再添加一个依赖 webpack-hot-middleware yarn add webpack-hot-middleware 。在 webpack.config.js 中修改一下内容:

// webpack.config.js
// 省略相关代码
var webpack = require("webpack")

module.exports = {
  entry: ['./build/dev-client.js', './src/main.js'],
  // 省略相关代码
  plugins: [
    // 省略相关代码
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ]
}

创建 build/dev-client.js 文件:

// dev-client.js
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')

hotClient.subscribe(function (event) {
  if (event.action === 'reload') {
    window.location.reload()
  }
})

安装 eventsource-polyfill 依赖以兼容不支持事件源的浏览器 yarn add eventsource-polyfill ,添加完以上内容后,修改 dev-server.js 文件:

// dev-server.js
// 省略相关代码
var webpackHotMiddleware = require("webpack-hot-middleware")

var compiler = webpack(webpackConfig);

var hotMiddleware = webpackHotMiddleware(compiler);

// 解决 index.html 修改后不刷新问题
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleware.publish({ action: 'reload' })
    cb()
  })
})

app.use(hotMiddleware);

// 省略相关代码

更改 package.jsondev 脚本 为 node build/dev-server.js ,运行 yarn run dev 启动服务。

测试效果,实现了 HMR ,并解决 webpack-dev-serverindex.html 修改不刷新的问题。

源代码

第三方模块兼容

大多数情况下,我们还会依赖一些对全局变量有要求的第三方库。例如各类 Jquery 插件,依赖于全局变量 $jquery 。对于这种情况,可以使用 ProvidePlugin 插件。参考一下情况:

// webpack.config.js

var webpack = require("webpack")

module.exports = {
  // 省略相关代码
  plugins: [
    // 省略相关代码
    new webpack.ProvidePlugin({
      $: 'jquery',
      jQuery: 'jquery'
    })
  ]
}

main.js 中使用 jquery :

// main.js

require('./css/common.css')

$("#app").addClass("box")

测试后一切正常!

兼容第三方模块还有其他方式,通过 imports-loaderexports-loader 都是解决第三方依赖的方式,可以参考官网相关资料

源代码