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

by kurosame
1 / 15

新規立ち上げのチームを異動して、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で置き換えれるので、これを機に置き換えた
変更箇所を抜粋すると以下のような感じになる

package.json
"scripts": {
  "start": "webpack-dev-server",
  "build": "webpack",
  "test": "NODE_ENV=test karma start karma.conf.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
.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
.eslintrc
"extends": ["airbnb-base", "prettier"],
"plugins": ["prettier"]

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

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

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

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

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

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

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


Babelを新しくする

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

1年前

.babelrc
"presets": ["es2015", "stage-2"]

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


現在

.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する必要がある

import 'babel-polyfill'

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

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

.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-env2.0.0について(現在β版)
"useBuiltIns": "usage"という設定が追加される
ブラウザが対応していない機能のpolyfillを自動でimportするため、
コード内でbabel-polyfillをimportする必要がない(コードを変える必要がない)


フロントエンドのビルドを改善する

1年前

minify + vendor系をbundleから分ける

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ファイルを元にマッピングする

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'
  })
]
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の場合の設定です

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

パフォーマンス検証

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

DllPlugin無し
 2017-12-06 16.47.46.png

DllPlugin有り
 2017-12-06 16.48.06.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テスト書く