JavaScript
ECMAScript
まとめ
babel

@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の場合→代入に失敗します。

  • プロトタイプチェーンにセッターがある場合→代入はセッターを駆動させます。


具体的な内容

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


@babel/plugin-transform-arrow-functions

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


  • コンストラクタとして使うとエラーを発するようなチェックを入れる

  • 名前を振る


  • thisをリネームではなく.bind(this)で再現する

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


@babel/plugin-transform-classes

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


@babel/plugin-computed-properties

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

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


@babel/plugin-transform-destructuring

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


@babel/plugin-transform-for-of

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


@babel/plugin-transform-parameters

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


@babel/plugin-transform-spread

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


@babel/plugin-transform-template-literals

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


@babel/plugin-transform-typeof-symbol

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


@babel/plugin-proposal-object-rest-spread

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


@babel/plugin-proposal-class-properties

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


@babel/plugin-proposal-nullish-coalescing-operator

左辺がnullundefinedなら右辺を、そうでなければ左辺を返す、??演算子を処理するプラグインです。looseとした場合、判定を== nullで済ませます(影響する値は例のdocument.allだけです)。


@babel/plugin-proposal-optional-chaining

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