1.webpack のコア概念#
- entry(エントリ):実行可能なモジュールまたはライブラリのエントリ。パッケージ化された後のエントリファイルを定義します。
- output(出力):webpack がどのように出力し、どこに出力するかを指示します。
path: パッケージファイルが保存される絶対パス
publicPath: ウェブサイトが実行されるときのアクセスパス
filename: パッケージ化された後のファイル名 - module(モジュール):webpack では、すべてがモジュールであり、1 つのモジュールは 1 つのファイルに対応します。webpack は設定された entry から始まり、すべての依存モジュールを再帰的に見つけ出します。
- chunk(チャンク):1 つの
chunk
は複数のモジュール
で構成されます。実行可能なモジュールとその依存モジュールを組み合わせて 1 つの chunk にすることが、これがパッケージ化です。 - loader(モジュール変換器):モジュールの元の内容を要求に応じて新しい内容に変換するために使用されます。例えば:es6 を es5 に変換する、scss を css に変換するなど。
- plugin(拡張):webpack の機能を拡張するプラグイン。webpack のビルドライフサイクルのノードに拡張フックを追加し、機能を追加します。
2.webpack ビルドプロセス#
- 初期化パラメータ:webpack の設定パラメータを解析し、シェルから渡されたパラメータと webpack.config.js ファイルの設定パラメータを統合し、最終的な設定結果を形成します。
- コンパイル開始:前のステップで得られたパラメータを使用してコンパイラオブジェクトを初期化し、すべての設定されたプラグインを登録します。プラグインは webpack のビルドライフサイクルのイベントノードを監視し、適切な反応をし、オブジェクトの run メソッドを実行してコンパイルを開始します。
- エントリの特定:設定された entry エントリを解析し、ファイルの AST 構文木を構築し、依存関係を見つけ、再帰的に進みます。
- モジュールのコンパイル:ファイルタイプと loader 設定に基づいて、すべての設定された loader を呼び出してファイルを変換し、そのモジュールが依存するモジュールを見つけ、すべてのエントリ依存ファイルがこのステップの処理を受けるまで再帰的に進みます。
- モジュールコンパイルの完了と出力:再帰が完了した後、各ファイルの結果を得て、各モジュールとそれらの間の依存関係を含み、entry 設定に基づいてコードブロック chunk を生成します。
- 出力完了:すべての chunk をファイルシステムに出力します。
3. 一般的な loader にはどのようなものがありますか?#
- babel-loader:es6 を es5 に変換します;
- css-loader:css を読み込み、モジュール化、圧縮、ファイルインポートなどの機能をサポートします;
- style-loader:css コードを js に注入し、DOM 操作を通じて css を読み込みます;
- eslint-loader:Eslint を使用して js コードをチェックします;
- image-loader:画像を読み込み、圧縮します;
- file-loader:ファイルをフォルダに出力し、コード内で相対 URL を使用して出力されたファイルを参照します;
- url-loader:file-loader に似ており、ファイルが非常に小さい場合、base64 方式でファイル内容をコードに注入します。
- source-map-loader:追加のソースマップファイルを読み込み、デバッグを容易にします。
4. ビジネスシナリオと対応する解決策#
1. シングルページアプリケーション#
シングルページアプリケーションは、実行エントリを指定するためにエントリを設定する必要があります。web-webpack-plugin の WebPlugin はこれらの作業を自動的に完了できます:webpack はエントリのすべての依存ファイルを含む chunk を生成しますが、chunk で生成された js を読み込むために html が必要です。もし css を抽出する必要がある場合は、HTML ファイルに抽出された css をインポートする必要があります。
シンプルな webpack 設定ファイルの例:
const { WebPlugin } = require('web-webpack-plugin');
module.exports = {
entry: {
app: './src/doc/index.js',
home: './src/doc/home.js'
},
plugins: [
// 1つの WebPlugin が 1つの html ファイルを生成します
new WebPlugin({
// 出力される html ファイル名
filename: 'index.html',
// この html が依存する `entry`
requires: ['app','home'],
}),
],
};
説明:require: ['app', 'home'] はこの html がどのエントリに依存しているかを指定し、エントリ生成の js と css は自動的に html に注入されます。
これらのリソースの注入方法を設定することもサポートされており、次の属性をサポートします:
- _dist は生産環境でのみインポートされるリソース;
- _dev は開発環境でのみインポートされるリソース;
- _inline はリソースの内容を html に埋め込みます;
- _ie は IE ブラウザでのみ必要なリソース。
これらの属性は js で設定できます。簡単な例を見てみましょう:
new WebPlugin({
filename: 'index.html',
requires: {
app:{
_dist:true,
_inline:false,
}
},
}),
これらの属性はテンプレート内でも設定でき、テンプレートを使用する利点はリソースの注入ポイントを柔軟に制御できることです。
new WebPlugin({
filename: 'index.html',
template: './template.html',
}),
<!DOCTYPE html>
<html lang="ja">
<head>
<link rel="stylesheet" href="app?_inline">
<script src="ie-polyfill?_ie"></script>
</head>
<body>
<div id="react-body"></div>
<script src="app"></script>
</body>
</html>
WebPlugin プラグインは fis3 の思想を参考にしており、webpack に欠けている HTML をエントリとする機能を補完しています。WebPlugin の詳細な機能については、ドキュメントを参照してください。
2.1 つのプロジェクトで複数のシングルページ#
1 つのプロジェクトには複数のシングルページアプリケーションが含まれることがあります。複数のシングルページアプリケーションを 1 つにまとめることもできますが、そうするとユーザーがアクセスしない部分も読み込まれてしまいます。プロジェクトに多くのシングルページアプリケーションがある場合、各シングルページアプリケーションにエントリと WebPlugin を設定する必要がありますか?もし新しいものを追加する場合、webpack 設定を追加する必要があります。これを行うのは面倒です。この時、web-webpack-plugin の AutoWebPlugin メソッドがこれらの問題を解決できます。
module.exports = {
plugins: [
// すべてのページのエントリディレクトリ
new AutoWebPlugin('./src/'),
]
};
分析:
AutoWebPlugin
は ./src/ ディレクトリ内のすべてのフォルダをシングルページのエントリとして扱い、すべてのページエントリに対して自動的にWebPlugin
を設定して対応する html を出力します。- 新しいページを追加するには、
./src/
内に新しいフォルダを作成し、そのシングルページアプリケーションが依存するコードを含めるだけで、AutoWebPlugin
が自動的にフォルダ名の html ファイルを生成します。
3. コード分割の最適化#
良いコード分割はブラウザのファーストスクリーン効果を大幅に向上させます。
最も一般的な react システム:
- 基本ライブラリである react、react-dom、redux、react-redux を別のファイルに抽出し、他のファイルと一緒にパッケージ化しないようにします。これにより、バージョンをアップグレードしない限り、このファイルは永遠に更新されません。これらの基本ライブラリとビジネスコードを 1 つのファイルにパッケージ化すると、ビジネスコードを変更するたびにファイルのハッシュ値が変わり、キャッシュが無効になり、ブラウザがこれらの基本ライブラリを含むコードを再度ダウンロードすることになります。したがって、基本ライブラリを 1 つのファイルにパッケージ化します。
// vender.js ファイルを基本ライブラリを別のファイルに抽出してビジネスコードに影響されないようにします
// すべてのページが依存するサードパーティライブラリ
// react 基本
import 'react';
import 'react-dom';
import 'react-redux';
// redux 基本
import 'redux';
import 'redux-thunk';
// webpack 設定
{
entry: {
vendor: './path/to/vendor.js',
},
}
- CommonsChunkPlugin を使用して、複数のコードブロックが依存するコードを抽出し、1 つの独立した chunk を形成できます。アプリケーションに複数のページがある場合、すべてのページの共通コードを抽出して、単一のページのコードを減らし、異なるページ間を切り替える際に、すべてのページの共通コードが以前に読み込まれており、再度読み込む必要がありません。したがって、CommonsChunkPlugin を使用して、複数のコードブロックが依存するコードを抽出し、1 つの独立した chunk を形成できます。
4. サーバーサイドレンダリングの構築#
サーバーサイドレンダリングのコードは nodejs 環境で実行される必要があり、ブラウザとは異なり、サーバーサイドレンダリングコードは commonjs 規範を採用し、css などの js 以外のファイルを含めるべきではありません。
webpack 設定は次のとおりです:
module.exports = {
target: 'node',
entry: {
'server_render': './src/server_render',
},
output: {
filename: './dist/server/[name].js',
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
},
{
test: /\.(scss|css|pdf)$/,
loader: 'ignore-loader',
},
]
},
};
分析:
-
target: 'node'
は、構築されたコードが node 環境で実行されることを示します。 -
libraryTarget: 'commonjs2'
は、出力されるコードが commonjs 規範であることを示します。 -
{test: /\.(scss|css|pdf)$/,loader: 'ignore-loader'}
は、node で実行できないサーバーサイドレンダリングに不要なファイルがパッケージ化されるのを防ぐためです。
5.fis3 から webpack への移行#
fis3 と webpack には多くの類似点と異なる点があります。類似点:どちらも commonjs 規範を採用しています。異なる点:css などの非 js リソースのインポート方法です。
fis3 は @require './index.scss' を使用しますが、webpack は require ('./index.scss') を使用します。
fis3 をスムーズに webpack に移行したい場合は、comment-require-loader を使用できます。
例えば、webpack の構築時に fis3 の方法を採用した imui モジュールを使用したい場合:
loaders:[{
test: /\.js$/,
loaders: ['comment-require-loader'],
include: [path.resolve(__dirname, 'node_modules/imui'),]
}]
5. カスタム webpack 拡張#
コミュニティであなたのアプリケーションシナリオの解決策が見つからない場合は、自分で loader または plugin を書く必要があります。
カスタム webpack 拡張を作成する前に、loader を作成するのか plugin を作成するのかを明確にする必要があります。次のように判断できます:
もしあなたの拡張が個々のファイルを変換することを目的としているなら、loader を作成します。それ以外はすべて plugin です。
ファイルを変換することは、次のようなことができます:
- babel-loader は es6 を es5 に変換します;
- file-loader はファイルを対応する URL に置き換えます;
- raw-loader はテキストファイルの内容をコードに注入します。
1.webpack loader の作成#
loader の作成は非常に簡単です。comment-require-loader の例を見てみましょう:
module.exports = function (content) {
return replace(content);
};
loader のエントリは関数をエクスポートする必要があり、この関数が行うことはファイルの内容を変換することです。
関数が受け取るパラメータ content は、変換前のファイルの文字列形式の内容であり、新しい文字列形式の内容を返す必要があります。これが変換後の結果となります。すべてのモジュール化されたインポートファイルは loader を通過します。ここから、loader は個々のファイルのみを処理でき、コードブロックを処理できないことがわかります。公式ドキュメントを参照してください。
2. webpack plugin の作成#
plugin の適用シナリオは広範囲にわたるため、少し複雑です。end-webpack-plugin の例を見てみましょう:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
// webpack ライフサイクルのイベントを監視し、適切な処理を行います
compiler.plugin('done', (stats) => {
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
this.failCallback(err);
});
}
}
module.exports = EndWebpackPlugin;
loader のエントリはクラスをエクスポートする必要があり、new EndWebpackPlugin () の際にコンストラクタでこのプラグインに必要なパラメータを渡します。webpack が起動すると、最初に plugin をインスタンス化し、その後 plugin の apply メソッドを呼び出します。プラグインは apply 関数内で webpack ライフサイクルのイベントを監視し、適切な処理を行います。
webpack plugin の 2 つのコア概念:
- compiler:webpack が起動してから終了するまで、1 つの Compiler が存在し、compiler は webpack の設定を保持します。
- compilation:webpack のファイル変更監視による自動コンパイルメカニズムにより、compilation は 1 回のコンパイルを表します。
Compiler と Compilation は、さまざまなイベントをブロードキャストします。webpack ライフサイクルには非常に多くのイベントがあります。
以上は最もシンプルなデモに過ぎません。より複雑なものについては、how to write a plugin を参照するか、web-webpack-plugin を参考にしてください。