Blog icon indicating copy to clipboard operation
Blog copied to clipboard

Webpack性能优化系列之缓存 (详解如何做babel缓存和文件资源缓存)

Open yuanyuanbyte opened this issue 4 years ago • 0 comments

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

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

缓存

缓存我们会从两个点出发:一个是对babel进行缓存,另一个是对整体的文件资源进行缓存

babel缓存

日常开发中,我们永远是js代码最多,像结构和样式要么少要么很少,即使多也没有办法做更多的处理。

为什么要对babel进行缓存呢?因为babel要对我们写的js代码做编译处理,编译成一种浏览器能识别的语法,即所谓的js兼容性处理。在编译过程中,假设有100个js模块,只改动其中1个js模块,不可能把全部的模块都重新编译一遍,其他99个应该是不变的,这一点跟前面文章讲过的HMR功能一样:一个模块变,只变这一个模块,其他模块不变。而生产环境下又不能使用HMR功能,因为HMR是基于devServer,生产环境是不需要devServer的。

开启babel缓存只需要在babel-loader的配置里加一个参数即可:cacheDirectory: true

 {
   test: /\.js$/,
   exclude: /node_modules/,
   loader: 'babel-loader',
   options: {
     presets: [
       [
         '@babel/preset-env',
         {
           useBuiltIns: 'usage',
           corejs: { version: 3 },
           targets: {
             chrome: '60',
             firefox: '50'
           }
         }
       ]
     ],
     // 开启babel缓存
     // 第二次构建时,会读取之前的缓存
     cacheDirectory: true
   }
 },

开启babel缓存,之后的 webpack 构建,将会尝试读取缓存,来避免在每次执行时,可能产生的、高性能消耗的 Babel 重新编译过程(recompilation process)。

文件资源缓存

写个例子来演示一下文件资源缓存

为了演示缓存,我们需要写一个服务器代码

server.js:

/*
  服务器代码
  启动服务器指令:
    npm i nodemon -g
    nodemon server.js 
    
    不需要下载nodemon
    node server.js
  访问服务器地址:
    http://localhost:3000

*/
const express = require('express');

const app = express();
// express.static向外暴露静态资源
// maxAge 资源缓存的最大时间,单位ms
app.use(express.static('build', { maxAge: 1000 * 3600 }));

app.listen(3000);

在这里插入图片描述

关于上述服务器代码疑问参考:Express细节探究(1)——app.use(express.static) - ADAM亚当 - 博客园

node server.js命令, 启动服务器

注意,上面的服务器设置了maxAge,即开启Cache-Control

通过network查看资源,刷新页面,可以看到资源来自cache,即来自缓存

在这里插入图片描述

查看请求可以发现请求头设置了Cache-Control,max-age=3600,即有效期为3600s,一个小时,意思就是这个资源会被强制缓存一个小时

在这里插入图片描述

这个缓存会带来新的问题:

假如我们修改代码,修改一下js代码

index.js:

import '../css/index.css';

function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

// eslint-disable-next-line
// console.log(sum(1, 2, 3, 4));// 修改前

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4, 5));// 修改后

再次构建,打开浏览器刷新,会发现结果没有变化

在这里插入图片描述

修改background-color: deeppink;,原来为pink,再次构建,发现样式同样的有没有变化

在这里插入图片描述

这是为什么呢?

这是因为当前资源在强制缓存期间,它是不会访问服务器的,直接读取本地缓存

这就带来了一个问题,假使我们的资源在强缓存期间出现了严重的bug,开发人员需要紧急修复,但因为资源被强制缓存,就算修复了,也会因为还在强缓存期间而无效。

我们可以通过修改资源名称来解决这个问题

有三种方法可以修改资源名称:

hash: 每次wepack构建时会生成一个唯一的hash值。

问题: 因为js和css同时使用一个hash值。

如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)

chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样

问题: js和css的hash值还是一样的

因为css是在js中被引入的,所以同属于一个chunk

contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样

这三种方法,可以发现只有contenthash是没有副作用的,所以最后的结论就是使用contenthash来修改资源名称

使用方式:

入口文件

 entry: './src/js/index.js',
 output: {
   filename: 'js/built.[contenthash:10].js',
   path: resolve(__dirname, 'build')
 },

样式文件

 new MiniCssExtractPlugin({
   filename: 'css/built.[contenthash:10].css'
 }),

完整的配置:

const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

/*
  缓存:
    babel缓存
      cacheDirectory: true
      --> 让第二次打包构建速度更快
    文件资源缓存
      hash: 每次wepack构建时会生成一个唯一的hash值。
        问题: 因为js和css同时使用一个hash值。
          如果重新打包,会导致所有缓存失效。(可能我却只改动一个文件)
      chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
        问题: js和css的hash值还是一样的
          因为css是在js中被引入的,所以同属于一个chunk
      contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样    
      --> 让代码上线运行缓存更好使用
*/

// 定义nodejs环境变量:决定使用browserslist的哪个环境
process.env.NODE_ENV = 'production';

// 复用loader
const commonCssLoader = [
  MiniCssExtractPlugin.loader,
  'css-loader',
  {
    // 还需要在package.json中定义browserslist
    loader: 'postcss-loader',
    options: {
      ident: 'postcss',
      plugins: () => [require('postcss-preset-env')()]
    }
  }
];

module.exports = {
  entry: './src/js/index.js',
  output: {
    filename: 'js/built.[contenthash:10].js',
    path: resolve(__dirname, 'build')
  },
  module: {
    rules: [
      {
        // 在package.json中eslintConfig --> airbnb
        test: /\.js$/,
        exclude: /node_modules/,
        // 优先执行
        enforce: 'pre',
        loader: 'eslint-loader',
        options: {
          fix: true
        }
      },
      {
        // 以下loader只会匹配一个
        // 注意:不能有两个配置处理同一种类型文件
        oneOf: [
          {
            test: /\.css$/,
            use: [...commonCssLoader]
          },
          {
            test: /\.less$/,
            use: [...commonCssLoader, 'less-loader']
          },
          /*
            正常来讲,一个文件只能被一个loader处理。
            当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:
              先执行eslint 在执行babel
          */
          {
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel-loader',
            options: {
              presets: [
                [
                  '@babel/preset-env',
                  {
                    useBuiltIns: 'usage',
                    corejs: { version: 3 },
                    targets: {
                      chrome: '60',
                      firefox: '50'
                    }
                  }
                ]
              ],
              // 开启babel缓存
              // 第二次构建时,会读取之前的缓存
              cacheDirectory: true
            }
          },
          {
            test: /\.(jpg|png|gif)/,
            loader: 'url-loader',
            options: {
              limit: 8 * 1024,
              name: '[hash:10].[ext]',
              outputPath: 'imgs',
              esModule: false
            }
          },
          {
            test: /\.html$/,
            loader: 'html-loader'
          },
          {
            exclude: /\.(js|css|less|html|jpg|png|gif)/,
            loader: 'file-loader',
            options: {
              outputPath: 'media'
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'css/built.[contenthash:10].css'
    }),
    new OptimizeCssAssetsWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      minify: {
        collapseWhitespace: true,
        removeComments: true
      }
    })
  ],
  mode: 'production',
  devtool: 'source-map'
};

参考

查看全部文章

博文系列目录

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

交流

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

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

weixin

yuanyuanbyte avatar Nov 19 '21 11:11 yuanyuanbyte