我理解的Vue模版编译过程

180 阅读3分钟

如果没有 render 选项就把 template 选项通过 compileToFunctions 函数转成 render函数,

var { render } = compileToFunctions(template, {});
options.render = render;

compileToFunctions 函数是 createCompiler 函数返回值对象中的一个方法,

var { compileToFunctions } = createCompiler(baseOptions);

createCompiler 函数的内容是 createCompilerCreator 函数的返回值

var createCompiler = createCompilerCreator()

createCompilerCreator 函数定义如下

function createCompilerCreator(baseCompile) {
	return function createCompiler(baseOptions) {
            function compile(){ // 核心函数 }
            return {
                compile: compile,
                compileToFunctions: createCompileToFunctionFn(compile)
            }
        }
}

以compileToFunctions 作为切入点,最终的归属地就在 createCompileToFunctionFn 函数返回值。 createCompileToFunctionFn 函数定义如下

function createCompileToFunctionFn(compile) {
    return function compileToFunctions(template, options, vm){
        // 真正的编译工作是依托于 compile 函数
        var compiled = compile(template, options);
        var res = {};
        res.render = createFunction(compiled.render, fnGenErrors);
        // ...
        return res;
    }
}
function createFunction(code, errors) {
    // ...
    return new Function(code);
}

真正的编译工作是依托于 compile 函数

function compile(template, options){
    var finalOptions = Object.create(baseOptions);
    var compiled = baseCompile(template, finalOptions);
    return compiled;
}

baseOptions包含编译器在运作的时候所需的配置选项。(常用的有 modules 和 directives)

var baseOptions = {
	expectHTML: true,
	modules: modules$1,
	directives: directives$1,
	isPreTag: isPreTag,
	isUnaryTag: isUnaryTag,
	mustUseProp: mustUseProp,
	canBeLeftOpenTag: canBeLeftOpenTag,
	isReservedTag: isReservedTag,
	getTagNamespace: getTagNamespace,
	staticKeys: genStaticKeys(modules$1)
};

compiled 是 baseCompile 对模板的编译结果,baseCompile 定义

// 正式的进入到Vue编译器词法分析阶段
function baseCompile(template, options) {
    // 用正则等方式解析 template 模板中的指令、class、style等数据,形成AST。
    var ast = parse(template.trim(), options);
    
    // 标记 static 静态节点
    // 后面当 update 更新界面时,会有一个 patch 的过程, diff 算法会直接跳过静态节点,从而减少了比较的过程,优化了 patch 的性能。
    if (options.optimize !== false) {
        optimize(ast, options);
    }
    
    // 将 AST 转化成 render function 字符串的过程,
    // 得到结果是 render 的字符串以及 staticRenderFns 字符串。
    var code = generate(ast, options);
    return {
        ast: ast,
        render: code.render,
        staticRenderFns: code.staticRenderFns
    }
}

流程整理

createCompilerCreator()(baseOptions).compileToFunctions(template, {}).render
createCompiler(baseOptions).compileToFunctions(template, {}).render
createCompileToFunctionFn(compile)(template, {}).render
compileToFunctions(template, {}).render
createFunction(compile(template, options).render, fnGenErrors)
new Function(compile(template, options).render)
new Function(baseCompile(template, finalOptions).render)
new Function(code.render)
new Function(generate(parse(template.trim(), options), options).render)

所以 template 转成 render 关键就是这几步

  1. parse - 用正则等方式解析 template 模板中的指令、class、style等数据,形成AST。
  2. optimize - 标记 static 静态节点。
  3. generate - 将 AST 转化成 render function 字符串的过程。
  4. new Function - 得到一个可以执行的渲染函数,该函数在被调用时将返回一个虚拟 DOM。

parse

preTransforms、transforms 和 postTransforms 在哪里处理?

export function parse (template, options) {
    parseHTML(template, {
        // ...
        // 调用 start 函数就会初始化 ast 和处理 preTransforms
        start (tag, attrs, unary, start, end) {
            // 初始化 ast
            let element: ASTElement = createASTElement(tag, attrs, currentParent);
            for (let i = 0; i < preTransforms.length; i++) {
                element = preTransforms[i](element, options) || element
            }
        }
        // 调用 end 函数会处理 transforms 和 postTransforms
        end (tag, start, end) {
            const element = stack[stack.length - 1];
            // closeElement 内部处理 transforms 和 postTransforms
            closeElement(element)
        }
    })
}

# start 函数哪里调用?
export function parseHTML (html, options) {
    while (html) {
        // ...
        // End tag:
        const endTagMatch = html.match(endTag)
        if (endTagMatch) {
            // ...
            // 这个函数内部会调用 end
            parseEndTag(endTagMatch[1], curIndex, index)
            continue
        }
        // Start tag:
        if (startTagMatch) {
            // 这个函数内部会调用 start
            handleStartTag(startTagMatch);
            // ...
            continue;
        }
    }
}

// 这里会调用 start
function handleStartTag (match) {
    // ...
    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end)
    }
}

// 这里会调用 end
function parseEndTag (tagName, start, end) {
    // ...
    if (options.end) {
        options.end(stack[i].tag, start, end)
    }
    // ...
}

function closeElement (element) {
    // ...
    if (!inVPre && !element.processed) {
      // processElement 内部会处理 transforms
      element = processElement(element, options)
    }
    // ...
    // 处理 postTransforms
    for (let i = 0; i < postTransforms.length; i++) {
      postTransforms[i](element, options)
    }
}

export function processElement (element, options) {
    // ...
    processKey(element)
    processRef(element)
    processSlotContent(element)
    processSlotOutlet(element)
    processComponent(element)
    // 处理 transforms
    for (let i = 0; i < transforms.length; i++) {
        element = transforms[i](element, options) || element
    }
    processAttrs(element)
    return element
}