NHN Cloud NHN Cloud Meetup!

最近の優れたフロントエンド開発環境作り(2018):ES6

前の記事で、Webpack4を使ってすぐに使用できる環境を作ってみました。今回はその環境をそのまま利用してES6開発環境を追加します。ES6はもう十分に使ってもよい時期だと思います。「自分はフロント開発者ではないし、ブラウザが対応していないのでES6はまだ書けない」と考えているなら、この記事をぜひ読んでほしいです。どうせならES6だけでなくES8まで使用することをお勧します。IE11以下のバージョンは、ES6や以降のバージョンがほとんど考慮されていませんが、Microsoft Edgeを含むすべてのモダンブラウザは、ES8(ECMAScript2017)のほとんどのスペックに対応しています。このようにモダンブラウザは熱心にスペックを追いかけています。プロジェクトの対応ブラウザがIE11以下のバージョンを含む場合は、Babelを使ってみよう。BabelはES6+バージョンをES5に変える変換ツールです。プロジェクトのコードベースはES6で読みやすく、簡潔性が維持され、実際にブラウザで使用するコードはBabelで変換されたES5コードが使用されます。必要に応じて変換過程で一部の最適化を適用することもできます。ソースマップがあるので、ブラウザ上でのデバッグも全く問題ありません。すでに多くのプロジェクトがBabelを利用して何の支障もなくES6コードで開発されています。もちろん変換プロセスが潜在的なエラーを生む可能性もありますが、極めて低いでしょう。ES6導入に迷っている場合は、今すぐ始めてみてほしいです。

Babel

Babelは、特定バージョンのECMAScriptコードを下位バージョンに変換します。CoffeeScriptを使ったことがある方はすぐに変換できるだろう。ただしBabelはCoffeeScriptと違いECMAScript標準に従います。他は特別な機能がなく、設定や使い方は簡単です。1つずつやってみよう。

npm install --save-dev babel-cli

まずはbabel-cliをインストールをします。 前の記事で作成したプロジェクトがあれば、それで進行してみよう。

// es6test.js
const myconst = 123;
let mylet = 456;

上記のようにファイルを作成し、ターミナルで以下のように実行すると、

npx babel ./es6test.js

Babelは元のコードをES5に変換した結果をターミナルに渡してくれます。


変換されると言いましたが全く変換されず、元のコードをそのまま適用しています。なぜこうしたのでしょうか?
babel-cli
はコード変換を行うコア機能のみで、実際のコードを変換する時は機能別に拡張プラグインが必要です。簡単に言うとアロー関数を変換するには、それができるプラグイン(transform plugin)を追加インストールする必要があります。しかし、機能別にプラグインを個別にインストールするのは面倒です。そこでプリセット(presets)を設置しましょう。プリセットはバージョン別に必要なプラグインを集めたものと考えればよいでしょう。毎年ECMAScriptバージョンに合わせて、プラグインとプリセットが追加開発されています。

npm install --save-dev babel-preset-env

babel-preset-envはそのようなプリセットとプラグインを集約管理しているモジュールです。かつてはバージョン別にプリセットモジュールが存在し、使用するバージョンのプリセットを個別インストールする必要がありましたが、今はすべて複写(duplicate)されています。babel-preset-envをインストールし、Babelを再実行しても、変換されず元のまま出力されます。プリセットをインストールした場合、どのような設定値を使用するかオプションで渡す必要があります。プリセットを提供するオプションは–presetsで、今回は画面にスプレーさせず変換されたファイルに保存するように–out-fileオプションも使用します。

npx babel ./es6test.js --out-file es5.js --presets=es2015

es5.jsファイルを開いてみると、以下のようにES5コードに変更されたことが分かります。

// es5.js
"use strict";

var myconst = 123;
var mylet = 456;

“use strict”が追加されました。ES6のモジュールコードは、スペック上では毎回Strictモードで動作します。ES5に変換されたので“use strict”に追加されたのです。反対にES6コードで“use strict”を使用するのは正しくありません。静的解析ツールが指摘することもあります。Strictモード追加のほかに、constとletキーワードがvarキーワードに変更されました。使用されたES6コードはconstとletだけの簡単なコードで、全体的にはほとんど変化がありません。varが変更されるだけでconstとletのスペックが正しく実装されていないようです。今回はconst変数の値を変更するコードを追加して、再度同じオプションでBabelを実行してみよう。

const myconst = 123;
let mylet = 456;

myconst = 555;

上記のようにエラーメッセージを出力してトランスファイルになりません。このようにBabelはスペックにそぐわないコードを変換の段階で発見してくれます。別のケースを見てみよう。

// es6test.js
const myconst = 123;
let mylet = 456;

if (myconst) {
  let mylet = 1;
  mylet += 1;
}

letはブロックスコープです。if文ブロックのmyletとブロック外のmyletは、物理的に異なる変数で相互に影響を受けてはなりません。つまりif文を抜くとmyletの値は依然として456でなければなりません。varキーワードで変数を宣言したES5は、関数のスコープと変数myletの値が2となります。BabelはこのコードをどのようにES5に変えるのか、もう一度実行してみましょう。

// es5.js
"use strict";

var myconst = 123;
var mylet = 456;

if (myconst) {
  var _mylet = 1;
  _mylet += 1;
}

最初から変数値名を変えてブロックスコープと同じ効果を出します。このように対象コードの使用内容に応じてスペック通り同様に動作するようにES5に変換します。const、letのみならず、同じ機能でも使用方法によって変更されるコードの内容が効率的に管理できます。

babelrc

Babelは.babelrcというファイル名でプロジェクトのBabel関連の設定が登録できます。package.jsonからbabelとキーに設定を追加することもできるが、.babelrcの使用をお勧めします。babel-cliを実行して毎回コマンドラインにオプションを渡すことができますが、Babelに使われるオプションは、通常プロジェクトごとに決まっており、管理と環境共有のため設定ファイルに保存します。コマンドラインオプションで使用できるほとんどの内容は設定ファイルに保存できます。実際にWebpackと連携すると、コマンドラインでBabelを実行することもありません。前述のコマンドラインオプションは、簡単に以下のように設定できます。

{
  "presets": ["es2015"]
}

変換対象と結果ファイルを保存するオプション–out-fileは、設定ファイルに保存できません。使用できるBabelオプションは、APIページのOptionsで確認できますが、バージョン7の準備のためか「old」というサブドメインのついたリンクのみ有効で、現在公式ページへのリンクは壊れています。.babelrcに保存し、残りはWebpackに任せるので特に設定を複雑にすることもありません。

{
  "presets": ["env"]
}

プリセットを“env”に設定するとbabel-preset-latestと呼ばれる現在サポート可能な最新バージョンのプリセットを使用して、追加プロジェクトの対応ブラウザを基盤に、Polyfillと必要な変換プラグインを管理するオプションが使用できます。Babelを使った最新のECMAScriptは、常に最新バージョンを使用することが多く、明示的にスペックを設定するよりは、常に最新バージョンのスペックを使用して、対象ブラウザにより変換されたコードが戻るランタイム・コードを最適化できるオプションへと発展しました。

{
  "presets": [
    ["env", {
      "targets": {
        "chrome": 52
      }
    }]
  ]
}

上記のように、オプションで対応ブラウザのバージョンを定義できます。モダンブラウザの場合は、特定バージョンに基づいて対応する政策よりも、最新バージョンに対応したり、最新バージョンといくらか前のバージョンに対応する方が有効です。文字列のクエリ形式で対応ブラウザを定義できるbrowserslistをBabelが使用しているため「最近の2バージョンまで」のような形で対応ブラウザを定義できます。
「最近の2バージョンのみに対応して、IEの場合はバージョン10以下を除く」という設定は、次の通りです。

{
  "presets": [
    ["env", {
      "targets": {
        "browsers": ["last 2 versions", "not ie <= 10"]
      }
    }]
  ]
}

配列形態でいくつかの条件を組み合わせて設定できます。面白いのは、グローバルなブラウザ使用統計をベースに設定できるオプションがあります。例えば「世界使用量が5%以上のブラウザのみに対応する」というような設定ができます。ただこれはあまりに不明確で個人的には推奨しません。browserslistで使用可能なクエリのリストは、こちらから確認できます。
まだ正式版に含まれていない新しいスペックはplugins配列に追加できます。

{
  "presets": [
    ["env", {
       "targets": {
        "browsers": ["last 2 versions", "not ie <= 9"]
      }
    }]
  ],
  "plugins": ["transform-object-rest-spread"]
}

object-rest-spreadRest/Spread Propertiesという名前でStage-4段階にあるスペックでES2018に追加される予定です。設定だけでは当然動作せず、プラグインを追加インストールする必要があります。

npm i babel-plugin-transform-object-rest-spread --save-dev

このように、まだ正式バージョンに含まれていない開発中のスペックは、プラグインの形で個別追加して当該機能を使うことができますが、Stage-3以上のスペックの使用をお勧めします。
使用可能なプラグインのリストは、ここから確認できます。すでに正式バージョンに含まれたスペックはプリセットでまとめて適用できるので、個別プラグインを使用することはありません。まだ含まれていないスペックのプラグインはExperimental(実験的)部分にまとめられています。

Webpack連動

.babelrcの設定が正しくできている場合はWebpackとの連動は難しくありません。まずWebpackでBabelを連動できるようにbabel-loaderをインストールします。

npm i babel-loader --save-dev

module.rulesオプションローダーを追加します。

module.exports = (env, options) => {
  const config = {
    entry: {
      app: ['./src/index.js']
    },
    //... 中略 ...
    module: {
      rules: [
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader'
          }
        }
      ]
    }

簡単にローダー設定について説明すると、testはローダーが適用されるファイルを定義し、excludeは除外するファイルを正規表現として定義します。exclude/node_modules/で定義していますが、こうすると/node_modules/ディレクトリの下位内容はすべて含まれません。rulesオプションは、特定条件のファイル基準に適用されるべきローダーを定義するため、rulesにローダーを1つずつ定義することもできます。特定ファイルに複数ローダーが適用される場合、重複して定義せずuseオプションを配列に渡します。

module: {
  rules: [
    {
      test: /\.css$/,
      use: [
        { loader: 'style-loader' },
        { loader: 'css-loader'}
      ]
    }
  ]
}

そしてWebpackで必ずやるべき設定がbabel-polyfillを追加する作業です。Babel-Polyfillは必要なECMAScriptバージョンに含まれたビルトインオブジェクトやメソッドを追加してくれます。すなわち特定バージョンのランタイム環境を作り出します。例えばPromiseを持たないブラウザ環境にPromiseを作ってくれます。Babelで変換されたコードを使おうと思うなら、バンドリングされたファイルをロードする前にブラウザにロードさせる必要があります。実際にbabel-polyfillはWebpackとは関係なく、Babelのために必要な部分ですが、Webpackを利用してバンドリングして使用します。Webpackで依存性(dependency)コードを管理する部分は、プロジェクト別に状況に合わせて適用するが、前の記事で紹介したように依存性コードは別に分離してバンドリングする方法で処理するのが一般的です。Babel-Polyfillもその分離するファイルに含めます。実際サービスコードでBabel-Polyfillを直接ロードすると、前の記事の最終Webpackの設定内容から変わることはありませんが、Babel-Polyfillはサービスコードから直接インポートして使用する理由がないため、サービスコードでは隠して、Webpackエントリーに含めます。

module.exports = (env, options) => {
  const config = {
    entry: {
      app: ['babel-polyfill', './src/index.js']
    },
    // .... 中略 ....
    optimization: {
      splitChunks: {
        cacheGroups: {
          commons: {
            test: /[\\/]node_modules[\\/]/,
            name: 'vendors',
            chunks: 'all'
          }
        }
      }
    }
  }
}

このように設定すると、Polyfillは他のプロジェクト依存と共にvendors.bunde.jsファイルにバンドリングされます。

まとめ

ES6++で開発できる環境を作りました。ES6++にまだ慣れていない方は、ここでコード中心に調べるだけでも、ある程度の使い方を把握することができ、便利な機能が多いことがわかるでしょう。JavaScriptの欠点を改善するために提供された機能でもあり、多くの機能が簡単にプロジェクトに活用できると思われます。

NHN Cloud Meetup 編集部

NHN Cloudの技術ナレッジやお得なイベント情報を発信していきます
pagetop