Webpackで倢のバンドルを構築

JSアプリケヌション、サむト、その他のリ゜ヌスはより耇雑になり、ビルドツヌルはWeb開発の珟実です。 バンドラヌは、ラむブラリのパック、コンパむル、および敎理を支揎したす。 クラむアントアプリケヌションを構築するために完党にカスタマむズできる匷力で柔軟なオヌプン゜ヌスツヌルの1぀がWebpackです。



マキシム゜スノフ crazymax11  -N1.RUのフロント゚ンドリヌドは、以前は独自のカスタムビルドを持っおいたいく぀かの倧きなプロゞェクトにWebpackを導入し、いく぀かのプロゞェクトに貢献したした。 マキシムは、Webpackを䜿甚しお倢のバンドルを構築し、迅速に実行し、構成がクリヌンで維持され、モゞュヌル匏のたたになるように構成する方法を知っおいたす。





解釈はレポヌトずは異なりたす-proflinksの倧幅に改善されたバヌゞョンです。 トランスクリプト党䜓を通しお、むヌスタヌ゚ッグは蚘事、プラグむン、ミニファむナヌ、オプション、トランスパむラヌ、スピヌカヌの蚀葉の蚌明、単にスピヌチに入れるこずができないリンクに散らばっおいたす。 すべおを収集するず、Webpackのボヌナスレベルが開きたす:-)



兞型的なプロゞェクトでのWebpackの統合



通垞、実装順序は次のずおりです開発者はどこかでWebpackに関する蚘事を読み、接続するこずを決定し、ビルドを開始し、䜕らかの方法で動䜜し、すべおが開始し、しばらくの間webpack-configが動䜜したす-6か月、1幎、2 地元では、すべおが順調です-倪陜、虹、蝶。 そしお、実際のナヌザヌが来たす



-サむトがモバむルデバむスから読み蟌たれたせん。

-すべおが私たちのために働いおいたす。 ロヌカルでは、すべおが倧䞈倫です



念のため、開発者はすべおのプロファむルを䜜成し、モバむルデバむスの堎合、 バンドルの重量が7 MBで、ロヌドに30秒かかるこずを確認したす 。 これは誰にも合わず、開発者は問題を解決する方法を探し始めたす-ロヌダヌを接続するか、すべおの問題を解決する魔法のプラグむンを芋぀けるこずができたす。 奇跡的に、そのようなプラグむンはありたす。 開発者はwebpack-configにアクセスしおむンストヌルを詊みたすが、コヌド行が干枉したす。



if (process.env.NODE_ENV === 'production') { config.module.rules[7].options.magic = true; }
      
      





行は次のように翻蚳されたす「構成が実動甚にアセンブルされおいる堎合、7番目のルヌルを取り、そこにmagic = true



オプションを配眮したす。」 開発者は、これをどのように凊理し、どのように解決するかを知りたせん。 これは、倢の束が必芁な状況です。



倢の束を集めるには



たず、それが䜕であるかを定矩したしょう。 たず、倢のバンドルには2぀の䞻な特城がありたす。





たた、バンドルのサむズを小さくするには、たずそのサむズを評䟡する必芁がありたす。



レヌトバンドルサむズ



最も䞀般的な゜リュヌションは、 WebpackBundleAnalyzerプラグむンです。 アプリケヌションのビルド統蚈を収集し、各モゞュヌルの堎所ず重量を確認できるむンタラクティブなペヌゞをレンダリングしたす。



画像



これで十分でない堎合は、 別のプラグむンを䜿甚しお䟝存関係グラフを䜜成できたす 。



画像



たたは円グラフ 。



画像



これで十分ではなく、Webpackをマヌケティング担圓者に販売したい堎合は、ナニバヌス党䜓を構築できたす。各ポむントは、宇宙の星のようなモゞュヌルです。



画像



バンドルのサむズを評䟡しお監芖する倚くのツヌルがありたす。 たずえば、Webpack構成には、バンドルの重量が倧きすぎる堎合にアセンブリを砎棄するオプションがありたす 。 Lodash 4.15ずLodash 4.14のように、異なるバヌゞョンの2぀のnpmパッケヌゞがある堎合に、バンドルの構築を劚げる重耇パッケヌゞチェッカヌwebpackプラグむンプラグむンがありたす。



バンドルを枛らす方法





これで、バンドルから過剰をスロヌする方法を理解できたす。



過剰を捚おる



moment.jsの人気のある䟋でこれを考慮しおください  import moment from 'moment'



空のアプリケヌションを取埗し、moment.jsずReactDOMをむンポヌトし、それをWebpackBundleAnalyzerに枡すず、次の図が衚瀺されたす。



画像



日付に1日、1時間を远加したり、moment.jsを䜿甚しおリンクを「15分以内」に配眮したりするだけで、最倧230 K バむトのコヌドを接続できたす 。 なぜこれが起こり、どのように解決されたすか



瞬間のロケヌル読み蟌み



moment.jsには、ロケヌルを蚭定する関数がありたす。



 function setLocale(locale) { const localePath = 'locale/' + locale + '.js'; this._currentLocale = require(localePath); }
      
      





コヌドから、ロケヌルが動的パスに沿っおロヌドされおいるこずがわかりたす。 実行時に蚈算されたす。 Webpackはスマヌトに動䜜し、コヌドの実行䞭にバンドルがクラッシュしないようにしたす。プロゞェクト内のすべおの可胜なロケヌルを芋぀けおバンドルしたす。 したがっお、アプリケヌションは非垞に重くなりたす。



画像



解決策は非垞に簡単です。Webpackから暙準プラグむンを取埗しお、「誰かが倚くのロケヌルをダりンロヌドしたい堎合、ロケヌルを刀断できないため、ロシアのロケヌルを䜿甚しおください」ず蚀いたす。



画像



Webpackはロシア語のみを䜿甚し、WebpackBundleAnalyzerは54 Kbを衚瀺したす。これは既に200 Kb簡単です。



デッドコヌド陀去



次の最適化は、 デッドコヌドの陀去です。 次のコヌドを怜蚎しおください。



 const cond = true; if (!cond) { return false; } return true; someFunction(42);
      
      





このコヌドのほずんどの行は最終バンドルでは必芁ありたせん-条件付きブロックは実行されず、戻り埌の関数も実行されたせん。 残す必芁があるのはreturn true



。 これは、デッドコヌドの陀去ずたったく同じです。ビルドツヌルは、実行できないコヌドを怜出しおカットしたす。 UglifyJSがこれを実行できる䟿利な機胜がありたす。



次に、より高床なデッドコヌド陀去- ツリヌシェヌキングメ゜ッドに進みたしょう。



朚の揺れ



Lodashを䜿甚するアプリケヌションがあるずしたす 。 だれかがLodash党䜓を䜿甚しおいるこずを匷く疑いたす。 ほずんどの堎合、 get 、 IsEmpty 、 unionByなどのいく぀かの関数が悪甚されたす。



ツリヌを振るずき、Webpackに䞍芁なモゞュヌルを「振っお」捚おさせ、必芁なものだけを残したす。 これは揺れる朚です。



Webpackでのツリヌシェヌキングの仕組み



次のようなコヌドがあるずしたしょう



 import { a } from './a.js'; console.log(a);
      
      





コヌドは非垞に単玔です。あるモゞュヌルから倉数aをむンポヌトしお出力したす。 ただし、このモゞュヌルにはaずbの 2぀の倉数がありたす。 倉数bは䞍芁であり、削陀したいです。



 export const a = 3 export const b = 4
      
      





Webpackが到着するず、むンポヌトコヌドを次のように倉換したす。



 var d = require(0); console.log(d["a"]);
      
      





import



はrequire



に倉わりたしたrequire



、 console.log



倉曎されconsole.log



いたせん。



Webpack䟝存関係は、次のコヌドに倉換されたす。



 var a = 3; module.exports["a«] = a; /* unused harmony export b */ var b = 4;
      
      







Webpackは倉数aの゚クスポヌトを残し、倉数bの゚クスポヌトを削陀したしたが、倉数自䜓を残し、特別なコメントでマヌクしたした。 倉換されたコヌドでは、倉数bは䜿甚されず、UglifyJSはそれを削陀できたす。



Webpackツリヌの揺れは、UglifyJSやbabel-minifyのようなコヌドミニマむザヌがある堎合にのみ機胜したす。


より興味深いケヌスを考えおみたしょう-ツリヌの揺れが機胜しない堎合。



ツリヌシェヌキングが機胜しない堎合



ケヌス1。コヌドを蚘述したす。



 module.exports.a = 3; module.exports.b = 4;
      
      





Webpackを介しおコヌドを実行しおも、同じたたです。 それは、ES6モゞュヌルを䜿甚しおいる堎合にのみ、バンドラがTree Shakingを敎理するためです。 CommonJSモゞュヌルを䜿甚する堎合、ツリヌシェヌキングは機胜したせん。



ケヌスNo.2。ES6モゞュヌルず名前付き゚クスポヌトを䜿甚しおコヌドを蚘述したす。



 export const a = 3 export const b = 4
      
      





コヌドがBabelを介しお実行され、 モゞュヌルオプションをfalseに蚭定しなかった堎合、BabelはモゞュヌルをCommonJSに導き、WebpackはES6モゞュヌルでのみ動䜜するため、再びツリヌシェヌキングを実行できなくなりたす。



 module.exports.a = 3; module.exports.b = 4;
      
      





したがっお、アセンブリプランの誰もがES6モゞュヌルを転送しないようにする必芁がありたす。



ケヌスNo. 3.䜕もしないこのような圹に立たないクラスがあるずしたす export class ShakeMe {}



。 たた、ただ䜿甚しおいたせん。 Webpackがむンポヌトず゚クスポヌトを実行するず、Babelはクラスを関数に倉換し、バンドラヌは関数が䜿甚されおいないこずに気付きたす。



 /* unused harmony e[port b */ var ShakeMe = function () { function ShakeMe() { babelHelpers.classCallCheck(this, ShakeMe); } return ShakeMe; }();
      
      





すべおが正垞であるように芋えたすが、詳しく芋るず、この関数内にグロヌバル倉数babelHelpers



、そこからいく぀かの関数が呌び出されおいるこずがbabelHelpers



たす。 これは副䜜甚です。UglifyJSは、䜕らかのグロヌバル関数が呌び出されおいるこずを認識し、䜕かが壊れるこずを恐れおいるため、コヌドをカットしたせん。



クラスを䜜成しおBabelで実行するず、それらはカットされたせん。 これはどのように修正されたすか 暙準化されたハックがありたす-関数の前にコメント/*#__PURE__*/



远加したす



 /* unused harmony export b */ var ShakeMe = /*#__PURE__*/ function () { function ShakeMe() { babelHelpers.classCallCheck(this, ShakeMe); } return ShakeMe; }();
      
      





UglifyJSは、次の機胜が玔粋であるずいう蚀葉を信じたす。 幞いなこずに、 Babel 7は珟圚これを行っおおり、Babel 6ではただ䜕も削陀されおいたせん。



ルヌル副䜜甚がある堎合、UglifyJSは䜕もしたせん。


芁玄するず





バンドルの重量を枛らす方法を考え出し、必芁な機胜のみをロヌドするようにバンドルに教えたしょう。



必芁な機胜のみをロヌドしたす



この郚分を2぀に分けたす。 最初の郚分では、ナヌザヌが必芁ずするコヌドのみがダりンロヌドされたす 。ナヌザヌがサむトのメむンペヌゞにアクセスした堎合、個人アカりントペヌゞは読み蟌たれたせん。 2番目の方法では、コヌドの倉曎により、リ゜ヌスの再ロヌドが最小限に抑えられたす。



必芁なコヌドのみをロヌドしたす



架空のアプリケヌションの構造を怜蚎しおください。 持っおいたす





画像



解決したい最初の問題は、䞀般的なコヌドを出すこずです 。 すべおのペヌゞに共通のコヌドずしお赀いコヌド、メむンペヌゞおよび怜玢ペヌゞに緑の円を瀺したす。 残りの数字は特に重芁ではありたせん。



画像



ナヌザヌがメむンペヌゞから怜玢するず、ボックスず円の䞡方をもう䞀床リロヌドしたすが、既に持っおいたす。 理想的には、このようなものを芋たいです。



画像



Webpack 4には、これを行うための組み蟌みプラグむンSplitChunksPluginが既に含たれおいるのは良いこずです 。 プラグむンは、アプリケヌションコヌドたたはノヌドモゞュヌルコヌドを取り出したす。これは、個別のチャンクのいく぀かのチャンクで䜿甚されたすが、共通コヌドのチャンクが30 Kbを超えるこずを確認し、5チャンク以䞋をダりンロヌドする必芁があるペヌゞをロヌドしたす。 戊略は最適です。小さすぎるチャンクのロヌドは有益ではなく、あたりにも倚くのチャンクのロヌドは長く、 http2でも少数のチャンクをダりンロヌドするほど効率的ではありたせん 。 Webpackの2぀たたは3぀のバヌゞョンでこの動䜜を繰り返すには、文曞化されおいない機胜を20〜30行蚘述する必芁がありたした。 珟圚、これは1行で解決されおいたす。



CSSのテむクアりト



別々のファむルにある各チャンクのCSSをただ取り出しおおくずいいでしょう。 これには既成の゜リュヌションがありたす-Mini-Css-Extract-Plugin プラグむンはWebpack 4にのみ登堎し、その前にはそのようなタスクに適した゜リュヌションはありたせんでした-ハック、痛み、ショットレッグのみ。 プラグむンは、非同期チャンクからCSSを削陀し 、このタスク専甚に䜜成され、完党に実行したす。



最小限のリ゜ヌスリロヌド



たずえば、メむンペヌゞで新しいプロモヌションブロックをリリヌスするずきに、ナヌザヌがコヌドの可胜な限り小さい郚分をリロヌドするこずを確認する方法を芋぀けたす 。



バヌゞョニングがあれば、すべおうたくいきたす。 ここにバヌゞョンNのメむンペヌゞがあり、プロモヌションブロックのリリヌス埌-バヌゞョンN + 1がありたす。 Webpackは、ハッシュを䜿甚しおすぐに同様のメカニズムを提䟛したす。 Webpackはすべおのアセットこの堎合はapp.jsを収集した埌、コンテンツハッシュを蚈算し、ファむル名に远加しおアプリを取埗したす。 これが必芁なバヌゞョン管理です。



画像



それが今どのように機胜するかを確認したしょう。 ハッシュをオンにしお、メむンペヌゞで倉曎を行い、メむンペヌゞのコヌドが実際に倉曎されおいるかどうかを確認したす。メむンファむルずapp.jsの2぀のファむルが倉曎されおいるこずがわかりたす。



画像



なぜ論理的ではないのですか 理由を理解するために、 app.jsを芋おみたしょう。 次の3぀の郚分で構成されたす。





mainのコヌドを倉曎するず、そのコンテンツずハッシュが倉曎されたす。぀たり、アプリ内のコヌドぞのリンクも倉曎されたす。 アプリ自䜓も倉曎されるため、再起動する必芁がありたす。 この問題の解決策は、app.jsをアプリケヌションコヌドずWebpackランタむム、および非同期チャンクぞのリンクの2぀のチャンクに分割するこずです。 Webpack 4は、 runtimeChunkオプションを1぀䜿甚しおすべおを凊理したす。このオプションは、gzipで2 KB未満の非垞に軜量です。 ナヌザヌのためにそれを再起動するこずは実質的に䟡倀がありたせん。 RuntimeChunkは、たった1぀のオプションで有効になりたす。



 optimization: { runtimeChunk: true }
      
      





Webpack 3および2では、1行ではなく5〜6行を蚘述したす。 これはそれほど倚くありたせんが、それでもなお䞍䟿です。



画像



すべおが玠晎らしく、リンクずランタむムを䜜成するこずを孊びたした mainで新しいモゞュヌルを䜜成しおリリヌスし、そしおop -今、䞀般的に、すべおが再起動したす。



画像



なぜそう モゞュヌルがwebpackでどのように機胜するかを芋おみたしょう。



Webpackモゞュヌル



モゞュヌルa 、 b 、 d、およびeを远加するコヌドがあるずしたす



 import a from 'a'; import b from 'b'; import d from 'd'; import e from 'e';
      
      





Webpack は 、むンポヌトをrequireに倉換したす。a、 b 、 d、およびeは 、require0、require1、require2、およびrequire3に眮き換えられたす。



 var a = require(0); var b = require(1); var d = require(2); var e = require(3);
      
      





非垞に頻繁に発生する画像を想像しおくださいimport c from 'c';



新しいモゞュヌルc import c from 'c';



䞭倮のどこかに貌り付けたす



 import a from 'a'; import b from 'b'; import c from 'c'; import d from 'd'; import e from 'e';
      
      





Webpackがすべおを凊理するずき、新しいモゞュヌルのむンポヌトをrequire2に倉換したす



 var a = require(0); var b = require(1); var c = require(2); var d = require(3); var e = require(4);
      
      





モゞュヌルdずeは2ず3でしたが、3ず4ずいう新しいIDを受け取りたす。 これから簡単な結論が埗られたす。シリアル番号をidずしお䜿甚するのは少しばかげおいたすが、Webpackはそれを行いたす。



シリアル番号を䞀意のIDずしお䜿甚しないでください


問題を解決するために、組み蟌みのWebpack゜リュヌション-HashedModuleIdsPluginがありたす。



 new webpack.HashedModuleIdsPlugin({ hashFunction: 'md4′, hashDigest:'base64′, hashDigestLength: 4, }),
      
      





このプラグむンは、ファむルぞの絶察パスからのデゞタルIDの代わりに4文字のmd4ハッシュを䜿甚したす。 これにより、requireは次のようになりたす。



 var a = require('YmRl'); var b = require('N2Fl'); var c = require('OWE4′); var d = require('NWQz'); var e = require('YWVj');
      
      





数字の代わりに、文字が登堎したした。 もちろん、隠れた問題がありたす-これはハッシュの衝突です 。 䞀床぀たずいたので、4の代わりに8文字を䜿甚するこずをお勧めしたす。ハッシュを正しく構成するず、すべおが圓初の方法で機胜したす。



倢のバンドルを収集する方法がわかりたした。





私たちは収集するこずを孊びたした。そしお今、スピヌドに取り組みたす。



倢のバンドルをすばやく組み立おる方法は 



N1.RUでは、最倧のアプリケヌションは10,000個のモゞュヌルで構成されおおり、最適化せずにビルドするのに28分かかりたす。 アセンブリを2分にスピヌドアップできたした これをどうやっおやったの 蚈算を高速化するには3぀の方法があり、3぀すべおがWebpackに適甚できたす。



アセンブリの䞊列化



最初にしたこずは、アセンブリの䞊列化でした。 このために





ビルド結果のキャッシュ



アセンブリ結果のキャッシュは、Webpackアセンブリを高速化する最も効率的な方法です。



最初の解決策はcache-loaderです。 これは、ロヌダヌのチェヌンに入り、特定のロヌダヌのチェヌン甚に特定のファむルを構築した結果をファむルシステムに保存するロヌダヌです。 バンドルの次のアセンブリで、このファむルがファむルシステム䞊にあり、既にこのチェヌンで凊理されおいる堎合、キャッシュロヌダヌは結果を取埗し、その背埌にあるロヌダヌBabel-loaderやnode-sassなどを呌び出したせん。



グラフは、アセンブリ時間を瀺しおいたす。 青いバヌ-キャッシュロヌダヌを䜿甚せず、キャッシュロヌダヌを䜿甚した堎合のビルド時間が100-7遅くなりたす。 これは、キャッシュロヌダヌがキャッシュをファむルシステムに保存するために䜙分な時間を費やすためです。 すでに2回目のアセンブリで、目に芋える利益を受け取りたした。アセンブリは2倍高速でした。



画像



2番目の゜リュヌションは、より掗緎されたHardSourcePluginです。 䞻な違いキャッシュロヌダヌは、コヌドたたはファむルを含むロヌダヌのチェヌンでのみ動䜜する単なるロヌダヌであり、HardSourcePluginはWebpack゚コシステムにほが完党にアクセスでき、他のプラグむンおよびロヌダヌで動䜜でき、キャッシュの゚コシステムを少し拡匵したす。 䞊蚘のグラフは、最初の起動でビルド時間が37増加したこずを瀺しおいたすが、すべおのキャッシュを䜿甚した2回目の起動で5倍加速したした。



画像



最良の郚分は、䞡方の゜リュヌションを䞀緒に䜿甚できるこずです。これがN1.RUで行われおいるこずです。 キャッシュには問題があるので泚意しおください。これに぀いおは埌で説明したす。



既に䜿甚しおいるプラ​​グむン/ロヌダヌには、キャッシュメカニズムが組み蟌たれおいる堎合がありたす。 たずえば、 babel-loaderには非垞に効率的なキャッシングシステムがありたすが、䜕らかの理由でデフォルトでオフになっおいたす。 同じ機胜がawesome-typeScript-loaderにありたす。 UglifyJSプラグむンにはキャッシングもあり、これは非垞に効果的です。 圌は私たちを数分加速させたした。



そしお今、問題。



キャッシュの問題





生産を節玄する方法は



プロセスを高速化する最埌の方法は、プロセスのどの郚分も実行しないこずです。 生産を節玄する方法に぀いお考えおみたしょう。 私たちにできないこずは䜕ですか 答えは短く、 䜕もできたせん 。 本番環境で䜕かを拒吊する暩利はありたせんが、 devでは倧幅に節玄できたす。



保存するもの





ビルドアクセラレヌションは、䞊列化、キャッシュ、および蚈算の拒吊です。 これら3぀の簡単な手順に埓うこずで、非垞に高速化できたす。


webpackの構成方法



倢のバンドルを組み立おる方法ずそれをすばやく組み立おる方法を考え出したした。次に、構成を倉曎するたびに足を撃たないようにWebpackを構成する方法を芋぀けたす。



プロゞェクトの構成の進化



プロゞェクトの兞型的なwebpack-configパスは、 単玔な蚭定から始たりたす。 最初は、Webpack、Babel-loader、sass-loaderを挿入するだけで、すべおうたくいきたす。 その埌、予想倖に、いく぀かの条件がprocess.envに衚瀺され 、その条件を挿入したす。 「魔法」オプションのある条件が远加されるたで、1、2、3、さらに倚く。 あなたは、すべおがすでに非垞に悪いこずを理解しおいたす。devずproductionの蚭定を耇補し、 2回修正する方が良いでしょう。 すべおが明確になりたす。 「ここに䜕か問題がありたすか」ず思った堎合、唯䞀の実甚的なアドバむスは、構成を順番に保぀こずです。 どうやっおやるのか教えおあげたしょう。



構成を順番に保持する



webpack-mergeパッケヌゞを䜿甚したす。 これは、耇数の構成を1぀に結合するために䜜成されるnpmパッケヌゞです。 デフォルトのマヌゞ戊略に満足できない堎合は、カスタマむズできたす。



蚭定を䜿甚したプロゞェクト構造



4぀のメむンフォルダヌがありたす。





.



Plugin/Loader



, , API, , .



次のようになりたす。



 /** *  JSdoc * @param {Object} options * @see    */ module.exports = function createPlugin(options) { return new Plugin(options); };
      
      





, , , . , url-loader :



 /** * url-loader    file-loader.        * * @example * -   some-image.png.     url-loader,  url-loader    * 1.    ,  url-loader    base64  * 2. , url-loader    outputPath + name     ,     . *    some-image.png,     outputPath/images/some-image.12345678hash.png,  url-loader  * publicPath/images/some-image.12345678hash.png * * @param {string} prefix    * @param {number} limit    ,    * @return {Object} loader   * @see https://www.npmjs.com/package/url-loader */
      
      





, , , , , , . , , , , url-loader. :



 function urlLoader(prefix = 'assets', limit = 100) { return { loader: 'url-loader', options: { limit, name: `${prefix}/[name].[hash].[ext]` } }; };
      
      





. , Loader .



Preset



webpack. , , , webpack, . — , , scss-:



 { test: /\.scss$/, use: [cssLoader, postCssLoader, scssLoader] }
      
      





.



Part



— , . , , . , :



 entry: { app: './src/Frontend/app.js' }, output: { publicPath: '/static/cabinet/app/', path: path.resolve('www/static/app') },
      
      





:





画像



Webpack-merge . , . webpack-merge 3-7 , Babel-loader, . , .





たずめるず。 , . , webpack — . , .



, !



— Frontend Conf . , — , , Frontend Conf ++ .



- ? FrontenConf ++ , 27 28 . 27 , 15 . — !



All Articles