YarnやWebpackでアセットがうまく管理されたWebページを作ってみる その1

フロントエンドのアセット管理事情を知るために、YarnとWebpackでシンプルなWebページを作ってみる。最終的に俺的ベストプラクティスを作るのが目標。

作ったものは以下のリポジトリにまとめていく。

> ikuwow/yarn-and-webpack
> https://github.com/ikuwow/yarn-and-webpack

バージョン

* yarn 0.23.2
* Webpack 2.4.1

今回作るのはただのWebページ。
アセットの管理がテーマなので、React, Angular, jQuery, Vue.jsなどは使わないただのJavaScript。
当然SPAなんていう高度なものではない。ご了承ください。

## Yarnとは

> Yarn
> https://yarnpkg.com/en/

npmの上位互換を謳う、Facebook, Exponent, Google, Tildeなどが作ったjsのパッケージマネージャ。`yarn.lock`ファイルが作られて厳密にバージョンを固定できたり、ダウンロードなどが並列で行われたりキャッシュが賢かったりして早いのが特徴。

> Yarn: A new package manager for JavaScript | Engineering Blog | Facebook Code
> https://code.facebook.com/posts/1840075619545360/yarn-a-new-package-manager-for-javascript/

確かに機能を見てると上位互換な感じ。package.jsonの記法も互換性があるので、いま`npm install`を叩いているプロジェクトにもほぼ何も変えずに導入できる。

今のところnpmと比較した際の欠点がわからない。バージョン1に到達していない点が懸念点か。

下に書いたように実際に早かったので、今回はこれでJavaScript特有のライブラリ地獄を乗り切る。

## yarnを使えるようにする

Macなら`brew install yarn`、Linux(debian系)なら`apt-get install yarn`と、一般的な方法で導入できる。

> Installation | Yarn
> https://yarnpkg.com/lang/en/docs/install/#alternatives-tab

`npm install yarn`もできるみたいだけど、やめましょう・・・。

```
$ yarn
yarn install v0.23.2
info No lockfile found.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Saved lockfile.
✨  Done in 0.44s.
```

Emojiが積極的に使われてて黒い画面がとっても楽しげになる。

## リポジトリのinit

今回使うGitHubでinitした空のリポジトリ。

```
$ ls
LICENSE README.md
```

`yarn init`でインタラクティブに初期化する。

```
$ yarn init
yarn init v0.23.2
question name (yarn-and-webpack):
question version (1.0.0): 0.1.0
question description: my test repository
question entry point (index.js):
question repository url (git@github.com:ikuwow/yarn-and-webpack.git):
question author (Ikuo Degawa <degawa@ikuwow.com>):
question license (MIT):
success Saved package.json
✨  Done in 25.44s.
IkuosMacmini:yarn-and-webpack degawaikuo$ ls
LICENSE      README.md    node_modules package.json yarn.lock
```

node_modules、package.json、yarn.lockができた。
生成されたpackage.jsonはnpmのときと全く同じようにみえる。
yarn.lockはなんと呼ぶ記法なのかはよくわからないが、編集するものじゃないし気にしないでおく。
コミットする。

```
echo node_modules > .gitignore
git add .
git commit -m 'Initialize yarn'
```

## webpackを入れてみる

```
$ yarn add webpack
yarn add v0.23.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...

（略...）

success Saved lockfile.
success Saved 280 new dependencies.
✨  Done in 21.03s.
```

`install`じゃなくて`add`。`npm`とは違い明確に別れている。

ここでnpmと比較するインストール時間を比較してみる。

```
$ rm -rf node_modules
$ npm cache clean
$ time npm install
（...略...）
real	0m29.508s
user	0m13.474s
sys	0m4.642s
```

```
$ rm -rf node_modules
$ yarn cache clean
$ time yarn
yarn install v0.23.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
✨  Done in 9.82s.

real	0m10.065s
user	0m8.322s
sys	0m5.152s
```

早かった。
ちなみに`yarn`は`yarn install`（npmでいう`npm install`）と同じ。

```
$ git add package.json yarn.lock
$ git commit -m 'install webpack'
```

## Webpackを使ってみよう

> Getting Started
> https://webpack.js.org/guides/get-started/

ここにある例をそのまま使う。
ひとまずwebpack以前のサンプルwebページを作る。

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Webpack 2 demo</title>
    <script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
</head>
<body>
    <h1>webpack 2 demo</h1>
    <script src="app/index.js"></script>
</body>
</html>
```

```app/index.js
/* jshint browser: true */
'use strict';

function component() {
    var element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
    return element;
}

document.body.appendChild(component());
```

適当にwebサーバーを作ってページを見れるようにしておく。

```
php -S localhost:8383
```

この状態だとグローバルにlodashを置いて`app/index.js`が利用する形無で以下の問題がある。

* 依存してるものが読み込まれなかったり読み込み順番が変わると全く動かなくなる可能性がある
* 依存しているものが読み込まれているのに使われていないと、不要なコードをブラウザにダウンロードさせることになる

というわけでWebpackを使ってこれを解消する。

```
yarn add lodash
```

yarnだと`npm install --save lodash`みたいに`--save`のようなオプションがいらないのがよいよい。

```diff
 /* jshint browser: true */
  'use strict';
  
 +import _ from 'lodash';
 +// const _ = require('lodash');
 +
  function component() {
      var element = document.createElement('div');
      element.innerHTML = _.join(['Hello', 'Webpack'], ' ');
```

```diff
 <head>
      <meta charset="UTF-8">
      <title>Webpack 2 demo</title>
 -    <script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
  </head>
  <body>
      <h1>webpack 2 demo</h1>
 -    <script src="app/index.js"></script>
 +    <script src="dist/bundle.js"></script>
  </body>
  </html>
```

`import`はes2015の機能で、ブラウザにはまだないけどWebpackやBabelなどのトランスコンパイラには実装されている。
Nodeの書き方で`const _ = require('lodash');`でも動く。

```
mkdir dist/
$(yarn bin)/webpack app/index.js dist/bundle.js
```

これで同じものが動く。
ついでに、lodashのダウンロードを待たなくてもhtmlが描画されるのでページが見えるまでの時間が短い。

コミットする。

```
echo dist >> .gitignore
git add .gitignore app/index.js index.html package.json yarn.lock
git commit -m 'First webpack use'
```

## Configファイルに書く

毎回`webpack`にオプションをたくさんつけるのは面倒なので、ファイルにまとめる。

プロジェクトのルートディレクトリで`webpack.config.js`または`webpackfile.js`というファイルを作る。

```webpack.config.js
'use strict';

const path = require('path');

module.exports = {
    entry: './app/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    }
};
```

第一引数を第二引数をファイルに移しただけ。
これで以下のように叩くと全く同じ動きをする。

```
$(yarn bin)/webpack
```

## その1のまとめ

yarnはnpmとコマンドがやや違うだけで基本的にnpmと同じように使えるので、導入ハードルはほぼ感じなかった。しかも速い。
Webpackはいままでちょくちょく使っていたが分かっていない部分も多いのでその2以降にしっかりやる。

次回はファイルやディレクトリを増やしたり、sass, html,画像などもwebpackで管理できるようにする予定。

その2: comming soon...