Vue.js 提供了 2 个版本,一个是 Runtime + Compiler 的,一个是 Runtime only 的,前者是包含编译代码的,可以把编译过程放在运行时做,后者是不包含编译代码的,需要借助 webpack 的 vue-loader 事先把模板编译成 render函数
    使用 Runtime + Compiler 的 Vue.js,它的入口是 src/platforms/web/entry-runtime-with-compiler.js,看一下它对 $mount 函数的定义

    1. const mount = Vue.prototype.$mount
    2. Vue.prototype.$mount = function (
    3. el?: string | Element,
    4. hydrating?: boolean
    5. ): Component {
    6. el = el && query(el)
    7. /* istanbul ignore if */
    8. if (el === document.body || el === document.documentElement) {
    9. process.env.NODE_ENV !== 'production' && warn(
    10. `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    11. )
    12. return this
    13. }
    14. const options = this.$options
    15. // resolve template/el and convert to render function
    16. if (!options.render) {
    17. let template = options.template
    18. if (template) {
    19. if (typeof template === 'string') {
    20. if (template.charAt(0) === '#') {
    21. template = idToTemplate(template)
    22. /* istanbul ignore if */
    23. if (process.env.NODE_ENV !== 'production' && !template) {
    24. warn(
    25. `Template element not found or is empty: ${options.template}`,
    26. this
    27. )
    28. }
    29. }
    30. } else if (template.nodeType) {
    31. template = template.innerHTML
    32. } else {
    33. if (process.env.NODE_ENV !== 'production') {
    34. warn('invalid template option:' + template, this)
    35. }
    36. return this
    37. }
    38. } else if (el) {
    39. template = getOuterHTML(el)
    40. }
    41. if (template) {
    42. /* istanbul ignore if */
    43. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    44. mark('compile')
    45. }
    46. // 编译入口
    47. // compileToFunctions方法就是把模板template编译生成render以及staticRenderFns
    48. const { render, staticRenderFns } = compileToFunctions(template, {
    49. outputSourceRange: process.env.NODE_ENV !== 'production',
    50. shouldDecodeNewlines,
    51. shouldDecodeNewlinesForHref,
    52. delimiters: options.delimiters,
    53. comments: options.comments
    54. }, this)
    55. options.render = render
    56. options.staticRenderFns = staticRenderFns
    57. /* istanbul ignore if */
    58. if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    59. mark('compile end')
    60. measure(`vue ${this._name} compile`, 'compile', 'compile end')
    61. }
    62. }
    63. }
    64. return mount.call(this, el, hydrating)
    65. }

    compileToFunctions定义在 src/platforms/web/compiler/index.js 中

    1. import { baseOptions } from './options'
    2. import { createCompiler } from 'compiler/index'
    3. const { compile, compileToFunctions } = createCompiler(baseOptions)
    4. export { compile, compileToFunctions }

    createCompiler方法接收一个编译配置参数
    createCompiler方法定义在src/compiler/index.js中

    1. // `createCompilerCreator` allows creating compilers that use alternative
    2. // parser/optimizer/codegen, e.g the SSR optimizing compiler.
    3. // Here we just export a default compiler using the default parts.
    4. // 实际上是通过调用createCompilerCreator方法返回
    5. // 该方法传入的参数是一个函数,真正的编译过程都在这个baseCompile函数里执行
    6. export const createCompiler = createCompilerCreator(function baseCompile (
    7. template: string,
    8. options: CompilerOptions
    9. ): CompiledResult {
    10. const ast = parse(template.trim(), options)
    11. if (options.optimize !== false) {
    12. optimize(ast, options)
    13. }
    14. const code = generate(ast, options)
    15. return {
    16. ast,
    17. render: code.render,
    18. staticRenderFns: code.staticRenderFns
    19. }
    20. })

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

    1. export function createCompilerCreator (baseCompile: Function): Function {
    2. return function createCompiler (baseOptions: CompilerOptions) {
    3. function compile (
    4. template: string,
    5. options?: CompilerOptions
    6. ): CompiledResult {
    7. const finalOptions = Object.create(baseOptions)
    8. const errors = []
    9. const tips = []
    10. let warn = (msg, range, tip) => {
    11. (tip ? tips : errors).push(msg)
    12. }
    13. if (options) {
    14. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    15. // $flow-disable-line
    16. const leadingSpaceLength = template.match(/^\s*/)[0].length
    17. warn = (msg, range, tip) => {
    18. const data: WarningMessage = { msg }
    19. if (range) {
    20. if (range.start != null) {
    21. data.start = range.start + leadingSpaceLength
    22. }
    23. if (range.end != null) {
    24. data.end = range.end + leadingSpaceLength
    25. }
    26. }
    27. (tip ? tips : errors).push(data)
    28. }
    29. }
    30. // merge custom modules
    31. if (options.modules) {
    32. finalOptions.modules =
    33. (baseOptions.modules || []).concat(options.modules)
    34. }
    35. // merge custom directives
    36. if (options.directives) {
    37. finalOptions.directives = extend(
    38. Object.create(baseOptions.directives || null),
    39. options.directives
    40. )
    41. }
    42. // copy other options
    43. for (const key in options) {
    44. if (key !== 'modules' && key !== 'directives') {
    45. finalOptions[key] = options[key]
    46. }
    47. }
    48. }
    49. finalOptions.warn = warn
    50. const compiled = baseCompile(template.trim(), finalOptions)
    51. if (process.env.NODE_ENV !== 'production') {
    52. detectErrors(compiled.ast, warn)
    53. }
    54. compiled.errors = errors
    55. compiled.tips = tips
    56. return compiled
    57. }
    58. return {
    59. compile,
    60. compileToFunctions: createCompileToFunctionFn(compile)
    61. }
    62. }
    63. }

    该方法返回了一个 createCompiler 的函数,它接收一个 baseOptions 的参数,返回的是一个对象,包括 compile 方法属性和 compileToFunctions 属性
    这个 compileToFunctions 对应的就是 $mount 函数调用的 compileToFunctions 方法,它是调用 createCompileToFunctionFn 方法的返回值
    createCompileToFunctionFn方法定义在src/compiler/to-function.js中

    1. export function createCompileToFunctionFn (compile: Function): Function {
    2. const cache = Object.create(null)
    3. return function compileToFunctions (
    4. template: string, // 编译模板template
    5. options?: CompilerOptions, // 编译配置options
    6. vm?: Component // Vue实例vm
    7. ): CompiledFunctionResult {
    8. options = extend({}, options)
    9. const warn = options.warn || baseWarn
    10. delete options.warn
    11. /* istanbul ignore if */
    12. if (process.env.NODE_ENV !== 'production') {
    13. // detect possible CSP restriction
    14. try {
    15. new Function('return 1')
    16. } catch (e) {
    17. if (e.toString().match(/unsafe-eval|CSP/)) {
    18. warn(
    19. 'It seems you are using the standalone build of Vue.js in an ' +
    20. 'environment with Content Security Policy that prohibits unsafe-eval. ' +
    21. 'The template compiler cannot work in this environment. Consider ' +
    22. 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
    23. 'templates into render functions.'
    24. )
    25. }
    26. }
    27. }
    28. // check cache
    29. const key = options.delimiters
    30. ? String(options.delimiters) + template
    31. : template
    32. if (cache[key]) {
    33. return cache[key]
    34. }
    35. // compile
    36. // 核心编译过程
    37. const compiled = compile(template, options)
    38. // check compilation errors/tips
    39. if (process.env.NODE_ENV !== 'production') {
    40. if (compiled.errors && compiled.errors.length) {
    41. if (options.outputSourceRange) {
    42. compiled.errors.forEach(e => {
    43. warn(
    44. `Error compiling template:\n\n${e.msg}\n\n` +
    45. generateCodeFrame(template, e.start, e.end),
    46. vm
    47. )
    48. })
    49. } else {
    50. warn(
    51. `Error compiling template:\n\n${template}\n\n` +
    52. compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
    53. vm
    54. )
    55. }
    56. }
    57. if (compiled.tips && compiled.tips.length) {
    58. if (options.outputSourceRange) {
    59. compiled.tips.forEach(e => tip(e.msg, vm))
    60. } else {
    61. compiled.tips.forEach(msg => tip(msg, vm))
    62. }
    63. }
    64. }
    65. // turn code into functions
    66. const res = {}
    67. const fnGenErrors = []
    68. res.render = createFunction(compiled.render, fnGenErrors)
    69. res.staticRenderFns = compiled.staticRenderFns.map(code => {
    70. return createFunction(code, fnGenErrors)
    71. })
    72. // check function generation errors.
    73. // this should only happen if there is a bug in the compiler itself.
    74. // mostly for codegen development use
    75. /* istanbul ignore if */
    76. if (process.env.NODE_ENV !== 'production') {
    77. if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
    78. warn(
    79. `Failed to generate render function:\n\n` +
    80. fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
    81. vm
    82. )
    83. }
    84. }
    85. return (cache[key] = res)
    86. }
    87. }

    compile函数在执行 createCompileToFunctionFn 的时候作为参数传入,它是 createCompiler 函数中定义的 compile 函数

    1. function compile (
    2. template: string,
    3. options?: CompilerOptions
    4. ): CompiledResult {
    5. const finalOptions = Object.create(baseOptions)
    6. const errors = []
    7. const tips = []
    8. let warn = (msg, range, tip) => {
    9. (tip ? tips : errors).push(msg)
    10. }
    11. // 处理配置参数
    12. if (options) {
    13. if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
    14. // $flow-disable-line
    15. const leadingSpaceLength = template.match(/^\s*/)[0].length
    16. warn = (msg, range, tip) => {
    17. const data: WarningMessage = { msg }
    18. if (range) {
    19. if (range.start != null) {
    20. data.start = range.start + leadingSpaceLength
    21. }
    22. if (range.end != null) {
    23. data.end = range.end + leadingSpaceLength
    24. }
    25. }
    26. (tip ? tips : errors).push(data)
    27. }
    28. }
    29. // merge custom modules
    30. if (options.modules) {
    31. finalOptions.modules =
    32. (baseOptions.modules || []).concat(options.modules)
    33. }
    34. // merge custom directives
    35. if (options.directives) {
    36. finalOptions.directives = extend(
    37. Object.create(baseOptions.directives || null),
    38. options.directives
    39. )
    40. }
    41. // copy other options
    42. for (const key in options) {
    43. if (key !== 'modules' && key !== 'directives') {
    44. finalOptions[key] = options[key]
    45. }
    46. }
    47. }
    48. finalOptions.warn = warn
    49. // 编译过程
    50. const compiled = baseCompile(template.trim(), finalOptions)
    51. if (process.env.NODE_ENV !== 'production') {
    52. detectErrors(compiled.ast, warn)
    53. }
    54. compiled.errors = errors
    55. compiled.tips = tips
    56. return compiled
    57. }

    baseCompile在执行 createCompilerCreator 方法时作为参数传入

    1. // `createCompilerCreator` allows creating compilers that use alternative
    2. // parser/optimizer/codegen, e.g the SSR optimizing compiler.
    3. // Here we just export a default compiler using the default parts.
    4. export const createCompiler = createCompilerCreator(function baseCompile (
    5. template: string,
    6. options: CompilerOptions
    7. ): CompiledResult {
    8. // 解析模板字符串生成AST
    9. const ast = parse(template.trim(), options)
    10. if (options.optimize !== false) {
    11. // 优化语法树
    12. optimize(ast, options)
    13. }
    14. // 生成代码
    15. const code = generate(ast, options)
    16. return {
    17. ast,
    18. render: code.render,
    19. staticRenderFns: code.staticRenderFns
    20. }
    21. })

    编译入口逻辑之所以这么绕,是因为 Vue.js 在不同的平台下都会有编译的过程,因此编译过程中的依赖的配置 baseOptions 会有所不同。而编译过程会多次执行,但这同一个平台下每一次的编译过程配置又是相同的,为了不让这些配置在每次编译过程都通过参数传入,Vue.js 利用了函数柯里化的技巧很好的实现了 baseOptions 的参数保留。同样,Vue.js 也是利用函数柯里化技巧把基础的编译过程函数抽出来,通过 createCompilerCreator(baseCompile) 的方式把真正编译的过程和其它逻辑如对编译配置处理、缓存处理等剥离开,这样的设计还是非常巧妙的