Webpackの依存関係を管理するためのガイド

公開: 2022-03-11

モジュール化の概念は、ほとんどの最新のプログラミング言語に固有の部分です。 ただし、JavaScriptには、最新バージョンのECMAScript ES6が登場するまで、モジュール化への正式なアプローチがありませんでした。

今日最も人気のあるJavaScriptフレームワークの1つであるNode.jsでは、モジュールバンドラーを使用してWebブラウザーにNPMモジュールをロードでき、コンポーネント指向のライブラリ(Reactなど)はJavaScriptコードのモジュール化を促進および促進します。

Webpackは、JavaScriptコード、およびスタイルシート、画像、フォントなどのすべての静的アセットをバンドルファイルに処理する利用可能なモジュールバンドラーの1つです。 処理には、コンパイル、連結、縮小、圧縮など、コードの依存関係を管理および最適化するために必要なすべてのタスクを含めることができます。

Webpack:初心者向けチュートリアル

ただし、Webpackとその依存関係の構成はストレスを伴う可能性があり、特に初心者にとっては必ずしも簡単なプロセスではありません。

このブログ投稿では、さまざまなシナリオに合わせてWebpackを構成する方法のガイドラインと例を示し、Webpackを使用したプロジェクトの依存関係のバンドルに関連する最も一般的な落とし穴を指摘しています。

このブログ投稿の最初の部分では、プロジェクトの依存関係の定義を単純化する方法について説明しています。 次に、複数ページおよび単一ページのアプリケーションのコード分割の構成について説明し、デモンストレーションします。 最後に、プロジェクトにサードパーティのライブラリを含める場合は、Webpackを構成する方法について説明します。

エイリアスと相対パスの構成

相対パスは依存関係に直接関連していませんが、依存関係を定義するときに使用します。 プロジェクトファイルの構造が複雑な場合、関連するモジュールパスを解決するのが難しい場合があります。 Webpack構成の最も基本的な利点の1つは、プロジェクト内の相対パスの定義を簡素化するのに役立つことです。

次のプロジェクト構造があるとしましょう。

 - Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js

必要なファイルへの相対パスによって依存関係を参照できます。ソースコードのコンテナーにコンポーネントをインポートする場合は、次のようになります。


Home.js

 Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';


Modal.js

 import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

スクリプトまたはモジュールをインポートするたびに、現在のディレクトリの場所を知り、インポートするものへの相対パスを見つける必要があります。 ネストされたファイル構造を持つ大きなプロジェクトがある場合、または複雑なプロジェクト構造の一部をリファクタリングしたい場合、この問題がどのように複雑になるかを想像できます。

この問題は、Webpackのresolve.aliasオプションで簡単に処理できます。 いわゆるエイリアス(ディレクトリまたはモジュールの名前とその場所)を宣言できます。プロジェクトのソースコード内の相対パスには依存しません。


webpack.config.js

 resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }

Modal.jsファイルで、datepickerをはるかに簡単にインポートできるようになりました。

 import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

コード分​​割

スクリプトを最終バンドルに追加したり、最終バンドルを分割したり、オンデマンドで個別のバンドルをロードしたりする必要があるシナリオがあります。 これらのシナリオのプロジェクトとWebpack構成のセットアップは、簡単ではない場合があります。

Webpack構成では、 EntryオプションはWebpackに最終バンドルの開始点を指示します。 エントリポイントには、文字列、配列、オブジェクトの3つの異なるデータ型を指定できます。

開始点が1つしかない場合は、これらの形式のいずれかを使用して同じ結果を得ることができます。

複数のファイルを追加する必要があり、それらが相互に依存しない場合は、配列形式を使用できます。 たとえば、 bundle.jsの最後にanalytics.jsを追加できます。


webpack.config.js

 module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };

複数のエントリポイントの管理

index.htmladmin.htmlなどの複数のHTMLファイルを含む複数ページのアプリケーションがあるとします。 エントリポイントをオブジェクトタイプとして使用することで、複数のバンドルを生成できます。 以下の構成では、2つのJavaScriptバンドルが生成されます。


webpack.config.js

 module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };


index.html

 <script src=”build/index.js”></script>


admin.html

 <script src=”build/admin.js”></script>

どちらのJavaScriptバンドルも、共通のライブラリとコンポーネントを共有できます。 そのために、 CommonsChunkPluginを使用できます。これは、複数のエントリチャンクで発生するモジュールを検索し、複数のページ間でキャッシュできる共有バンドルを作成します。


webpack.config.js

 var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };

ここで、バンドルされたスクリプトの前に<script src="build/common.js"></script>を追加することを忘れないでください。

遅延読み込みの有効化

Webpackは静的アセットを小さなチャンクに分割でき、このアプローチは標準の連結よりも柔軟性があります。 大きなシングルページアプリケーション(SPA)がある場合、1つの大きなバンドルの読み込みに時間がかかる可能性があり、ユーザーは通常、各ビューのすべての依存関係を必要としないため、1つのバンドルに単純に連結することは適切なアプローチではありません。

アプリケーションを複数のバンドルに分割し、共通の依存関係を連結し、ブラウザーのキャッシュ動作から利益を得る方法については、前に説明しました。 このアプローチは、マルチページアプリケーションでは非常にうまく機能しますが、シングルページアプリケーションでは機能しません。

SPAの場合、現在のビューをレンダリングするために必要な静的アセットのみを提供する必要があります。 SPAアーキテクチャのクライアント側ルーターは、コード分割を処理するのに最適な場所です。 ユーザーがルートを入力すると、結果のビューに必要な依存関係のみをロードできます。 または、ユーザーがページを下にスクロールするときに依存関係を読み込むこともできます。

この目的のために、Webpackが静的に検出できるrequire.ensureまたはSystem.import関数を使用できます。 Webpackは、この分割点に基づいて個別のバンドルを生成し、オンデマンドで呼び出すことができます。

この例では、2つのReactコンテナーがあります。 管理者ビューとダッシュボードビュー。


admin.jsx

 import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }


dashboard.jsx

 import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }

ユーザーが/dashboardまたは/adminのいずれかを入力すると、対応する必要なJavaScriptバンドルのみがロードされます。 以下に、クライアント側ルーターがある場合とない場合の例を示します。


index.jsx

 if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }


index.jsx

 ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );

個別のバンドルへのスタイルの抽出

Webpackでは、 style-loadercss-loaderなどのローダーがスタイルシートを前処理し、出力JavaScriptバンドルに埋め込みますが、場合によっては、スタイルなしコンテンツのフラッシュ(FOUC)が発生する可能性があります。

最終的なJavaScriptバンドルに埋め込む代わりに、すべてのスタイルを個別のCSSバンドルに生成できるExtractTextWebpackPluginを使用してFOUCを回避できます。


webpack.config.js

 var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }

サードパーティのライブラリとプラグインの処理

同じコンポーネントを最初から開発することに時間を費やしたくないため、多くの場合、サードパーティのライブラリ、さまざまなプラグイン、または追加のスクリプトを使用する必要があります。 アクティブに維持されておらず、JavaScriptモジュールを理解しておらず、事前定義された名前でグローバルに依存関係が存在することを前提としている、利用可能な多くのレガシーライブラリとプラグインがあります。

以下は、jQueryプラグインの例と、最終的なバンドルを生成できるようにWebpackを適切に構成する方法の説明です。

ProvidePlugin

ほとんどのサードパーティプラグインは、特定のグローバル依存関係の存在に依存しています。 jQueryの場合、プラグインは定義されている$またはjQuery変数に依存し、コードで$('div.content').pluginFunc()を呼び出すことでjQueryプラグインを使用できます。

WebpackプラグインProvidePluginを使用して、グローバル$識別子に遭遇するたびにvar $ = require("jquery")を追加できます。


webpack.config.js

 webpack.ProvidePlugin({ '$': 'jquery', })

Webpackがコードを処理するとき、プレゼンス$を探し、 require関数で指定されたモジュールをインポートせずにグローバル依存関係への参照を提供します。

インポートローダー

一部のjQueryプラグインは、グローバル名前空間で$を想定しているか、 thiswindowオブジェクトであることに依存しています。 この目的のために、グローバル変数をモジュールに挿入するimports-loaderを使用できます。


example.js

 $('div.content').pluginFunc();

次に、 imports-loaderを構成することにより、 $変数をモジュールに挿入できます。

 require("imports?$=jquery!./example.js");

これは単にvar $ = require("jquery");を付加します。 example.jsに。

2番目のユースケース:


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }

=>記号(ES6 Arrow関数と混同しないでください)を使用することにより、任意の変数を設定できます。 最後の値は、 windowオブジェクトを指すようにグローバル変数thisを再定義します。 これは、ファイルのコンテンツ全体を(function () { ... }).call(window);でラップするのと同じです。 そして、引数としてwindowを使用してthis関数を呼び出します。

CommonJSまたはAMDモジュール形式を使用してライブラリを要求することもできます。

 // CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });

一部のライブラリとモジュールは、さまざまなモジュール形式をサポートできます。

次の例では、AMDおよびCommonJSモジュール形式を使用し、jQueryに依存するjQueryプラグインがあります。


jquery-plugin.js

 (function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }

特定のライブラリに使用するモジュール形式を選択できます。 definefalseに等しいと宣言した場合、WebpackはモジュールをAMDモジュール形式で解析しません。また、変数exportsfalseに等しいと宣言した場合、WebpackはモジュールをCommonJSモジュール形式で解析しません。

エクスポーズローダー

モジュールをグローバルコンテキストに公開する必要がある場合は、 expose-loaderを使用できます。 これは、たとえば、Webpack構成の一部ではなく、グローバル名前空間のシンボルに依存する外部スクリプトがある場合、またはブラウザーのコンソールでシンボルにアクセスする必要があるブラウザープラグインを使用する場合に役立ちます。


webpack.config.js

 module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }

jQueryライブラリは、Webページ上の他のスクリプトのグローバル名前空間で使用できるようになりました。

 window.$ window.jQuery

外部依存関係の構成

外部でホストされているスクリプトのモジュールを含める場合は、構成でそれらを定義する必要があります。 そうしないと、Webpackは最終バンドルを生成できません。

Webpack構成のexternalsオプションを使用して、外部スクリプトを構成できます。 たとえば、CDNのライブラリを別の<script>タグを介して使用しながら、プロジェクトでモジュールの依存関係として明示的に宣言することができます。


webpack.config.js

 externals: { react: 'React', 'react-dom': 'ReactDOM' }

ライブラリの複数のインスタンスのサポート

サードパーティのライブラリと依存関係を管理するために、フロントエンド開発でNPMパッケージマネージャーを使用するのは素晴らしいことです。 ただし、バージョンが異なる同じライブラリの複数のインスタンスが存在する可能性があり、それらが1つの環境でうまく連携しない場合があります。

これは、たとえば、Reactライブラリで発生する可能性があります。Reactライブラリでは、NPMからReactをインストールでき、後で、追加のパッケージまたはプラグインを使用して別のバージョンのReactを利用できるようになります。 プロジェクトの構造は次のようになります。

 project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react

react-pluginからのコンポーネントには、プロジェクト内の他のコンポーネントとは異なるReactインスタンスがあります。 これで、Reactの2つの別々のコピーができましたが、それらは異なるバージョンにすることができます。 このアプリケーションでは、このシナリオによってグローバルな可変DOMが混乱する可能性があり、Webコンソールログにエラーメッセージが表示されます。 この問題の解決策は、プロジェクト全体で同じバージョンのReactを使用することです。 Webpackエイリアスで解決できます。


webpack.config.js

 module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }

react-pluginがReactを要求しようとすると、プロジェクトのnode_modulesのバージョンを使用します。 使用しているReactのバージョンを知りたい場合は、ソースコードにconsole.log(React.version)を追加できます。

Webpackの構成ではなく、開発に焦点を当てる

この投稿は、Webpackのパワーとユーティリティのほんの一部にすぎません。

JavaScriptのバンドルを最適化および合理化するのに役立つ他の多くのWebpackローダーおよびプラグインがあります。

初心者の場合でも、このガイドはWebpackの使用を開始するための確固たる基盤を提供します。これにより、バンドル構成ではなく開発に集中できるようになります。

関連:コントロールの維持:WebpackとReactのガイド、Pt。 1