【フロントエンドが学ばなければならない】webpack処理ファイル名ハッシュの問題を正確に制御する

背景知識

1. 静的リソースが初めてロードされた後, ブラウザはそれをキャッシュします. 通常, キャッシュの有効期限が切れていなければ, 同じリソースが再度要求されることはありません. では, リソースが変更されたときにブラウザにリソースが変更されたことを通知する方法.更新されていますか?リソース ファイルのhash命名は、この問題を解決するために生まれました。

2. ブラウザーは、ページ リソースが要求されるたびに最新のリソースを取得することを期待する一方で、リソースが変更されていないときにキャッシュ オブジェクトを再利用できることを期待します。

このとき、[ファイル名+ファイルのハッシュ値]の方法を使えば、ファイル名さえ渡せばリソースが更新されたかどうかを判別できます。

前に書いた

1. webpackには組み込みのhash計算、hash生成されたファイルの出力ファイルにフィールドを追加できます。

2.組み込みwebpackが 3 つあります。hash

  • hash: ビルドごとに 1 つ生成されますhashプロジェクト全体に関連しており、プロジェクトにファイルの変更がある限り、変更されますhash
  • contenthash: 1 つのファイルの内容に関連します。指定されたファイルの内容が変更されると、変更が行われますhash
  • chunkhash: webpackパッケージ化によって生成されたものにchunk関連します。一つ一つentry、無駄がありますhash

文章

1.基本構成

const path = require('path')

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js')
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  }
}

src/foo.js内容は次のとおりです。

import React from 'react'
console.log(React.toString())

の代わりにを使用することもできますが、生成される 2 つのコードは異なることoutput.filename注意してください。[hash][chunkhash]hash

hash次のように使用します。

app.03700a98484e0f02c914.js  70.4 kB       0  [emitted]  app
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

chunkhash次のように使用します。

app.f2f78b37e74027320ebf.js  70.4 kB       0  [emitted]  app
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

entry単体の場合はどちらでも構いません. 例では[email protected]バージョンを使用しましたがwebpack,hashソースコードを変更しないと文字列が変わってしまう問題を修正しました. ただし、以前のバージョンでは同じ未修正のコードを変更するとhash矛盾が生じる可能性があるため、使用するバージョンに問題があるかどうかに関係なく、次の構成を使用することをお勧めします。以降の構成chunkhashhashas を使用して生成されます。

二、ハッシュ vs チャンクハッシュ

webpackさまざまなモジュールの依存関係を処理する必要があるため、js依存関係を処理するための組み込みのテンプレート (以下、 と呼びますruntime) があるjsためも最終的な にパッケージ化されますbundle実際のプロジェクトでは、コードのこの部分を分離する必要があることがよくあります. たとえば、クラス ライブラリを個別にパッケージ化したい場合、別のクラス ライブラリを生成しない場合、コードの変更と変更により、クラス ライブラリが毎回変更されruntimeますjsそのため、クラス ライブラリを分離しても意味がありません。hashしたがって、ここではruntime別のjs

次のように構成を変更します。

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js')
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

webpackで宣言されていない名前を指定するwebpack.optimize.CommonsChunkPlugin、このファイルにコードがドキュメントに記載されているので、ここで好きな名前を指定できます。nameentryruntimename

では、今パッケージ化されたとき、どのように見えるでしょうか?

app.aed80e077eb0a6c42e65.js    68 kB       0  [emitted]  app
runtime.ead626e4060b3a0ecb1f.js  5.82 kB       1  [emitted]  runtime
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

と同じではないことわかります。では、代わり?appruntimehashhashchunkhash

app.357eff03ae011d688ac3.js    68 kB       0  [emitted]  app
runtime.357eff03ae011d688ac3.js  5.81 kB       1  [emitted]  runtime
   [6] ./src/foo.js 55 bytes {
    
    0} [built]
    + 11 hidden modules

ここから、 と の違いをhash確認できます。これには、それぞれの(それぞれとして理解できます) 。パッケージ化されたすべてのファイルは同じであるため、パッケージ出力が複数のファイルになると、必ず使用されますchunkhashchunkhashchunkchunkentryhashchunkhash

クラス ライブラリ ファイルは個別にパッケージ化されています

一般的なプロジェクトでは、クラス ライブラリ ファイルが頻繁に更新されることはなく、たとえばreactビジネス コードを更新する頻度が高くなります。次に、クラス ライブラリ コードをできるだけ長くブラウザにキャッシュできることを願っていますが、そのためには、クラス ライブラリ ファイルを個別にパッケージ化する必要があります。

構成ファイルを変更します。

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react']  // 所有类库都可以在这里声明
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    // 单独打包,app中就不会出现类库代码
    // 必须放在runtime之前
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

次に、次のパッケージングを実行しましょう。

vendor.72d208b8e74b753cf09c.js    67.7 kB       0  [emitted]  vendor
    app.fdc2c0fe8694c1690cb3.js  494 bytes       1  [emitted]  app
runtime.035d95805255d39272ba.js    5.85 kB       2  [emitted]  runtime
   [7] ./src/foo.js 55 bytes {
    
    1} [built]
  [12] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

vendorapp離れていてhash体が違う 綺麗ですね。若い人たちを喜ぶのは時期尚早です。という名前の新しいファイルを作成しましょう。bar.jsコードは次のとおりです。

import React from 'react'

export default function() {
    
    
  console.log(React.toString())
}

次に、foo.js次のように。

import bar from './bar.js'
console.log(bar())

この変更から、クラス ライブラリに関連するコンテンツを変更していないことがわかります. まだ私たちvendorの に変更されないはずです. 結果は私たちが望むものですか?reactvendorhash

vendor.424ef301d6c78a447180.js    67.7 kB       0  [emitted]  vendor
    app.0dfe0411d4a47ce89c61.js  845 bytes       1  [emitted]  app
runtime.e90ad557ba577934a75f.js    5.85 kB       2  [emitted]  runtime
   [7] ./src/foo.js 45 bytes {
    
    1} [built]
   [8] ./src/bar.js 88 bytes {
    
    1} [built]
  [13] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

webpack顔面強打で残念╮(╯_╰)

理由は何ですか?これは、 のもう 1 つのモジュールであるファイルをもう 1 つ追加したwebpackためwebpackモジュールは順序付けられた順序に従って命名されます。つまり、[0,1,2....]途中でモジュールを追加し、各モジュールvendorの順序が変更されます。中のモジュールidが変わったので、hashそれも変わりました。結論は:

1.app変更は内容が変更されたため
2.vendor変更は彼の内容が変更module.id
3.runtime変更はそれ自体がモジュールの依存関係を維持しているため

それで、それを解決する方法は?

NamedModulesPlugin と HashedModuleIdsPlugin

これら 2 つpluginによりwebpack、モジュールの名前に番号を使用する必要がなくなり、各モジュールに一意の名前が付けられ、モジュールidの変更hash使い方?

{
    
    
  plugins: [
    new webpack.NamedModulesPlugin(),
    // new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

NamedModulePlugin一般的に開発で使用され、より読みやすいモジュールの名前を確認できますが、パフォーマンスは比較的低くなります。HashedModuleIdsPluginフォーマルな場での使用をお勧めします。

このプラグインを使用した後の 2 つのパッケージの結果を見てみましょう
。コード変更前:

vendor.91148d0e2f4041ef2280.js      69 kB       0  [emitted]  vendor
    app.0228a43edf0a32a59426.js  551 bytes       1  [emitted]  app
runtime.8ed369e8c4ff541ad301.js    5.85 kB       2  [emitted]  runtime
[./src/foo.js] ./src/foo.js 56 bytes {
    
    1} [built]
   [0] multi react 28 bytes {
    
    0} [built]
    + 11 hidden modules

コード変更後:

vendor.91148d0e2f4041ef2280.js      69 kB       0  [emitted]  vendor
    app.f64e232e4b6d6a59e617.js  917 bytes       1  [emitted]  app
runtime.c12d50e9a1902f12a9f4.js    5.85 kB       2  [emitted]  runtime
[./src/bar.js] ./src/bar.js 88 bytes {
    
    1} [built]
   [0] multi react 28 bytes {
    
    0} [built]
[./src/foo.js] ./src/foo.js 43 bytes {
    
    1} [built]
    + 11 hidden modules

変化はなく、効果は同じであるvendorことがわかります。hashHashedModuleIdsPlugin

非同期モジュール

システムが大きくなり、多くのモジュールが存在するようになると、すべてのモジュールが一度にバンドルされると、最初のロードが非常に遅くなります。現時点では、非同期ロードをwebpackネイティブにサポートし、非常に便利な非同期ロードを行うことを検討します。

jsと呼ばれる別のものを作成しましょうasync-bar.js, foo.jsin :

import('./async-bar').then(a => console.log(a))

パック:

      0.1415eebc42d74a3dc01d.js  131 bytes       0  [emitted]
 vendor.19a637337ab59d16fb34.js      69 kB       1  [emitted]  vendor
    app.f7e5ecde27458097680e.js    1.04 kB       2  [emitted]  app
runtime.c4caa7f9859faa94b02e.js    5.88 kB       3  [emitted]  runtime
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    0} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    2} [built]
   [0] multi react 28 bytes {
    
    1} [built]
[./src/foo.js] ./src/foo.js 92 bytes {
    
    2} [built]
    + 11 hidden modules

さて、現時点では、 がvendor変更されている、さらに恐ろしいことはまだこれからです. と呼ばれるasync-baz.js別のモジュールを構築しました。foo.js

import('./async-baz').then(a => console.log(a))

次に、再度パックします。

      0.eb2218a5fc67e9cc73e4.js  131 bytes       0  [emitted]
      1.61c2f5620a41b50b31eb.js  131 bytes       1  [emitted]
 vendor.1eada47dd979599cc3e5.js      69 kB       2  [emitted]  vendor
    app.1f82033832b8a5dd6e3b.js    1.17 kB       3  [emitted]  app
runtime.615d429d080c11c1979f.js     5.9 kB       4  [emitted]  runtime
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    1} [built]
[./src/async-baz.js] ./src/async-baz.js 32 bytes {
    
    0} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    3} [built]
   [0] multi react 28 bytes {
    
    2} [built]
[./src/foo.js] ./src/foo.js 140 bytes {
    
    3} [built]
    + 11 hidden modules

モジュールごとhashに変更されます。. .

モジュールが再び数字IDになったの?! !

さて、本題に取り掛かりましょう。まだ解決策があります。つまり、NamedChunksPlugin以前は各chunk名前を。最新バージョンでは、この名前はこの名前なしで通常どおりパッケージ化できるようです。しかし、ここではそれを使用して非同期モジュールの名前を処理できますwebpackplugins次のコードを of に追加します。

new webpack.NamedChunksPlugin((chunk) => {
    
     
  if (chunk.name) {
    
     
    return chunk.name; 
  } 
  return chunk.mapModules(m => path.relative(m.context, m.request)).join("_"); 
})

次に、パッケージングを実行します。2 回の結果は次のとおりです。

         app.5faeebb6da84bedaac0a.js    1.11 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.f263e4cd58ad7b17a4bf.js     5.9 kB       runtime  [emitted]  runtime
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules
         app.55e3f40adacf95864a96.js     1.2 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
async-baz.js.a85440cf862a8ad3a984.js  144 bytes  async-baz.js  [emitted]
     runtime.deeb657e46f5f7c0da42.js    5.94 kB       runtime  [emitted]  runtime
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/async-baz.js] ./src/async-baz.js 32 bytes {
    
    async-baz.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo.js] ./src/foo.js 140 bytes {
    
    app} [built]
    + 11 hidden modules

結果はすべて の代わりidに。

chunknameを生成するロジック コードに注意してください。必要に応じて変更できます。

上記の方法では、大量のコードが生成される.vueファイル開発モードを使用するなど、いくつかの問題が発生するため、パッケージング時にエラーが報告されます。もちろん、対応するネーミング方法は自分で見つけることができます. ここでは、ネイティブに. を使用する場合は、次の注意事項を記述してください:m.requestvue-loaderwebpackimport

import(/* webpackChunkName: "views-home" */ '../views/Home')

次に、構成ファイルを使用するだけnew NamedChunksPlugin()で、名前を自分で綴る必要はありません。chunkこの時点で非同期には既に名前があるためです。

さらにエントリを追加

修正webpack.config.js:

{
    
    
  ...
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react'],
    two: path.join(__dirname, 'src/foo-two.js')
  },
  ...
}

追加内容はenrty次のとおりです。

// foo-two.js
import bar from './bar.js'
console.log(bar)

import('./async-bar').then(a => console.log(a))
// import('./async-baz').then(a => console.log(a))

はい、 とまったくfoo.js同じ。もちろん、ロジックを変更できます。引用することを忘れないでくださいbar.js

次に、結果をパックします。. .

         app.77b13a56bbc0579ca35c.js  612 bytes           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.bbe8e813f5e886e7134a.js    5.93 kB       runtime  [emitted]  runtime
         two.9e4ce5a54b4f73b2ed60.js  620 bytes           two  [emitted]  two
      vendor.8ad1e07bfa18dd78ad0f.js    69.5 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    vendor} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo-two.js] ./src/foo-two.js 143 bytes {
    
    two} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules

hashすべてのドキュメントがどのように変更されましたか? ! ! !

その理由はvendor、 as にcommon chunk宣言した部分が含まれているだけでなく、各 で参照されている共通コードも含まれているためです。この結果が必要な場合もありますが、私たちの場合、これが私が解決したいものです 質問 ლ (゚д゚ლ)entryentry

ここで何をすべきかCommonsChunkPlugin、宣言したコンテンツのみwebpackvendor本当に伝えるために使用できるパラメーターがあります。

{
    
    
  plugins: [
    ...
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
      minChunks: Infinity
    }),
  ]
}

このパラメータは、共通コードをvendorできる。したがって、再度パックします。

         app.5faeebb6da84bedaac0a.js    1.13 kB           app  [emitted]  app
async-bar.js.457b1711c7e8c6b6914c.js  144 bytes  async-bar.js  [emitted]
     runtime.b0406822caa4d1898cb8.js    5.93 kB       runtime  [emitted]  runtime
         two.9be2d4a28265bfc9d947.js    1.13 kB           two  [emitted]  two
      vendor.05493d3691191b049e65.js      69 kB        vendor  [emitted]  vendor
[./src/async-bar.js] ./src/async-bar.js 32 bytes {
    
    async-bar.js} [built]
[./src/bar.js] ./src/bar.js 88 bytes {
    
    app} {
    
    two} [built]
   [0] multi react 28 bytes {
    
    vendor} [built]
[./src/foo-two.js] ./src/foo-two.js 143 bytes {
    
    two} [built]
[./src/foo.js] ./src/foo.js 143 bytes {
    
    app} [built]
    + 11 hidden modules

うーん、おなじみの味。

この時点で、 との戦いwebpack変わりますhashwebpack

最後に、構成は次のとおりです。

const path = require('path')
const webpack = require('webpack')

module.exports = {
    
    
  entry: {
    
    
    app: path.join(__dirname, 'src/foo.js'),
    vendor: ['react'],
    two: path.join(__dirname, 'src/foo-two.js')
  },
  externals: {
    
    
    jquery: 'jQuery'
  },
  output: {
    
    
    filename: '[name].[chunkhash].js',
    path: path.join(__dirname, 'dist')
  },
  plugins: [
    new webpack.NamedChunksPlugin((chunk) => {
    
     
      if (chunk.name) {
    
     
        return chunk.name; 
      } 
      return chunk.mapModules(m => path.relative(m.context, m.request)).join("_"); 
    }),
    new webpack.NamedModulesPlugin(),
    // new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'vendor',
      minChunks: Infinity
    }),
    new webpack.optimize.CommonsChunkPlugin({
    
    
      name: 'runtime'
    })
  ]
}

補足知識ポイント

webpack のハッシュ原理

webpackhash暗号化およびハッシュ アルゴリズムを介しcryptoて実装され、(生成時に、デフォルトは)、(ハッシュ ダイジェストのプレフィックス長、デフォルトは)、(ハッシュ アルゴリズム、デフォルトは)、(オプションの選択されたソルト値) およびカスタム ハッシュを実現するためのその他のパラメーター。webpackhashDigesthash'hex'hashDigestLength20hashFunction'md5'hashSalt

CommonsChunkPlugin

webpack4. _CommonsChunkPluginchange log

CommonsChunkPlugin削除後はモジュール分割optimization.splitChunksに興味のある方は以下の公式詳細ドキュメントへ。

公式声明によると、デフォルト設定はすでにほとんどのユーザーにとって非常に優れていますが、注意が必要な問題が 1 つあります. デフォルト設定では、非同期リクエストのモジュールを抽出して分割するだけです. 分割しentryたいoptimization.splitChunks.chunks = 'all'残りは自分で勉強できます。

以前にruntime分割した、設定が に設定されている場合にファイルを自動的に分割するoptimization.runtimeChunkようになりtrueました。runtime

醜いJsプラグイン

pluginこれを使用する必要はありません。 as をoptimization.minimize使用するだけで、以下で自動的に実行されますtrueproduction modetrue

optimization.minimizer独自のコンプレッサーを構成することが可能です。

—————————— 【本文終わり】 ——————————

フロントエンド学習交換グループ、対面で参加したい場合は、グループに参加できます: 832485817 , 685486827 ;
フロントエンドトップラーニング交流会 (1) フロントエンドトップラーニング交流会 (2)

最後に: 慣習は構成よりも優れている - ソフトウェア開発におけるシンプルさの原則

—————————— 【終了】 ——————————

私:
個人ウェブサイト: https://neveryu.github.io/neveryu/
Github: https://github.com/Neveryu
Sina Weibo: https://weibo.com/Neveryu
WeChat: Miracle421354532

より多くの学習リソースについては、私の Sina Weibo に注目してください... OK

おすすめ

転載: blog.csdn.net/csdn_yudong/article/details/126094623