🧊

`@typescript-eslint/typescript-estree`をブラウザで動かし、型チェックもやる

一見簡単に見えるこれが意外と大変だった・・・ので、その学びをメモ。

ブラウザで動きつつ、型チェックオプションも有効化できるtypescript-estreeは、ここで試せます!(宣伝)

https://leaysgur.github.io/js-multi-ast-viewer/

@typescript-eslint/typescript-estree

TypeScriptのASTといえばTS-ESTreeというわけで、現状のデファクトであるパーサー実装がコレ。

typescript-eslint/typescript-eslint: :sparkles: Monorepo for all the tooling which enables ESLint to support TypeScript https://github.com/typescript-eslint/typescript-eslint

パーサー実装の本体であるtypescript-estreeの実装は、

  • ファイルをTSCでパース
    • 型チェックが必要なら、TSのProjectを用意してDiagnosticsを取得
  • それをESTreeライクに整形

という2段階の処理になってる。

@typescript-eslint/parser@typescript-eslint/typescript-estreeの関係 | Memory ice cubes https://leaysgur.github.io/posts/2025/05/08/123105/

で、ASTはブラウザで見たいよねってなった時に、愚直にimportしてビルドしようとすると、まぁエラーになるんだな〜これが。

いろんな事情があるけど、基本的にはTypeScriptとESLintをブラウザで動かすのが簡単ではないってところに起因してると思う。

なので、それぞれの問題を解決してやれば、ブラウザでも動かせるようになる。ただただ面倒なだけ。

今回は、TS-ESTreeのASTだけでなく、型チェックオプションであるerrorOnTypeScriptSyntacticAndSemanticIssuesも使えるようにしたかったので、余計に大変だった。

先行者たち

たとえば、ast-explorerの場合。

AST Explorer https://ast-explorer.dev/

unenvのモックを駆使してrolldownでブラウザ向けに事前ビルドしておいて、それを使ってる。

https://github.com/sxzz/ast-explorer/blob/9ddc3d0f2c5b9f9e0f086ddc6af3b138da113e58/scripts/build-parser.ts

単にASTが欲しいだけなら、これがもっともクリーンな正攻法になると思う。

ただし、typescript-estreeではなくparserのほうを使ってるので、型チェックはできてない。

たとえば、astexplorerの場合。

AST explorer https://astexplorer.net/

こちらも同様に、webpack.config.jsで、同じようにNode依存の部分をモックしてる。

https://github.com/fkling/astexplorer/blob/8888701e97c2efebe6fb118484e67cfc0077d08c/website/webpack.config.js#L275

最後に、本家のPlaygroundの場合。

https://typescript-eslint.io/play/

型チェックもできてるように思えたので仕組みを追ってみたところ、

  • どうやら別建てでTSCを動かしつつ
  • monacoエディターとも連携したり
  • その流れでTS-ESTreeを表示したり

と、だいぶがんばってた。

https://github.com/typescript-eslint/typescript-eslint/tree/1c0e1ae8d88d210e255814ee998bb9d7eefe6ba8/packages/website-eslint

ともあれ方向性としてはこれが求めていたものなので、TSCとTS-ESTreeを分けて考えることにした。

TypeScriptをブラウザで動かして型チェックもする

というわけで、自作していく。

公式によると、ブラウザであれこれしたい場合は、Virtual File Systemを使うというのが筋らしい。

TypeScript-Website/packages/typescript-vfs at v2 · microsoft/TypeScript-Website https://github.com/microsoft/TypeScript-Website/tree/70bf492500c6b2f2296ec28fc0d825d5a98ffd86/packages/typescript-vfs

ただ・・・、READMEをチラ見するとすぐ気付くと思うけど、それなりに難解なので、これまた既存の便利なライブラリを利用する。

ts-morph/packages/bootstrap at latest · dsherret/ts-morph https://github.com/dsherret/ts-morph/tree/a1c61c7e04928de5d8fc12f32a76e3ed5b7ffea1/packages/bootstrap

みんな大好きts-morphには、ずばりな用途のサブパッケージがあって、@ts-morph/bootstrapが顧客が本当に欲しかったもの。

これを使うと、なんとこれだけのコードで、ブラウザで型チェックができちゃう。

import { createProject, ts } from "@ts-morph/bootstrap";

const project = await createProject({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile("main.ts", "let a: number | null = null;");

const diagnostics = ts.getPreEmitDiagnostics(project.craeteProrgram());

便利すぎる。

このsourceFileがTS ASTになってるので、あとはこれをTS-ESTreeに変換するだけ。

@typescript-eslint/typescript-estree/use-at-your-own-risk

次に、TS ASTをTS-ESTreeに変換したいけど、それ用の処理は表向きには公開されてない・・・。

が、さっき見てたPlayground用に抜け道が公開されてるので、それを使う。use-at-your-own-riskという名に従いながら。

https://github.com/typescript-eslint/typescript-eslint/blob/1c0e1ae8d88d210e255814ee998bb9d7eefe6ba8/packages/typescript-estree/src/use-at-your-own-risk.ts

astConverter()が欲しかったやつで、sourceFileを渡せばそれで終わり。

import { createProject } from "@ts-morph/bootstrap";
import { astConverter } from "@typescript-eslint/typescript-estree/use-at-your-own-risk";

const project = await createProject({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile(filename, code);

const { estree } = astConverter(sourceFile, options, false);

これでTS-ESTreeのASTが手に入るので、あとはよしなに表示すればよい。

型エラーに関しては、どうやらTSから発せられたすべてのエラーをthrowしてるわけではないらしかった。

というわけで、このファイルに従ってフィルターしてあげる。

https://github.com/typescript-eslint/typescript-eslint/blob/1c0e1ae8d88d210e255814ee998bb9d7eefe6ba8/packages/typescript-estree/src/semantic-or-syntactic-errors.ts

これで、typescript-estreeparseAndGenerateServices()という一番ローレベルなAPIを再現できたというわけ。

一連のコードはここにある。

https://github.com/leaysgur/js-multi-ast-viewer/blob/3cdf679f9f3e06fa38d622a853ba707f8a475142/src/parsers/typescript-estree.ts

これが最小手数なのでは?と思ってるけど、もっとシュッとできる方法があれば教えてください!

感想

大変だった・・・!

そして遅延ロードできるようにはしてるものの、やっぱりファイルサイズがデカいのよな〜。なんと9MBくらいかかる。

それに引き換えoxc-parserなら、2MBいかないくらいで済むし、WASMなのでブラウザでも動くし、だいぶ敷居が低いと思う。 TSの型エラーは完全に対応できてないけど、そこはこれからに期待しよう。