JavaScript
babel

Babel v7でStage Presetが削除される

Babelには、まだ仕様が確定してなかったり、アイデアレベルだったりする機能でもとりあえず使えるようにしてしまおうという、プリセット機能が存在します。
その中でもStage Presetは、ステージXのプリセットを一括して導入できる便利機能です。

が、Babel v7ではStage Presetが削除されることが決定しました。
以下はRemoving Babel's Stage Presetsの日本語訳です。

Removing Babel's Stage Presets

Babel v7において、@babel/preset-stage-0のようなStage Presetsの提供を停止することを決定しました。

我々はこの決定を決して軽視しているわけではありません。
TC39、Babel、コミュニティとの間で何があったかの背景を示します。

Some History

プリセットは、共有可能なプラグインのリストです。

公式のStage Presetsでは、TC39でステージングされたJavaScriptの新文法を使用できるようにします。

stage-3stage-2のような各プリセットは、実際はそのステージにあるプラグインとそれ以上のステージにあるプラグインが全て含まれています。
たとえばstage-2にはstage-3の内容が全て含まれています。

これによって、実験的な構文を試したいユーザは、個々のプラグインを個別にインストール・設定する必要がなく、プリセットをインストールするだけで済むようになっているのです。

Stage Presetsが追加されたのはBable v6のリリース直後のことです。
それ以前のv5ではコンフィグで設定するようになっていました。
以下において例示されるスクリプトはBabel v6のものです。

プリセットは一般的に以下のように記述します。

{
  "presets": ["es2015", "react", "stage-0"]
}

babel-preset-stage-0のソースは以下のようになっています。

module.exports = {
  presets: [
    require("babel-preset-stage-1")
  ],
  plugins: [
    require("babel-plugin-transform-do-expressions"),
    require("babel-plugin-transform-function-bind")
  ]
};

Problems

私たちが望むもの、新しく『まだ確定していない』未来のJavaScriptを使うために、これらのプリセットは非常に優れた方法でした。
振り返ってみると、これは本当にうまくいっていました。
うまく行きすぎました。

Too Good a Job?

CoffeeScriptのような言語、Traceurのようなツールは、JavaScriptをコンパイルする、という考え方を確立しました。
Babelは、将来の新しい構文を容易に今すぐ使えるようにするようにしました。
実験的な内容を取り入れることに対する疑念や心配は、すっかり期待へと変貌しました。

Babelのようなコンパイラは広く世間に受け入れられ、より多くの開発者がES2015を使えるようになりました。
Reactの成長はJSXの文法、クラスプロパティ、スプレッド構文などを広めると共に、プロポーザルについての知識も広めました。

Babelは最初のセットアップ時に一回だけ、単純にstage-0が実行され、その後顧みられることはありません。
Babelは基礎的なインフラとなり、他のツールの背後に溶け込み、SyntaxErrorや、依存性・インテグレーションの問題が発生しない限り表に出てくることはありません。

これはつまり、実験的な機能がプロダクション環境でも使われていて、自然の環境でstage-0がテストされていることを意味しています。
それは素晴らしいことですが、しかしこれはつまり、プロポーザルの仕様が変更されたり、あるいは却下された場合に、多くの企業やツール、開発者がたいへんなトラブルに遭遇するということです。

Back and Forth

Stage Presetsについてどうするべきか、#4914#4955#7770などのIssueで長らく議論を続けてきました。
私はBabel v7についての古い記事で、Stage Presetsは削除しないとも言いました。

しかしながら、Stage Presetsを持ったままでいると、Babel自体に問題が発生することが判明しました。
async/awaitを使うにはどうのプリセットを使えばいい?のような質問を頻繁に見かけます。多くの開発者はstage-0の意味を知らず、ましてやpackage.jsonやソースを見る人などほとんどいません。
・プロポーザルのステージが進み、stage-3からstage-4になったとすると、stage-3は破壊的変更になります。特に@babel/preset-envを使って、一部プロポーザルをコンパイルしないようにしているときはより悪化します。

"ES7 Decorators"

問題の一つは、正確に命名することです。
よく知られているように、名付けは難しいことです。

ES6には多くの別名があります。Harmony、ES Next、ES6、そしてES2015。
人々は新しいアイデアを聞くと、単純に最新の番号を選んで名前を付けがちです。

従って、ツイートなりブログポストなりを少し検索してみるだけで"ES7 Decorators"という呼び方が一般的になってしまっていることがわかります。

https://twitter.com/dan_abramov/status/785082176610115584

::でのバインディングはステージ0の実験的提案にすぎず、決してJavaScriptになることはない。これを"ES7"と呼んではいけない。 - Dan Abramov

勘違いしたままの命名が広まってしまうと、その実装について誤った期待が続いてしまうでしょう。

Jay Phelpsがよい記事を書いています。
彼は、現在のステージを付けて呼ぶのが最善であろうと主張しています。
すなわち"Stage2 Decorators"、あるいは単に"Decorators Proposal"です。

"ES7 Decorators"の何がよくないのかというと、DecoratorsがES7であると誤解されてしまうことです。
私はnode_modulesのコンパイルに関する記事で主張しましたが、特定のステージにいることはあまり保証されません。
プロポーザルはすぐに停滞したり、巻き戻ったり、あるいは完全に削除されたりします。

プロポーザルプラグインの名前を@babel/plugin-transform-から@babel/plugin-proposal-変更したのは、プロポーザルであることがはっきりわかりやすくするためです。

BabelScript

プロポーザル用のプリセットを用意することは、プロポーザルの進捗が進んだり、安定した実装が保証されるかのように思えるかもしれません。

ステージ2やそれ以下のプロポーザルでは特に、既存コードを壊してしまうおそれがあるため、プロポーザルを改善するかわりに実装を現状のままに保たせてしまおうとする不用意な圧力をかけてしまう可能性があるため、TC39は注意を要します。

Babelを使う開発者は、JavaScriptではなく"BabelScript"を使っているんだよ、としばしば冗談を言っています。
何らかの機能についてBabelプラグインが一度作成されたとしたら、それは完全にフィックスされて言語の一部になっているということです(もちろん真実ではありません)
一部の開発者にとっては、新しい構文やアイデア(stage-'-1')を見つけたときに最初に考えることは、Babelプラグインが存在するか否かです。

Setting Expectations

Babel以後、開発者がES2015を使うことは一般的になりました。
開発者がより新しく、より実験的な機能を試してみることは必然でした。
かつてのバージョンでは、それらの機能は設定ファイルのstage-xフラグを変更するだけで使えました。

新機能を導入するのに最も簡単な方法であることから、Babelを導入したら真っ先に設定変更することがデフォルトになりました。
Babel v6でそれは何もしないようになりましたが、当初は多くの苦情を引き起こしました。

そして今やライブラリ、ボイラープレート、講演、ツイート、スライドなどでstage-0がデフォルトになっています。

https://twitter.com/ryanflorence/status/627154904302288897

stage-0はプロダクションで使うなってことだぞ - Ryan Florence

何年も議論が続きましたが、適切な解決方法は見つかりませんでした。
Console.warnをそのデメリットも理解して使っている人に使うなとは言えないでしょう。
オプションが存在しないというのは不合理だと、当時は考えられました。

何も考えずにstage-0を有効にするのは危険であるだけでなく、有効にしたプロポーザルを結局使っていないこともあります。
理想は、そのプロポーザルがどのステージにあるかにかかわらず、自分に必要な機能だけを有効にして使っていくようになることです。
この懸念事項については、Mike Pennisiが良いポストをしています。

エコシステムやJavaScriptについて、特定の方法だけを推し進めたり強制したりするのは、我々の意図しているところではありません。
新しいアイデアについての議論や実現を維持していくことが目標です。

Hesitations

Other Considerations

我々はまたこれらの選択も模索しました。

プリセットをリネームし、安定度をより明確にする。バージョン管理の問題は解決しない。
・バージョン管理をstage-0.xのようにプリセット毎に独立して行い、必要に応じてバージョンアップする。
・古いプリセットには警告やエラーを出す。

いずれにせよ、我々がステージという仕組みを持ち続けている限り、開発者は使いたいプロポーザルがどのステージにあるかを調べなければならないでしょう。

Why Now?

前はどうして削除しないと言ったのか?
Stage Presetsは長らくBabelの一部であって、Babelの使い方に複雑化が増すことに対しての懸念がありました。
Stage Presetsには多くのツール、ドキュメント、記事、そして知識が存在します。
結局は他の誰かが必然的にStage Presetsを作成するのだから、そうなるくらいなら公式で維持した方がよいと以前は考えていました。

We're trying to determine the right level of feedback: if it's only the committee that decides what goes into the language, it may lead to well-specified features that are not needed, but if the community expects that in-progress, experimental proposals are considered stable or ok to use in production without consequence, then we'll have other issues.
性急すぎず慎重すぎず、ことを進めたいと考えています。
Babelはその実験を行うには適切な場所ですが、しかしその境界がどこにあるかは知る必要があります。

プリセットの削除は"機能"と見做されます。
各自はプロポーザルを使用するときはそれぞれを明示的に指定しなければならず、不安定性や有用性、複雑さは各プロポーザルによってまちまちです。

おそらく最初は多大な反発が来ることを予期していますが、長期的にはStage Presetsを削除することが我々にとってよりよい決定であると信じています。
しかしながら、それは使いやすさを切り捨てたり、新規ユーザを諦めたり、ドキュメントを放棄したりするということでは決してありません。
我々はこれからも、プロジェクトの安定を維持し、物事をよりよくするツールを提供し、知っていることをドキュメント化します。

Migrating

自動的なマイグレーションのためにbabel-upgradeを用意しました。
npx babel-upgradeで実行可能です。

マイグレーションはStage Presetsを削除するだけです。
いくつかのステージでは、開発者はどのようなプロポーザルが存在するかを知っておく必要があります。
プロポーザルの中には不安定なものも存在するからです。
create-react-appなど、他のプリセットやツールチェーンを使っている場合は、直接的に影響することはありません。

Stage Presetsは7.0.0-beta.52で廃止されました。
すぐにマイグレーションできない場合は、解決するまでバージョンをbeta.54に固定することをお勧めします。
beta.54より後では、移行方法を示すメッセージと共にエラーが発生します。

もうひとつの解決策としては、同じプラグインを含む独自のプリセットを作成する方法があり、こちらの方法ではBabelを自由にアップグレード可能です。
将来的には、babel-initでプラグインをインタラクティブにセットアップしたり、babel-upgradeで現在のステージングプラグインを表示・追加できるようにしたいと思っています。

おそらく今後は、Babelは表に出てこない低レベルツールとしてとどまり、開発者はcreate-react-appのような高レベルフレームワークに頼っていくようになるべきでしょう。

Preventing Proposal Lock-In

James DiGioiaが、パイプライン演算子|>の使用方法の変更に関するポストを書いています。

この記事のポイントは、プロポーザルが未だ流動的であって、記述方法が複数あるということです。
Babelのプラグインとしては、現在3種類ある記述方法を何れも使用可能にするために、プラグインの記述方法が変更されるべきと考えました。
これはTC39とBabelにとって、比較的新しいアプローチです。

以前はプロポーザルのプラグインをconfigに追加すれば動きましたが、現在ではデフォルト動作がなくなり、どのプロポーザルを選択するかのフラグをユーザに要求します。
現時点では優先されているオプションがないことを明確にしています。

{
  "plugins": [
-   "@babel/plugin-proposal-pipeline-operator"
+   ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
  ]
}

この書式は、プロポーザルは今後も変わる可能性があり、誰からのフィードバックも受け入れる用意がある、ということを示唆するために今後継続していく予定です。

Ecosystem Maintenance Burden

言語の"syntax budget"は、言語自体の複雑さにのみ影響されるのではなく、ツールのそれも含みます。
新しい構文が追加されるたびに、他のJavaScriptプロジェクトのメンテナに新たな負担が発生します。

構文が追加されるたびに、他の多くもアップデートが必要になります。
パーサー(babylon)、シンタックスハイライト(language-babel)、Linter(babel-eslint)、テストフレームワーク(jest・ava)、フォーマッタ(prettier)、カバレッジツール(istanbul)、Minifier(babel-minify)、その他諸々。

ステージ0のプロポーザルをサポートするために、acorn、eslint、jshint、typescriptなどに多くのIssueが上がっています。
Babelがサポートしているからです。
プロポーザルの変更に追随するプロジェクトは多くありません。
メンテナンスがとても大変だからです。
絶え間ない更新や攪乱にもかかわらず、Babelがついていっていること自体が驚くべきことです。

いったい誰がそんな仕事をしているのでしょう。
全てが機能することを確認するのは誰の責任?
あらゆるプロジェクトで、開発者(主にボランティア)のリソースが全く足りていませんが、それでも我々は全面的にIssueを受け付けています。

Babelは実験的な機能でも受け入れる珍しい方針を採っています。
同時に、他のプロジェクトがプロポーザルに慎重な姿勢を取ることは自然なことです。
新しい言語機能を希望するのであれば、TC39に参加して、プロポーザルをステージ4に引き上げましょう。

The Future

Babelプロジェクトの目的は、TC39のプロセスの一部として機能することです。
より新しい(ステージ0から2)プロポーザルを実装し、ユーザからのフィードバックを受けながら、さらに古いバージョンのJavaScriptもサポートするという革新的プロジェクトです。
このポストは、JavaScriptエコシステムの中でBabelがどのような位置を目指しているかを解説しています。
Babel v7のRCはすぐにリリースされる予定です。


もしBabelにコントリビュートしたいと思ってくれたのであれば、Patreonで支援することができます。
あなたの会社にOpen Collectiveのスポンサーになってもらうか、仕事としてBabelに関わってください。
我々はcollective ownershipに感謝しています。

全てのレビュアーに感謝します。
Twitterで気軽にご意見ください。

感想

ステージ0なんて明日いきなり動かなくなっても不思議ではない実験的機能でしかないのに、誰も彼もが何も考えずにstage-0って書いてるせいでTC39側に動きを変えるなという無形の圧力がかかっとるんじゃ、という内容。
ただ、いつ『動作を変えたせいで大変なコストがかかった』という記述が出てくるのかと思っていたら最後まで出てこず、『動作を変えたら大変なコストがかかるぞ』としか言ってないのが残念というかオチが弱い。

ということで今後のバージョンでpreset-stage-Xは使用できません。
必要な場合は、必要なプロポーザルを個別に導入しましょう。
たとえばOptional Chainingを使いたい場合は、@babel/preset-stage-0ではなく@babel/plugin-proposal-optional-chainingと書くことになります。

というかそもそも『わざわざ古い文法に書き換える』という現状が異常なので、いい加減どうにかしてほしいですね。
だってPHP7のソースをPHP5に書き換えるライブラリとか聞いたことないでしょう。
ましてやDraft段階のRFCを全部使えるようにするライブラリなど、今も昔もこれからも絶対出てくるわけないです。

もっともJavaScript以外のほとんどの言語はサーバ側実行だからサーバ側の調整でなんとかなるのに対し、JavaScriptはユーザのブラウザに依存するから仕方ないという面もあるのですけどね。