本指南继续沿用起步、管理输出和代码分离中的代码示例。

所以我们次章节下载,此章节代码继续练习(webpack v4.30.0(8)官方指南说明文档 代码分):

以上,我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist 目录,然后把打包后的内容放置在此目录中。只要 /dist 目录中的内容部署到服务器上,客户端(通常是浏览器)就能够访问网站此服务器的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。

此指南的重点在于通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。

输出文件的文件名(Output Filenames)
通过使用 output.filename 进行文件名替换,可以确保浏览器获取到修改后的文件。[hash] 替换可以用于在文件名中包含一个构建相关(build-specific)的 hash,但是更好的方式是使用 [chunkhash] 替换,在文件名中包含一个 chunk 相关(chunk-specific)的哈希。
让我们使用起步 中的示例,以及管理输出 中的 plugins 来作为项目的基础,所以我们不必手动处理维护 index.html 文件:

project



webpack.config.js文件代码:

  const path = require('path');
  //const webpack = require('webpack');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  module.exports = {
   entry: {
     app: './src/index.js',
     //print: './src/print.js',
//another: './src/another-module.js'
   },
    plugins: [
new CleanWebpackPlugin(),
     new HtmlWebpackPlugin({
       //title: 'Code Splitting'
   title: 'Caching'
     }),
],
//optimization与entry/plugins同级
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: "commons",
chunks: "initial",
minChunks: 2
}
}
}
},
    output: {
      //filename: '[name].bundle.js',
  //chunkFilename: '[name].bundle.js',
  filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };

使用此配置,然后运行我们的构建脚本 npm run build,应该产生以下输出:

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 2e0329884ac13979bb5e
Version: webpack 4.30.0
Time: 2322ms
Built at: 2019-04-30 22:28:41
                                 Asset       Size  Chunks             Chunk Names
           app.cce2a47216fdbc8a9685.js   2.22 KiB       0  [emitted]  app
                            index.html  196 bytes          [emitted]
vendors~lodash.860de944a10487dea319.js   69.4 KiB       1  [emitted]  vendors~lodash
Entrypoint app = app.cce2a47216fdbc8a9685.js
[0] ./src/index.js 811 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {1} [built]
[3] (webpack)/buildin/module.js 497 bytes {1} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules
可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,我们以为文件名会保持不变。然而,如果我们真的运行,可能会发现情况并非如此:(译注:这里的意思是,如果不做修改,文件名可能会变,也可能不会。)

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 2e0329884ac13979bb5e
Version: webpack 4.30.0
Time: 762ms
Built at: 2019-04-30 22:31:37
                                 Asset       Size  Chunks             Chunk Names
           app.cce2a47216fdbc8a9685.js   2.22 KiB       0  [emitted]  app
                            index.html  196 bytes          [emitted]
vendors~lodash.860de944a10487dea319.js   69.4 KiB       1  [emitted]  vendors~lodash
Entrypoint app = app.cce2a47216fdbc8a9685.js
[0] ./src/index.js 811 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {1} [built]
[3] (webpack)/buildin/module.js 497 bytes {1} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

这也是因为 webpack 在入口 chunk 中,包含了某些样板(boilerplate),特别是 runtime 和 manifest。(译注:样板(boilerplate)指 webpack 运行时的引导代码)

输出可能会因当前的 webpack 版本而稍有差异。新版本不一定有和旧版本相同的 hash 问题,但我们以下推荐的步骤,仍然是可靠的。

提取模板(Extracting Boilerplate)
就像我们之前从代码分离了解到的,CommonsChunkPlugin (4.0已经弃用)可以用于将模块分离到单独的文件中。然而 CommonsChunkPlugin (4.0已经弃用)有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。通过指定 entry 配置中未用到的名称,此插件会自动将我们需要的内容提取到单独的包中:

webpack.config.js文件代码:

  const path = require('path');
+ const webpack = require('webpack');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  module.exports = {
    entry: './src/index.js',
    plugins: [
      new CleanWebpackPlugin(['dist']),
      new HtmlWebpackPlugin({
        title: 'Caching'
-     })
+     }),
+     new webpack.optimize.CommonsChunkPlugin({
+       name: 'manifest'
+     })
    ],
    output: {
      filename: '[name].[chunkhash].js',
      path: path.resolve(__dirname, 'dist')
    }
  };
由于CommonsChunkPlugin (4.0已经弃用)所以修改为以下代码:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
app: './src/index.js',
//print: './src/print.js',
//another: './src/another-module.js'
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
//title: 'Code Splitting'
title: 'Caching'
}),
//new webpack.optimize.CommonsChunkPlugin({
//name: 'manifest'
//})
],
//optimization与entry/plugins同级
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
commons: {
test: /[/]node_modules[/]/,
name: "vendor",
chunks: "all"
}
}
}
},
output: {
//filename: '[name].bundle.js',
//chunkFilename: '[name].bundle.js',
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};

让我们再次构建,然后查看提取出来的 manifest bundle:

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 4b7d51568a0eca1c89af
Version: webpack 4.30.0
Time: 6511ms
Built at: 2019-05-01 01:15:24
                           Asset       Size  Chunks             Chunk Names
     app.a5abcc7f21af49f79c91.js  315 bytes       0  [emitted]  app
                      index.html  275 bytes          [emitted]
manifest.cbf29ebb8e46b166541c.js   2.16 KiB       1  [emitted]  manifest
  vendor.d47562ec6c785420c596.js   69.4 KiB       2  [emitted]  vendor
Entrypoint app = manifest.cbf29ebb8e46b166541c.js app.a5abcc7f21af49f79c91.js
[0] ./src/index.js 811 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {2} [built]
[3] (webpack)/buildin/module.js 497 bytes {2} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。这可以通过使用新的 entry(入口) 起点,以及再额外配置一个 CommonsChunkPlugin 实例的组合方式来实现:

webpack.config.js文件代码:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
//print: './src/print.js',
//another: './src/another-module.js'
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
//title: 'Code Splitting'
title: 'Caching'
}),
//new webpack.optimize.CommonsChunkPlugin({
//name: 'manifest'
//})
],
//optimization与entry/plugins同级
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
commons: {
test: /[/]node_modules[/]/,
name: "vendor",
chunks: "all"
}
}
}
},
output: {
//filename: '[name].bundle.js',
//chunkFilename: '[name].bundle.js',
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
注意,引入顺序在这里很重要。CommonsChunkPlugin 的 'vendor' 实例,必须在 'manifest' 实例之前引入。

让我们再次构建,然后查看新的 vendor bundle:

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 899e56ac397f6acb519b
Version: webpack 4.30.0
Time: 6060ms
Built at: 2019-05-01 02:20:36
                           Asset       Size  Chunks             Chunk Names
                      index.html  276 bytes          [emitted]
    main.2156e5dd37158b7befcb.js  315 bytes       0  [emitted]  main
manifest.4aa4392d21f41a91bc88.js   2.16 KiB       1  [emitted]  manifest
  vendor.7f6afb8177dc44c70784.js   69.4 KiB       2  [emitted]  vendor
Entrypoint main = manifest.4aa4392d21f41a91bc88.js main.2156e5dd37158b7befcb.js
[0] ./src/index.js 811 bytes {0} [built]
[2] (webpack)/buildin/global.js 472 bytes {2} [built]
[3] (webpack)/buildin/module.js 497 bytes {2} [built]
[4] multi lodash 28 bytes {2} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

模块标识符(Module Identifiers)
让我们向项目中再添加一个模块 print.js:

project


print.js文件代码:

export default function print(text) {
console.log(text);
};
src/index.js文件代码:

import _ from 'lodash';
import Print from './print';
function component() {
var element = document.createElement('div');
// lodash 是由当前 script 脚本 import 导入进来的
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());

再次运行构建,然后我们期望的是,只有 main bundle 的 hash 发生变化,然而……

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 3dbff4efba36f62c5e6e
Version: webpack 4.30.0
Time: 6338ms
Built at: 2019-05-01 02:32:51
                           Asset       Size  Chunks             Chunk Names
                      index.html  353 bytes          [emitted]
    main.a221803a3edf707e5c01.js  333 bytes       0  [emitted]  main
manifest.c0278de5497934698f1b.js   1.42 KiB       1  [emitted]  manifest
  vendor.eee7eda963a15156f9a1.js   69.4 KiB       2  [emitted]  vendor
Entrypoint main = manifest.c0278de5497934698f1b.js vendor.eee7eda963a15156f9a1.js main.a221803a3edf707e5c01.js
[1] (webpack)/buildin/global.js 472 bytes {2} [built]
[2] (webpack)/buildin/module.js 497 bytes {2} [built]
[3] ./src/index.js + 1 modules 435 bytes {0} [built]
    | ./src/index.js 345 bytes [built]
    | ./src/print.js 85 bytes [built]
[4] multi lodash 28 bytes {2} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [2] (webpack)/buildin/global.js 472 bytes {0} [built]
    [3] (webpack)/buildin/module.js 497 bytes {0} [built]
        + 2 hidden modules

……我们可以看到这三个文件的 hash 都变化了。这是因为每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。因此,简要概括:
main bundle 会随着自身的新增内容的修改,而发生变化。
vendor bundle 会随着自身的 module.id 的修改,而发生变化。
manifest bundle 会因为当前包含一个新模块的引用,而发生变化。
第一个和最后一个都是符合预期的行为 -- 而 vendor 的 hash 发生变化是我们要修复的。幸运的是,可以使用两个插件来解决这个问题。第一个插件是 NamedModulesPlugin,将使用模块的路径,而不是数字标识符。虽然此插件有助于在开发过程中输出结果的可读性,然而执行时间会长一些。第二个选择是使用 HashedModuleIdsPlugin,推荐用于生产环境构建:
webpack.config.js文件代码:

const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
//print: './src/print.js',
//another: './src/another-module.js'
vendor: [
'lodash'
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
//title: 'Code Splitting'
title: 'Caching'
}),
new webpack.HashedModuleIdsPlugin(),
//new webpack.optimize.CommonsChunkPlugin({
//name: 'manifest'
//})
],
//optimization与entry/plugins同级
optimization: {
runtimeChunk: {
name: "manifest"
},
splitChunks: {
cacheGroups: {
commons: {
test: /[/]node_modules[/]/,
name: "vendor",
chunks: "all"
}
}
}
},
output: {
//filename: '[name].bundle.js',
//chunkFilename: '[name].bundle.js',
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
}
};
现在,不管再添加任何新的本地依赖,对于每次构建,vendor hash 都应该保持一致:

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: 30f3369715993c991b3e
Version: webpack 4.30.0
Time: 6142ms
Built at: 2019-05-01 02:36:57
                           Asset       Size  Chunks             Chunk Names
                      index.html  353 bytes          [emitted]
    main.d3079ea732adb655b0ac.js  346 bytes       0  [emitted]  main
manifest.c0278de5497934698f1b.js   1.42 KiB       1  [emitted]  manifest
  vendor.51e6635dd882847f1b1d.js   69.4 KiB       2  [emitted]  vendor
Entrypoint main = manifest.c0278de5497934698f1b.js vendor.51e6635dd882847f1b1d.js main.d3079ea732adb655b0ac.js
[0] multi lodash 28 bytes {2} [built]
[YuTi] (webpack)/buildin/module.js 497 bytes {2} [built]
[tjUo] ./src/index.js + 1 modules 435 bytes {0} [built]
    | ./src/index.js 345 bytes [built]
    | ./src/print.js 85 bytes [built]
[yLpj] (webpack)/buildin/global.js 472 bytes {2} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [YuTi] (webpack)/buildin/module.js 497 bytes {0} [built]
    [yLpj] (webpack)/buildin/global.js 472 bytes {0} [built]
        + 2 hidden modules

然后,修改我们的 src/index.js,临时移除额外的依赖:

src/index.js文件代码:

import _ from 'lodash';
//import Print from './print';
function component() {
var element = document.createElement('div');

// lodash 是由当前 script 脚本 import 导入进来的
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
//element.onclick = Print.bind(null, 'Hello webpack!');
return element;
}
document.body.appendChild(component());

最后,再次运行我们的构建:

Administrator@PC-20190222QKVD MINGW64 /d/webpack-demo
$ npm run build
> webpack-demo@1.0.0 build D:webpack-demo
> webpack
Hash: c9640cf070fe565d3c47
Version: webpack 4.30.0
Time: 2179ms
Built at: 2019-05-01 02:43:53
                           Asset       Size  Chunks             Chunk Names
                      index.html  353 bytes          [emitted]
    main.68114281d08e66390c1a.js  253 bytes       0  [emitted]  main
manifest.c0278de5497934698f1b.js   1.42 KiB       1  [emitted]  manifest
  vendor.51e6635dd882847f1b1d.js   69.4 KiB       2  [emitted]  vendor
Entrypoint main = manifest.c0278de5497934698f1b.js vendor.51e6635dd882847f1b1d.js main.68114281d08e66390c1a.js
[0] multi lodash 28 bytes {2} [built]
[YuTi] (webpack)/buildin/module.js 497 bytes {2} [built]
[tjUo] ./src/index.js 349 bytes {0} [built]
[yLpj] (webpack)/buildin/global.js 472 bytes {2} [built]
    + 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [YuTi] (webpack)/buildin/module.js 497 bytes {0} [built]
    [yLpj] (webpack)/buildin/global.js 472 bytes {0} [built]
        + 2 hidden modules
我们可以看到,这两次构建中,vendor bundle 的文件名称,都是 51e6635dd882847f1b1d

结论
缓存从凌乱变得清晰直接。然而以上预先演示,只能帮助你在部署一致性(deploying consistent)和资源可缓存(cachable assets)方面,有个好的开始。想要了解更多信息,请查看以下的进一步阅读部分。



点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部