Template のパースと AST
続いては compiler-core
に実装されている方の parser と AST について見ていきます.
SFC の parser はこれを利用しています.
AST
AST (Abstract Syntax Tree) です.
おそらく,Vue.js のコンパイラが持っている中間的なオブジェクトでもっとも複雑なオブジェクトです.
ここに各ディレクティブの情報や,マスタッシュ,スロット,などが AST として表現されています.
実装は,compiler-core
の ast.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 のコンパイラには関係ありません.
これは,後に説明する IR
と transform
という概念に関連しますが,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 中に登場するシンプルな式です. 何がシンプルで何がシンプルでないかを説明するのは少し難しいですが,例えば a
や o.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
, IfBranch
は v-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}
構造的には IfNode
が IfBranchNode
を複数持つ構造で,IfBranchNode
は condition
(条件) と,children
(その条件にあった時の Node) を持ちます.v-else
の場合は condition
が undefined
になります.
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">
の場合は,source
が list
, value
が it
になります.
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 }}
の間の文字列の +
のとして表現するよりも," + "
というリテラルとして扱う方が効率的です.
以下のような AST になるイメージです.
{
"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 を生成するためのパーサーの実装についてみてみましょう!