@babel/pluginのloose/specモードについてまとめ

Babel 7がリリースされたということで、気になる点について調査を行っていたのですが、その過程で一部のプラグインに`loose`モードがあるということを知ったので、それについてまとめてみます。

# `loose`モードとは

JSXのようなそもそもJavaScriptにないものは別として、Babelのプラグインが変換する各文法構造については、EcmaScriptとして厳密な動作仕様が決まっています。

ただ、変換して実行する過程で、「厳密に言えば仕様どおりになっていなくても、重箱の隅はつつかないから処理が早い/コードが短い、実用充分なコードになればそれでいい」という需要もあります。そういうのに対応するのが`loose`モードです。

逆に、「厳密な動作のほうが需要が少ない」と判断された場合には、`spec`で厳密な動作に切り替える、という仕組みのものもあります。

## `Object.defineProperty`と代入の違い

`loose`モードでは、プロパティの設定を`Object.defineProperty`（`[[DefineOwnProperty]]`）ではなく代入（`[[Put]]`）で簡略化していることがあります。代入では`enumerable`などdescriptorが設定できないこと以外に、以下の違いがでます。

* プロトタイプチェーンにある同じプロパティが`writable:false`の場合→代入に失敗します。
* プロトタイプチェーンにセッターがある場合→代入はセッターを駆動させます。

# 具体的な内容

ここからは、プラグインごとに`loose`や`spec`モードにしたときにどうなるかを並べていきます。

## [`@babel/plugin-transform-arrow-functions`](https://babeljs.io/docs/en/babel-plugin-transform-arrow-functions)

これはArrow Functionを普通の関数に書き換えるプラグインですが、`spec`モードにすると以下のような挙動になります。

* コンストラクタとして使うとエラーを発するようなチェックを入れる
* 名前を振る
* `this`をリネームではなく`.bind(this)`で再現する

`Function.prototype.name`に頼るのでなければ、毎回チェック処理が挟まるデメリットのほうが大きそうです。

## [`@babel/plugin-transform-classes`](https://babeljs.io/docs/en/babel-plugin-transform-classes)

これは`class`構文を従来の形に書き直すものですが、`loose`モードではメソッド定義を`Object.defineProperty`ではなく代入で行います。本来`false`になるはずの`enumerable`が`true`となるので要注意です。

## [`@babel/plugin-computed-properties`](https://babeljs.io/docs/en/babel-plugin-transform-computed-properties)

これはComputed Property（`{[foo()]: bar}`のような、角カッコでくくってオブジェクトリテラルのキーを動的に指定できる機能）の変換を行うものですが、こちらも`loose`モードではプロパティの設定を`Object.defineProperty`ではなく代入で行います。

これはオブジェクトリテラルにしか使えない機能である以上、`Object.prototype`を汚染している場合でもなければ、差は出ないと思います。

## [`@babel/plugin-transform-destructuring`](https://babeljs.io/docs/en/babel-plugin-transform-destructuring)

これは分割代入を処理するプラグインですが、`loose`を指定した場合は「配列以外の`iterable`を想定しない」という挙動になります。オブジェクトや配列にしか使わないなら影響はありません。

## [`@babel/plugin-transform-for-of`](https://babeljs.io/docs/en/babel-plugin-transform-for-of)

これは、`for-of`を変換するためのプラグインです。`loose`モードでは、「配列だけ別枠で認識して高速化する」代わりに「イテレーターを`for-of`している途中に例外で打ち切られると、イテレーターの`return`が呼ばれない」という挙動になります。なお、配列以外に`for-of`を使わない場合は、`assumeArray`という設定もできます。

## [`@babel/plugin-transform-parameters`](https://babeljs.io/docs/en/babel-plugin-transform-parameters)

これは、引数のデフォルト値やrest引数、分割代入などを変換するためのプラグインです。`loose`を使うと、デフォルト引数のある関数のarityが仕様と変化することになりますが、引数を`arguments`に頼らず処理できるようになって効率化する、とのことです。

## [`@babel/plugin-transform-spread`](https://babeljs.io/docs/en/babel-plugin-transform-spread)

これは、引数や配列などで、iterableの`...`による展開を処理するためのプラグインです。`loose`にすると、配列専用になります。

## [`@babel/plugin-transform-template-literals`](https://babeljs.io/docs/en/babel-plugin-transform-template-literals)

これは、バッククオートで区切ったテンプレートリテラルを変換するためのプラグインです。`loose`では、`concat`ではなく`+`で文字列を組み立てるようになります。`Symbol.toPrimitive`の設定されたオブジェクトを処理する場合に違いが出る、とのことです。

## [`@babel/plugin-transform-typeof-symbol`](https://babeljs.io/docs/en/babel-plugin-transform-typeof-symbol)

これは、`Symbol`に対する`typeof`が`"symbol"`を返すようにするためのプラグインです。プラグイン自体に`loose`の設定はないのですが、`@babel/preset-env`に`loose`を指定すると、プラグインごと省略されます。`typeof`の値を`"function"`や`"undefined"`などと比較するだけであれば、影響は出ません。

## [`@babel/plugin-proposal-object-rest-spread`](https://babeljs.io/docs/en/babel-plugin-proposal-object-rest-spread)

これは、オブジェクトに対して`...`で展開するようなものを実装するためのプラグインです。これも`loose`モードではプロパティの設定を`Object.defineProperty`ではなく代入で行います。getterやsetterのあるオブジェクトなどでは違いがでてくることがあります。

## [`@babel/plugin-proposal-class-properties`](https://babeljs.io/docs/en/babel-plugin-proposal-class-properties)

これはReactでよく見かける、プロパティの定義をクラス内に直接書いてしまえる文法の変換用プラグインです。これまた`loose`モードではプロパティの設定を`Object.defineProperty`ではなく代入で行います。親クラスに`writable:false`のプロパティやセッターがあると結果が違ってきます（これは仕様上も`enumerable`は`true`です）。

## [`@babel/plugin-proposal-nullish-coalescing-operator`](https://babeljs.io/docs/en/babel-plugin-proposal-nullish-coalescing-operator)

左辺が`null`か`undefined`なら右辺を、そうでなければ左辺を返す、`??`演算子を処理するプラグインです。`loose`とした場合、判定を`== null`で済ませます（影響する値は[例の`document.all`](https://qiita.com/jkr_2255/items/f9b7218d7a2b54424c12)だけです）。

## [`@babel/plugin-proposal-optional-chaining`](https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining)

CoffeeScriptにもあった、左辺が`null`でも`undefined`でもないときだけプロパティ参照を行う、`?.`演算子を処理するプラグインです。`loose`とした場合、こちらも判定を`== null`と、`document.all`を考慮しない形にします。