yuanyuanbyte/Blog

Webpack性能优化系列之 HMR 热模块替换

yuanyuanbyte opened this issue · 0 comments

本系列的主题是 Webpack,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末

如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。

webpack性能优化

webpack性能优化主要指两个环境:

  • 开发环境性能优化
  • 生产环境性能优化

除了前面的开发环境和生产环境配置,还有以下性能优化处理:

开发环境性能优化包括:

  • 优化打包构建速度
    • HMR
  • 优化代码调试
    • source-map

生产环境性能优化包括:

  • 优化打包构建速度
    • oneOf
    • babel缓存
    • 多进程打包
    • externals
    • dll
  • 优化代码运行的性能
    • 缓存(hash-chunkhash-contenthash)
    • tree shaking
    • code split
    • 懒加载/预加载
    • pwa

HMR 热模块替换

为什么要使用HMR 热模块替换功能?

我们回看一下前面总结的开发环境配置:

/*
  开发环境配置:能让代码运行
    运行项目指令:
      webpack 打包运行项目
      npx webpack-dev-server 启动webpack-dev-server
*/

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      // loader的配置
      {
        // 处理less资源
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
      },
      {
        // 处理css资源
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        // 处理图片资源
        test: /\.(jpg|png|gif)$/,
        loader: 'url-loader',
        options: {
          limit: 8 * 1024,
          name: '[hash:10].[ext]',
          // 关闭es6模块化
          esModule: false,
          outputPath: 'imgs'
        }
      },
      {
        // 处理html中img资源
        test: /\.html$/,
        loader: 'html-loader'
      },
      {
        // 处理其他资源
        exclude: /\.(html|js|css|less|jpg|png|gif)/,
        loader: 'file-loader',
        options: {
          name: '[hash:10].[ext]',
          outputPath: 'media'
        }
      }
    ]
  },
  plugins: [
    // plugins的配置
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ],
  mode: 'development',
  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true
  }
};

下面我们看一下这种配置存在什么问题

运行指令:npx webpack-dev-server ,启动webpack-dev-server

然后在index.js文件中打印一段话:console.log('index.js文件被加载了~');

在这里插入图片描述

只要index.js文件重新被加载,就会打印上面那段话

现在去修改样式文件

在这里插入图片描述

#box {
  width: 400px;// 200px修改为400px
  height: 200px;
  background-image: url('../imgs/angular.jpg');
  background-repeat: no-repeat;
  background-size: 100% 100%;
}

修改之后,很明显整体页面被刷新,内容被重新打印了一次

在这里插入图片描述

意思就是:当我们修改css文件时,明明js文件没有变化,但js文件也被重新加载了一次,所以打包的时候,看似只修改了css文件,实际上是把js文件也重新打包了一次,这是一个问题。

再写一个模块:

在这里插入图片描述

在index.js中引入上面那个print模块,并且在下面调用一下:

在这里插入图片描述

效果:

在这里插入图片描述

发现模块被加载,并且模块的print方法被调用执行了

现在我们修改一下print.js文件,'hello print'改为`'hello webpack'

console.log('print.js被加载了~');

function print() {
  const content = 'hello webpack';
  console.log(content);
}

export default print;

保存查看效果,可以发现整个页面被重新刷新了,所有代码重新执行:

在这里插入图片描述

这个意思就代表:假设以后我们有100个js模块,100个样式模块,如果只修改其中某一个模块,这整200个模块就需要重新打包,这个速度可以想想是非常慢的,更不要说以后的模块会越来越多,那打包情况只会越来越慢。

所以我们想实现这样的功能:如果只有一个模块发生变化,那么只需要修改这一个模块代码就足够了,其他模块理应是不变的

做这个功能要使用webpack的HMR 热模块替换

HMR: hot module replacement 热模块替换 / 模块热替换

作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块),极大提升构建速度

HMR功能的使用非常简单,只需要在devServer里配置hot:true即可:

  devServer: {
    contentBase: resolve(__dirname, 'build'),
    compress: true,
    port: 3000,
    open: true,
    // 开启HMR功能
    // 当修改了webpack配置,新配置要想生效,必须重新webpack服务
    hot: true
  }

修改配置后,运行指令:npx webpack-dev-server ,重新启动webpack-dev-server

开启HMR功能跟之前更新有什么区别?

首先打印结果这里已经显示HMR功能已经开启:

在这里插入图片描述

下面修改样式文件,保存,可以发现只更新一个样式文件,js文件并不会重新加载:

在这里插入图片描述

可以发现:样式文件可以使用HMR功能,之所以能使用是因为style-loader内部实现了~

这也是为什么我们在开发环境要使用style-loader,生产环境要用MiniCssExtractPlugin.loader提取成单独文件,因为开发环境借助style-loader能让性能更好,打包速度更快,但是上线的时候考虑性能优化,所以不能用

前面样式文件可以使用HMR功能,js文件行不行呢?

修改print.js文件,保存,发现整个页面被重新刷新了一遍,这一点可以知道js文件默认不能使用HMR功能

html文件可以使用HMR功能吗?

在html文件中修改一段代码:

在这里插入图片描述

保存后发现当开启HMR功能后,html文件改动后并没有任何变化:

在这里插入图片描述

html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了~

通过修改entey入口,改成一个数组,把html文件引入就可以解决上面的问题了:

entry: ['./src/js/index.js', './src/index.html'],

修改配置后,打包运行项目

在这里插入图片描述

测试发现html文件也是没有HMR功能的,一旦html文件发生变化,整体页面就会重新刷新

那么html文件需要HMR功能吗?

html不像js文件,一个项目里会有很多个js模块,而运行的html只会有一个,一旦发生变化,就一定要变化,它只需要变化一个文件,既然一定要变化,那就没办法优化,所以html文件不用做HMR功能

前面讲到了js默认不能使用HMR功能,这并不代表js就不需要HMR功能,如何对js文件使用HMR功能?

用法:

if (module.hot) {
  // 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
  module.hot.accept('./print.js', function() {
    // 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
    // 会执行后面的回调函数
    print();
  });
}

在这里插入图片描述

修改print.js,查看效果

在这里插入图片描述

可以发现,print.js会重新更新,但index.js不会变,即index.js不会重新加载,样式和结构也不会重新加载,这就是js文件的HMR功能的使用

注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。

为什么呢?

因为入口文件会将其他文件全部引入,一旦入口文件变化,其他文件重新引入,就会重新加载,这是没办法阻止的,所以入口文件是做不了HMR功能的,只能对入口文件引入的一些依赖或者其他文件做HMR功能。

比如修改入口文件下面的打印代码:

在这里插入图片描述

保存,可以发现所有东西都要重新打包构建:

在这里插入图片描述

参考

查看全部文章

博文系列目录

  • JavaScript 深入系列
  • JavaScript 专题系列
  • JavaScript 基础系列
  • 网络系列
  • 浏览器系列
  • Webpack 系列
  • Vue 系列
  • 性能优化与网络安全系列
  • HTML 应知应会系列
  • CSS 应知应会系列

交流

各系列文章汇总:https://github.com/yuanyuanbyte/Blog

我是圆圆,一名深耕于前端开发的攻城狮。

weixin