Vue.jsのreactive systemがどのように実装されているか追ってみた. 初回はコンパイラの挙動を確認.
- Vuejs v2.6.10
src/compiler/index.js
compilerのentrypointとなるのがsrc/compiler/index.js
.
(Vue.jsはflowを用いてtype checkを行っている. compiler周りの型はflow/compiler.js
に記載.)
parse(template, options)
でASTを生成し,generate(ast, options)
でcodeを生成する.
Virtual DOMのpatch/mergeを効率よく行うため,返り値のCompiledResult
にcode
だけでなくASTを持たせている.
staticRenderFns
についてはparser部分で説明する.
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
src/compiler/parser/index.js
.vue
ファイルからASTを生成する部分. parse
関数内部で関数定義してて長いので,重要なところだけピックアップする.
VueのテンプレートはHTMLのDOMをベースにしていて,特に
- Mustache
- Directive の2点を拡張記法としてもっている.
そのためパース自体はhtml-parse
を用いており,parse
の主な仕事はHTML DOMのASTを返すのとパース時のエラー出力となっている.
/**
* Convert HTML string to AST.
*/
export function parse (
template: string,
options: CompilerOptions
): ASTElement | void {
warn = options.warn || baseWarn
/* omit */
parseHTML(template, {
/* omit */,
start (tag, attrs, unary, start, end) { /* omit */ },
})
return root // ASTのroot Node
}
関係ないけど階段状にインポートしてるの好き.
import {
addProp,
addAttr,
baseWarn,
addHandler,
addDirective,
getBindingAttr,
getAndRemoveAttr,
getRawBindingAttr,
pluckModuleFunction,
getAndRemoveAttrByRegex
} from '../helpers'
Abstract Syntax Tree
generate
が受け取るast
の型ASTElement
はflow/compiler.js
に定義されている.
ASTは以下の3種類に分類され,ASTNode
がそれぞれのtype
を元に判別している.
ASTElement
: type: 1ASTText
: type: 2ASTExpression
: type: 3
type ASTNode = ASTElement | ASTText | ASTExpression
ASTElement
を例にとると以下のようになっている.
declare type ASTElement = {
type: 1;
tag: string;
attrsList: Array<ASTAttr>;
attrsMap: { [key: string]: any };
rawAttrsMap: { [key: string]: ASTAttr };
parent: ASTElement | void;
children: Array<ASTNode>;
/* omit */
staticRoot?: boolean;
text?: string;
component?: string;
if?: string;
for?: string;
transition?: string | true;
/* omit */
model?: {
value: string;
callback: string;
expression: string;
};
}
ASTの構造はASTElement
がrootとなり,branchにASTNode
を再帰的に持つ構造になる.
staticRoot?
やcomponent?
等のパラメータは,そのASTNode
以下がstatic
であるかdynamic
であるか,等レンダリングの最適化の際に参照される.
この最適化機構はsrc/compiler/optimizer.js
のmarkStatic
及びmarkStaticRoot
が担う.
src/compiler/codegen/index.js
generate
はASTからrender
とstaticRenderFns
の2つを生成する.
render
がVueのDynamic Renderingを担当し,staticRenderFns
は静的な(変更のない)DOMを生成する.
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
genElement
はASTElement
のstaticRoot?
やfor?
等を確認し,それぞれのディレクティブ・タグに対応するElement
, Component
を生成する.
各々の生成はgenStatic
, genIf
のようにそれぞれのジェネレータで行う.
各々のジェネレータは受け取ったASTを処理した後に再帰的にgenElement
を呼び出す.
最終的にgenElement
は最小単位であるElement
の生成を行う.
Element
は<tag data>children</tag>
からなるDOMである.
以下がその生成部分.
data = genData(el, state)
/* omit */
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
Element
のdata部分はgenData
が担当する. data部分に含まれるのはclass
, directive
, model
.
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ','
/* omit */
// component v-model
if (el.model) {
data += `model:{value:${
el.model.value
},callback:${
el.model.callback
},expression:${
el.model.expression
}},`
}
/* omit */
return data
}
最終的にCompiledResult
が生成される.
declare type CompiledResult = {
ast: ?ASTElement;
render: string;
staticRenderFns: Array<string>;
stringRenderFns?: Array<string>;
errors?: Array<string | WarningMessage>;
tips?: Array<string | WarningMessage>;
};
Summary
- Vueのコンパイラは
parser
とgenerator
からなる. parser
はhtml-parse
を元にASTを生成する.generator
はASTを元に動的部分と静的部分のrender
群を生成する
Next
次回はsrc/core/observer
を読む.
(名前からVueのreactive systemがObserver patternベースなんだろうな)
Comments