序文
CDE プロジェクトを開発する場合、毎回プロジェクトのパッケージ化とビルドに時間がかかり、下図に示すように、開発環境でのビルドが完了するまでに 1 分かかると同時に、非常に長い時間がかかります。以下の図に示すように、開発環境でリアルタイムにパッケージ化してコンパイルするまでに
10 秒近くかかることがわかります。プロジェクトの webpack.config.js の構成を読んだところ、最適化の余地があると感じたので、開発効率を向上させるために、関連する webpack の構成をリファクタリングしました。
最適化 1: Webpack パブリック構成をマージして抽出する
Webpackの設定は開発環境の設定と本番環境の設定に分かれており、両環境の設定ファイルには重複する設定や一部異なる設定が多数存在します。デバッグの効率性を考慮して、通常はリアルタイムで dev-server をパッケージに渡します。そのため、毎回パッケージ化するためにターミナルにスクリプト コマンドを入力する必要はありません。また、オンライン段階で実際のパッケージ ファイルを取得する必要があるため、 dev-server を通じてパッケージ化されません。パッケージング効率を向上させるために、開発フェーズではパッケージ化されたコンテンツは圧縮されませんが、オンラインフェーズでは、アクセス効率を向上させるために、パッケージ化中にパッケージ化されたコンテンツを圧縮する必要があります。
古い設定ファイルの問題とそれに対応する改善点
古い設定ファイルは、開発環境とオンライン環境の設定を 1 つのファイルに書き込みます。これは、設定ファイルを維持するのに非常に不利です。そのため、環境ごとに異なる設定を異なるファイルに書き込む必要があります。ルート パスの下にある次のディレクトリ構造を作成し、2 つの環境の共通構成を webpack.common.js ファイルに抽出します。webpack.dev.js ファイルと webpack.prod.js ファイルには、開発環境の固有の構成が保存されます。開発環境
と実稼働環境の固有の共通構成をマージするには、webpack-merge モジュールを使用する必要があります。
キーコードの例
webpack.dev.js
const {
merge } = require("webpack-merge");
const CommonConfig = require("./webpack.common.js");
module.exports = merge(CommonConfig, DevConfig);
webpack.prod.js
const {
merge } = require("webpack-merge");
const CommonConfig = require("./webpack.common.js");
module.exports = merge(CommonConfig, ProdConfig);
パッケージ.json
"scripts": {
"dev": "webpack-dev-server --config webpack-config/webpack.dev.js",
"build": "webpack --config webpack-config/webpack.prod.js"
}
最適化 2: HappyPack はマルチプロセス パッケージングを実装します (開発環境と運用環境の両方に適用可能)
Node.js 上で実行される Webpack はシングルスレッド モデルです。つまり、Webpack はタスクを 1 つずつ処理する必要があり、複数のタスクを同時に処理できません。また、HappyPack を使用すると、Webpack が複数のタスクを同時に処理できるようになり、マルチコア CPU コンピューターの機能を最大限に活用でき、構築速度が向上します。タスクを複数のサブプロセスに分解して同時実行し、サブプロセスの処理後に結果をメインプロセスに送信します。JavaScript はシングルスレッドモデルなので、マルチコア CPU の機能を果たしたい場合は、マルチプロセスでしか実現できませんが、マルチスレッドでは実現できません。
実際に使用する場合は、HappyPack が提供するローダーを使用してオリジナルのローダーを置き換え、オリジナルのローダーを HappyPack プラグインを介して渡す必要があります。
以下では、HappyPack を使用して古い設定ファイルを変換します。
Webpack の初期設定 (HappyPack を使用する前)
module.exports = {
//...
module: {
rules: [
{
test: /\.ts(x)?$/,
include: [path.resolve(__dirname, 'src')],
use: ['babel-loader', 'ts-loader'],
},
],
},
}
webpack.common.js (HappyPackを使用した設定)
const HappyPack = require('happypack');
module.exports = {
//...
module: {
rules: [
{
test: /\.ts(x)?$/,
include: [path.resolve(__dirname, '../src')],
// 把对 .ts(x) 文件的处理转交给 id 为 ts 的 HappyPack 实例
use: ['happypack/loader?id=ts']
},
],
},
plugins: [
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'ts',
// loaders属性表示如何处理 .ts(x) 文件,用法和 Loader 配置中一样
loaders: ['babel-loader','ts-loader']
],
};
ローダーの構成では、ファイルの処理は happypack/loader に渡され、次の queryString ( id=ts ) は、ファイルを処理する happypack インスタンスを選択するように happypack-loader に指示します。
プラグイン設定で、happypack インスタンスを追加して、happypack/loader に ts(x) ファイルの処理方法を指示します。オプションの id 属性値は上記の queryString(id=babel) に対応し、オプションのloaders 属性はそうではありません。 happypack の前のローダーの構成は同じままです。
改善された効果:
happyPackによる処理後、開発環境でのビルド時間は60秒以上から20秒以上に短縮され、パッケージ化とビルドにかかる時間は従来の半分以下となり、パッケージング向上の効果が得られました。パフォーマンスは顕著です。
マルチプロセスを有効にする場合の注意点:
複数のプロセスを有効にしても構築速度が必ずしも向上するとは限りません。プロセスの開始、破棄、およびプロセス間での通信が必要であり、このプロセスのオーバーヘッドも比較的大きいため、開かれるサブプロセスが多ければ多いほど良いのです。大規模なプロジェクトの場合、パッケージ化は遅く、複数の処理により速度が向上しますが、小規模なプロジェクトの場合、パッケージ化は高速で、複数の処理により速度が低下します。
HappyPack の thread パラメータは、開くサブプロセスの数を設定するために使用されます。デフォルトは 3 です。作成者が 4 つのサブプロセスを開くように設定した場合、構築速度は 3 つのサブプロセスの場合とほぼ同じです。 5 つのサブプロセスが開かれ、構築速度は 3 より速く、4 は遅くなります。したがって、マルチプロセスを有効にするかどうか、および有効にするプロセスの数は、必要に応じて使用する必要があります。
最適化 3: スタイル ファイルの抽出と圧縮 (運用環境に適用)
スタイル ファイルの処理では、処理用のローダー チェーンの最後で style-loader が使用される場合、CSS は直接 js にパッケージ化されます。本番環境では、CSS ファイルを個別に生成したいと考えています。cssは別途生成されるため、jsと並行してcssをダウンロードでき、ページ読み込み効率が向上します。同時に、ファイル サイズを削減し、ページの読み込み効率を向上させるために、運用環境では css ファイルを圧縮する必要もあります。開発環境では、ファイルの解凍や圧縮に時間がかかるため、パッケージングやビルドの速度を向上させ、できるだけ早く開発するために、開発環境ではスタイルファイルの解凍や圧縮は行っておりません。
古い設定ファイルの問題とそれに対応する改善点
元の設定ファイルでは、開発環境でも本番環境でも、scss ファイルの最後のステップがスタイルローダーによって処理されており、不合理であるため、本番環境での scss ファイルの処理を改善します。 css-extract-plugin はスタイル ファイルを抽出
し、css-minimizer-webpack-plugin を使用してスタイル ファイルを圧縮します。css-minimizer-webpack-plugin は webpack の optimization.minimizer を設定する必要があるため、、これによりデフォルトの JS 圧縮オプションがオーバーライドされ、実稼働環境になります。以下の JS コードは圧縮されていないため、JS コードもプラグイン terser-webpack-plugin によって圧縮する必要があります。
コード例は次のとおりです:
webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserJSPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
module.exports = {
//...
module: {
rules: [
{
test: /\.(sass|scss)$/,
use:[
// 使用MiniCssExtractPlugin.loader代替style-loader,抽离css文件
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader', {
loader: 'sass-resources-loader',
options: {
resources: path.join(
srcPath,
'styles/_variables.scss',
),
},
}]
}
],
},
plugins: [
// 抽离 css 文件
new MiniCssExtractPlugin({
filename:'css/main.[contenthash:8].css'
}),
],
optimization: {
// 压缩 css
//因为覆盖了原本的配置,所以只压缩css的话,js就不被压缩了,所以需要让js也压缩;
minimizer: [new TerserJSPlugin(), new CssMinimizerPlugin()],
}
}
最適化 4: DLL ダイナミック リンク ライブラリ (開発環境に適用)
開発環境では、プロジェクトがパッケージ化およびビルドされるたびに、react、react-dom、antd およびその他の変更されないサードパーティ製ライブラリが一度再パッケージ化され、Dll ダイナミック リンク ライブラリが事前にパッケージ化されます。 -パーティ モジュールは dll ファイルにパッケージ化され、開発環境でパッケージ化およびビルドされる場合、dll ファイルから直接取得できます。これらのモジュールはパッケージ化されなくなり、サードパーティ モジュールのみがパッケージ化されます。サードパーティ モジュールにより、Webpack のパッケージ化効率が向上します。
dllPlugin は開発環境にのみ適用できることに注意してください。実稼働環境は一度パッケージ化されるため、実稼働環境で使用する必要はありません。
dll ダイナミック リンク ライブラリの使用手順:
1. webpack.dll.js ファイルを別途設定します。このファイルは、変更されないサードパーティ ライブラリ
webpack.dll.js をパッケージ化するために特別に使用されます。
module.exports = {
mode: 'production',
// JS 执行入口文件
entry: {
// 把 React, antd相关模块的放到一个单独的动态链接库
vendor: ['react', 'react-dom','antd','react-router-dom'],
},
output: {
// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
// 也就是 entry 中配置的 vendor
filename: '[name].dll.js',
// 输出的文件都放到 dll 目录下
path: path.join(__dirname, '..', 'dll'),
// library表示打包的是一个库,存放动态链接库的全局变量名称,
// 例如对应 vendor 来说就是 _dll_vendor
// 之所以在前面加上 _dll_ 是为了防止全局变量冲突
library: '_dll_[name]',
}
}
2. パッケージ化されたプロジェクトの構成ファイルで、add-asset-html-webpack-plugin
webpack.dev.jsを介して、事前にパッケージ化されたライブラリを HTML に挿入します。
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')
module.exports = {
//..
plugins: [
new AddAssetHtmlWebpackPlugin({
filepath:path.join(__dirname, '..', 'dll/vendor.dll.js'),
})
}
3. サードパーティとともに特別にパッケージ化された構成ファイルに、マニフェスト ファイルmanifest.json を生成するための構成を追加します (manifest.json ファイルには、対応するvendor.dll.js ファイルにどのモジュールが含まれているか、およびパスが明確に記述されています)各モジュールとIDのインデックスファイル)
webpack.dll.js
const DllPlugin = require('webpack/lib/DllPlugin')
module.exports = {
//..
plugins: [
// 接入 DllPlugin
new DllPlugin({
// 动态链接库的全局变量名称,需要和 output.library 中保持一致
// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
// 例如 vendor.manifest.json 中就有 "name": "_dll_vendor"
name: '_dll_[name]',
// 描述动态链接库的 manifest.json 文件输出时的文件名称
path: path.join(__dirname, '..', 'dll/[name].manifest.json'),
}),
]
}
4. パッケージ化されたプロジェクトの構成ファイルで、サードパーティ ライブラリをパッケージ化するときに最初にクエリするマニフェスト ファイルを webpack に指示します。リストに現在使用されているサードパーティ ライブラリが含まれている場合、webpack は手動で実行されているため、パッケージ化されません。
html .dev.jsに導入される
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
//..
new DllReferencePlugin({
manifest: require(path.join(__dirname, '..', 'dll/vendor.manifest.json'))
}),
}
5. ベンダーのパッケージ化
package.json で npm スクリプトを構成する
{
...
"scripts": {
"dll": "webpack --config webpack-config/webpack.dll.js",
}
}
Yarn DLL を実行すると、dll ディレクトリが生成されます。このディレクトリには、vendor.dll.js と Vendor.manifest.json という 2 つのファイルが含まれます。前者にはライブラリのコードが含まれ、後者にはリソース リストが含まれます。
6. 開発環境で構築プロジェクトを実行し、
yarn dev を実行します。
改善効果
ダイナミック リンク ライブラリ DLL を設定した後、プロジェクトの構築時間が約 2 秒短縮され、構築効率がある程度向上したことがわかりました。
最適化 5: babel-loader がキャッシュを開き、二次構築速度を向上させます (開発環境に適用可能)
babel-loader は、指定されたフォルダーを使用して、babel によって処理されたモジュールをキャッシュすることができます。これにより、2 回目のコンパイル時に、変更されていない部分のキャッシュが直接使用され、再度コンパイルされなくなります。
webpack.dev.js
module.exports = {
//...
module: {
rules: [
{
test: /\.ts(x)?$/,
include: [path.resolve(__dirname, 'src')],
// 当有设置cacheDirectory时,指定的目录将用来缓存 loader 的执行结果。
// 如下配置,loader 将使用默认的缓存目录 node_modules/.cache/babel-loader
use: ['babel-loader?cacheDirectory', 'ts-loader'],
},
],
},
}
最適化 6: 適切なソース マップを選択する
Webpack がソース コードをパッケージ化する場合、ソース コード内のエラーや警告の元の位置を追跡するのは困難な場合がありますが、ソース マップはコンパイルされたコードを元のソース コードにマップし直すことができます。ソース マップは本質的に情報ファイルであり、コード変換前後の対応する位置情報を保存します。変換および圧縮されたコードに対応する変換前のソース コードの位置、つまりソース コードと製品コード間のマッピングを記録します。ソース マップは、パッケージ化プロセス中に、コードが圧縮され、空白が削除され、バベルでコンパイルおよび変換された後、コード間に大きな違いがあるためにコードをデバッグできないという問題を解決します。
ソース マップの設定:
JavaScript ソース マップの設定は非常に簡単で、webpack 設定に devtool を追加するだけです。
css、scss、およびlessの場合は、ソースマップ構成項目を追加する必要があります。次の例に示すように:
module.exports = {
// ...
devtool: 'source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
sourceMap: true,
},
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
},
}
] ,
}
],
},
};
JavaScript ソース マップ構成の選択:
Webpack は複数のソース マップ フォームをサポートします。devtool:'source-map' として設定されることに加えて、さまざまなニーズに応じて、cheap-source-map、eval-source-map などを選択することもできます。完全なソース マップを生成すると全体の構築時間が長くなるため、通常はソース マップの短縮バージョンになります。パッケージ化速度が比較的速い場合は、ソース マップの簡略バージョンを選択することをお勧めします。たとえば、開発環境では、通常は eval-cheap-module-source-map が適切な選択です。これは、パッケージ化の速度とソース コード情報の復元との間の適切な妥協点です。
以下に、ソース マップ構成の共通コンポーネントの意味を紹介します。
(1) eval:
ソース マップ ファイルは個別に生成されず、マッピング関係はパッケージ化されたファイルに保存され、eval によって保存されます。
(2) ソース-map:
ソース マップ ファイルは個別に生成され、マッピング関係は別のファイルに保存されます
(3) inline:
ソース マップ ファイルは個別に生成されず、マッピング関係はパッケージ化されたファイルに保存されます。 (4)安価
:
生成されたマッピング情報はエラー行のみを特定できますが、エラー列は特定できません
(5) モジュール:
コードのマッピング関係を保存するだけでなく、サードパーティ モジュールのマッピング関係を保存します。これにより、エラーが発生した場合でもサードパーティ モジュールがより適切になる可能性があります。
eval-cheap-module-source-map は、行エラー情報のみを必要とし、サードパーティ モジュール エラー情報を含み、別個のソース マップ ファイルを生成しません。開発環境ではコード圧縮は行われないため、ソースマップに列情報がなくてもブレークポイントのデバッグには影響しません。このソースマップの生成速度も速いためです。したがって、開発環境ではdevtoolをcheap-module-eval-source-mapに設定します。
改善された効果:
プロジェクトの元の構成ファイルの開発環境のソース マップは、「inline-source-map」を選択します。作成者の実践によれば、開発環境では、「inline-source-map」を使用して特定のコードを変更し、リアルタイム コンパイル速度は 3 秒です。「eval-cheap-module-source-map」を使用するように変更し、コードに同じ変更を加えます。リアルタイム コンパイル速度は 1 ~ 2 秒で、速度は大幅に改善されました。