1年前に作ったフロントエンド環境を色々新しくした

新規立ち上げのチームを異動して、1年前に作った会社のフロントエンド環境（Vue.js + webpack(+Gulp)）をベースに環境を作っていたのですが、
色々古かったので、新しくしました

今回作ったものはGitHubにあげてます
https://github.com/kurosame/vuejs-boilerplate

この中からやったことをいくつかピックアップして紹介したいと思います

---

## パッケージを最新にする

```
npm install -g npm-check-updates
npm-check-updates -u // package.jsonのパッケージを全て最新にして上書く
npm update // package.jsonに記載してあるバージョンに更新する
npm install
```

今回みたいにバージョンアップするパッケージが多い場合は、これでいいと思う
他のパッケージに依存しているパッケージがあったとしたら、一緒に上げないと意味ないかもしれないので

---

## Gulpをやめる
webpackを使っていれば、Gulpはいらないっていう話ではなく、
今のプロジェクトに関しては不要かなっていう判断をした

主に以下のGulpタスクが動いていた

・バンドルファイルの変更を検知して、`browser-sync`を使ってブラウザをリロードしてるタスク
・unit testのkarmaを動かすタスク
・コードの変更をwatchするタスク
・`webpack-stream`を使ってコードをバンドルするタスク
・dist内を全部削除するタスク
・assetsをdistにコピーするタスク

全て`npm-scripts + webpack + webpack-dev-server`で置き換えれるので、これを機に置き換えた
変更箇所を抜粋すると以下のような感じになる

```js:package.json
"scripts": {
  "start": "webpack-dev-server",
  "build": "webpack",
  "test": "NODE_ENV=test karma start karma.conf.js"
}
```

```js:webpack.config.js
const Copy = require('copy-webpack-plugin')
const Clean = require('clean-webpack-plugin')

devServer: {
  contentBase: 'dist',
  historyApiFallback: true,
  open: true,
  port: 8000,
  proxy: {
    '/api/*': 'http://mock.example.jp:8001'
  }
},
plugins: [
  // assetsをコピー
  new Copy([
    {
      from: 'assets',
      to: 'assets'
    }
  ]),

  // distを綺麗にする
  new Clean(['dist/**/*'], {
    root: `${__dirname}/..`,
    verbose: false
  })
]
```

---

## コード静的解析の強化
### 1年前
```
yarn add --dev eslint-config-vue eslint-plugin-vue
```

```json:.eslintrc
"extends": "vue"
```

`eslint-plugin-vue`を導入した当初はたぶんあまりルールが厳しくなく、
必要なルールを後から追加していくというのを考えていたが、結局あまりやらなかったので、
今回は初めからきついルールにして後からルールを減らしていく方式に変えた
それが次のページ

ちなみに`eslint-plugin-vue`がVue.jsのESLintの公式パッケージらしいので、
1月1日にリリースされたv4.0.0を今後使うかもしれません

---

### 現在
```
yarn add --dev eslint-config-airbnb-base prettier eslint-config-prettier eslint-plugin-prettier
```

```json:.eslintrc
"extends": ["airbnb-base", "prettier"],
"plugins": ["prettier"]
```

ルールが厳しいAirbnbのルールにした
後、コードフォーマッタになるのですがPrettierを導入

`eslint-config-prettier`や`eslint-plugin-prettier`を入れているのはESLintとPrettierが競合するのを防ぐ為に入れてる
これらのプラグインを設定してあげることで、ESLintのルールとしてPrettierを追加できる
（これをやらないとたぶんPrettierで直されたコードがESLintに引っかかるという無限ループに陥るので。。笑）

また、IDEやエディタがPrettierに対応していたら、是非設定してみてください
私はVSCodeを使っているので、以下のプラグインを入れてます

Prettier - Code formatter
https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode

上記プラグインをインストール後、VSCodeの設定で以下を追加

```json
"editor.formatOnSave": true,
"prettier.eslintIntegration": true
```

保存する度に`.eslintrc`の設定をみて、コードフォーマッタをかけてくれるので
めちゃめちゃ快適にコード書けます！

---

## Babelを新しくする
`babel-preset-es2015`とかはもう廃止されるはずなので、今更ですが`babel-preset-env`に変えた

### 1年前
```json:.babelrc
"presets": ["es2015", "stage-2"]
```

ES2015のstage-2(Draft)の文法も使いたい（使ってるのでトランスパイルしてほしい）という設定になる
ブラウザが既にサポートされている文法であっても全てES5に変換している

---

### 現在
```json:.babelrc
"presets": [
  [
    "env",
    {
      "targets": {
        "browsers": ["last 2 versions", "IE 11"]
      },
      "modules": false,
      "useBuiltIns": true
    }
  ]
]
```

targetsでブラウザのバージョンを指定している
そのバージョンのブラウザで既にサポートされているES文法であれば、ES5へのトランスパイルは行われない

`> 5% in JP`
browsersオプションでは上記のように日本でシェア5%より大きいブラウザに限定っていう細かい指定も可能

ちなみにオプションを指定しないとES2015以降全ての変換を行うことになる

---

### 補足
`babel-preset-env`とか`babel-polyfill`とか`babel-plugin-transform-runtime`とかの違い

`babel-preset-env`が行うのは文法の変換のみ
オプションのtargetsに指定したブラウザにそもそも無い機能については、importする必要がある

それを実現するのが`babel-polyfill`
これをimportすればブラウザでサポートされていない機能を補填してくれる

エントリーポイントに指定しているJSとかで以下のようにimportする必要がある

```js
import 'babel-polyfill'
```

先程の`.babelrc`の設定で`"useBuiltIns": true`としていたが、
これをtrueにしておくとブラウザが対応していない機能のみimportすることができる
ただし、コード内で使ってなくてもブラウザが対応していない機能であればimportする

`babel-plugin-transform-runtime`について
コード内で新しい機能を見つけたらES5に変換するプラグイン
ブラウザが既に対応している機能でも変換してしまう

```json:.babelrc
"plugins": ["transform-runtime"]
```

---

## で、どれ使うの？
http://kangax.github.io/compat-table
これ見たら分かると思いますが、モダンブラウザの中ではほぼIEの為にあるようなもの

話が逸れますが、
IE対応による懸念って、IE用に個別実装が必要だから別途工数かかるよねって思ってる人が多い気がしますが
実際今紹介したライブラリのおかげでIE用に実装する部分はほとんどないです
本当の懸念はIEが無ければ必要なかった`babel-polyfill`による機能のimportや、もしくは`babel-plugin-transform-runtime`を使用することによるパフォーマンス低下です
今後IE対応をするか否かを判断する機会があれば、IEで一部の人は助かるけど、その代わりに全体の質が落ちるかもよってのを意識してもらえると良いと思います

ちなみにVue.jsに関してを言えばv3以降はIEをサポートしません
https://medium.com/@gustojs/vuejs-3-and-other-top-news-from-q-a-event-with-core-vue-devs-c9834946ae7b

個人的には現状は
IE対応が必要な場合は、`babel-plugin-transform-runtime`を使う
IEが捨てれるなら`babel-polyfill`を使う
っていう感じで考えてますが
`babel-plugin-transform-runtime`がどの程度パフォーマンスに影響するかにもよると思います
あまり影響がなければ、`babel-plugin-transform-runtime`でもありかなと思います

`babel-preset-env`2.0.0について（現在β版）
`"useBuiltIns": "usage"`という設定が追加される
ブラウザが対応していない機能のpolyfillを自動でimportするため、
コード内で`babel-polyfill`をimportする必要がない（コードを変える必要がない）

---

## フロントエンドのビルドを改善する
### 1年前
minify + vendor系をbundleから分ける

```js:webpack.config.js
plugins: [
  // minify
  new webpack.optimize.UglifyJsPlugin(),

  // node_modules配下をvendor.jsにする
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module =>
      module.resource &&
      /\.js$/.test(module.resource) &&
      ~module.resource.indexOf('node_modules')
  }),
]
```

上記のようにすることでminifyとbundle.jsからvendor系を別ファイル（vendor.js）として出力していた
このやり方だとbundle.jsは軽くなるが、フロントエンドのビルドが速くなるわけでない

---

### 現在
minify + DllPlugin

DllPluginとは

```
The DllPlugin and DllReferencePlugin provide means to split bundles in a way that can drastically improve build time performance.
```

この機能を使ってできることは、node_modules配下など開発中に変更があまり入らないモジュールを予めバンドルしておく（例えばvendor.jsとして）
そしてbundle.jsからは上記のバンドル済みのvendor.jsを参照することで、bundle.jsの軽量化とバンドル時間の短縮を実現する
vendor.jsとの依存関係はmanifestファイルを元にマッピングする

```js:webpack.vendor.config.js
entry: {
  vendor: ['axios', 'vue', 'vue-router', 'vuex', 'vuex-router-sync']
},
output: {
  filename: '[name].js',
  path: path.resolve(__dirname, 'dist'),
  library: '[name]_library' // vendor_libraryという変数名でグローバルスコープに入れる
},
plugins: [
  new webpack.optimize.UglifyJsPlugin(),
  new webpack.DllPlugin({
    path: path.join(__dirname, 'dist', '[name]-manifest.json'), // manifestファイル出力先
    name: '[name]_library'
  })
]
```

```js:webpack.config.js
// bundle.js用設定
plugins: [
  // minify
  new webpack.optimize.UglifyJsPlugin(),

  // DllPlugin
  new webpack.DllReferencePlugin({
    context: __dirname,
    manifest: require('./dist/vendor-manifest.json') // manifestファイルへのパス
  })
]
```

unit testの方もvendor.jsを読み込むのを忘れずに、、
以下はkarmaの場合の設定です

```js:karma.conf.js
files: [
  './dist/vendor.js'
]
```

---

### パフォーマンス検証
まだスタートアップしたばかりでコードが少なく、速度面は効果が見えづらいのですが、、

DllPlugin無し
<img width="428" alt=" 2017-12-06 16.47.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/7ef65762-da63-61ab-f596-fd5b3df436d8.png">

DllPlugin有り
<img width="379" alt=" 2017-12-06 16.48.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/168197/c0ba62df-6474-96d4-ef30-811b4c7a5a55.png">

bundle.jsから丸々vendor系ライブラリが除かれるので、だいぶ軽くなりました

メリット
bundle.jsを軽くできる
フロントエンドのバンドルする速度を上げれる

デメリット
vendor系ライブラリを追加・更新した際に、予めvendor.jsをバンドルする手間が増える
vendor専用のwebpack.configを用意する必要がある
vendor専用のwebpack.configのentryに都度追加・削除する必要がある

チーム内でデメリットが許容できるなら、やってみても良いのではないでしょうか

---

## TypeScriptで書いてみる

やってて解決に困った点とかは以下にまとめてます
https://qiita.com/kurosame/items/3c28f45c8b2e65f5c69d

メリット：
JavaScriptと書き方がほとんど変わらず書ける（Vue.js 2.5以降）
ランタイムエラーになっていたものがコンパイルエラーで検知できるようになった
IDEの補完やエラー検知がJavaScriptより優れている

デメリット：
TypeScript用の環境構築や設定ファイルが必要
バンドル速度がだいぶ落ちた（たぶんTSLintのTypeCheckのせい）
型定義が無いライブラリは型定義を自作するか、他の類似ライブラリを検討する必要がありそう

---

## 次やること
・avoriaz使ってUIのテスト書く
もしくはvue-test-utils（現在β版）を使って書く

・E2Eテスト書く

2018/06/22追記
Vue.jsのユニットテストとE2E関連は以下記事にまとめました
https://qiita.com/kurosame/items/8d82ef8d36c106c6e8bc