模板编译的主要目的是将模板 (template) 转换为渲染函数 (render) 。

  1. <div>
  2. <h1 @click="handler">title</h1>
  3. <p>some content</p>
  4. </div>

渲染函数 render

  1. render (h) {
  2. return h('div', [
  3. h('h1', { on: { click: this.handler} }, 'title'),
  4. h('p', 'some content')
  5. ])
  6. }
  • 模板编译的作用
    • Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
    • 用户只需要编写类似 HTML 的代码 - Vue 模板,通过编译器将模板转换为返回 VNode 的 render 函数
    • .vue 文件会被 webpack 在构建的过程中转换成 render 函数

模板编译的结果

带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板。

  1. <div id="app">
  2. <h1>Vue<span>模板编译过程</span></h1>
  3. <p>{{ msg }}</p>
  4. <comp @myclick="handler"></comp>
  5. </div>
  6. <script src="../../dist/vue.js"></script>
  7. <script>
  8. Vue.component("comp", {
  9. template: "<div>I am a comp</div>",
  10. })
  11. const vm = new Vue({
  12. el: "#app",
  13. data: {
  14. msg: "Hello compiler",
  15. },
  16. methods: {
  17. handler() {
  18. console.log("test")
  19. },
  20. },
  21. })
  22. console.log(vm.$options.render)
  23. </script>

编译后 render 输出的结果:

  1. (function anonymous() {
  2. with (this) {
  3. return _c(
  4. "div",
  5. { attrs: { id: "app" } },
  6. [
  7. _m(0),
  8. _v(" "),
  9. _c("p", [_v(_s(msg))]),
  10. _v(" "),
  11. _c("comp", { on: { myclick: handler } }),
  12. ],
  13. 1
  14. )
  15. }
  16. })
  • _c 是 createElement() 方法,定义的位置 instance/render.js 中
  • 相关的渲染函数(_开头的方法定义),在 instance/render-helps/index.js 中
  1. // instance/render-helps/index.js
  2. target._v = createTextVNode
  3. target._m = renderStatic
  4. // core/vdom/vnode.js
  5. export function createTextVNode(val: string | number) {
  6. return new VNode(undefined, undefined, undefined, String(val))
  7. }
  8. // 在 instance/render-helps/render-static.js
  9. export function renderStatic(
  10. index: number,
  11. isInFor: boolean
  12. ): VNode | Array<VNode> {
  13. const cached = this._staticTrees || (this._staticTrees = [])
  14. let tree = cached[index]
  15. // if has already-rendered static tree and not inside v-for,
  16. // we can reuse the same tree.
  17. if (tree && !isInFor) {
  18. return tree
  19. }
  20. // otherwise, render a fresh tree.
  21. tree = cached[index] = this.$options.staticRenderFns[index].call(
  22. this._renderProxy,
  23. null,
  24. this // for render fns generated for functional component templates
  25. )
  26. markStatic(tree, `__static__${index}`, false)
  27. return tree
  28. }
  • 把 template 转换成 render 的入口 src\platforms\web\entry-runtime-with-compiler.js。

模板编译的过程

解析、优化、生成

编译的入口

  • src\platforms\web\entry-runtime-with-compiler.js

    1. Vue.prototype.$mount = function (
    2. ……
    3. // 把 template 转换成 render 函数
    4. const { render, staticRenderFns } = compileToFunctions(template, {
    5. outputSourceRange: process.env.NODE_ENV !== 'production',
    6. shouldDecodeNewlines,
    7. shouldDecodeNewlinesForHref,
    8. delimiters: options.delimiters,
    9. comments: options.comments
    10. }, this)
    11. options.render = render
    12. options.staticRenderFns = staticRenderFns
    13. ……
    14. )
  • 调试 compileToFunctions() 执行过程,生成渲染函数的过程

    • compileToFunctions: src\compiler\to-function.js
    • complie(template, options):src\compiler\create-compiler.js
    • baseCompile(template.trim(), finalOptions):src\compiler\index.js

模板编译 - 图1

解析 - parse

  • 解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串。
  1. // src\compiler\index.js
  2. const ast = parse(template.trim(), options)
  3. //src\compiler\parser\index.js
  4. parse()

结构化指令的处理

v-if 和 v-for

最终生成单元表达式:

  1. // src\compiler\parser\index.js
  2. // structural directives
  3. // 结构化的指令
  4. // v-for
  5. processFor(element)
  6. processIf(element)
  7. processOnce(element)
  8. // src\compiler\codegen\index.js
  9. export function genIf(
  10. el: any,
  11. state: CodegenState,
  12. altGen?: Function,
  13. altEmpty?: string
  14. ): string {
  15. el.ifProcessed = true // avoid recursion
  16. return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
  17. }
  18. // 最终调用 genIfConditions 生成三元表达式

最终编译的结果:

  1. ƒ anonymous(
  2. ) {
  3. with (this) {
  4. return _c('div', { attrs: { "id": "app" } }, [
  5. _m(0),
  6. _v(" "),
  7. (msg) ? _c('p', [_v(_s(msg))]) : _e(), _v(" "),
  8. _c('comp', { on: { "myclick": onMyClick } })
  9. ], 1)
  10. }
  11. }
  1. v-if/v-for 结构化指令只能在编译阶段处理,如果我们要在 render 函数处理条件或循环只能使用 js 中的 if for
  1. Vue.component('comp', {
  2. data: () {
  3. return {
  4. msg: 'my comp'
  5. }
  6. },
  7. render(h) {
  8. if (this.msg) {
  9. return h('div', this.msg)
  10. }
  11. return h('div', 'bar')
  12. }
  13. })

优化 - optimize

  • 优化抽象语法树,检测子节点中是否是纯静态节点 永远不会更改的节点
  • 一旦检测到纯静态节点,值不会改变的节点
    • 提升为常量,重新渲染的时候不在重新创建节点
    • 在 patch 的时候直接跳过静态子树 ```javascript // src\compiler\index.js if (options.optimize !== false) { optimize(ast, options) } // src\compiler\optimizer.js /**
  • Goal of the optimizer: walk the generated template AST tree
  • and detect sub-trees that are purely static, i.e. parts of
  • the DOM that never needs to change. *
  • Once we detect these sub-trees, we can: *
    1. Hoist them into constants, so that we no longer need to
  • create fresh nodes for them on each re-render;
    1. Completely skip them in the patching process. */ 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. // 标记非静态节点 markStatic(root) // second pass: mark static roots. // 标记静态根节点 markStaticRoots(root, false) } ```

生成 - generate

  1. // src\compiler\index.js
  2. const code = generate(ast, options)
  3. // src\compiler\codegen\index.js
  4. export function generate(
  5. ast: ASTElement | void,
  6. options: CompilerOptions
  7. ): CodegenResult {
  8. const state = new CodegenState(options)
  9. const code = ast ? genElement(ast, state) : '_c("div")'
  10. return {
  11. render: `with(this){return ${code}}`,
  12. staticRenderFns: state.staticRenderFns
  13. }
  14. }
  15. // 把字符串转换成函数
  16. // src\compiler\to-function.js
  17. function createFunction(code, errors) {
  18. try {
  19. return new Function(code)
  20. } catch (err) {
  21. errors.push({ err, code })
  22. return noop
  23. }
  24. }

总结

模板编译 - 图2