Study Notes
本博主會持續(xù)更新各種前端的技術(shù)劫樟,如果各位道友喜歡唱逢,可以關(guān)注螟凭、收藏蔓姚、點贊下本博主的文章酌儒。
Vue.js 源碼剖析-模板編譯
Vue 模板編譯
-
為什么需要模板編譯
Vue 2.x 使用 VNode 描述視圖以及各種交互,用戶自己編寫 VNode 比較復雜
-
模板編譯的目的
- 將模板(template)字符串轉(zhuǎn)換為渲染函數(shù)(render)
- 用戶只需要編寫類似 HTML 的代碼 - Vue 模板叨襟,通過編譯器將模板轉(zhuǎn)換為返回 VNode 的 render 函數(shù)
- .vue 文件在 webpack 構(gòu)建的過程中會被轉(zhuǎn)換成 render 函數(shù)
沙盒工具
官方提供 Vue 2.x 模板編譯沙盒
<iframe src="https://icqo5.csb.app/" style="width: 100%; height: 500px; border: 0px; border-radius: 4px; overflow: hidden; --darkreader-inline-border-top: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial;" title="vue-20-template-compilation" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" data-darkreader-inline-border-top="" data-darkreader-inline-border-right="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left=""></iframe>
Vue 2.6 Template Explorer 模板編譯沙盒
<iframe src="https://vue-template-explorer.netlify.app/#%3Cdiv%3E%0A%20%20%20%20%20%20%20%20%3Cheader%3E%0A%20%20%20%20%20%20%20%20%20%20%3Ch1%3EI'm%20a%20template!%3C%2Fh1%3E%0A%20%20%20%20%20%20%20%20%3C%2Fheader%3E%0A%20%20%20%20%20%20%20%20%3Cp%20v-if%3D%22message%22%3E%7B%7B%20message%20%7D%7D%3C%2Fp%3E%0A%20%20%20%20%20%20%20%20%3Cp%20v-else%3ENo%20message.%3C%2Fp%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E" style="width: 100%; height: 500px; border: 0px; border-radius: 4px; overflow: hidden; --darkreader-inline-border-top: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial;" title="vue-20-template-compilation" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" data-darkreader-inline-border-top="" data-darkreader-inline-border-right="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left=""></iframe>
Vue 3 Template Explorer 模板編譯沙盒
<iframe src="https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Cdiv%3E%5Cr%5Cn%20%20%3Cheader%3E%5Cr%5Cn%20%20%20%20%3Ch1%3EI'm%20a%20template!%3C%2Fh1%3E%5Cr%5Cn%20%20%3C%2Fheader%3E%5Cr%5Cn%20%20%3Cp%20v-if%3D%5C%22message%5C%22%3E%7B%7B%20message%20%7D%7D%3C%2Fp%3E%5Cr%5Cn%20%20%3Cp%20v-else%3ENo%20message.%3C%2Fp%3E%5Cr%5Cn%3C%2Fdiv%3E%22%2C%22ssr%22%3Afalse%2C%22options%22%3A%7B%22mode%22%3A%22function%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup%22%2C%22foo%22%3A%22setup%22%2C%22bar%22%3A%22props%22%7D%7D%7D" style="width: 100%; height: 500px; border: 0px; border-radius: 4px; overflow: hidden; --darkreader-inline-border-top: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial;" title="vue-20-template-compilation" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" data-darkreader-inline-border-top="" data-darkreader-inline-border-right="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left=""></iframe>
分析 render 函數(shù)
function anonymous() {
with (this) {
return _c('div', [
_m(0),
message ? _c('p', [_v(_s(message))]) : _c('p', [_v('No message.')]),
]);
}
}
- _c 是 createElement() 方法床蜘,定義的位置 src/core/instance/render.js 中
- 相關(guān)的渲染函數(shù)(_開頭的方法定義),在 src/core/instance/render-helps/index.js 中
// src/core/instance/render.js
// 對編譯生成的render進行渲染的方法
// 具體可前往上一章虛擬Dom源碼剖析
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
// src/core/instance/render-helps/index.js
// 生成文本虛擬節(jié)點
target._v = createTextVNode;
// 用于渲染靜態(tài)資源的方法
target._m = renderStatic;
// 將數(shù)據(jù)轉(zhuǎn)換為字符串格式
target._s = toString;
// src/core/vdom/vnode.js
// 生成文本虛擬節(jié)點
export function createTextVNode(val: string | number) {
return new VNode(undefined, undefined, undefined, String(val));
}
// src/core/instance/render-helpers/render-static.js
/**
* Runtime helper for rendering static trees.
* 運行時油航,用于渲染靜態(tài)抽象語法樹
*/
export function renderStatic(
index: number,
isInFor?: boolean,
): VNode | Array<VNode> {
// static trees can be rendered once and cached on the contructor options
// so every instance shares the same cached trees
// 靜態(tài)樹可以渲染一次并緩存在構(gòu)造器選項上,每個實例共享相同的緩存中的靜態(tài)樹
const renderFns = this.$options.staticRenderFns;
const cached = renderFns.cached || (renderFns.cached = []);
let tree = cached[index];
// if has already-rendered static tree and not inside v-for,
// we can reuse the same tree by doing a shallow clone.
// 如果已經(jīng)渲染了靜態(tài)樹而不是在v-for內(nèi)部卢鹦,
// 我們可以通過執(zhí)行淺復制來重用同一棵樹。
if (tree && !isInFor) {
return Array.isArray(tree) ? cloneVNodes(tree) : cloneVNode(tree);
}
// otherwise, render a fresh tree.
// 否則劝堪,渲染一棵新的樹
tree = cached[index] = renderFns[index].call(this._renderProxy, null, this);
markStatic(tree, `__static__${index}`, false);
return tree;
}
// src/shared/util.js
/**
* Convert a value to a string that is actually rendered.
* 將值轉(zhuǎn)換為實字符串冀自。
*/
export function toString(val: any): string {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2)
: String(val);
}
編譯過程
編譯入口分析
// 把 template 轉(zhuǎn)換成 render 函數(shù)
const { render, staticRenderFns } = compileToFunctions(
template,
{
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments,
},
this,
);
/* @flow */
import { baseOptions } from './options';
import { createCompiler } from 'compiler/index';
// baseOptions 平臺相關(guān)的options
const { compile, compileToFunctions } = createCompiler(baseOptions);
export { compile, compileToFunctions };
export const createCompiler = createCompilerCreator(function baseCompile(
template: string,
options: CompilerOptions,
): CompiledResult {
// 把模板轉(zhuǎn)換成 ast 抽象語法樹
// 抽象語法樹,用來以樹形的方式描述代碼結(jié)構(gòu)
const ast = parse(template.trim(), options);
// 優(yōu)化抽象語法樹
optimize(ast, options);
// 把抽象語法樹生成字符串形式的 js 代碼
const code = generate(ast, options);
return {
// 抽象語法樹
ast,
// 渲染函數(shù)
render: code.render,
// 靜態(tài)渲染函數(shù)秒啦,生成靜態(tài) VNode 樹
staticRenderFns: code.staticRenderFns,
};
});
// baseOptions 平臺相關(guān)的options
// src/platforms/web/compiler/options.js 中定義
export function createCompilerCreator(baseCompile: Function): Function {
return function createCompiler(baseOptions: CompilerOptions) {
function compile(
template: string,
options?: CompilerOptions,
): CompiledResult {
const finalOptions = Object.create(baseOptions);
const errors = [];
const tips = [];
finalOptions.warn = (msg, tip) => {
(tip ? tips : errors).push(msg);
};
if (options) {
// merge custom modules
// 合并自定義模塊
if (options.modules) {
finalOptions.modules = (baseOptions.modules || []).concat(
options.modules,
);
}
// merge custom directives
// 合并自定義指令
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives,
);
}
// copy other options
// 克隆其他配置
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key];
}
}
}
const compiled = baseCompile(template, finalOptions);
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast));
}
compiled.errors = errors;
compiled.tips = tips;
return compiled;
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile),
};
};
}
解析 - parse
解析器將模板解析為抽象語樹 AST熬粗,只有將模板解析成 AST 后,才能基于它做優(yōu)化或者生成代碼字符串余境。
/**
* Convert HTML string to AST.
* 將HTML字符串轉(zhuǎn)換為AST
*/
export function parse(
template: string,
options: CompilerOptions,
): ASTElement | void {
warn = options.warn || baseWarn;
// 解析 options
// no: false
platformIsPreTag = options.isPreTag || no;
platformMustUseProp = options.mustUseProp || no;
platformGetTagNamespace = options.getTagNamespace || no;
// 根據(jù)key獲取options.modules對應的數(shù)據(jù)
transforms = pluckModuleFunction(options.modules, 'transformNode');
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
// delimiters完整版的vue才有驻呐,只有在編譯的時候才會使用到灌诅,
// 作用是改變差值表達式使用的符號
// https://cn.vuejs.org/v2/api/#delimiters
delimiters = options.delimiters;
const stack = [];
const preserveWhitespace = options.preserveWhitespace !== false;
let root;
let currentParent;
let inVPre = false;
let inPre = false;
let warned = false;
function warnOnce(msg) {
if (!warned) {
warned = true;
warn(msg);
}
}
function endPre(element) {
// check pre state
if (element.pre) {
inVPre = false;
}
if (platformIsPreTag(element.tag)) {
inPre = false;
}
}
// 對模板解析
parseHTML(template, {
warn,
expectHTML: options.expectHTML,
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldKeepComment: options.comments,
// 解析過程中的回調(diào)函數(shù),生成 AST
start(tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
const ns =
(currentParent && currentParent.ns) || platformGetTagNamespace(tag);
// handle IE svg bug
/* istanbul ignore if */
if (isIE && ns === 'svg') {
attrs = guardIESVGBug(attrs);
}
// 生成AST(抽象語法樹)
let element: ASTElement = createASTElement(tag, attrs, currentParent);
if (ns) {
element.ns = ns;
}
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true;
process.env.NODE_ENV !== 'production' &&
warn(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
`<${tag}>` +
', as they will not be parsed.',
);
}
// apply pre-transforms
for (let i = 0; i < preTransforms.length; i++) {
element = preTransforms[i](element, options) || element;
}
// 跳過這個元素和它的子元素的編譯過程含末。
// 可以用來顯示原始 Mustache 標簽猜拾。
// 跳過大量沒有指令的節(jié)點會加快編譯。
// https://cn.vuejs.org/v2/api/#v-pre
if (!inVPre) {
processPre(element);
if (element.pre) {
inVPre = true;
}
}
if (platformIsPreTag(element.tag)) {
inPre = true;
}
if (inVPre) {
// 處理el屬性
processRawAttrs(element);
} else if (!element.processed) {
// structural directives
// 結(jié)構(gòu)化指令的處理
// v-for
processFor(element);
// v-if
processIf(element);
// v-once
processOnce(element);
// element-scope stuff
// 處理el
processElement(element, options);
}
// 檢查根元素約束
function checkRootConstraints(el) {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.',
);
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
);
}
}
}
// tree management
// 抽象語法樹管理
if (!root) {
root = element;
checkRootConstraints(root);
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
// 允許根元素帶有v-if佣盒,v-else-if和v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element);
// 將if語法樹添加到根語法樹里
addIfCondition(root, {
exp: element.elseif,
block: element,
});
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`,
);
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
// 處理if語法樹
processIfConditions(element, currentParent);
} else if (element.slotScope) {
// scoped slot
currentParent.plain = false;
const name = element.slotTarget || '"default"';
(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[
name
] = element;
} else {
currentParent.children.push(element);
element.parent = currentParent;
}
}
if (!unary) {
currentParent = element;
stack.push(element);
} else {
endPre(element);
}
// apply post-transforms
for (let i = 0; i < postTransforms.length; i++) {
postTransforms[i](element, options);
}
},
end() {
// remove trailing whitespace
// 刪除尾部空格
const element = stack[stack.length - 1];
const lastNode = element.children[element.children.length - 1];
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop();
}
// pop stack
stack.length -= 1;
currentParent = stack[stack.length - 1];
endPre(element);
},
// 處理文本元素
chars(text: string) {
if (!currentParent) {
if (process.env.NODE_ENV !== 'production') {
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.',
);
} else if ((text = text.trim())) {
warnOnce(`text "${text}" outside root element will be ignored.`);
}
}
return;
}
// IE textarea placeholder bug
/* istanbul ignore if */
if (
isIE &&
currentParent.tag === 'textarea' &&
currentParent.attrsMap.placeholder === text
) {
return;
}
const children = currentParent.children;
text =
inPre || text.trim()
? isTextTag(currentParent)
? text
: decodeHTMLCached(text)
: // only preserve whitespace if its not right after a starting tag
preserveWhitespace && children.length
? ' '
: '';
if (text) {
let expression;
if (
!inVPre &&
text !== ' ' &&
(expression = parseText(text, delimiters))
) {
children.push({
type: 2,
expression,
text,
});
} else if (
text !== ' ' ||
!children.length ||
children[children.length - 1].text !== ' '
) {
children.push({
type: 3,
text,
});
}
}
},
// 處理注釋元素
comment(text: string) {
currentParent.children.push({
type: 3,
text,
isComment: true,
});
},
});
return root;
}
v-if/v-for 結(jié)構(gòu)化指令只能在編譯階段處理挎袜,如果我們要在 render 函數(shù)處理條件或循環(huán)只能使用
js 中的 if 和 for
在模板中使用的 v-if 和 v-for:
<ul v-if="items.length">
<li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
這些都可以在渲染函數(shù)中用 JavaScript 的 if/else 和 map 來重寫:
props: ['items'],
render: function (createElement) {
if (this.items.length) {
return createElement('ul', this.items.map(function (item) {
return createElement('li', item.name)
}))
} else {
return createElement('p', 'No items found.')
}
}
<iframe src="https://astexplorer.net/#/gist/f409cd9d81c248be28486f20e174784c/a4ed34b2e2532619ca2a2b41317b45790026224b" style="width: 100%; height: 500px; border: 0px; border-radius: 4px; overflow: hidden; --darkreader-inline-border-top: initial; --darkreader-inline-border-right: initial; --darkreader-inline-border-bottom: initial; --darkreader-inline-border-left: initial;" title="vue-20-template-compilation" allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb" sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin" data-darkreader-inline-border-top="" data-darkreader-inline-border-right="" data-darkreader-inline-border-bottom="" data-darkreader-inline-border-left=""></iframe>
優(yōu)化 - optimize
- 優(yōu)化抽象語法樹
- 靜態(tài)節(jié)點 --> 永遠不會更改的節(jié)點
- 標記非靜態(tài)節(jié)點和標記靜態(tài)根節(jié)點
- 檢測靜態(tài)節(jié)點,設(shè)置為靜態(tài)肥惭,不需要在每次重新渲染的時候重新生成節(jié)點
- patch 階段跳過靜態(tài)節(jié)點
// 優(yōu)化抽象語法樹
optimize(ast, options);
export function optimize(root: ?ASTElement, options: CompilerOptions) {
if (!root) return;
isStaticKey = genStaticKeysCached(options.staticKeys || '');
isPlatformReservedTag = options.isReservedTag || no;
// first pass: mark all non-static nodes.
// 第一次通過:標記非靜態(tài)節(jié)點盯仪。
markStatic(root);
// second pass: mark static roots.
// 第二遍:標記靜態(tài)根節(jié)點。
markStaticRoots(root, false);
}
function markStatic(node: ASTNode) {
// 判斷當前的astNode是否是靜態(tài)的
node.static = isStatic(node);
// 元素節(jié)點
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
// 不要將組件插槽的內(nèi)容設(shè)為靜態(tài)
// 1.組件無法更改插槽節(jié)點
// 2.靜態(tài)插槽內(nèi)容無法進行熱重裝
// 是組件蜜葱,不是slot全景,沒有inline-template,直接返回牵囤,阻止向下執(zhí)行
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return;
}
// 遍歷children
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i];
// 標記非靜態(tài)
markStatic(child);
if (!child.static) {
// 如果有一個child不是靜態(tài)爸黄,設(shè)置當前節(jié)點不是靜態(tài)
node.static = false;
}
}
// 處理條件渲染
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block;
// 標記非靜態(tài)
markStatic(block);
if (!block.static) {
// 如果有一個block不是靜態(tài)帕胆,設(shè)置當前節(jié)點不是靜態(tài)
node.static = false;
}
}
}
}
}
function markStaticRoots(node: ASTNode, isInFor: boolean) {
// 判斷是否為元素節(jié)點
if (node.type === 1) {
// 判斷當前節(jié)點是否是靜態(tài)節(jié)點或者是否只渲染一次
if (node.static || node.once) {
// 標記其在for循環(huán)中是否是靜態(tài)的
node.staticInFor = isInFor;
}
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
// 如果一個元素內(nèi)只有文本節(jié)點屠阻,此時這個元素不是靜態(tài)的Root
// vue 認為這種優(yōu)化會帶來負面的影響
// 判斷當前節(jié)點是靜態(tài)節(jié)點,并且存在子節(jié)點糠亩,并且當前節(jié)點的子節(jié)點不能只存在一個文本節(jié)點
// 如果不滿足汹桦,優(yōu)化成本將大于收益
if (
node.static &&
node.children.length &&
!(node.children.length === 1 && node.children[0].type === 3)
) {
// 標記根節(jié)點為靜態(tài)
node.staticRoot = true;
return;
} else {
// 標記根節(jié)點為非靜態(tài)
node.staticRoot = false;
}
// 檢測當前節(jié)點的子節(jié)點中是否有靜態(tài)的Root
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for);
}
}
// 檢測當前節(jié)點的條件渲染中是否有靜態(tài)的Root
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor);
}
}
}
}
function isStatic(node: ASTNode): boolean {
if (node.type === 2) {
// expression 表達式
return false;
}
if (node.type === 3) {
// text 文本
return true;
}
return !!(
node.pre ||
(!node.hasBindings && // no dynamic bindings
!node.if &&
!node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in 不能是內(nèi)置組件
isPlatformReservedTag(node.tag) && // not a component 不能是組件
!isDirectChildOfTemplateFor(node) && // 不能是v-for下的直接節(jié)點
Object.keys(node).every(isStaticKey))
);
}
生成 - generate
- 將抽象語法樹轉(zhuǎn)換為字符串形式的 js 代碼
// 將抽象語法樹轉(zhuǎn)換為字符串形式的 js 代碼
const code = generate(ast, options);
export function generate(
ast: ASTElement | void,
options: CompilerOptions,
): CodegenResult {
// CodegenState 代碼生成過程中使用的狀態(tài)對象
const state = new CodegenState(options);
// 如果存在ast鲁驶,生成代碼,否則返回_c("div")
const code = ast ? genElement(ast, state) : '_c("div")';
return {
render: `with(this){return ${code}}`,
// 靜態(tài)渲染函數(shù)舞骆,即靜態(tài)根節(jié)點的渲染函數(shù)
staticRenderFns: state.staticRenderFns,
};
}
export function genElement(el: ASTElement, state: CodegenState): string {
// el.staticProcessed用于判斷當前靜態(tài)根節(jié)點是否已處理钥弯,防止重復處理
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state);
} else if (el.once && !el.onceProcessed) {
return genOnce(el, state);
} else if (el.for && !el.forProcessed) {
return genFor(el, state);
} else if (el.if && !el.ifProcessed) {
return genIf(el, state);
// 以下處理非靜態(tài)節(jié)點
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el, state) || 'void 0';
} else if (el.tag === 'slot') {
return genSlot(el, state);
} else {
// component or element
// 處理組件和內(nèi)置標簽
let code;
if (el.component) {
code = genComponent(el.component, el, state);
} else {
// 生成元素的屬性/指令/事件等
// 處理各種指令,包括genDirective(model/text/html)
const data = el.plain ? undefined : genData(el, state);
// 將節(jié)點轉(zhuǎn)換為相應的代碼
const children = el.inlineTemplate ? null : genChildren(el, state, true);
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`;
}
// module transforms
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code);
}
return code;
}
}
// hoist static sub-trees out
function genStatic(el: ASTElement, state: CodegenState): string {
// 將狀態(tài)置為已處理
el.staticProcessed = true;
// 將靜態(tài)根節(jié)點轉(zhuǎn)換為生成vnode的js代碼
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`);
return `_m(${state.staticRenderFns.length - 1}${
el.staticInFor ? ',true' : ''
})`;
}