Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JavaScript パーサーに using 対応をする過程で与えたエコシステムへの影響

JavaScript パーサーに using 対応をする過程で与えたエコシステムへの影響

このプレゼンテーションは、baseballyama氏とota-meshi氏が、JavaScriptパーサー `acorn` に `using` 構文(Explicit Resource Management) を対応させる過程と、その実装がJavaScriptエコシステム全体に与えた影響について解説するものです。

発表の概要

前半:`acorn` への `using` 構文の実装 (baseballyama氏)
`using` 構文とは、リソースのクリーンアップ処理(`[Symbol.dispose]`)をブロックの終わりで自動的に呼び出す構文です。非同期用の `await using` もあります。
構文の複雑さとして、`using` は予約語ではないため、`using using = ...` は有効ですが、分割代入が使えない、`await using` の後に改行が許されないなど、非常に複雑なルールがあります。
実装プロセスとして、仕様書を読み、Babelなどの既存実装を参考にしながら、`acorn` のコードを初めて読み解き、`using` と `await using` の対応を段階的に行いました。この分野ではAIは役に立たなかったと報告されています。

後半:エコシステムへの影響 (ota-meshi氏)
トップレベル `using` の問題として、`using` 宣言は、ES Module のトップレベルでは許可されますが、Script のトップレベルでは構文エラーになります。
CommonJS (CJS) の特異性として、CJS は一般的に Script 扱いですが、Node.js ではファイル全体が関数でラップされるため、トップレベルに見える `using` 宣言が実際には関数内となり、エラーになりません。
エコシステムへの波及として、この `acorn` での対応がきっかけとなり、CJS の特異な動作(トップレベル `return` や `using`)をどう扱うかが Acorn, Babel, ESLint, SWC などのメンテナー間で議論されました。
結論として、解決策として、`acorn` は `sourceType: "commonjs"` という新しいオプションを導入しました。この変更は Babel や SWC にも波及しました。最終的な結論として「CommonJS の使用をやめましょう」と提言されています。

Avatar for Yuichiro Yamashita

Yuichiro Yamashita

November 16, 2025
Tweet

More Decks by Yuichiro Yamashita

Other Decks in Technology

Transcript

  1. baseballyama 自己紹介
 • baseballyama (山下 裕一朗)
 • フライル株式会社
 ◦ ソフトウェアエンジニア


    
 • Svelte コアチームメンバー
 ◦ 元々は Svelte コンパイラを担当
 ◦ 最近は主に eslint-plugin-svelte や svelte-eslint-parser を担当
 

  2. 最小のコード例
 {
 using resource = {
 value: 'Hello, using!',
 [Symbol.dispose]()

    {
 console.log('Resource disposed');
 }
 };
 console.log(resource.value);
 }
 
 // 実行結果:
 // Hello, using!
 // Resource disposed

  3. 最小のコード例
 {
 using resource = {
 value: 'Hello, using!',
 [Symbol.dispose]()

    {
 console.log('Resource disposed');
 }
 };
 console.log(resource.value);
 }
 
 // 実行結果:
 // Hello, using!
 // Resource disposed
 【解説】
 1. console.log('Resource disposed') を実行
 2. ブロックを抜ける際に
   Symbol.dispose() が自動実行
 3. リソースのクリーンアップが保証される
 Symbol が 必要 using キーワード
  4. もう少し具体的な例
 class AsyncDB {
 constructor(name) {
 console.log(`接続: ${name}`);
 }
 


    async [Symbol.asyncDispose]() {
 console.log('切断処理中...');
 await new Promise(resolve => setTimeout(resolve, 100));
 console.log('切断完了');
 }
 }
 async function main() {
 await using db = new AsyncDB('mydb');
 console.log('クエリ実行');
 }
 
 main().then(() => console.log('完了'));
 
 // 実行結果:
 // 接続: mydb
 // クエリ実行
 // 切断処理中...
 // 切断完了
 // 完了

  5. もう少し具体的な例
 class AsyncDB {
 constructor(name) {
 console.log(`接続: ${name}`);
 }
 


    async [Symbol.asyncDispose]() {
 console.log('切断処理中...');
 await new Promise(resolve => setTimeout(resolve, 100));
 console.log('切断完了');
 }
 }
 async function main() {
 await using db = new AsyncDB('mydb');
 console.log('クエリ実行');
 }
 
 main().then(() => console.log('完了'));
 
 // 実行結果:
 // 接続: mydb
 // クエリ実行
 // 切断処理中...
 // 切断完了
 // 完了
 await using キーワード Symbol が 必要
  6. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }

  7. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 


    💡 “using” は予約語ではないので変数名に使用可能 ✅
  8. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌
  9. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 予約語の一覧
 ⚠ “using” は予約語ではない
 “let” は strict モードに おいては予約語
  10. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした)
  11. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 関連する構文定義
 ⚠ “instanceof” は予約語であり、 BindingList を満たさないため、
   “await using” として処理してはいけない。

  12. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌
  13. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) ✅ 💡 “using” は分割代入と同時には使用できない ❌ 関連する構文定義
 分割代入は禁止されている ⚠ 分割代入を使用しているため “await using” として処理してはいけない。

  14. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる)
  15. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) ✅ 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる) 関連する構文定義
 改行は禁止されている ⚠ 改行を使用しているため“await using” として処理してはいけない。
   結果、 “using” という名前の Promise を await していると解釈される。

  16. 正しい構文はどれでしょうか
 1. { using using = resource; }
 
 2.

    { using let = resource; }
 
 3. async function f() {
 await using instanceof foo; 
 }
 
 4. async function test() {
 await using [first, ...rest] = arr; 
 }
 
 5. async function f() {
 await using
 x = resource
 }
 💡 “using” は予約語ではないので識別子に使用可能 ✅ 💡 “let” は予約語なので識別子には使用できない   (非 strict モードを除く) ❌ 😅 ここで “using” は識別子として扱われる   (私が acorn にコミットを積んだ時点では正しく   処理できていませんでした) 💡 “using” は分割代入と同時には使用できない ❌ 💡 “using” は改行の有無によって意味が変わる   (ここで “using” は識別子として扱われる)
  17. このセクションに行く前に前提を揃えましょう
 • (復習) 通称 using とは 
 正式には Explicit Resource

    Management と呼ばれる ES2026 に入る予定の機能。 
 “using” というキーワード (のようなもの) を使用することで、 
 今までは finally に書いていたような処理を宣言的に書ける機能。 
 • acorn とは 
 代表的な JavaScript パーサーの一種。ESLint / Prettier / Rollup など、 
 我々が普段使っているツールチェインの基礎として使われているライブラリ。 
 • パーサー とは 
 プログラムを文字列として読み取り構造化された情報に変換するプログラムのこと。 
 構造化した結果は、AST / CST と呼ばれるツリー構造であることが多い。 

  18. 実際にどう対応したか
 1. とりあえず仕様を読む 
 流石に仕様を理解しないとどうにもならないかなと思い仕様書を眺めた 
 https://github.com/tc39/proposal-explicit-resource-management 
 https://arai-a.github.io/ecma262-compare/?pr=3000 


    2. とりあえず AI に対応させてみる 
 → 結果的に全くうまくいかず、結局全て手書きすることになった 
 3. Babel / Chrome で動かしてみる 
 今回はありがたいことに既に Babel で using 対応がされていたので、Babel のパーサーを 実行してみたり、 Chrome でコードを実行してどうなるかを観察した 

  19. 実際にどう対応したか
 4. acorn のソースコードを読みなんとなく理解する 
 実はこの時点で初めて acorn のソースコードを読んだ 
 (acorn

    のパーサーが LL パーサーベースであることもここで知った) 
 (🙏今日は LLパーサー が何かは解説しません)
 5. まずは using の対応をしてみる 
 とにかくテストを追加しながら1個ずつ対応していった 
 変数宣言 → for of → ES2025以下の場合の対応 → module でない場合の対応 
 6. await using の対応をしてみる 
 using と同じ流れで対応
 

  20. 学んだこと
 • 仕様とBNFはスラスラ読めるようになった方がいい
 僕が普段読むのはせいぜい MDN や RFC なので、プログラミング言語の仕様書 (特に BNF)

    を読むのに苦労した。数式に慣れる、みたいなのと同じで日々 BNF を読み続けば慣 れるのだとは思うが、僕は普段からパーサーの開発をしているわけではないので、慣れる までに時間がかかるだろうなとは思った。 
 
 • AIが役に立たない領域があった
 “using” が新しい構文だからなのか、AI が acorn のようなプログラムに対する学習をあま りしていないのかはわからないが、ほとんどAIは役に立たなかった。 
 (テストの不足とかのレビューは少ししてもらったけど) 
 これは ESLint ルールで Scope Manager を多用するルールを実装する時にも同じことが言 えて、学習リソースが足りない分野だとまだまだ人間の出番はあるのかなと感じた。 

  21. 学んだこと
 • 大きな課題も複数の小さな課題に分解するとうまくいく
 「“using” 対応をする」というと、大きすぎる課題で何から取り組めばいいのかがわからなく なってしまうが、「まずは変数宣言をサポートしてみよう」といった自分の手に負えそうな単 位に分解して取り組むと最終的に大きな課題を解決できる。 
 
 •

    まずは取り組むことが大事
 僕にとって acorn は全く内部を知らないライブラリだったが、一度やるぞと決めたらなんだ かんだ対応できた。(何もわからない状態からでも2日で実装完了した) 
 やるぞと心に決めることにさえ成功すれば、実装が完了したも同然である。 
 

  22. ota-meshi 自己紹介
 • ota-meshi (太田 洋介)
 • フューチャー株式会社
 ◦ フロントエンドエンジニア


    
 • Vueコアチームメンバー
 • eslint-plugin-vueのメンテナー
 • eslint-plugin-svelteのメンテナー
 • ESLintチームAlumni
 ◦ ノルマがきつくてコミッターはやめた。
 ◦ 最近は Import Attributes とか新構文対応だけ参加してる 

  23. // トップレベル
 using x = foo();
 
 1. 有効
 2.

    エラー
 3. 場合による
 このusing宣言は有効ですか?

  24. Module? Script?
 • Module
 ◦ よく `import` とか `export`とか書いてるJavaScriptのこと。 


    ◦ 別の言い方だと `<script type="module">`で実行されるJavaScriptのこと。 
 • Script
 ◦ ModuleじゃないJavaScriptのこと。 
 ◦ 厳密モードにするには`"use strict"`しないといけないあれら。
 ◦ 別の言い方だと `<script>`や`<script type="text/javascript">`で実行されるJavaScriptの こと。
 • 厳密に構文をパースして仕様通りに構文エラーを検知するにはModuleなのかScriptなの かが超重要
 • Acornは`sourceType`というオプションで判断してパースしている。

  25. // トップレベル
 using x = foo();
 
 1. 有効
 2.

    エラー
 3. 場合による
 このusing宣言は、
 場合によって有効/エラーが変わる

  26. • Node.jsのCommonJSは内部的に関数でラップされる。
 • そのためScriptであっても、トップレベルのusingは、実質的にはトップレベ ルのusingではない。
 • そのため構文エラーではなく、usingは問題なく使用できる。
 (function (exports, require,

    module, __filename, __dirname, import) { // トップレベル? using x = foo(); })
 CommonJS × トップレベルのusing宣言?
 でもそれってNode.jsが 仕様に違反してるだけじゃないの?
  27. AcornのCommonJSサポート
 • どういうオプションにするかissueで議論された。 
 ◦ https://github.com/acornjs/acorn/issues/1373#issuecomment-2955462597 
 ◦ https://github.com/acornjs/acorn/issues/1376 


    ◦ Babelメンテナーの JLHwungさんも参加
 • トップレベルの`return`を許可する、`allowReturnOutsideFunction `というオプションがすでにあるので、こ れと同じように`allowTopLevelUsingDeclaration `みたいなオプションはどうか? 
 • 今後も似たようなオプション増殖させるの辛すぎない?Closure Compilerの` --assume_function_wrapper ` みたいなオプションにするのはどう? 
 • 実はトップレベルで` new.target`構文も使える(意味はないが)。 
 

  28. AcornのCommonJSサポート
 JavaScriptパーサーのCommonJSサポート
 • Acornは`sourceType`オプションに`"commonjs"`を受け入れられるようにしてCommonJS特有のパースを することにした(これまでは` "module"`と`"script"`のみが使えた)。
 https://github.com/acornjs/acorn/pull/1377 
 • Espreeは`sourceType:

    "commonjs"`オプションでAcornの` allowReturnOutsideFunction `オプションを有効 にしていたが、Acornの` sourceType`がそのまま使えるようになる予定 
 https://github.com/eslint/js/issues/662 
 • BabelメンテナーのJLHwungさんも議論に参加していたので、Babelも同じオプションを追加することに決定 した。
 https://github.com/babel/babel/pull/17390 
 • 議論を見ていたSWCのメンテナーの方が、SWCでも同様のオプションを受け入れられるようにしてた。 
 https://github.com/swc-project/swc/pull/10600