React初学者のためのガイドで著者のPete Hunt氏がオススメしていたwebpack入門を和訳しました。
意訳が含まれるため、誤りやより良い表現などがあればご指摘頂けると助かります。
原文:https://github.com/petehunt/webpack-howto
Webpack入門
このガイドの目的
これはwebpackで物事を成し遂げるためのクックブックです。インスタグラムで実際に使用されているものをほぼ網羅した実践的な内容となっています。
私からのアドバイス:まずはこれをwebpackの参考資料として手元に置いて始めてみましょう。公式ドキュメントは理解を深めるために後で参照することにしましょう。
前提条件
- browserify、RequireJSまたは類似したものを知っていること
- 下記のいずれかに価値を見出していること
- バンドルの分割
- 非同期ローディング
- 画像やCSSのような静的アセットのパッケージ化
1. webpackを使う理由
- Browserifyに似た機能を持つ一方で、アプリケーションを複数のファイルに分割することもできます。SPA内で複数ページを持つような場合は、ユーザーは閲覧しているページのコードだけをダウンロードします。他のページへ遷移した場合は、共通のコードを再ダウンロードすることはありません。
- CSS、Sass、Less、AltJSや画像など多くのリソースのビルドやバンドルが可能であるため、多くの場合、gruntやgulpに取って代わります。
他のモジュールシステム(Angular、ES6)の中にあって、AMDとCommonJSの双方をサポートしていますが、特別な理由がない限りCommonJSを使用するようにしてください。
2. Browserifyな人々のためのwebpack
これらは同等です。
browserify main.js > bundle.js
webpack main.js bundle.js
しかしながら、webpackはbrowserifyよりも強力なため、整理された状態を保つために通常は webpack.config.js
が欲しくなるでしょう。
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
}
};
単なるJSなので気軽に本物のコードを入れてみてください。
3. webpackの起動方法
webpack.config.js
を含むディレクトリに移動して起動します。
-
webpack
開発用に一度だけビルドします。 -
webpack -p
本番用(最小化)に一度だけビルドします。 -
webpack --watch
開発用に継続的差分ビルド(高速!)します。 -
webpack -d
ソースマップを含めます。
4. AltJS
browserifyのトランスフォームとRequireJSプラグインに該当するものはwebpackのloaderです。webpackにCoffeeScriptやJSX + ES6サポート(npm install babel-loader coffee-loader
が必要)をロードさせる方法を示します。
さらなる依存関係のためにbabel-loaderの導入手順も参照してください。(読むのが面倒であれば、 npm install babel-core babel-preset-es2015 babel-preset-react
を実行してください)
//webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.coffee$/,
loader: 'coffee-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
}
};
拡張子の指定なしでrequireさせるためには、webpackがファイルを特定するための resolve.extensions
パラメータを追加しなければなりません。
//webpack.config.js
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.coffee$/,
loader: 'coffee-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: ['es2015', 'react']
}
}
]
},
resolve: {
// これでrequire('file.coffee')の代わりにrequire('file')を使えるようになります
extensions: ['', '.js', '.json', '.coffee']
}
};
5. スタイルシートと画像
まず、静的アセット(nodeの require()
と同じように命名された)を require()
するためにコードを更新してください。
require('./bootstrap.css');
require('./myapp.less');
var img = document.createElement('img');
img.src = require('./glyph.png');
CSS(もしくはlessなど)を必要とする時、webpackはJSバンドル内に文字列として埋め込み、 require()
で <style>
タグをページに挿入します。画像を必要とする時、webpackはバンドル内にURLを埋め込み、 require()
からそれを返します。
しかし、webpackにこれを教えてやる必要があります。(再びローダーにより)
// webpack.config.js
module.exports = {
entry: './main.js',
output: {
path: './build', // 画像とJSの置き場所です
publicPath: 'http://mycdn.com/', // URLを生成するのに使います
filename: 'bundle.js'
},
{
loaders: [
{ test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // ローダーを繋げるために!を使います
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' } // 8kb以下の画像はbase64 URLとして埋め込み、他は直接URLを参照させます
]
}
};
6. 機能フラグ
開発環境だけに留めておきたいコード(ログ保存など)やドッグフーディング用サーバー(従業員がテスト中の未公開機能など)がある場合、コード内で魔法のグローバル変数を参照してください。
if (__DEV__) {
console.warn('Extra logging');
}
// ...
if (__PRERELEASE__) {
showSecretFeature();
}
次に、webpackにこの魔法のグローバル変数を教えてあげましょう。
// webpack.config.js
// definePluginは生の文字列を引数に取って挿入するため、必要ならJSの文字列を使うこともできます。
var definePlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
__PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
});
module.exports = {
entry: './main.js',
output: {
filename: 'bundle.js'
},
plugins: [definePlugin]
};
これでconsoleから BUILD_DEV=1 BUILD_PRERELEASE=1 webpack
とすることでビルドできます。 webpack -p
は未使用コード除去のuglifyを実行し、このブロックに内包されるものは取り除かれるため、秘密の機能や文字列は外部に公開されません。
7. 複数のエントリポイント
プロフィールページとフィードページがあるとしましょう。プロフィールを閲覧したいだけのユーザーにはフィードのコードはダウンロードさせたくありません。そこで、複数のバンドルを用意してみましょう。各ページごとに1つのメインモジュール(エントリポイントと呼ばれます)を作成します。
// webpack.config.js
module.exports = {
entry: {
Profile: './profile.js',
Feed: './feed.js'
},
output: {
path: 'build',
filename: '[name].js' // 上記entryのキーに対応したテンプレート
};
プロフィール用に <script src="build/Profile.js"></script>
をページに挿入します。フィードも同様です。
8. 共通コードの最適化
フィードとプロフィールは多くの共通点(Reactや共通スタイルシートやコンポーネントなど)があります。webpackは共通点を把握し、ページ間でキャッシュされる共有バンドルを作成します。
// webpack.config.js
var webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
entry: {
Profile: './profile.js',
Feed: './feed.js'
},
output: {
path: 'build',
filename: '[name].js' // 上記entryのキーに対応したテンプレート
},
plugins: [commonsPlugin]
};
<script src="build/common.js"></script>
を前のステップで追加したスクリプトタグの前に加え、フリーキャッシュを体感してください。
9. 非同期ローディング
CommonJSは同期的ですがwebpackは非同期的に依存関係を解決する方法も提供します。これはページ毎に必要なクライアントサイドのルーターに役立ちますが、実際に必要とするまでその機能をダウンロードする必要はありません。
非同期ロードしたい分割ポイントを指定してください。下記のような具合で。
if (window.location.pathname === '/feed') {
showLoadingState();
require.ensure([], function() { // この構文は奇妙ですが有効です。
hideLoadingState();
require('./feed').show(); // この関数が呼ばれると、同期的に利用可能であることが保証されます。
});
} else if (window.location.pathname === '/profile') {
showLoadingState();
require.ensure([], function() {
hideLoadingState();
require('./profile').show();
});
}
webpackは残りの作業を行い、追加のチャンクファイルを生成してロードします。
webpackはこれらのファイルがルートディレクトリにあると想定し、例えばhtmlのscriptタグにロードします。その設定には output.publicPath
が使えます。
// webpack.config.js
output: {
path: '/home/proj/public/assets', // webpackが生成したものを配置するパス
publicPath: '/assets/' // ファイルをrequireした時のパス
}
その他のリソース
webpackを活用した実際の成功事例を http://youtu.be/VkTCL6Nqm6Y で見てみましょう。
これはOSConでPete Huntがインスタグラムでのwebpack活用について語ったものです。
よくある質問
webpackはモジュラーに見えません
webpackは極めて高度にモジュール化されています。browserifyやrequirejsのような他の選択肢と比べた場合に、webpackの素晴らしい点は、プラグイン自身がビルドプロセスの中でより多くの場所に自分自身を挿入できることが挙げられます。コア部分にビルドされる多くものが標準でロードされたプラグインのように見え、また上書きも可能です。(例 CommonJSのreqruire()パーサーなど)