Vue compiler

329 阅读2分钟

Vue 模板编译的目标

template(模板) --> render(渲染函数) 

我们平时开发时也有用到 render 函数

render(h) {
  return h("div", { attrs: { id: "demo" } }, [
    h("span", ["子元素,文本节点"]),
  ]);
}

最终渲染出来的 dom 是这样的

<div id="demo">
    <span>子元素,文本节点</span>
</div>

但我们平时不可能都这么写, 我们只需要写 template Vue 就可以自动把我们写的 template 编译成 render 函数。

一段这样的模板

 <div id="app">
      <h1>vue<span>模板编译</span></h1>
      <p>{{foo}}</p>
      <comp></comp>
 </div>

经过Vue 处理后可以生成这样的 render 函数。 不是很熟悉 render 函数写法的,可以看看render 文档

ƒ anonymous() {
    with(this){
        return _c('div',    // _c 就是 createElement 函数
        {
            attrs:{"id":"app"}
        },
        [
            _m(0), // _m 是 renderStatic 函数的别名
                   // 0 对应 staticRenderFns 数组索引为 0 的 render 函数
            _v(" "), // _v 是 createTextVNode 的别名
            _c('p',[
                _v(_s(foo)) // _s 是toString 的别名
                ]
            ),
            _v(" "),
            _c('comp')
        ],
        1)      // determine the normalization needed for the children array.
                // 0: no normalization needed
                // 1: simple normalization needed (possible 1-level deep nested array)
                // 2: full normalization needed
    }
}

更多函数别名可以从源码中获取函数别名

整体流程

当我们使用 runtime + compiler 版本的Vue,并指定 template 或 el 选项, 则会执行compileToFunctions 进行编译,并返回 render 和 staticRenderFns。

compileToFunctions 的定义是在 src/platforms/web/entry-runtime-with-compiler.js ,是 createCompiler 方法的返回值。createCompiler 接受一个 baseOptions 选项。

createCompiler 定义在 src\compiler\index.js 返回 compile 和 compileToFunctions。

createCompiler 是createCompilerCreator 的返回结果,createCompilerCreator 接受一个回调函数baseCompile,该回调函数 返回 compiled = { ast, render, staticRenderFns}。

createCompilerCreator 定义在 src/compiler/create-compiler.js中。

createCompileToFunctionFn 定义在 src/compiler/to-function.js 中,接收一个compile 函数 返回 compileToFunctions 函数,compileToFunctions 函数返回 render 函数 和 staticRenderFns 数组

真正的编译过程都是在 baseCompile 函数里执行的,baseCompile 主要执行了3步,

 const ast = parse(template, options) // 解析 模板成 ast
 optimize(ast, options) // 优化 ast , 添加标记,标记哪些是静态不变的
 const code = generate(ast, options) // 根据ast 生成 code

整个 compile 的流程大概是下面这样

// 伪代码
function createCompilerCreator(baseCompile) {
    return function createCompiler (baseOptions) {
        function compile(template, options) {
            const compiled = baseCompile(template.trim(), finalOptions)
            return compiled
        }
        
        return {
            compile,
            compileToFunctions: createCompileToFunctionFn(compile)
        }
    }
}

function createCompileToFunctionFn(compile){
    const cache = {}
    return function compileToFunctions(template, options, vm){
        const compiled = compile(template, options)
        // compiled中有 render, staticRenderFns
        return cache[key] = {
            render,
            staticRenderFns
        }
    }
}

用的时候大概是这样的

// 伪代码
const createCompiler = createCompilerCreator(baseCompile) 
const compileToFunctios = createCompileToFunctionFn(compile)
const { compile, compileToFunctios} = createCompiler(baseOptions)
const { render, staticRenderFns } = compileToFunctios(template, options, vm)

编译入口之所以用高阶函数这么做主要为了实现 参数(baseOptions)的保留,并把基础编译过程函数(baseCompile)和其他逻辑(配置处理finalOptions, 缓存处理 cache[key])等进行剥离。