Skip to content

编译原理

一、编译核心原理

1.通用模板编译器

编译器就是将“源代码”(source code)转化为“目标代码”(target code)的一段代码(code)。整个过程就叫编译(compile)。

compile

由上图可以看到,整个编译过程分为编译前端和编译后端,编译前端包含词法分析、语法分析和语义分析,他通常与目标平台无关,仅负责分析源代码。编译后端则通常与目标平台有关,编译后端包含中间代码生成、优化和目标代码生成。

2.vue 中的模板编译器

整个 vue 的模板编译的过程简单总结,就是如下三步

js
// 1 解析模板:将.vue文件中的template模板转化为AST语法树
const templateAST = parse(template);
// 2 转化代码:将AST语法树转化为JS的语法树
const jsAST = transform(templateAST);
// 3 生成代码:将JS的语法树代码生成为vue需要的渲染函数代码
const code = generate(jsAST);

compile

3.vue 的一个示例

一段 html 源代码

js
<div id="vue">Hello {{ name }}</div>

经过 parse 编译之后的结果==>模板 AST

js
{
  type: 0,
  source: '<div id="vue">Hello {{name}}</div>',
  children: [
    {
      type: 1,
      tag: 'div',
      ns: 0,
      tagType: 0,
      props: [
        {
          type: 6,
          name: 'id',
          nameLoc: {
            start: {
              column: 6,
              line: 1,
              offset: 5,
            },
            end: {
              column: 8,
              line: 1,
              offset: 7,
            },
            source: 'id',
          },
          value: {
            type: 2,
            content: 'vue',
            loc: {
              start: {
                column: 9,
                line: 1,
                offset: 8,
              },
              end: {
                column: 14,
                line: 1,
                offset: 13,
              },
              source: '"vue"',
            },
          },
          loc: {
            start: {
              column: 6,
              line: 1,
              offset: 5,
            },
            end: {
              column: 14,
              line: 1,
              offset: 13,
            },
            source: 'id="vue"',
          },
        },
      ],
      children: [
        {
          type: 2,
          content: 'Hello ',
          loc: {
            start: {
              column: 15,
              line: 1,
              offset: 14,
            },
            end: {
              column: 21,
              line: 1,
              offset: 20,
            },
            source: 'Hello ',
          },
        },
        {
          type: 5,
          content: {
            type: 4,
            loc: {
              start: {
                column: 23,
                line: 1,
                offset: 22,
              },
              end: {
                column: 27,
                line: 1,
                offset: 26,
              },
              source: 'name',
            },
            content: 'name',
            isStatic: false,
            constType: 0,
          },
          loc: {
            start: {
              column: 21,
              line: 1,
              offset: 20,
            },
            end: {
              column: 29,
              line: 1,
              offset: 28,
            },
            source: '{{name}}',
          },
        },
      ],
      loc: {
        start: {
          column: 1,
          line: 1,
          offset: 0,
        },
        end: {
          column: 35,
          line: 1,
          offset: 34,
        },
        source: '<div id="vue">Hello {{name}}</div>',
      },
    },
  ],
  helpers: {},
  components: [],
  directives: [],
  hoists: [],
  imports: [],
  cached: 0,
  temps: 0,
  loc: {
    start: {
      column: 1,
      line: 1,
      offset: 0,
    },
    end: {
      column: 35,
      line: 1,
      offset: 34,
    },
    source: '<div id="vue">Hello {{name}}</div>',
  },
}

经过 transform 转化后的结果==>JavaScript AST

js
{
  type: 0,
  source: '<div id="vue">Hello {{name}}</div>',
  children: [
    {
      type: 1,
      tag: 'div',
      ns: 0,
      tagType: 0,
      props: [
        {
          type: 6,
          name: 'id',
          nameLoc: {
            start: {
              column: 6,
              line: 1,
              offset: 5,
            },
            end: {
              column: 8,
              line: 1,
              offset: 7,
            },
            source: 'id',
          },
          value: {
            type: 2,
            content: 'vue',
            loc: {
              start: {
                column: 9,
                line: 1,
                offset: 8,
              },
              end: {
                column: 14,
                line: 1,
                offset: 13,
              },
              source: '"vue"',
            },
          },
          loc: {
            start: {
              column: 6,
              line: 1,
              offset: 5,
            },
            end: {
              column: 14,
              line: 1,
              offset: 13,
            },
            source: 'id="vue"',
          },
        },
      ],
      children: [
        {
          type: 8,
          loc: {
            start: {
              column: 15,
              line: 1,
              offset: 14,
            },
            end: {
              column: 21,
              line: 1,
              offset: 20,
            },
            source: 'Hello ',
          },
          children: [
            {
              type: 2,
              content: 'Hello ',
              loc: {
                start: {
                  column: 15,
                  line: 1,
                  offset: 14,
                },
                end: {
                  column: 21,
                  line: 1,
                  offset: 20,
                },
                source: 'Hello ',
              },
            },
            ' + ',
            {
              type: 5,
              content: {
                type: 4,
                loc: {
                  start: {
                    column: 23,
                    line: 1,
                    offset: 22,
                  },
                  end: {
                    column: 27,
                    line: 1,
                    offset: 26,
                  },
                  source: 'name',
                },
                content: 'name',
                isStatic: false,
                constType: 0,
              },
              loc: {
                start: {
                  column: 21,
                  line: 1,
                  offset: 20,
                },
                end: {
                  column: 29,
                  line: 1,
                  offset: 28,
                },
                source: '{{name}}',
              },
            },
          ],
        },
      ],
      loc: {
        start: {
          column: 1,
          line: 1,
          offset: 0,
        },
        end: {
          column: 35,
          line: 1,
          offset: 34,
        },
        source: '<div id="vue">Hello {{name}}</div>',
      },
      codegenNode: {
        type: 13,
        tag: '"div"',
        props: {
          type: 4,
          loc: {
            start: {
              column: 1,
              line: 1,
              offset: 0,
            },
            end: {
              column: 35,
              line: 1,
              offset: 34,
            },
            source: '<div id="vue">Hello {{name}}</div>',
          },
          content: '_hoisted_1',
          isStatic: false,
          constType: 2,
          hoisted: {
            type: 15,
            loc: {
              start: {
                column: 1,
                line: 1,
                offset: 0,
              },
              end: {
                column: 35,
                line: 1,
                offset: 34,
              },
              source: '<div id="vue">Hello {{name}}</div>',
            },
            properties: [
              {
                type: 16,
                loc: {
                  start: {
                    line: 1,
                    column: 1,
                    offset: 0,
                  },
                  end: {
                    line: 1,
                    column: 1,
                    offset: 0,
                  },
                  source: '',
                },
                key: {
                  type: 4,
                  loc: {
                    start: {
                      column: 6,
                      line: 1,
                      offset: 5,
                    },
                    end: {
                      column: 8,
                      line: 1,
                      offset: 7,
                    },
                    source: 'id',
                  },
                  content: 'id',
                  isStatic: true,
                  constType: 3,
                },
                value: {
                  type: 4,
                  loc: {
                    start: {
                      column: 9,
                      line: 1,
                      offset: 8,
                    },
                    end: {
                      column: 14,
                      line: 1,
                      offset: 13,
                    },
                    source: '"vue"',
                  },
                  content: 'vue',
                  isStatic: true,
                  constType: 3,
                },
              },
            ],
          },
        },
        children: {
          type: 8,
          loc: {
            start: {
              column: 15,
              line: 1,
              offset: 14,
            },
            end: {
              column: 21,
              line: 1,
              offset: 20,
            },
            source: 'Hello ',
          },
          children: [
            {
              type: 2,
              content: 'Hello ',
              loc: {
                start: {
                  column: 15,
                  line: 1,
                  offset: 14,
                },
                end: {
                  column: 21,
                  line: 1,
                  offset: 20,
                },
                source: 'Hello ',
              },
            },
            ' + ',
            {
              type: 5,
              content: {
                type: 4,
                loc: {
                  start: {
                    column: 23,
                    line: 1,
                    offset: 22,
                  },
                  end: {
                    column: 27,
                    line: 1,
                    offset: 26,
                  },
                  source: 'name',
                },
                content: 'name',
                isStatic: false,
                constType: 0,
              },
              loc: {
                start: {
                  column: 21,
                  line: 1,
                  offset: 20,
                },
                end: {
                  column: 29,
                  line: 1,
                  offset: 28,
                },
                source: '{{name}}',
              },
            },
          ],
        },
        patchFlag: '1 /* TEXT */',
        isBlock: true,
        disableTracking: false,
        isComponent: false,
        loc: {
          start: {
            column: 1,
            line: 1,
            offset: 0,
          },
          end: {
            column: 35,
            line: 1,
            offset: 34,
          },
          source: '<div id="vue">Hello {{name}}</div>',
        },
      },
    },
  ],
  helpers: {},
  components: [],
  directives: [],
  hoists: [
    {
      type: 15,
      loc: {
        start: {
          column: 1,
          line: 1,
          offset: 0,
        },
        end: {
          column: 35,
          line: 1,
          offset: 34,
        },
        source: '<div id="vue">Hello {{name}}</div>',
      },
      properties: [
        {
          type: 16,
          loc: {
            start: {
              line: 1,
              column: 1,
              offset: 0,
            },
            end: {
              line: 1,
              column: 1,
              offset: 0,
            },
            source: '',
          },
          key: {
            type: 4,
            loc: {
              start: {
                column: 6,
                line: 1,
                offset: 5,
              },
              end: {
                column: 8,
                line: 1,
                offset: 7,
              },
              source: 'id',
            },
            content: 'id',
            isStatic: true,
            constType: 3,
          },
          value: {
            type: 4,
            loc: {
              start: {
                column: 9,
                line: 1,
                offset: 8,
              },
              end: {
                column: 14,
                line: 1,
                offset: 13,
              },
              source: '"vue"',
            },
            content: 'vue',
            isStatic: true,
            constType: 3,
          },
        },
      ],
    },
  ],
  imports: [],
  cached: 0,
  temps: 0,
  codegenNode: {
    type: 13,
    tag: '"div"',
    props: {
      type: 4,
      loc: {
        start: {
          column: 1,
          line: 1,
          offset: 0,
        },
        end: {
          column: 35,
          line: 1,
          offset: 34,
        },
        source: '<div id="vue">Hello {{name}}</div>',
      },
      content: '_hoisted_1',
      isStatic: false,
      constType: 2,
      hoisted: {
        type: 15,
        loc: {
          start: {
            column: 1,
            line: 1,
            offset: 0,
          },
          end: {
            column: 35,
            line: 1,
            offset: 34,
          },
          source: '<div id="vue">Hello {{name}}</div>',
        },
        properties: [
          {
            type: 16,
            loc: {
              start: {
                line: 1,
                column: 1,
                offset: 0,
              },
              end: {
                line: 1,
                column: 1,
                offset: 0,
              },
              source: '',
            },
            key: {
              type: 4,
              loc: {
                start: {
                  column: 6,
                  line: 1,
                  offset: 5,
                },
                end: {
                  column: 8,
                  line: 1,
                  offset: 7,
                },
                source: 'id',
              },
              content: 'id',
              isStatic: true,
              constType: 3,
            },
            value: {
              type: 4,
              loc: {
                start: {
                  column: 9,
                  line: 1,
                  offset: 8,
                },
                end: {
                  column: 14,
                  line: 1,
                  offset: 13,
                },
                source: '"vue"',
              },
              content: 'vue',
              isStatic: true,
              constType: 3,
            },
          },
        ],
      },
    },
    children: {
      type: 8,
      loc: {
        start: {
          column: 15,
          line: 1,
          offset: 14,
        },
        end: {
          column: 21,
          line: 1,
          offset: 20,
        },
        source: 'Hello ',
      },
      children: [
        {
          type: 2,
          content: 'Hello ',
          loc: {
            start: {
              column: 15,
              line: 1,
              offset: 14,
            },
            end: {
              column: 21,
              line: 1,
              offset: 20,
            },
            source: 'Hello ',
          },
        },
        ' + ',
        {
          type: 5,
          content: {
            type: 4,
            loc: {
              start: {
                column: 23,
                line: 1,
                offset: 22,
              },
              end: {
                column: 27,
                line: 1,
                offset: 26,
              },
              source: 'name',
            },
            content: 'name',
            isStatic: false,
            constType: 0,
          },
          loc: {
            start: {
              column: 21,
              line: 1,
              offset: 20,
            },
            end: {
              column: 29,
              line: 1,
              offset: 28,
            },
            source: '{{name}}',
          },
        },
      ],
    },
    patchFlag: '1 /* TEXT */',
    isBlock: true,
    disableTracking: false,
    isComponent: false,
    loc: {
      start: {
        column: 1,
        line: 1,
        offset: 0,
      },
      end: {
        column: 35,
        line: 1,
        offset: 34,
      },
      source: '<div id="vue">Hello {{name}}</div>',
    },
  },
  loc: {
    start: {
      column: 1,
      line: 1,
      offset: 0,
    },
    end: {
      column: 35,
      line: 1,
      offset: 34,
    },
    source: '<div id="vue">Hello {{name}}</div>',
  },
  transformed: true,
}

经过 generate 转化后得到一段 code 字符串代码

js
"const _Vue = Vue
const {  } = _Vue

const _hoisted_1 = { id: "vue" }

return function render(_ctx, _cache) {
  with (_ctx) {
    const { toDisplayString: _toDisplayString, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue

    return (_openBlock(), _createElementBlock("div", _hoisted_1, "Hello " + _toDisplayString(name), 1 /* TEXT */))
  }
}"

最后经过 new Function 函数进行编译得到 render 函数

js
import {
  toDisplayString as _toDisplayString,
  openBlock as _openBlock,
  createElementBlock as _createElementBlock,
} from "vue";

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (
    _openBlock(),
    _createElementBlock(
      "div",
      { id: "vue" },
      "Hello " + _toDisplayString(_ctx.name),
      1 /* TEXT */
    )
  );
}

可以通过进行查询

https://vue-next-template-explorer.netlify.app/

Released under the MIT License.