Skip to content

Template のパースと AST

続いては compiler-core に実装されている方の parser と AST について見ていきます.
SFC の parser はこれを利用しています.

AST

AST (Abstract Syntax Tree) です.

おそらく,Vue.js のコンパイラが持っている中間的なオブジェクトでもっとも複雑なオブジェクトです.
ここに各ディレクティブの情報や,マスタッシュ,スロット,などが AST として表現されています.

実装は,compiler-coreast.ts にあります.

packages/compiler-core/src/ast.ts

全体像を読んでみましょう.

Node の種類を見てみると,いくつかの区分けがあることがわかります.

29export enum NodeTypes {
30  ROOT,
31  ELEMENT,
32  TEXT,
33  COMMENT,
34  SIMPLE_EXPRESSION,
35  INTERPOLATION,
36  ATTRIBUTE,
37  DIRECTIVE,
38  // containers
39  COMPOUND_EXPRESSION,
40  IF,
41  IF_BRANCH,
42  FOR,
43  TEXT_CALL,
44  // codegen
45  VNODE_CALL,
46  JS_CALL_EXPRESSION,
47  JS_OBJECT_EXPRESSION,
48  JS_PROPERTY,
49  JS_ARRAY_EXPRESSION,
50  JS_FUNCTION_EXPRESSION,
51  JS_CONDITIONAL_EXPRESSION,
52  JS_CACHE_EXPRESSION,
53
54  // ssr codegen
55  JS_BLOCK_STATEMENT,
56  JS_TEMPLATE_LITERAL,
57  JS_IF_STATEMENT,
58  JS_ASSIGNMENT_EXPRESSION,
59  JS_SEQUENCE_EXPRESSION,
60  JS_RETURN_STATEMENT,
61}
  • 無印
  • containers
  • codegen
  • ssr codegen

結論から言ってしまうと,codegen と ssr codegen は Vapor のコンパイラには関係ありません.
これは,後に説明する IRtransform という概念に関連しますが,Vapor Mode では codegen のための情報は IR に集約されています.
しかし実は,従来の Vue.js (非 Vapor Mode) には IR という概念がなく,あくまで AST として出力コードも表現していました.
Vapor Mode では transformer によって AST を IR に変換しますが,Vue.js (非 Vapor Mode) では AST (無印, containers) を AST (codegen, ssr codegen) に変換してそれを codegen に渡しています.

今回は,Vapor Mode のコンパイラの設計について説明するため,codegen と ssr codegen については触れません.
それ以外についてみていきましょう!

無印

まずは特に区分のないベーシックな AST Node の種類です.

29export enum NodeTypes {
30  ROOT,
31  ELEMENT,
32  TEXT,
33  COMMENT,
34  SIMPLE_EXPRESSION,
35  INTERPOLATION,
36  ATTRIBUTE,
37  DIRECTIVE,

Root

Root はその名の通り,template のルートを表しています.
children にまた Node を持っています.

111export interface RootNode extends Node {
112  type: NodeTypes.ROOT
113  source: string
114  children: TemplateChildNode[]
115  helpers: Set<symbol>
116  components: string[]
117  directives: string[]
118  hoists: (JSChildNode | null)[]
119  imports: ImportItem[]
120  cached: (CacheExpression | null)[]
121  temps: number
122  ssrHelpers?: symbol[]
123  codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
124  transformed?: boolean
125
126  // v2 compat only
127  filters?: string[]
128}

Element

Element は要素を表す Node です.
<p><div> などの要素がこれに該当します.
コンポーネントやスロットもこれに該当します.

これらもまた children に Node を持っています.
属性情報やディレクティブ情報も持っています.

136export interface BaseElementNode extends Node {
137  type: NodeTypes.ELEMENT
138  ns: Namespace
139  tag: string
140  tagType: ElementTypes
141  props: Array<AttributeNode | DirectiveNode>
142  children: TemplateChildNode[]
143  isSelfClosing?: boolean
144  innerLoc?: SourceLocation // only for SFC root level elements
145}
146
147export interface PlainElementNode extends BaseElementNode {
148  tagType: ElementTypes.ELEMENT
149  codegenNode:
150    | VNodeCall
151    | SimpleExpressionNode // when hoisted
152    | CacheExpression // when cached by v-once
153    | MemoExpression // when cached by v-memo
154    | undefined
155  ssrCodegenNode?: TemplateLiteral
156}
157
158export interface ComponentNode extends BaseElementNode {
159  tagType: ElementTypes.COMPONENT
160  codegenNode:
161    | VNodeCall
162    | CacheExpression // when cached by v-once
163    | MemoExpression // when cached by v-memo
164    | undefined
165  ssrCodegenNode?: CallExpression
166}
167
168export interface SlotOutletNode extends BaseElementNode {
169  tagType: ElementTypes.SLOT
170  codegenNode:
171    | RenderSlotCall
172    | CacheExpression // when cached by v-once
173    | undefined
174  ssrCodegenNode?: CallExpression
175}

Text

Text はその名の通り Text です.
<p>hello</p>hello がこれに該当します.

183export interface TextNode extends Node {
184  type: NodeTypes.TEXT
185  content: string
186}

Comment

Comment はコメントです.
<!-- comment --> がこれに該当します.

188export interface CommentNode extends Node {
189  type: NodeTypes.COMMENT
190  content: string
191}

SimpleExpression

SimpleExpression は template 中に登場するシンプルな式です. 何がシンプルで何がシンプルでないかを説明するのは少し難しいですが,例えば ao.a はシンプルで,(() => 42)() などはシンプルではありません.

{{ foo }}foo や,<button @click="handlers.onClick">handlers.onClick がこれに該当します.

232export interface SimpleExpressionNode extends Node {
233  type: NodeTypes.SIMPLE_EXPRESSION
234  content: string
235  isStatic: boolean
236  constType: ConstantTypes
237  /**
238   * - `null` means the expression is a simple identifier that doesn't need
239   *    parsing
240   * - `false` means there was a parsing error
241   */
242  ast?: BabelNode | null | false
243  /**
244   * Indicates this is an identifier for a hoist vnode call and points to the
245   * hoisted node.
246   */
247  hoisted?: JSChildNode
248  /**
249   * an expression parsed as the params of a function will track
250   * the identifiers declared inside the function body.
251   */
252  identifiers?: string[]
253  isHandlerKey?: boolean
254}

Interpolation

これはマスタッシュです.
{{ foo }} がこれに該当します.

256export interface InterpolationNode extends Node {
257  type: NodeTypes.INTERPOLATION
258  content: ExpressionNode
259}

Attribute

属性 (ディレクティブではない) がこれに該当します.
<div id="app">id="app" がこれに該当します.

193export interface AttributeNode extends Node {
194  type: NodeTypes.ATTRIBUTE
195  name: string
196  nameLoc: SourceLocation
197  value: TextNode | undefined
198}

Directive

ディレクティブです.

v-on:click="handler"v-for="item in items" がこれに該当します.
もちろん,@click="handler"#head などのショートハンドも含まれます.

200export interface DirectiveNode extends Node {
201  type: NodeTypes.DIRECTIVE
202  /**
203   * the normalized name without prefix or shorthands, e.g. "bind", "on"
204   */
205  name: string
206  /**
207   * the raw attribute name, preserving shorthand, and including arg & modifiers
208   * this is only used during parse.
209   */
210  rawName?: string
211  exp: ExpressionNode | undefined
212  arg: ExpressionNode | undefined
213  modifiers: string[]
214  /**
215   * optional property to cache the expression parse result for v-for
216   */
217  forParseResult?: ForParseResult
218}

containers

containers は,特定の構造を持つ Node です.

39  COMPOUND_EXPRESSION,
40  IF,
41  IF_BRANCH,
42  FOR,
43  TEXT_CALL,

順番は前後してしまいますが,わかりやすいものからみていきましょう

If, IfBranch

If, IfBranchv-if, v-else-if, v-else で表現される Node です.

286export interface IfNode extends Node {
287  type: NodeTypes.IF
288  branches: IfBranchNode[]
289  codegenNode?: IfConditionalExpression | CacheExpression // <div v-if v-once>
290}
291
292export interface IfBranchNode extends Node {
293  type: NodeTypes.IF_BRANCH
294  condition: ExpressionNode | undefined // else
295  children: TemplateChildNode[]
296  userKey?: AttributeNode | DirectiveNode
297  isTemplateIf?: boolean
298}

構造的には IfNodeIfBranchNode を複数持つ構造で,IfBranchNodecondition (条件) と,children (その条件にあった時の Node) を持ちます.
v-else の場合は conditionundefined になります.

For

v-for で表現される Node です.

300export interface ForNode extends Node {
301  type: NodeTypes.FOR
302  source: ExpressionNode
303  valueAlias: ExpressionNode | undefined
304  keyAlias: ExpressionNode | undefined
305  objectIndexAlias: ExpressionNode | undefined
306  parseResult: ForParseResult
307  children: TemplateChildNode[]
308  codegenNode?: ForCodegenNode
309}
311export interface ForParseResult {
312  source: ExpressionNode
313  value: ExpressionNode | undefined
314  key: ExpressionNode | undefined
315  index: ExpressionNode | undefined
316  finalized: boolean
317}

<div v-for="it in list"> の場合は,sourcelist, valueit になります.

CompoundExpression

これは少しわかりずらい概念です.

compound は「複合の」と言った意味があり,この Node は複数の Node から構成される Node です.

261export interface CompoundExpressionNode extends Node {
262  type: NodeTypes.COMPOUND_EXPRESSION
263  /**
264   * - `null` means the expression is a simple identifier that doesn't need
265   *    parsing
266   * - `false` means there was a parsing error
267   */
268  ast?: BabelNode | null | false
269  children: (
270    | SimpleExpressionNode
271    | CompoundExpressionNode
272    | InterpolationNode
273    | TextNode
274    | string
275    | symbol
276  )[]
277
278  /**
279   * an expression parsed as the params of a function will track
280   * the identifiers declared inside the function body.
281   */
282  identifiers?: string[]
283  isHandlerKey?: boolean
284}

{{ foo }} + {{ bar }} などがこれに該当します.

これは,直感的には Interpolation + Text + Interpolation という構造になりそうですが,
Vue.js のコンパイラはこれらをまとめて CompoundExpression として扱います.

注目するべきものは,children に string や Symbol といった方が見受けられる点です.

269  children: (
270    | SimpleExpressionNode
271    | CompoundExpressionNode
272    | InterpolationNode
273    | TextNode
274    | string
275    | symbol

これは,部分的な文字列はもはや何かの AST Node として扱うのではなく,リテラルで簡略的に扱うための仕組みです.\

{{ foo }} + {{ bar }}

部分はわざわざ Text Node

の間の文字列の + のとして表現するよりも," + " というリテラルとして扱う方が効率的です.

以下のような AST になるイメージです.

json
{
  "type": "CompoundExpression",
  "children": [
    { "type": "Interpolation", "content": "foo" },
    " + ",
    { "type": "Interpolation", "content": "bar" }
  ]
}

TextCall

319export interface TextCallNode extends Node {
320  type: NodeTypes.TEXT_CALL
321  content: TextNode | InterpolationNode | CompoundExpressionNode
322  codegenNode: CallExpression | SimpleExpressionNode // when hoisted
323}

Text を createText という関数呼び出しとして表現する際の Node です.
とりあえず,あまり気にしなくて良いです.


とりあえずここまでで必要な AST の Node について見てきました.
ここからはこれらの AST を生成するためのパーサーの実装についてみてみましょう!