シンプルだけどシッカリ働く最新のECMAScript開発環境


どうも、最近スマホゲー界隈からコンバートしてきた新米フロントエンド戦士です。
数年前はブラウザゲー界隈にいたんですが…完全に浦島太郎状態です。

ここ数ヶ月で色々学ばせてもらいつつ、自分なりのNode開発環境が整ったので公開しちゃいます。

完成した環境はこちらのv1.0(masterの最新はこちらでクロスプラットフォーム対応を行ったものになります)

2018/8/28 追記

本日 Babel7がリリースされました!

それに伴いパッケージ名が変更されていました。
babel-core -> @babel/core
babel-preset-env -> @babel/preset-env

また

babel-preset-env2.0でbabel-polyfillは不要になるらしい

の件が実験的機能として導入され、以下のようにuseBuiltIns:usageと設定すると必要に応じて自動でpolyfillしてくれるようになりました。

webpack.config.jsから抜粋
  presets: [
    [
      '@babel/preset-env', { 'useBuiltIns': 'usage', ... }
    ]
  ]

目指す開発環境

以下のような環境を目指しました。

  • 分かりやすくシンプルに
  • 最新のECMAScriptを使える
  • ローカルサーバーをサクッと立てる
  • プログラムも静的ファイルも変更監視で自動リロード

各項目について説明しいきます。

分かりやすくシンプルに

いろんなNodeモジュールが公開されていて色々試したくなりますが、目指すところは極力シンプル!

使うモジュールはこんな感じです。

package.jsonから抜粋
  "dependencies": {
    "babel-polyfill": "^6.26.0"
  },
  "devDependencies": {
    "cpx": "^1.5.0",
    "mkdirp": "^0.5.1",
    "rimraf": "^2.6.2",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "webpack": "^3.5.6",
    "webpack-dev-server": "^2.7.1"
  }

ザックリ分類すると

  • ファイル/ディレクトリ操作系:cpx mkdirp rimraf
  • ビルドツールのwebpack系:webpack webpack-dev-server
  • トランスパイラのbabel系:babel-core babel-loader babel-preset-env babel-polyfill

タスクランナー系は使わず、npm-scriptでなんとかする方針でいきます。

そしてディレクトリ構造はこんな感じにしました。

root/
  ├─ src/ ビルド前のindex.htmlやプログラム類を置く
  ├─ assets/ ビルド前の画像などのリソース類を置く
  └─ dist/ ビルド後のindex.htmlやプログラム類とassets配下のコピーを置く

ビルド前のプログラムやリソースと、ビルド後の生成物をきっちり分けられるようにしました。
distディレクトリはビルド毎にまるっと削除して作り直します。

モジュールの説明

mkdirpとrimraf

バンドル後のコードや画像など、最終的な精製物を配置するdistディレクトリの初期化に使います。
クロスプラットフォームなディレクトリ作成/削除モジュールです。
下記コマンドでdistディレクトリの削除と作り直しができます。

rimraf dist/ && mkdirp dist/

cpx

画像など素材をdistディレクトリにコピーするために使います。
さらに、コピーした元ファイルの変更監視もできます。
こちらもクロスプラットフォームだと見た記憶がありますが、未確認です。(知ってる方いたら教えてください)クロスプラットフォームで使えます。(こちらで確認しました)
下記コマンドでsrc配下のindex.htmlとassets配下のリソース類をdist配下に配置できます。

cpx src/index.html dist/
cpx 'assets/**/*' dist/

webpack + babel-core + babel-loader

ES2015構文のimport/exportを使ってプログラムをモジュール化するために使います。
webpackでビルドするとモジュールが結合されて1つのJavaScriptファイルになります。

babel-polyfill

babel-loaderの構文変換だけでは補えない新機能(Promiseとか)を補ってくれます。

babel-preset-env

非推奨のbabel-preset-es2015やbabel-preset-es2017に代わるpresetで、最新ECMAScript構文が使えるようになります。
また、ビルドターゲットがサポートしていない構文のみ無駄なく変換してくれます。

最新のECMAScriptを使える

import/exportだけでなく最新のECMAScript構文を使うために、babel-polyfillbabel-preset-envを設定します。

babel-polyfillの設定

webpack.config.jsから抜粋
  entry: [
    'babel-polyfill',
    path.resolve('src', 'index.js')
  ],

entryにindex.jsと共に書いておけば、一緒にバンドルしてくれます。
ただし、これだとbabel-polyfillで用意している全機能がバンドルされ、ビルド後のファイルが少し大きくなってしまいます。
index.jsファイル内の先頭行でimportしてbabel-loaderのuseBuiltInsオプションを付けることで使用する機能のみバンドルすることも出来ますが、babel-preset-env2.0でbabel-polyfillは不要になるらしいので、それまではシンプルさ重視にしました。

babel-preset-envの設定

webpack.config.jsから抜粋
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: path.resolve('node_modules'),
        loader: 'babel-loader',
        options: {
          presets: [
            ['env', {
              'targets': {
                'node': 'current',             // 動かしてるPCのNodeバージョン
                'browsers': 'last 2 versions' // 各種ブラウザの直近2バージョン
              }
            }]
          ]
        }
      }
    ]
  },

babel-loaderのpresetとしてenvを設定します。
targetsとして開発端末のNodeバージョンと、各種ブラウザの直近2バージョンで動くコードにトランスパイルするよう設定しています。

ローカルサーバーをサクッと立てる

ローカルサーバーはwebpack-dev-serverでサクッと立てます。

webpack-dev-serverの設定

webpack.config.jsから抜粋
  devServer: {
    port: 8000,  // http://localhost:8000として立てる
    open: true   // ビルド後にブラウザでhttp://localhost:8000を開く
  }

上記設定を追加し、webpackの代わりにwebpack-dev-serverをコマンド実行すればビルド後のアプリケーションがhttp://localhost:8000で立ち上がります。

プログラムも静的ファイルも変更監視で自動リロード

ビルド対象のindex.jsが依存関係を持つプログラムに関しては、webpack-dev-serverを使うだけで変更があったら自動的にビルド & ブラウザリロードしてくれます。
しかし、任意のタイミングで読み込みたい画像などの静的ファイルは、別途工夫が必要です。

webpack-dev-serverの設定追加

webpack.config.jsから抜粋
  devServer: {
    port: 8000,
    open: true,
    contentBase: path.resolve('dist'), //追加
    watchContentBase: true             //追加
  }

distディレクトリをwatchする追加を設定しました。これでdist配下のファイルはプログラム以外も変更監視の対象になります。

しかし、静的ファイルはassetsディレクトリで管理してビルド時にdist配下にコピーする方針なので、dist配下で直接変更は行われません。
そこでdist配下へのコピー時にcpxの変更監視機能を使用します。

cpxの変更監視設定

cpx src/index.html dist/ -w
cpx 'assets/**/*' dist/ -w

-wオプションを付けることで、コピー元に変更があったらdist配下に自動的にコピーされるようになります。
dist配下にコピーされると、webpack-dev-serverが変更検知してブラウザリロードを行ってくれるという寸法です。

npm-scriptについて

最後に、npm-scriptの説明も軽くしておきます。

package.jsonから抜粋
  "scripts": {
    "start": "npm run mkdist && npm run cpassets -- -w & npm run cphtml -- -w & webpack-dev-server",
    "build": "npm run mkdist && npm run cpassets && npm run cphtml && webpack --optimize-minimize",
    "mkdist": "rimraf dist/ && mkdirp dist/",  // startとbuildから呼ばれる
    "cpassets": "cpx 'assets/**/*' dist/",     // startとbuildから呼ばれる
    "cphtml": "cpx src/index.html dist/"       // startとbuildから呼ばれる
  }

npm run start(npm startでもOK)

dist掃除〜webpack-dev-server起動を行います。
mkdistでdistを掃除完了後、cpassetsとcpassetsに-wオプションを付けつつ、webpack-dev-serverと&で繋ぎ並列実行させています。

npm run build

dist掃除〜ビルド&圧縮してdistに配置を行います。
監視が必要ないのでstartとは異なり、mkdist cpassets cphtmlを直列で行った後、ファイル圧縮の--optimize-minimizeオプション付きでwebpackを実行します。