webpack实现静态资源缓存

时间:2023-02-24 13:52:12

一、配置缓存

我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的/dist目录,然后把打包后的内容放置在此目录中。只要/dist目录中的内容部署到 server 上,client(通常是浏览器)就能够访问此 server 的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为缓存的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快。

然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。

在使用webpack构建的项目中,稍有不慎的话,即使服务器设置了缓存策略,可能构建的项目无法实现静态资源缓存

第一部分重点在于通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存(这里主要是强缓存),而在文件内容变化后,能够请求到新的文件。

  1. 添加hash值来定义输出文件的文件名
output: {
      filename: '[name].[contenthash].js', // contenthash 只有在内容发生改变才会变
      path: path.resolve(__dirname, 'dist'), //输出路径   __dirname 代表当前文件的绝对路径
      clean: true, // //  在生成文件之前清空 output 目录
},

在提取css时我们也可以这么命名文件名

// css 提取
plugins: [
  new MiniCssExtractPlugin({
     filename: 'css/[name].[contenthash:10].css',
  }),
]

然后运行我们的 npm run build ,可以看到,打包的js/css文件名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,文件名会保持不变。

根据webpack版本的差异,运行时可能还是会导致文件名更新(因为 webpack 在入口 chunk 中,包含了某些 boilerplate(引导模板),特别是 runtime 和 manifest。)。

继续进行下面的操作,以确保我们的输出结果可靠。

output:{...}
plugins:[...]
optimization: {
    usedExports: true, // 开启 tree shaking
    moduleIds: 'deterministic', // 资源标识符 区分本地模块和第三方依赖,避免导入本地模块,第三方的打包名称发生变化。
    runtimeChunk: 'single', // 将 runtime 代码拆分为一个单独的 chunk。
    splitChunks: {
      cacheGroups: { // 将第三方库(library)提取到单独的 vendor chunk 文件中
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
        },
      },
    },
  },

缓存可能很复杂,但是从应用程序或站点用户可以获得的收益来看,这值得付出努力。

此前资源缓存配置服务于线上。

附加:babel缓存(服务于构建),开启babel缓存,在第二次打包时,打包构建速度更快

{
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        options: {
          // 预设:指示babel做怎样的兼容性处理
          presets: [
            [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定core-js版本
                corejs: {
                  version: 3,
                },
                // 指定兼容浏览器版本范围
                targets: {
                  chrome: '70', // 谷歌版本70及以上
                  firefox: '60',
                  ie: '9',
                  safari: '10',
                  edge: '17',
                }
              }
            ]
          ],
          // 开启babel 缓存
          cacheDirectory: true
       }
 }


配置结束

二、区分一下几种不同的hash

我们都知道,webpack有各种hash值,包括每次项目构建hash,不同入口的chunkhash、文件的内容contenthash,这么多hash,它们有什么区别呢?

hash

hash是跟整个webpack构建项目相关的,每次项目构建hash对应的值都是不同的,即使项目文件没有做“任何修改”。

其实是有修改的,因为每次webpack打包编译都会注入webpack的运行时代码,导致整个项目有变化,所以每次hash值都会变化的。

以本人项目代码为例,代码两次构建前后没有做任何修改的对比图
可以看出,前后两次对应项目构建hash改变了。由此推断使用该方式是无法达到缓存的,因为每次hash都会变化
webpack实现静态资源缓存
webpack实现静态资源缓存

chunkhash

chunkhash,从字面上就能猜出它是webpack打包的chunk相关的。具体来说webpack是根据入口entry配置文件来分析其依赖项并由此来构建该entrychunk,并生成对应的hash值。不同的chunk会有不同的hash值。一般在项目中把公共的依赖库和程序入口文件隔离并进行单独打包构建,用chunkhash来生成hash值,只要依赖公共库不变,那么其对应的chunkhash就不会变,从而达到缓存的目的。

一般在项目中对webpackentry使用chunkhash,具体表现在output配置项上:

moudule.exports = {
  entry: {
   app: './src/main.js',
   vendor: ['react', 'redux', 'react-dom', 'react-redux', 'react-router-redux']
  },
  output: {
    path:path.join(__dirname, '/dist/js'),
    filename: '[name].[chunkhash].js'
  }
 ...
}

contenthash

contenthash表示由文件内容产生的hash值,内容不同产生的contenthash值也不一样。在项目中,通常做法是把项目中css都抽离出对应的css文件来加以引用。比方在webpack配置这样来用:

module.exports = {
  ...
  plugins: [
     new ExtractTextPlugin({
    filename: 'static/[name]_[chunkhash:7].css', // chunkhash 修改为 contenthash
    disable: false,
    allChunks: true
     })
  ...
  ]

上面配置有一个问题,因为使用了chunkhash,它与依赖它的chunk共用chunkhash。

比方在上面app chunk例子中依赖一个index.css文件,index.css的hash是跟着app的chunkhash走的,只要app文件变更的话,那么即使index.css文件没有变化,它的hash值也是会跟着变化的,导致缓存失效。

那么这时我们可以使用extra-text-webpack-plugin里的contenthash值,保证即使css文件所处的模块里就算其他文件内容改变,只要css文件内容不变,它的hash值就不会变。

三、JS缓存

CommonsChunkPlugin

webpack插件CommonsChunkPlugin的主要作用是抽取webpack项目入口chunk的公共部分,具体的用法就不做过多介绍,不太了解可以参考webpack官网介绍;
该插件是webpack项目常用的一个优化功能,几乎在每个webpack项目中都会用到。使用该插件带来的好处:

  1. 提升webpack打包速度和项目体积:将webpack入口的chunk文件中所有公共的代码提取出来,减少代码体积;同时提升webpack打包速度。
  2. 利用缓存机制:依赖的公共模块文件一般很少更改或者不会更改,这样独立模块文件提取出可以长期缓存。
module.exports = {
  entry: {
    app: "./app.js",
    vendor: ["react","react-dom", "redux", "react-redux", "react-router-redux"]
  },
  ....
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({names: ["vendor"]}),
    new webpack.optimize.CommonsChunkPlugin({
        name: 'manifest',
        chunks: ['vendor']
    })
  ]
};

四、CSS缓存

webpack实现css的缓存,就是使用上面介绍过的contenthash,该hash属性值其实是extra-text-webpack-plugin计算的。具体实现css的缓存,其实就像下面一样使用contenthash即可

module.exports = {
  ...
  plugins: [
     new ExtractTextPlugin({
    filename: 'static/[name]_[contenthash:7].css',
    disable: false,
    allChunks: true
     })
  ...
  ]

五、图片/字体缓存

对于图片、字体等静态资源,在使用webpack构建提取时,其实是使用了file-loader来完成的,生成对应的文件hash值也就是由对应的file-loader来计算的。那么这些静态文件的hash值使用的是什么hash值呢,其实就是hash属性值。如下面代码所示:

module.exports = {
 ...
 rules: [
   ...
    {
      test: /\.(gif|png|jpe?g)(\?\S*)?$/,
      loader: require.resolve('url-loader'),
      options: {
        limit: 10000,
        name: path.posix.join('static',  '[name]_[hash:7].[ext]')
      }
    },
    font: {
      test: /\.otf|ttf|woff2?|eot(\?\S*)?$/,
      loader: require.resolve('url-loader'),
      options: {
        limit: 10000,
        name: path.posix.join('static', '[name]_[hash:7].[ext]')
      }
    }
 ]
}

可以看到上面使用的是hash属性值,此hash非webpack每次项目构建的hash,它是由file-loader根据文件内容计算出来的,不要误认为是webpack构建的hash。