フロントエンド開発の環境構築

電脳麻将HTML5 + CSS3 + JavaScript で動作するフロントエンドのWebアプリです。サーバ側にコードはありません。ですので今までは Node.js を使わずに開発していました。思考アルゴリズムの検証でAI同士に自動対戦をさせる場合もブラウザ上で実行していたのですが、1000半荘の対戦に8時間以上かかるようになり、さすがにブラウザ上での実行は厳しくなってきました*1

そこで Node.js を導入することにします。せっかく導入するのですから以下の方針とします。

  1. AI同士の自動対戦や大量の牌譜解析を Node.js 上で行う
  2. プログラムをNode.jsで実行できる範囲でES6の記述に改める
  3. ユニットテストを導入する

1.のためにプログラムを require() を使用したNode流の記述に改めます。ブラウザが理解できるJavaScriptには webpack で変換します。
2.のために Babel を導入します*2が、ES6での記述はNodeが理解できる範囲にとどめ、バックエンド側で変換は行いません。*3
3.のためのフレームワークとして Mocha を導入します。アサーションはNode標準のものを使います。

今回はこれらのソフトウェアのインストールと設定までを行います。

Node.js のインストール

開発環境は OS X 10.9.5(Mavericks) なのですが、pkg 形式のインストーラは使わず、Homebrew を使ってインストールしました。まず Homebrew 自身を最新化します。

$ brew update

Node.js をインストールします*4

$ brew install node

Node.js と npm がインストールされました。

$ node -v
v8.4.0
$ npm -v
5.3.0

プロジェクトディレクトリの初期化

プロジェクトを npm に対応させるために、プロジェクトのルートディレクトリで npm init を実行し、
package.json を生成します。

$ npm init -y

すでに README.md などがある場合は、その内容も反映されるようです。修正が必要な箇所は手作業で修正します。

webpack のインストールと設定

webpackをインストールします。グローバルにインストールしてもよいのですが、まだ仕様が安定していないのでモジュールにより異なるバージョンを使うことが多いようです。という訳でローカルにインストールしますが、開発時にのみ使うので -D オプションを指定します。

$ npm install -D webpack

新たにディレクトnode_modules/ が作成され、そこにモジュールがインストールされます。package.jsondevDependencies にはインストールしたモジュールのバージョン情報が追加されます。npm 5 からは package-lock.json というファイルも自動生成されるようになりました。*5

npm run build で webpack がビルドを行うよう package.jsonscripts にタスクを追加します。

  "scripts": {
    "build": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

webpack の動作は webpack.config.js で指定します*6。まずは入力と出力のみ指定します。

module.exports = {
    entry:  './src/js/entry.js',
    output: {
        path:     __dirname + '/www/js/',
        filename: 'majiang.js'
    },
};

entry は変換前のJavaScriptエントリーポイント相対パスで指定します。エントリーポイントとは、そこから require() をたどればすべてのモジュール参照を解決できる起点となるファイルです。
output は変換後のJavaScriptの置き場所とファイル名です。こちらは絶対パスで指定する必要があります。__dirname にカレントディレクトリが設定されるのでこれを利用して設定すればいいです。

Mocha のインストールと設定

Mochaをインストールします。開発時のみ使用するのでオプションに -D を指定します。

$ npm install -D mocha

npm test でMochaでテストを行うよう、package.jsonscripts.test を修正します。

  "scripts": {
    "build": "webpack",
    "test": "mocha -u tdd"
  },

TDDスタイルでテストコードを書く場合は、オプションに -u tdd が必要です。

Babel のインストールと設定

ES6に対応していないブラウザ(IESafari)のために Babel で変換をかけます。Babel には多くのモジュールがありますが、Babel本体の babel-core と webpack 向けインタフェースの babel-loader をインストールします。

$ npm install -D babel-loader babel-core

webpack.config.jsmodule の項目を追加します。

    module: {
        rules: [
            { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }
        ]
    },

上記の設定で、node_modules 以外の .js ファイルに変換がかかるようになります。

Babel がどのような変換をするかは preset で指定する必要があります。env を指定すると環境に合わせて*7変換してくれるようです。env 用のプラグイン babel-preset-envをインストールします。

$ npm install -D babel-preset-env

プロジェクトルートに以下の内容の .babelrc を作成し、env を使用することを Babel に伝えます。

{
  "presets": ["env"]
}

IEにも対応する場合は、さらに babel-polyfill も必要になります。babel-polyfill はES6で追加されたメソッドなどを実行時に既存のクラスに追加します。実行時のモジュールなので -D を指定せずにインストールします。

$ npm install babel-polyfill

babel-polyfill を実行時のJavaScriptに含めるよう webpack.config.js を修正します。

    entry:  [ 'babel-polyfill', './src/js/entry.js' ],

ソースマップの出力と圧縮

Babelに変換されたJavaScriptをブラウザ上でデバッグするためにはソースマップが必要です。webpack のオプション --devtool inline-source-mapinline-source-map 形式でソースマップを出力します。また、変換後のソースを直接見ることはないので圧縮してしまいましょう。webpack のオプション -p で圧縮を指定します。

  "scripts": {
    "build": "webpack --devtool inline-source-map",
    "release": "webpack -p",
    "test": "mocha -u tdd"
  },

package.jsonscripts を上記のように変更しました。npm run build でソースマップつきのデバッグ用の出力をします。npm run release は本番用の出力です。ソースマップは出力せず*8、ファイルも圧縮します。

*1:ブラウザ上で実行する場合、ブラウザが画面の前面に出ていないと実行がスリープしてしまうので長時間の試験がやりにくい。と思っていたのですが、実はヘッドレスChromeで解決できそうです。ですが今後のことを考え、この機会にNode.js化します

*2:すでに for ... of とか限定的に使っているのでIEでは動作しないのだが、この状態が改善されるかも

*3:APIドキュメント生成に ESDoc を使うのであれば、requireをimportにする必要がありますが、それはやり過ぎと思い断念しました

*4:もし複数のバージョンの Node.js を切替えて使いたいのであれば、ここで nodebrew をインストールするのですが、私は perlbrew すら使っていないのでパス

*5:インストールされたすべてのモジュールのバージョン番号を特定しているようです。npmの破綻ぶりがうかがえます😝

*6:恐ろしいことにこの設定ファイルはJavaScriptの「プログラム」なので、モジュールをロードしたり式を評価したりできます

*7:使用しているNodeのバージョンとか

*8:babel-polyfill を使うとソースマップは700kbほどになります