この記事はWebpack — The Confusing Partsを、筆者の許諾を得て意訳しています。
何か誤りがありましたら、ご指摘いただけると幸いです。
(以下、訳)
ReactとReduxで作られたアプリケーションにとって、Webpackは最先端を行くモジュールバンドラです。Angluar2やその他のフレームワークを使っている人々は、たいへんWebpackのお世話になっていることでしょう。
私が初めてWebpackの設定ファイルを見た時、それはさながら宇宙人のようで非常にわかりづらく見えました。しばらく試しているうちに、今では次のように考えるようになりました。Webpackは単に独特のシンタックスと新しい哲学を持っており、それがとっつきにくさの原因になっているのだと。偶発的とはいえ、これらの哲学は、Webpackの人気を押し上げた原因の1つでもあります。
Webpackのとっつきにくさを受けて、私はいくつかの記事を書こうと思いました。これらの記事を読んで、人々がカンタンにWebpackに入門して強力な機能を使えるようになれれば幸いです。さて、最初の記事を書いていきます。
Webpackのコア哲学
Webpackの2つのコア哲学とは
-
全てはモジュールである。― JSファイルをモジュール化できるように、その他のCSSや画像やHTMLだってモジュールになりうるのです。つまり、
require("myJSfile.js")
やrequire("myCSSfile.css")
といった記述ができます。この意味するところは、どんなファイルだって小さくて管理しやすい部品に分割したり、再利用したりことができるということです。 - ”ほしいもの”を”ほしいとき”に。―従来のモジュールバンドラは、全てのモジュールを、"bundle.js"のような1つの巨大なファイルとして出力します。しかし現実世界のアプリケーションでは、この"bundle.js"の容量は10MB〜15MBになってしまい、ロードに永久の時間がかかってしまいます。Webpackはコード分割を行って、複数の"bundle"ファイルを生成するようなたくさんの機能があります。また、_欲しい時に欲しいものだけを_ロードするためにアプリケーションのパーツを非同期的にロードすることもできます。
それでは、たくさんの”わかりづらい”側面について見ていきましょう。
1. 開発環境 vs 本番環境
最初に知るべきことは、Webpackには__膨大な数の機能__があるということです。そのうちのいくつかは”開発環境専用”で、いくつかは”本番環境専用”で、いくつかは”開発環境と本番環境”の両方で使用可能です。
適宜、画像を拡大して読んでみてください。
通常のプロジェクトにおいては、とてもたくさんの機能を使うので、普通は2つの大きなWebpack設定ファイルを使うことになります。
バンドルファイルを生成するには__package.json__に以下のように記述します。
“scripts”: {
//npm run build to build production bundles
“build”: “webpack --config webpack.config.prod.js”,
//npm run dev to generate development bundles and run dev. server
“dev”: “webpack-dev-server”
}
2. Webpack CLI VS Webpack-dev-server
Webpackをはじめとするモジュールバンドラは、2つのインターフェイスを提供することをぜひ知っておいてください。
- Webpack CLIツール ― デフォルトのインターフェイス(Webpack自身の一部としてインストールされます)
- webpack-dev-serverツール ― Node.js製のサーバ(別途インストールする必要があります)
Webpack CLI(本番ビルド向き)
このツールはCLI経由、あるいはWebpack設定ファイル(設定ファイルデフォルト名:webpack.config.js)経由でオプションを渡します。それらはバンドル時にWebpackに渡されます。
Webpackを学習する初期段階においては、CLIから始めるのもよいでしょう。しかし、そのうち本番ビルドのためだけに使うようになるでしょう。
用法
OPTION 1:
// グローバル環境にインストール
npm install webpack --g
// ターミナル上で使用
$ webpack //<--Generates bundle using webpack.config.js
OPTION 2 :
// ローカル環境にインストールして、package.jsonに追記。
npm install webpack --save
// package.jsonのscriptsに追記。
“scripts”: {
“build”: “webpack --config webpack.config.prod.js -p”,
...
}
// 以下のように起動。
"npm run build"
Webpack-dev-server(開発ビルド向き)
webpack-dev-serverはポート__8080__番を使うExpressのNode.jsサーバです。このサーバは内部的にWebpackを実行します。webpack-dev-serverを用いる利点は、できることの幅が広がります点です。例:”ライブリロード”, ”Hot Module Replacement”(HMR)
用法:
OPTION 1:
// グローバル環境にインストール
npm install webpack-dev-server --save
// ターミナル上で使用
$ webpack-dev-server --inline --hot
OPTION 2:
// package.jsonのscriptsに追記。
“scripts”: {
“start”: “webpack-dev-server --inline --hot”,
...
}
// 起動。
$ npm start
ブラウザで以下を開く:
http://localhost:8080
Webpack vs webpack-dev-serverオプション
"inline"や"hot"のようないくつかのオプションはwebpack-dev-server専用のオプションであることに注意しましょう。一方、"hide-modules"のようなオプションはCLI専用です。
webpack-dev-server CLI オプション vs 設定ファイルオプション
特筆すべきことは、webpack-dev-serverへのオプションの渡し方には以下の2通りがあるということです。
- webpack.config.jsの"devServer"オブジェクトを通じて。
- CLIのオプションとして。
// CLI経由
webpack-dev-server --hot --inline
// webpack.config.js経由
devServer: {
inline: true,
hot:true
}
私は、webpack.config.js経由だと時々うまくいかない事に気づきました。なので、package.jsonの中でCLIのオプションとして渡すようにしています。
//package.json
{
scripts:
{“start”: “webpack-dev-server --hot --inline”}
}
注:hot: trueと-hotの両方を渡さないようにしてください。
"hot" vs "inline" webpack-dev-serverオプション
"inline"オプションは、"ライブリロード"をページ全体に適用します。"hot"オプションは、"Hot Module Reloading"を可能にします。その結果、変更のあったコンポーネントのみをリロードします(ページ全体ではなく)。両方のオプションを渡した場合には、ソースコードに変更があったときにwebpack-dev-serverがまずHMRを試みます。それがうまくいかない場合に、ページ全体をリロードします。
// ソースコードに変更があったとき、以下の3つの例はすべてあたらしくバンドルファイルを生成します。ただし…
// 1. ブラウザをリロードしません。
$ webpack-dev-server
// 2. ページ全体をリロードします。
$ webpack-dev-server --inline
//3. モジュールだけをリロードします。ただし、失敗時にはページ全体をリロードします。
$ webpack-dev-server --inline --hot
3. "entry" ― 文字列 vs 配列 vs オブジェクト
__Entry__は起点となるモジュールをWebpackに教えます。Entryには、文字列か配列かオブジェクトを指定できます。これはちょっとわかりづらく思えるかもれませんが、それぞれ目的が異なるのです。
もし、単一のエントリーポイントだけならば、どんなデータ型でも指定可能で、生成結果は同じです。
entry ― 配列
しかし、__相互に依存していない__複数ファイルを指定したいときは配列が使えます。
例:HTML内に"googleAnalytics.js"を読み込ませたいとします。このJSファイルをbundle.jsの最後部に追加するよう指定する場合、以下のとおりです。
entry ― オブジェクト
さて、ようやくSPAではなく__本当の__大規模アプリケーションの話をしましょう。複数のビューを持ち、複数のHTMLファイルを持つと想定します。(例:index.html, profile.html)オブジェクトを使うことで、複数のバンドルファイルをいっぺんに生成するようにWebpackに指定できます。
以下の設定ファイルは2つのJSファイルを生成します。indexEntry.jsとprofileEntry.jsの2つで、それぞれindex.htmlとprofile.html内で使用できます。
用法:
// profile.html
<script src=”dist/profileEntry.js”></script>
// index.html
<script src=”dist/indexEntry.js”></script>
注:ファイル名は"entry"オブジェクトのキー名に由来します。
entry ― 組み合わせ
また、entryオブジェクト内で配列を使うこともできます。例えば、以下の設定ファイルは3つのファイルを生成します。vendor.jsとindex.jsとprifile.jsの3つです。そのうちのvendor.jsは3つのベンダファイルを含みます。
4. output ― "path" vs "publicPath"
__output__は生成したファイルをどこに格納するかをWebpackに伝えます。outputは"path"と"publicPath"の2つのプロパティを持ちます。ややこしいですね。
"path"は単にWebpackに生成したファイルの格納場所を使えます。一方で"publicPath"はWebpackのプラグインが利用するもので、本番ビルド時にCSSやHTMLファイル内のURLを更新します。
たとえば、CSSファイル内で'./test.png'というurlを書いたとしましょう。これはローカルホストでは読み込みに成功するかもしれません。しかし、本番環境においては'test.png'は実際にはCDN上にあるかもしれません。つまり本番環境でつかうCDNへと、URLを手動で更新する必要があるのです。
その代わり、Webpackの__publicPath__を使うことができます。その際、publicPathを認識することのできるプラグインも使いましょう。これらは本番ビルド時に、URLを自動で更新します。
// 開発環境: サーバも画像もローカルホストにあります。
.image {
background-image: url(‘./test.png’);
}
// 本番環境: サーバはHerokuにあり、画像はCDNにあるとします。
.image {
background-image: url(‘https://someCDN/test.png’);
}
5. ローダーとローダーチェーン
ローダーとは、色々な種類のファイルの'load'や'import'を手助けしてくれる、追加のnodeモジュールです。ロードの結果、ブラウザが読めるJSやスタイルシートのようなフォーマットになります。一部のローダーを使えば、そのようなファイルをJS内に取り込む事ができます。その際、"require"を使うこともできますし、ES6の"import"を使うこともできます。
例:ES6で書かれたJSをブラウザが読めるES5に変換するために、__babel-loader__を使うことができます。
module: {
loaders: [{
test: /\.js$/, ←マッチする場合のみロードを適用。
exclude: /node_modules/, ←node_modulesディレクトリをロード対象から除外。
loader: ‘babel’ ← babelを使うことを明示。 (‘babel-loader’の略記)
}]
ローダーチェーン
複数のローダーを数珠つなぎにして、おなじ形式のファイルに適用することもできます。数珠つなぎは__"!"で区切られ、右から左へ__作用します。
例えば、"myCssFile.css"というCSSファイルがあるとしましょう。このファイルの中身を、__<style>CSSの中身</style>__という形でHTMLの中に含めたい。そんなときcss-loaderとstyle-loaderの2つのローダーを使えば実現できます。
module: {
loaders: [{
test: /\.css$/,
loader: ‘style!css’ <--(style-loader!css-loaderの略記)
}]
これがどのように作用するかは以下のとおりです。
- Webpackはモジュール内のCSSファイルの依存性を調査します。つまり、JSファイル内に"require(myCssFile.css)"があるかどうかチェックするのです。もし依存性を発見した場合、Webpackはそのファイルに__まずcss-loaderを__宛てがいます。
- __css-loader__は全てのCSSとそのCSSが持つ依存性をJSONにロードします。(CSSの依存性はたとえば @import otherCSSのようなもの)JSONはstyle-loaderに渡されます。
- style-loader__はJSONファイルを受け取って、<style>CSSの中身</style>__のようなstyleタグに追記します。そして、index.htmlファイル内にそのタグを挿入します。
6. ローダー自身も設定可能
ローダー自身も、パラメータによって違う挙動をするように設定可能です。
以下の例では、url-loaderに対して、1024バイト以下の画像のURLのみを使うするように指定しています。これは以下の2通りの方法で"limit"というパラメータを指定することで実現できます。
7. .babelrcファイル
ES6をどのようにES5に変換するかを知るために、babel-loaderは"presets"を用います。また、ReactJSXをJSにパースする方法も指定できます。これらは"query"というパラメータを通じて、設定可能です。
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}
]
}
しかし、多くのプロジェクトにおいては、babelの設定は肥大化しがちです。そのため、__.babelrc__と呼ばれるbabel-loaderの設定ファイルに記述し分けておくことも可能です。babel-loaderは自動的に.babelrcをロードしてくれます。
以下がよく目にする例です。
//webpack.config.js
module: {
loaders: [
{
test: /\.jsx?$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel'
}
]
}
//.bablerc
{
“presets”: [“react”, “es2015”]
}
8. プラグイン
バンドル生成時に作用する追加のnodeモジュールがプラグインです。
例えば、__UglifyJsPlugin__はbundle.jsを取得して、ファイルサイズ削減のために圧縮を行います。
__extract-text-webpack-plugin__も似たようなもので、内部的にはcss-loaderとstyle-loaderを用いています。このプラグインは全てのCSSを一箇所に集めて、最終的にはstyles.cssというファイルに抜き取り、index.htmlにstyle.cssへのリンクを追記します。
// webpack.config.js
// 全ての.cssファイルを取得して、内容を結合して、単一のstyles.cssに抜き出す。
var ETP = require("extract-text-webpack-plugin");
module: {
loaders: [
{test: /\.css$/, loader:ETP.extract("style-loader","css-loader") }
]
},
plugins: [
new ExtractTextPlugin("styles.css") // styles.cssに抜き出す。
]
}
注:HTMLのスタイル属性として、インラインでCSSを付与したいならば、extract-text-webpack-pluginなしでも可能です。ローダーを以下のように指定すればいいのです。
module: {
loaders: [{
test: /\.css$/,
loader: ‘style!css’ <--(style-loader!css-loaderの略記)
}]
9. ローダー vs プラグイン
お気づきかもしれませんが、__ローダーは個別のファイル単位に作用します。__あるいは、バンドルの生成__前__に作用します。
一方で、__プラグインはバンドルファイルに作用し、バンドル生成過程の最終段階で働きます。__そして、いくつかのcommonsChunksPluginsのようなプラグインは作用範囲が広く、どのようにバンドルが生成されるかを規定します。
10. ファイルの拡張子を解決する
多くのWebpackの設定ファイルはresolve extensionsプロパティを持っており、以下の例のように__空文字__が含まれています。空文字は、拡張子なしのimportをサポートします。たとえば__require("./myJSFile")や、import myJSFile from './myJSFile'__のような拡張子なしの記述です。
{
resolve: {
extensions: [‘’, ‘.js’, ‘.jsx’]
}
}
筆者の他のポスト(original)
Webpack
React and Redux
- Step by Step Guide To Building React Redux Apps
- A Guide For Building A React Redux CRUD App (3-page app)
- Using Middlewares In React Redux Apps
- Adding A Robust Form Validation To React Redux Apps
- Securing React Redux Apps With JWT Tokens
- Handling Transactional Emails In React Redux Apps
- The Anatomy Of A React Redux App