🧊

Prettierがjs-in-xxxをどう処理しているか

Vue/HTML/Markdownなどに埋め込まれたJS/TS部分が、どうフォーマットされるのか調べたかった。

Here be dragons…, yet another dragons… 🐉

js-in-xxxになれるparser

まず、どこで観測できるか。

  • Markdown系
    • markdown: コードブロック内
    • mdx: コードブロック内、import/export
  • HTML系
    • html: <script>タグ内、onclick等のイベントハンドラ属性
    • vue: <script>タグ内、テンプレート内の各種ディレクティブ・式
    • angular: テンプレート内の各種バインディング・ディレクティブ

HTML系のparserは、他にもmjmlとかlwcとかもあるけど、そこでは処理されない。

埋め込み処理の流れ

  • 親ファイル側の処理の一環として、埋め込み部を処理するパートがある
  • その際、textToDoc()という内部的な処理があって、そこで状況に応じたparserprinterを使う
  • それがbabeltypescriptといった既存のJS/TSの処理でも使われてるビルトイン実装な場合もあれば
  • 特殊なケースでは__vue_expressionのような特別な実装が使われる

コードとしては、src/language-xxx/embed.jsから追っていくとよい。

この基本の流れは、css-in-jsでもjs-in-vueでもなんでも同じ。

ただそれぞれの組み合わせごとに、サポートしてる埋め込み対象も異なるなら、やってることもまちまちなんよな・・・。

今回の主旨であるjs-in-xxxという組み合わせでいうと、language-html/embed.jslanguage-markdown/embed.jsから追う。

markdown/mdxの場合

3パターンある。

  • 1: コードブロック
  • 2: MDXの場合、import/exportのそれぞれを、babelを使って個別に対応
  • 3: MDXの場合、JSX部も__js_expressionを使って対応

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-markdown/embed.js#L6

htmlの場合

ピュアなHTMLの場合は2パターン。

  • 1: <script>タグ -> babel
    • type="module"なら、__babelSourceType: "module"を設定
  • 2: イベントハンドラ(onclick="...") -> babel
    • __isHtmlInlineEventHandlerというフラグで、末尾;を処理したり、シングルクォートを使ったり
  • __embeddedInHtmlというフラグにも関連がある

vueの場合

  • 1: <script>タグ -> babel or typescript
    • lang="ts"かどうか

単純なのはこれだけで、あと全部は特殊なケース。

標準JSとして無効な構文を、有効なJSの構文になるようラップしてからパースしたりしてる。

2: <script generic="...">属性

(使われてるの見たことないけど)

  • 入力: T extends Record<string, unknown>, U
  • ラップ: type T<T extends Record<string, unknown>, U> = any
  • 抽出: AST.program.body[0].typeParameters.params

babel-tsを使って、__isEmbeddedTypescriptGenericParametersというフラグで処理する。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-html/embed/print-vue-script-generic-attribute-value.js#L24

3: v-forディレクティブ

なんと左辺と右辺で処理フローが違う!

左辺は、

  • 入力: (item, index) in items
  • ラップ: function _(item, index) {}
  • 抽出: AST.program.body[0].params

これも特殊なケースなのに、babel or babel-tsで無理やり処理されてるシリーズ。 フラグは__isVueForBindingLeftってやつ。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-html/embed/vue-v-for-directive.js#L27

右辺のパーサーは、__js_expression or __ts_expressionになる。

4: v-slotディレクティブ

  • 入力: { item, index }
  • ラップ: function _({ item, index }) {}
  • 抽出: AST.program.body[0].params

ここもbabel or babel-tsでやってて、フラグは__isVueBindingsというやつ。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-html/embed/vue-bindings.js#L19

5: @click(v-on)ディレクティブ

少し変わってて、初手は__vue_expressionで処理しようとして、エラーになったら、__vue_event_bindingにフォールバックする。

これは@click="foo()"@click="a++;b()"の差をよしなにするためらしいが、アクロバティックすぎる・・・。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-html/embed/vue-attributes.js#L79

6: それ以外

  • {{ expr }}: __vue_expression or __vue_ts_expression
  • v-if, v-show: __js_expression or __ts_expression
  • :class, :style (v-bind): __vue_expression or __vue_ts_expression

angularの場合

angular-estree-parserを使ってて、こっちはbabelを使ってない。

いちおう内部的なparser名はこういう感じ。

  • 1: __ng_action: (click)="onClick()"
  • 2: __ng_binding: [class]="myClass"
  • 3: __ng_interpolation: {{ expr | uppercase }}
  • 4: __ng_directive: *ngFor="let item of items; let i = index; trackBy: trackFn"

最初の2つはJS/TS互換なシンタックスだが、あと2つは違う。

特殊なparserたちのまとめ

これまで見てきた__vue_expressionとかのこと。textToDocOptions.parserで調べると出てくるやつらをまとめておく。

だいたいは、babelのパーサー定義時に、しれっと一緒に定義されてる。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-js/parse/babel.js#L265-L275

  • __(j|t)s_expression: babel or babel-tsisExpression: trueで使ってる
  • __vue(_ts)_expression: 同上
  • __vue(_ts)_event_binding: babel or babel-tsそのまま

__ng_xxxbabelを使ってない。(大事なことなので2回) これらは全部angular-estree-parserparseXxx()を使ってる。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-js/parse/angular.js#L82-L89

printerはというと、ぜんぶestreeで賄う。

https://github.com/prettier/prettier/blob/070c89bba46235f4948560ed612a11e89ccd2da9/src/language-js/print/index.js#L23-L29

特殊なフラグたちのまとめ

そして次に、特殊な処理のために使われるフラグたち。

もうわけがわからんね。

ポイントとしては、これらのフラグはparserで差し込まれるけど、それを使うのはprinter側ってところ。