step by stepで始めるwebpack

※webpack1系の記事です。
1→2への移行ガイドを見る限り破壊的な変更点はそんなにありませんが、`preLoaders`や`postLoaders`がなくなっていたり、差が出ていますのでご注意ください。
https://webpack.js.org/guides/migrating/

---

[webpack](https://webpack.github.io/)を手を動かしつつ学べる初学者向け資料を作成しました。
[公式のチュートリアル](http://webpack.github.io/docs/tutorials/getting-started/)もあるのですが、webpackが使用されているOSSのボイラープレートを見る限り、世の中の使われ方に沿ってないかも？と思い書きました。
これから始める人の手助けになれば幸いです。

また[最終形のソース](https://github.com/howdy39/study-webpack)をupしてあるので詰まったら見てください。


## [webpack](https://webpack.github.io/)とは
[webpack で始めるイマドキのフロントエンド開発](http://qiita.com/yosisa/items/61cfd3ede598e194813b)の説明が良かったので引用させていただきます。m(_ _)m

>webpack は WebApp に必要なリソースの依存関係を解決し、アセット（配布物）を生成するビルドツール（要するにコンパイラ）です。JavaScript だけでなく、CoffeeScript や TypeScript、CSS 系、画像ファイルなどを扱うことができます。
WebApp のビルドツールは Grunt や Gulp が有名です。これらは基本的に、ビルド手順をタスクという形で自ら定義する必要があり、フロントエンド開発に馴染みのない開発者にとっては敷居が高いものでした（少なくとも、自分はそうでした）。
webpack を使えば、Grunt も Gulp も必要ありません！覚えるべきことはほとんどありません。（必要なら）簡単な設定ファイルを書いて webpack コマンドを実行するだけです。

ただWebpackはビルドツールなのでビルド以外のタスクが出てきたらGruntやGulpに任せた方が無難だと思います。

また最近発表されたReact.jsアプリの雛形生成ツールでも使われていますね。
[Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する](http://qiita.com/chibicode/items/8533dd72f1ebaeb4b614)


![image](https://qiita-image-store.s3.amazonaws.com/0/35595/1c0bb48e-9d37-ad95-1658-a3bc6e2bed25.png)


## 前提
Nodejs,npmが導入済みであること

## 流れ

|Step  |目的|
|------|---|
|Step 1|準備|
|Step 2|簡単なjsでwebpackを実行してみる|
|Step 3|設定ファイル（webpack.config.js）を導入|
|Step 4|Loaderを導入|
|Step 5|Loaderの様々な指定方法|
|Step 6|拡張子の省略（resolve.extensions）|
|Step 7|Pluginを導入|
|Step 8|ソースマップの出力（devtool）|


## Step 1 準備

```console:package.jsonの作成
npm init
```

webpackのインストールも行っておきます。
本記事ではグローバルではなくプロジェクト内にインストールする形にします。

```console:webpackのインストール
npm install --save-dev webpack
```



## Step 2 簡単なjsでwebpackを実行してみる
このStepでは簡単なjsファイルを作ってwebpackを実行します。
webpackがどのような処理をしているのかを理解しましょう。


### cats.jsの作成
猫の一覧を配列で返すJavaScriptです。

```cats.js
var cats = ['tama', 'kuro', 'tora']
module.exports = cats;
```


### app.jsの作成
エントリーポイントとなるメインのJavaScriptです。
猫の一覧を読み込んでログ出力します。

```app.js
var cats = require('./cats.js');
console.log(cats);
```


### webpackの実行
早速実行してみます。
以下のようにして実行が可能です。

```console:webpackの実行
node node_modules/.bin/webpack app.js bundle.js

Hash: 2781572ec422f461e66a
Version: webpack 1.13.1
Time: 64ms
    Asset     Size  Chunks             Chunk Names
bundle.js  1.58 kB       0  [emitted]  main
   [0] ./app.js 51 bytes {0} [built]
   [1] ./cats.js 58 bytes {0} [built]
```

app.jsがwebpackに読み込ませるファイルでbundle.jsが出力するファイル名です。
このapp.jsをエントリーポイントと呼びます。


### 実行方法の最適化
上で書いたような実行も可能ですが、毎回こんな長いパス打つのは大変です。
package.jsonに書くことで``node_modules/.bin/``にでパスが自動で通るためコマンドをシンプルにできます。

```diff:package.jsonを修正
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
+   "build": "webpack app.js bundle.js"
  },
```

```console:npmコマンド経由でwebpackを実行
npm run build
```


### bundle.jsを実行
作るだけ作って実行はしていませんでした。
作成されたbundle.jsを実行してみましょう。

```console:作成したbundle.jsを実行
node bundle.js

[ 'tama', 'kuro', 'tora' ]
```

cats.jsの中身が表示されていますね。



## Step 3 設定ファイル（webpack.config.js）を導入
このStepでは`webpack.config.js`という設定ファイルでwebpackの実行時の設定を行うように変更します。
上記の名前でファイルを作成しておくと`webpack`と打つだけで`webpack.config.js`が自動で読み込まれます。


### webpack.config.jsへの移行
webpack.config.jsファイルを作成して、以下のようにエントリーポイントと出力ファイル名を記述します。
※エントリーポイントの頭に`./`がつくのに注意してください。

### webpack.config.jsの作成

```javascript:webpack.config.js
module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
  }
};
```

### package.jsonを修正
package.json内のコマンドをwebpackだけにします。

```diff:package.jsonを修正
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
-   "build": "webpack app.js bundle.js"
+   "build": "webpack"
  },
```

### 確認
ビルド&実行して動作を確認してください。

```console:npmコマンド経由でwebpackを実行
npm run build
```

```console:実行
node bundle.js
```


### ソースディレクトリ・出力ディレクトリを作成
プロジェクト直下のファイルが多くなってきました。
今後辛くなってくるのでソースディレクトリと出力ディレクトリを分けます。

### ディレクトリの作成とファイルの移動

```console:ディレクトリの作成とファイルの移動
mkdir src
mv app.js src
mv cats.js src
```

```console:bundle.jsの削除
rm bundle.js
```

### webpack.config.jsを修正

```diff:webpack.config.js
module.exports = {
- entry: './app.js',
+ entry: './src/app.js',
  output: {
+   path: 'dist',
    filename: 'bundle.js'
  }
};
```

### 確認
ビルド&実行して動作を確認してください。

```console:npmコマンド経由でwebpackを実行
npm run build
```

```console:実行
node dist/bundle.js
```




## Step 4 Loaderを導入
このStepではwebpackの肝であるLoaderの概要と基本的な使い方を習得しましょう。

### Loaderとは？
LoaderはTypeScriptやBabel、PNGやJPEG、SassやLESS、JSONやYAMLなど様々なファイルを読み込むことが可能になる拡張機能のことです。


### JSONを読み込んでみよう
cats.jsは配列を返すJavaScriptでしたね。
これをJSONで読み込むようにしてみましょう。


### cats.jsonの作成
※わかりやすくするため`cats.json`という猫がいることにします。。

```json:cats.json
["cats.json", "tama", "kuro", "tora"]
```

### [json-loader](https://github.com/webpack/json-loader)のインストール
JSONを読み込むにはjson-loaderというLoaderを使います。

```console:json-loaderのインストール
npm install --save-dev json-loader
```

### json-loaderをロードするように設定
cats.jsからcats.jsonに変更し、requireの頭に`json-loader!`を追加します。

```diff:app.js
- var cats = require('./cats.js');
+ var cats = require('json-loader!./cats.json');
  console.log(cats);
```

また、以下のように **`-loader`は省略可能**です
以後は省略記法で記載していきます。

```diff:app.js
- var cats = require('json-loader!./cats.json');
+ var cats = require('json!./cats.json');
  console.log(cats);
```

### 確認
出力にcats.jsonが含まれていることを確認してください。

```console:npmコマンド経由でwebpackを実行
npm run build
```

```console:実行
node dist/bundle.js

[ 'cats.json', 'tama', 'kuro', 'tora' ]
```

### json-loaderは何をしている？

実際の[json-loaderのソース](https://github.com/webpack/json-loader/blob/master/index.js)を見てみましょう。

```javascript:json-loader
module.exports = function(source) {
	this.cacheable && this.cacheable();
	var value = typeof source === "string" ? JSON.parse(source) : source;
	this.value = [value];
	return "module.exports = " + JSON.stringify(value, undefined, "\t") + ";";
}
```

上の方は無視して最後のreturn行を見てください。
`"module.exports = "`に`JSONを文字列化したもの`つなげてreturnしていますね。
cats.jsと同じようなJavaScriptの文を生成しているのがわかるかと思います。
JSONで書かれたの文字列をJavaScriptで書かれた文字列に変換しているわけですね。



### 複数のLoaderを扱う
Loader同士は繋げることが可能です。
例としてYAMLファイルを扱えるyaml-loaderを扱います。

### cats.ymlの作成
※わかりやすくするため`cats.yml`という猫がいることにします。

```json:cats.yml
[cats.yml, tama, kuro, tora]
```

### [yaml-loader](https://github.com/okonet/yaml-loader)のインストール

```console:yaml-loaderのインストール
npm install --save-dev yaml-loader
```

### app.jsの修正
cats.jsonからcats.ymlに変更します。
`json!`の後に`yaml!`を追加します。

※Loaderは右から左の順に実行されます。
※yaml-loaderはJSONを出力します。
 そのため以下の様にLoaderを繋ぐ必要があります。
 YAML ⇢ yaml-loader ⇢ JSON ⇢ json-loader ⇢ JavaScript


```diff:app.js
- var cats = require('json!./cats.json');
+ var cats = require('json!yaml!./cats.yml');
  console.log(cats);
```

### 確認
出力にcats.ymlが含まれていることを確認してください。

```console:npmコマンド経由でwebpackを実行
npm run build
```

```console:実行
node dist/bundle.js

[ 'cats.yml', 'tama', 'kuro', 'tora' ]
```


### つまりLoaderとは
通常、require()はJavaScriptを読み込むためのものですが、Loaderを通すことでJavaScript以外のリソースを読み込めるようにするためのものです。
最終的にJavaScriptの形に変換します。





## Step 5 Loaderの様々な指定方法
このStepではLoaderの指定方法の違いを見ていきます。


### requireで指定
これは今までに出てきたrequire('hoge-loader!〜)の形です。


### module.Loadersで指定
ファイルを読み込む処理が発生するたびにrequireにLoaderを書いていくのは大変です。
そこでデフォルトのLoaderを定義しておくことで記述を省略できます。

設定プロパティは以下の2つが主です。

* testプロパティに対象となるファイルを検索する正規表現を指定
* loaderプロパティに通したいLoaderを指定

.ymlに対してはyaml-laoder->json-loaderで通すのでしたね。
それを行うための設定が以下のようになります。

```diff:webpack.config.js
module.exports = {
  entry: './app.js',
  output: {
    filename: 'bundle.js'
- }
+ },
+ module: {
+   loaders: [
+     {test: /\.yml$/, loader: 'json!yaml'}
+   ]
+ }
};
```

拡張子で設定することが多いようです。
testに複数マッチするように設定してしまうとと、マッチした全てのloaderを通すことになるので注意です。

上記設定をおこなったので以下の様にrequire部分でloaderを省略が可能です。

```diff:app.js
- var cats = require('json!yaml!./cats.yml');
+ var cats = require('./cats.yml');
  console.log(cats);
```


### module.preLoadersで指定
これは最初に通すLoaderです。


### module.postLoadersで指定
これは最後に通すLoaderです。


### 順番と使い分け
順番としては以下の様になります。

1. module.preLoaders（設定ファイル) 
1. module.Loaders（設定ファイル) 
1. require内のLoader（ソースコード) 
1. module.postLoaders（設定ファイル) 

基本はmodule.Loadersに設定するといいと思います。


### loadersの指定方法について補足

### 複数loaderの指定
`!`で繋げて文字列で指定する書き方の他に配列で指定することも可能です。

```diff
loaders: [
  {
    test: /\.yml$/,
-    loader: 'json!yaml'
+    loaders: ['json', 'yaml']
  }
]
```

### queryの指定
loaderの種類によってオプションとしてパラメータを受け取れるものがあります。
その場合、文字列ではなくオブジェクトで指定が可能です。
※複数loaderを繋げる場合は文字列で書くしかないかも？

```diff
loaders: [
  {
    test: /\.hoge$/,
-    loader: 'hoge?key1=value1'
+    loader: 'hoge'
+    query: { key1: 'value1' }
  }
]

```

## Step 6 拡張子の省略（resolve.extensions）
通常requireに指定するファイル名には拡張子をつけますがその省略が可能です。
その設定を行うのがresolve.extensionsです。


### 拡張子の省略
resolve.extensionsのデフォルト値は
``["", ".webpack.js", ".web.js", ".js"]``
のようになっており``.js``が最初から含まれています。
例えばjsを読み込む場合は、以下のように.jsが省略可能です。

```diff:.jsを省略した例
- var cats = require('./cats.js');
+ var cats = require('./cats');
  console.log(cats);
```

### 順番に注意
extensionsは配列になっており順番が重要です。
見つかるまで前から順に探します。

つまり`require('./cats')`と指定した場合は以下の流れです。

1. ``cats``のファイルを探しにいく -> 見つからない
2. ``cats.webpack.js``のファイルを探しにいく -> 見つからない
3. ``cats.web.js``のファイルを探しにいく -> 見つからない
4. ``cats.js``のファイルを探しにいく -> 見つかった！ -> require('./cats.js')として扱う。


### 上書きされるので注意
resolve.extensionsの設定は追加ではなく上書きです。
そのためextensionsの最初の空文字``""``を消して``[".js"]``だけ指定してしまうと拡張子付きのrequire('./cats.js')という書き方ができなくなってしまいます。


### 省略しつつYAMLを読み込む
デフォルトでは`.yml`は入っていませんでしたね。
`.yml`を拡張子なしで読み込んでみましょう。

以下の様にresolve.extenisonを上書きする設定を追加します。

```diff:webpack.config.js
module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
+ resolve: {
+   extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
+ },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
  }
};
```

```diff:app.jsの修正（cats.ymlを拡張子を省略して読み込む）
- var cats = require('./cats.yml');
+ var cats = require('./cats');
  console.log(cats);
```

### cats.jsをリネームしてcats.ymlが読み込まれることを確認
cats.jsを読み込ませないためにcats.jsを退避しておき、cats.ymlを読み込まれることを確認しましょう。

```console:cats.jsをリネーム
mv src/cats.js src/cats.js_bk
```

```console:npmコマンド経由でwebpackを実行&確認
npm run build
node dist/bundle.js

[ 'cats.yml', 'tama', 'kuro', 'tora' ]
```



### 省略するメリット
省略したらどのファイルを読み込むのかがわかりにくくなる反面、疎結合にできます。

先程の例でいうと呼び元は猫の一覧が欲しいのです。
それがJavaScriptであろうとJSONであろうとYAMLであろうと呼び元のソースコードは変えずにファイルの有無でファイルを切り替えることが可能です。




## Step 7 Pluginを導入
webpackはPluginを用いることで機能を拡張する事が可能です。
このstepでは例として、トップページのhtmlを自動生成するPluginと圧縮するプラグインを導入します。

### [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin)のインストール
htmlを自動生成するためのプラグインです。

```console:html-webpack-pluginのインストール
npm install --save-dev html-webpack-plugin
```


### 設定ファイルを修正
圧縮も一緒に設定してしまいます。
圧縮は`webpack.optimize.UglifyJsPlugin`を使用します。
（圧縮はwebpackの標準プラグインなのでnpmインストールは不要です）

```diff:webpack.config.js
+ var webpack = require('webpack');
+ var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
  },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
- }
+ },
+ plugins: [
+   new webpack.optimize.UglifyJsPlugin(),
+   new HtmlWebpackPlugin({
+     title: 'Sample Page'
+   })
+ ]
}; 
```
※HtmlWebpackPluginにtitleだけ設定していますが、テンプレートHTMLから作ったり様々な機能があります。


### htmlが作られたことを確認
```console:npmコマンド経由でwebpackを実行
npm run build

Hash: 212358e547c6141ea787
Version: webpack 1.13.1
Time: 936ms
     Asset       Size  Chunks             Chunk Names
 bundle.js  304 bytes       0  [emitted]  main
index.html  182 bytes          [emitted]  
   [0] ./src/app.js 48 bytes {0} [built]
    + 1 hidden modules
Child html-webpack-plugin for "index.html":
        + 3 hidden modules
```

bundle.jsを読み込むhtmlが作られます。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Sample Page</title>
  </head>
  <body>
  <script type="text/javascript" src="bundle.js"></script></body>
</html>
```

### ブラウザでindex.htmlを開いて確認
ブラウザでindex.htmlを開いてみましょう。
consoleにちゃんと猫の一覧が表示されていますね。

![image](https://qiita-image-store.s3.amazonaws.com/0/35595/b88f51c2-572a-bbe7-a514-77e3abcc378a.png)


またsourcesタブを開いてbundle.jsを見てみると圧縮されていることが確認できます。
![image](https://qiita-image-store.s3.amazonaws.com/0/35595/829de639-6996-f113-0667-5db65c3f81bd.png)



## Step 8 ソースマップの出力（devtool）
webpackではソースマップの出力の仕組みが元々組み込まれているのでプラグイン等不要で出力可能です。


### ソースマップを設定
```diff:webpack.config.js
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/app.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  resolve: {
    extensions: ["", ".webpack.js", ".web.js", ".js", ".yml"]
  },
  module: {
    loaders: [
      {test: /\.yml$/, loader: 'json!yaml'}
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      title: 'Sample Page'
    })
- ]
+ ],
+ devtool: 'source-map'
}; 
```

```console:npmコマンド経由でwebpackを実行
npm run build
```

### ブラウザでindex.htmlを開いて確認

index.htmlを開いてsourcesタブを表示します。
`webpack://`の配下に圧縮前のファイルが表示されます。
圧縮前のファイルにもブレークポイントも貼れるのでデバッグも問題ありませんね。

![image](https://qiita-image-store.s3.amazonaws.com/0/35595/5f340bd9-5cc2-78f4-b233-8694f19da052.png)


### 補足：devtoolの設定値
本記事では'source-map'を指定しましたが7種類もあるようです。
https://webpack.github.io/docs/configuration.html#devtool
'source-map'は情報量が多い反面、実行に時間がかかるみたいですね。
'source-map'で遅いと感じたら違うのを検討していく形でしょうか。


## あとがき
本記事からさらに学びたい方は公式ドキュメントの[使い方の例](http://webpack.github.io/docs/examples.html)を見ていくと良いと思います。

webpackはLoaderやPluginの種類が豊富なのでそのへんの逆引きリファレンスみたいなやつの需要あるかもしれないなぁ。