制御の維持:WebpackとReactのガイド、Pt。 1

公開: 2022-03-11

新しいReactプロジェクトを開始するときは、Reactアプリの作成、 react-boilerplate 、ReactStarterKitなどの多くのテンプレートから選択できます。

何千もの開発者によって採用されたこれらのテンプレートは、非常に大規模なアプリケーション開発をサポートすることができます。 しかし、それらは開発者の経験とバンドル出力をさまざまなデフォルトでサドルのままにします。これは理想的ではないかもしれません。

ビルドプロセスをより高度に制御したい場合は、カスタムWebpack構成に投資することを選択できます。 このWebpackチュートリアルから学ぶように、このタスクはそれほど複雑ではなく、他の人の構成のトラブルシューティングを行うときにも役立つ可能性があります。

Webpack:はじめに

今日のJavaScriptの記述方法は、ブラウザーが実行できるコードとは異なります。 最近のブラウザではまだサポートされていない他の種類のリソース、トランスパイルされた言語、実験的な機能に依存することがよくあります。 WebpackはJavaScriptのモジュールバンドラーであり、このギャップを埋め、開発者の経験に関しては無料でクロスブラウザー互換のコードを生成できます。

始める前に、このWebpackチュートリアルに示されているすべてのコードは、GitHubで完全なWebpack/Reactサンプル構成ファイルの形式でも入手できることを覚えておく必要があります。 そちらを参照して、質問があればこの記事に戻ってください。

基本構成

Legato(バージョン4)以降、Webpackを実行するために構成は必要ありません。 ビルドモードを選択すると、ターゲット環境により適したデフォルトのセットが適用されます。 この記事の精神では、これらのデフォルトを取り除き、各ターゲット環境に適切な構成を実装します。

まず、 webpackwebpack-cliをインストールする必要があります。

 npm install -D webpack webpack-cli

次に、 webpack.config.jsに次のオプションを備えた構成を設定する必要があります。

  • devtool :開発モードでのソースマップの生成を有効にします。
  • entry :Reactアプリケーションのメインファイル。
  • output.path :出力ファイルを保存するルートディレクトリ。
  • output.filename :生成されたファイルに使用するファイル名パターン。
  • output.publicPath :ファイルがWebサーバーに展開されるルートディレクトリへのパス。
 const path = require("path"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; const isDevelopment = !isProduction; return { devtool: isDevelopment && "cheap-module-source-map", entry: "./src/index.js", output: { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" } }; };

上記の構成は、プレーンなJavaScriptファイルに対して正常に機能します。 ただし、WebpackとReactを使用する場合は、コードをユーザーに出荷する前に、追加の変換を実行する必要があります。 次のセクションでは、Babelを使用してWebpackがJavaScriptファイルをロードする方法を変更します。

JSローダー

Babelは、コード変換用の多くのプラグインを備えたJavaScriptコンパイラです。 このセクションでは、これをローダーとしてWebpack構成に導入し、最新のJavaScriptコードを一般的なブラウザーで理解できるように変換するように構成します。

まず、 babel-loader@babel/coreをインストールする必要があります。

 npm install -D @babel/core babel-loader

次に、 moduleセクションをWebpack構成に追加して、JavaScriptファイルのロードをbabel-loaderに任せます。

 @@ -11,6 +11,25 @@ module.exports = function(_env, argv) { path: path.resolve(__dirname, "dist"), filename: "assets/js/[name].[contenthash:8].js", publicPath: "/" + }, + module: { + rules: [ + { + test: /\.jsx?$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + cacheDirectory: true, + cacheCompression: false, + envName: isProduction ? "production" : "development" + } + } + } + ] + }, + resolve: { + extensions: [".js", ".jsx"] } }; };

別の構成ファイルbabel.config.jsを使用してBabelを構成します。 次の機能を使用します。

  • @babel/preset-env :最新のJavaScript機能を下位互換性のあるコードに変換します。
  • @babel/preset-react :JSX構文をプレーンバニラJavaScript関数呼び出しに変換します。
  • @babel/plugin-transform-runtime :Babelヘルパーを共有モジュールに抽出することにより、コードの重複を減らします。
  • @babel/plugin-syntax-dynamic-import :ネイティブのPromiseサポートがないブラウザーで動的なimport()構文を有効にします。
  • @babel/plugin-proposal-class-properties :クラスベースのReactコンポーネントを作成するための、パブリックインスタンスフィールド構文の提案のサポートを有効にします。

また、React固有の本番環境の最適化をいくつか有効にします。

  • babel-plugin-transform-react-remove-prop-typesは、本番コードから不要なprop-typesを削除します。
  • @babel/plugin-transform-react-inline-elementsは、コンパイル中にReact.createElementを評価し、結果をインライン化します。
  • @babel/plugin-transform-react-constant-elementsは、静的なReact要素を定数として抽出します。

以下のコマンドは、必要なすべての依存関係をインストールします。

 npm install -D @babel/preset-env @babel/preset-react @babel/runtime @babel/plugin-transform-runtime @babel/plugin-syntax-dynamic-import @babel/plugin-proposal-class-properties babel-plugin-transform-react-remove-prop-types @babel/plugin-transform-react-inline-elements @babel/plugin-transform-react-constant-elements

次に、 babel.config.jsに次の設定を入力します。

 module.exports = { presets: [ [ "@babel/preset-env", { modules: false } ], "@babel/preset-react" ], plugins: [ "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "@babel/plugin-proposal-class-properties" ], env: { production: { only: ["src"], plugins: [ [ "transform-react-remove-prop-types", { removeImport: true } ], "@babel/plugin-transform-react-inline-elements", "@babel/plugin-transform-react-constant-elements" ] } } };

この構成により、関連するすべてのブラウザーと互換性のある方法で最新のJavaScriptを記述できます。 Reactアプリケーションで必要になる可能性のある他のタイプのリソースがあります。これについては、次のセクションで説明します。

CSSローダー

Reactアプリケーションのスタイリングに関しては、少なくとも、プレーンなCSSファイルを含めることができる必要があります。 次のローダーを使用して、Webpackでこれを実行します。

  • css-loader :CSSファイルを解析し、画像、フォント、追加のスタイルインポートなどの外部リソースを解決します。
  • style-loader :開発中に、実行時にロードされたスタイルをドキュメントに挿入します。
  • mini-css-extract-plugin :ブラウザのキャッシュを利用するために、ロードされたスタイルを本番環境で使用するために個別のファイルに抽出します。

上記のCSSローダーをインストールしましょう:

 npm install -D css-loader style-loader mini-css-extract-plugin

次に、Webpack構成のmodule.rulesセクションに新しいルールを追加します。

 @@ -1,4 +1,5 @@ const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -25,6 +26,13 @@ module.exports = function(_env, argv) { envName: isProduction ? "production" : "development" } } + }, + { + test: /\.css$/, + use: [ + isProduction ? MiniCssExtractPlugin.loader : "style-loader", + "css-loader" + ] } ] },

また、 pluginsセクションにMiniCssExtractPluginを追加します。これは、本番モードでのみ有効にします。

 @@ -38,6 +38,13 @@ module.exports = function(_env, argv) { }, resolve: { extensions: [".js", ".jsx"] - } + }, + plugins: [ + isProduction && + new MiniCssExtractPlugin({ + filename: "assets/css/[name].[contenthash:8].css", + chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" + }) + ].filter(Boolean) }; };

この構成はプレーンCSSファイルで機能し、SassやPostCSSなどのさまざまなCSSプロセッサで機能するように拡張できます。これについては次の記事で説明します。

画像ローダー

Webpackを使用して、画像、ビデオ、その他のバイナリファイルなどの静的リソースをロードすることもできます。 このようなタイプのファイルを処理する最も一般的な方法は、 file-loaderまたはurl-loaderを使用することです。これにより、必要なリソースのURL参照がコンシューマーに提供されます。

このセクションでは、一般的な画像形式を処理するためurl-loaderを追加します。 url-loaderfile-loaderは、元のファイルのサイズが特定のしきい値よりも小さい場合、ファイル全体がbase64でエンコードされたコンテンツとしてURLに埋め込まれるため、追加のリクエストが不要になることです。

まず、 url-loaderをインストールします。

 npm install -D url-loader

次に、Webpack構成のmodule.rulesセクションに新しいルールを追加します。

 @@ -34,6 +34,16 @@ module.exports = function(_env, argv) { isProduction ? MiniCssExtractPlugin.loader : "style-loader", "css-loader" ] + }, + { + test: /\.(png|jpg|gif)$/i, + use: { + loader: "url-loader", + options: { + limit: 8192, + name: "static/media/[name].[hash:8].[ext]" + } + } } ] },

SVG

SVGイメージの場合、インポートされたファイルをReactコンポーネントに変換する@svgr/webpackローダーを使用します。

@svgr/webpackをインストールします:

 npm install -D @svgr/webpack

次に、Webpack構成のmodule.rulesセクションに新しいルールを追加します。

 @@ -44,6 +44,10 @@ module.exports = function(_env, argv) { name: "static/media/[name].[hash:8].[ext]" } } + }, + { + test: /\.svg$/, + use: ["@svgr/webpack"] } ] },

ReactコンポーネントとしてのSVGイメージは便利であり、 @svgr/webpackはSVGOを使用して最適化を実行します。

注:特定のアニメーションやマウスオーバー効果では、JavaScriptを使用してSVGを操作する必要がある場合があります。 幸い、 @svgr/webpackはデフォルトでSVGコンテンツをJavaScriptバンドルに埋め込み、これに必要なセキュリティ制限を回避できるようにします。

ファイルローダー

他の種類のファイルを参照する必要がある場合は、汎用file-loaderがその役割を果たします。 これはurl-loaderと同様に機能し、それを必要とするコードにアセットURLを提供しますが、それを最適化しようとはしません。

いつものように、最初にNode.jsモジュールをインストールします。 この場合、 file-loader

 npm install -D file-loader

次に、Webpack構成のmodule.rulesセクションに新しいルールを追加します。 例えば:

 @@ -48,6 +48,13 @@ module.exports = function(_env, argv) { { test: /\.svg$/, use: ["@svgr/webpack"] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + loader: require.resolve("file-loader"), + options: { + name: "static/media/[name].[hash:8].[ext]" + } } ] },

ここでは、CSSファイルから参照できるフォントをロードするためのfile-loaderを追加しました。 この例を拡張して、必要な他の種類のファイルをロードできます。

環境プラグイン

WebpackのDefinePlugin()を使用して、ビルド環境からアプリケーションコードに環境変数を公開できます。 例えば:

 @@ -1,5 +1,6 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const webpack = require("webpack"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -65,7 +66,12 @@ module.exports = function(_env, argv) { new MiniCssExtractPlugin({ filename: "assets/css/[name].[contenthash:8].css", chunkFilename: "assets/css/[name].[contenthash:8].chunk.css" - }) + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify( + isProduction ? "production" : "development" + ) + }) ].filter(Boolean) }; };

ここでは、 process.env.NODE_ENVを、ビルドモードを表す文字列"development"または"production"に置き換えました。

HTMLプラグイン

index.htmlファイルがない場合、JavaScriptバンドルは役に立たず、そこに座っているだけで誰もそれを見つけることができません。 このセクションでは、 html-webpack-pluginを紹介してHTMLファイルを生成します。

html-webpack-pluginをインストールします:

 npm install -D html-webpack-plugin

次に、 html-webpack-pluginをWebpack構成のpluginsセクションに追加します。

 @@ -1,6 +1,7 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -71,6 +72,10 @@ module.exports = function(_env, argv) { "process.env.NODE_ENV": JSON.stringify( isProduction ? "production" : "development" ) + }), + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, "public/index.html"), + inject: true }) ].filter(Boolean) };

生成されたpublic/index.htmlファイルは、バンドルをロードし、アプリケーションをブートストラップします。

最適化

ビルドプロセスで使用できる最適化手法はいくつかあります。 コードの縮小化から始めます。これは、機能の面で費用をかけずにバンドルのサイズを縮小できるプロセスです。 コードを最小化するために、JavaScriptコード用のterser-webpack-plugin webpack-pluginとCSS用optimize-css-assets-webpack-pluginの2つのプラグインを使用します。

それらをインストールしましょう:

 npm install -D terser-webpack-plugin optimize-css-assets-webpack-plugin

次に、構成にoptimizationセクションを追加します。

 @@ -2,6 +2,8 @@ const path = require("path"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const webpack = require("webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); +const TerserWebpackPlugin = require("terser-webpack-plugin"); +const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); module.exports = function(_env, argv) { const isProduction = argv.mode === "production"; @@ -75,6 +77,27 @@ module.exports = function(_env, argv) { isProduction ? "production" : "development" ) }) - ].filter(Boolean) + ].filter(Boolean), + optimization: { + minimize: isProduction, + minimizer: [ + new TerserWebpackPlugin({ + terserOptions: { + compress: { + comparisons: false + }, + mangle: { + safari10: true + }, + output: { + comments: false, + ascii_only: true + }, + warnings: false + } + }), + new OptimizeCssAssetsPlugin() + ] + } }; };

上記の設定により、最新のすべてのブラウザとのコード互換性が保証されます。

コード分​​割

コード分​​割は、アプリケーションのパフォーマンスを向上させるために使用できるもう1つの手法です。 コード分​​割は、2つの異なるアプローチを参照できます。

  1. 動的なimport()ステートメントを使用して、バンドルサイズのかなりの部分を構成するアプリケーションの部分を抽出し、それらをオンデマンドでロードできます。
  2. ブラウザのキャッシュを利用し、再訪問者のパフォーマンスを向上させるために、変更頻度の低いコードを抽出できます。

Webpack構成のoptimization.splitChunksセクションに、サードパーティの依存関係と一般的なチャンクを個別のファイルに抽出するための設定を入力します。

 @@ -99,7 +99,29 @@ module.exports = function(_env, argv) { sourceMap: true }), new OptimizeCssAssetsPlugin() - ] + ], + splitChunks: { + chunks: "all", + minSize: 0, + maxInitialRequests: 20, + maxAsyncRequests: 20, + cacheGroups: { + vendors: { + test: /[\\/]node_modules[\\/]/, + name(module, chunks, cacheGroupKey) { + const packageName = module.context.match( + /[\\/]node_modules[\\/](.*?)([\\/]|$)/ + )[1]; + return `${cacheGroupKey}.${packageName.replace("@", "")}`; + } + }, + common: { + minChunks: 2, + priority: -10 + } + } + }, + runtimeChunk: "single" } }; };

ここで使用したオプションを詳しく見てみましょう。

  • chunks: "all" :デフォルトでは、一般的なチャンク抽出は、動的import()でロードされたモジュールにのみ影響します。 この設定により、エントリポイントのロードの最適化も可能になります。
  • minSize: 0 :デフォルトでは、特定のサイズのしきい値を超えるチャンクのみが抽出の対象になります。 この設定により、サイズに関係なく、すべての一般的なコードの最適化が可能になります。
  • maxInitialRequests: 20およびmaxAsyncChunks: 20 :これらの設定により、エントリポイントのインポートとスプリットポイントのインポートでそれぞれ並行してロードできるソースファイルの最大数が増加します。

さらに、次のcacheGroups構成を指定します。

  • vendors :サードパーティモジュールの抽出を構成します。
    • test: /[\\/]node_modules[\\/]/ :サードパーティの依存関係を照合するためのファイル名パターン。
    • name(module, chunks, cacheGroupKey) :グループは、同じモジュールからのチャンクを共通の名前を付けてグループ化します。
  • common :アプリケーションコードからの共通チャンク抽出を構成します。
    • minChunks: 2 :少なくとも2つのモジュールから参照されている場合、チャンクは一般的であると見なされます。
    • priority: -10vendorsのキャッシュグループのチャンクが最初に考慮されるように、 commonのキャッシュグループに負の優先度を割り当てます。

また、 runtimeChunk: "single"を指定することにより、複数のエントリポイント間で共有できる単一のチャンクにWebpackランタイムコードを抽出します。

開発サーバー

これまで、アプリケーションの本番ビルドの作成と最適化に重点を置いてきましたが、Webpackには、ライブリロードとエラーレポートを備えた独自のWebサーバーもあり、開発プロセスに役立ちます。 これはwebpack-dev-serverと呼ばれ、個別にインストールする必要があります。

 npm install -D webpack-dev-server

このスニペットでは、 devServerセクションをWebpack構成に導入します。

 @@ -120,6 +120,12 @@ module.exports = function(_env, argv) { } }, runtimeChunk: "single" + }, + devServer: { + compress: true, + historyApiFallback: true, + open: true, + overlay: true } }; };

ここでは、次のオプションを使用しました。

  • compress: true :アセットの圧縮を有効にしてリロードを高速化します。
  • historyApiFallback: true :履歴ベースのルーティングのindex.htmlへのフォールバックを有効にします。
  • open: true :開発サーバーを起動した後にブラウザを開きます。
  • overlay: true :ブラウザウィンドウにWebpackエラーを表示します。

APIリクエストをバックエンドサーバーに転送するようにプロキシ設定を構成する必要がある場合もあります。

Webpack and React:パフォーマンスが最適化されて準備ができています!

Webpackを使用してさまざまなリソースタイプをロードする方法、開発環境でWebpackを使用する方法、および本番ビルドを最適化するためのいくつかのテクニックを学びました。 必要に応じて、独自のReact / Webpackセットアップのインスピレーションを得るために、いつでも完全な構成ファイルを確認できます。 そのようなスキルを磨くことは、React開発サービスを提供する人にとっては標準的な運賃です。

このシリーズの次のパートでは、TypeScriptの使用法、CSSプリプロセッサ、サーバー側のレンダリングとServiceWorkersを含む高度な最適化手法など、より具体的なユースケースの手順を使用してこの構成を拡張します。 Reactアプリケーションを本番環境に移行するためにWebpackについて知っておく必要のあるすべてのことを学ぶのを楽しみにしていてください。