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
矛盾が生じる可能性があるため、使用するバージョンに問題があるかどうかに関係なく、次の構成を使用することをお勧めします。以降の構成chunkhash
はhash
as を使用して生成されます。
二、ハッシュ 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
と、このファイルにコードがドキュメントに記載されているので、ここで好きな名前を指定できます。name
entry
runtime
name
では、今パッケージ化されたとき、どのように見えるでしょうか?
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
と同じではないことがわかります。では、代わり?app
runtime
hash
hash
chunkhash
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
確認できます。これには、それぞれの(それぞれとして理解できます) 。パッケージ化されたすべてのファイルは同じであるため、パッケージ出力が複数のファイルになると、必ず使用されます。chunkhash
chunkhash
chunk
chunk
entry
hash
chunkhash
クラス ライブラリ ファイルは個別にパッケージ化されています
一般的なプロジェクトでは、クラス ライブラリ ファイルが頻繁に更新されることはなく、たとえば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
vendor
app
離れていて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
の に変更されないはずです. 結果は私たちが望むものですか?react
vendor
hash
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
ことがわかります。hash
HashedModuleIdsPlugin
非同期モジュール
システムが大きくなり、多くのモジュールが存在するようになると、すべてのモジュールが一度にバンドルされると、最初のロードが非常に遅くなります。現時点では、非同期ロードをwebpack
ネイティブにサポートし、非常に便利な非同期ロードを行うことを検討します。
js
と呼ばれる別のものを作成しましょうasync-bar.js
, foo.js
in :
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
名前を。最新バージョンでは、この名前はこの名前なしで通常どおりパッケージ化できるようです。しかし、ここではそれを使用して非同期モジュールの名前を処理できますwebpack
。plugins
次のコードを 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
に。
chunk
nameを生成するロジック コードに注意してください。必要に応じて変更できます。
上記の方法では、大量のコードが生成される.vue
ファイル開発モードを使用するなど、いくつかの問題が発生するため、パッケージング時にエラーが報告されます。もちろん、対応するネーミング方法は自分で見つけることができます. ここでは、ネイティブに. を使用する場合は、次の注意事項を記述してください:m.request
vue-loader
webpack
import
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
宣言した部分が含まれているだけでなく、各 で参照されている共通コードも含まれているためです。この結果が必要な場合もありますが、私たちの場合、これが私が解決したいものです 質問 ლ (゚д゚ლ)entry
entry
ここで何をすべきかCommonsChunkPlugin
、宣言したコンテンツのみwebpack
をvendor
本当に伝えるために使用できるパラメーターがあります。
{
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
は変わりますhash
。webpack
最後に、構成は次のとおりです。
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 のハッシュ原理
webpack
hash
は暗号化およびハッシュ アルゴリズムを介しcrypto
て実装され、(生成時に、デフォルトは)、(ハッシュ ダイジェストのプレフィックス長、デフォルトは)、(ハッシュ アルゴリズム、デフォルトは)、(オプションの選択されたソルト値) およびカスタム ハッシュを実現するためのその他のパラメーター。webpack
hashDigest
hash
'hex'
hashDigestLength
20
hashFunction
'md5'
hashSalt
CommonsChunkPlugin
webpack4
. _CommonsChunkPlugin
change log
CommonsChunkPlugin
削除後はモジュール分割optimization.splitChunks
に興味のある方は以下の公式詳細ドキュメントへ。
公式声明によると、デフォルト設定はすでにほとんどのユーザーにとって非常に優れていますが、注意が必要な問題が 1 つあります. デフォルト設定では、非同期リクエストのモジュールを抽出して分割するだけです. 分割しentry
たいoptimization.splitChunks.chunks = 'all'
。残りは自分で勉強できます。
以前にruntime
分割した、設定が に設定されている場合にファイルを自動的に分割するoptimization.runtimeChunk
ようになりtrue
ました。runtime
醜いJsプラグイン
plugin
これを使用する必要はありません。 as をoptimization.minimize
使用するだけで、以下で自動的に実行されます。true
production mode
true
optimization.minimizer
独自のコンプレッサーを構成することが可能です。
—————————— 【本文終わり】 ——————————
フロントエンド学習交換グループ、対面で参加したい場合は、グループに参加できます: 832485817 , 685486827 ;
最後に: 慣習は構成よりも優れている - ソフトウェア開発におけるシンプルさの原則
—————————— 【終了】 ——————————
私:
個人ウェブサイト: https://neveryu.github.io/neveryu/
Github: https://github.com/Neveryu
Sina Weibo: https://weibo.com/Neveryu
WeChat: Miracle421354532
より多くの学習リソースについては、私の Sina Weibo に注目してください... OK