编译方式 模版->真实 DOM 的渲染过程中,有把模版编译成 render 函数,这个过程就叫编译 vue 提供了两种编译方法:Runtime + Compiler 和 Runtime only Runtime + Compiler:如果在 vue 对象中用了 template,在运行中就可以编译的 Runtime only:在编译阶段把.vue 文件编译成 rener 函数 编译入口 $moun…

编译方式

  1. 模版->真实 DOM 的渲染过程中,有把模版编译成 render 函数,这个过程就叫编译
  2. vue 提供了两种编译方法:Runtime + Compiler 和 Runtime only
  3. Runtime + Compiler:如果在 vue 对象中用了 template,在运行中就可以编译的
  4. Runtime only:在编译阶段把.vue 文件编译成 rener 函数

编译入口

  1. $mount 函数的定义
// src/platforms/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount;
Vue.prototype.$mount = function(el?: string | Element, hydrating?: boolean): Component {
  el = el && query(el);

  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(`Do not mount Vue to <html> or <body> - mount to normal elements instead.`);
    return this;
  }

  const options = this.$options;
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template);
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(`Template element not found or is empty: ${options.template}`, this);
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this);
        }
        return this;
      }
    } else if (el) {
      template = getOuterHTML(el);
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile');
      }
      // 拿到template后
      // 这个就是编译入口
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      );
      options.render = render;
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end');
        measure(`vue ${this._name} compile`, 'compile', 'compile end');
      }
    }
  }
  return mount.call(this, el, hydrating);
};
  1. compileToFunctions 编译入口
// src/platforms/web/compiler/index.js
// compileToFunctions是createCompiler的返回值,该方法接收一个编译参数,
createCompiler;
import { baseOptions } from './options';
import { createCompiler } from 'compiler/index';
const { compile, compileToFunctions } = createCompiler(baseOptions);
export { compile, compileToFunctions };
  1. createCompiler 过程
// src/compiler/index.js
// createCompiler是通过调用createCompilerCreator方法返回的,这个方法传入一个函数,
export const createCompiler = createCompilerCreator(function baseCompile(template: string, options: CompilerOptions): CompiledResult {
  const ast = parse(template.trim(), options);
  if (options.optimize !== false) {
    optimize(ast, options);
  }
  const code = generate(ast, options);
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  };
});
  1. createCompilerCreator
// src/compiler/create-compiler.js
// 返回一个createCompiler函数,接收一个baseOptions
export function createCompilerCreator(baseCompile: Function): Function {
  return function createCompiler(baseOptions: CompilerOptions) {
    function compile(template: string, options?: CompilerOptions): CompiledResult {
      const finalOptions = Object.create(baseOptions);
      const errors = [];
      const tips = [];
      finalOptions.warn = (msg, tip) => {
        (tip ? tips : errors).push(msg);
      };

      if (options) {
        // merge custom modules
        if (options.modules) {
          finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
        }
        // merge custom directives
        if (options.directives) {
          finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives);
        }
        // copy other options
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key];
          }
        }
      }

      const compiled = baseCompile(template, finalOptions);
      if (process.env.NODE_ENV !== 'production') {
        errors.push.apply(errors, detectErrors(compiled.ast));
      }
      compiled.errors = errors;
      compiled.tips = tips;
      return compiled;
    }
    // 返回compile和compileToFunctions,compileToFunctions就是$mount函数调用的compileToFunctions 方法
    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    };
  };
}
  1. createCompileToFunctionFn
// src/compiler/to-function/js
// 核心编译过程:const compiled = compile(template, options);
export function createCompileToFunctionFn(compile: Function): Function {
  const cache = Object.create(null);
  // compileToFunctions定义:接收三个参数:模板template,配置options,Vue实例vm
  return function compileToFunctions(template: string, options?: CompilerOptions, vm?: Component): CompiledFunctionResult {
    options = extend({}, options);
    const warn = options.warn || baseWarn;
    delete options.warn;

    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      // detect possible CSP restriction
      try {
        // Function可以把代码字符串变成function
        new Function('return 1');
      } catch (e) {
        if (e.toString().match(/unsafe-eval|CSP/)) {
          warn('It seems you are using the standalone build of Vue.js in an ' + 'environment with Content Security Policy that prohibits unsafe-eval. ' + 'The template compiler cannot work in this environment. Consider ' + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 'templates into render functions.');
        }
      }
    }
    // 缓存操作,同一个模板不反复编译
    const key = options.delimiters ? String(options.delimiters) + template : template;
    if (cache[key]) {
      return cache[key];
    }
    // 核心编译过程
    // compile在执行createCompileToFunctionFn时传入
    const compiled = compile(template, options);
    // check compilation errors/tips
    if (process.env.NODE_ENV !== 'production') {
      if (compiled.errors && compiled.errors.length) {
        warn(`Error compiling template:\n\n${template}\n\n` + compiled.errors.map((e) => `- ${e}`).join('\n') + '\n', vm);
      }
      if (compiled.tips && compiled.tips.length) {
        compiled.tips.forEach((msg) => tip(msg, vm));
      }
    }
    // turn code into functions
    const res = {};
    const fnGenErrors = [];
    res.render = createFunction(compiled.render, fnGenErrors);
    res.staticRenderFns = compiled.staticRenderFns.map((code) => {
      return createFunction(code, fnGenErrors);
    });

    // check function generation errors.
    // this should only happen if there is a bug in the compiler itself.
    // mostly for codegen development use
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production') {
      if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
        warn(`Failed to generate render function:\n\n` + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), vm);
      }
    }
    return (cache[key] = res);
  };
}
  1. compile 过程
// src/compiler/create-compiler.js
function compile(template: string, options?: CompilerOptions): CompiledResult {
  const finalOptions = Object.create(baseOptions);
  const errors = [];
  const tips = [];
  finalOptions.warn = (msg, tip) => {
    (tip ? tips : errors).push(msg);
  };

  if (options) {
    // merge custom modules
    if (options.modules) {
      finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
    }
    // merge custom directives
    if (options.directives) {
      finalOptions.directives = extend(Object.create(baseOptions.directives || null), options.directives);
    }
    // copy other options
    for (const key in options) {
      if (key !== 'modules' && key !== 'directives') {
        finalOptions[key] = options[key];
      }
    }
  }
  // 处理完配置参数,然后执行编译过程
  const compiled = baseCompile(template, finalOptions);
  if (process.env.NODE_ENV !== 'production') {
    errors.push.apply(errors, detectErrors(compiled.ast));
  }
  compiled.errors = errors;
  compiled.tips = tips;
  return compiled;
}
  1. baseCompile 在执行 createCompilerCreator 方法时作为参数传入
export const createCompiler = createCompilerCreator(function baseCompile(template: string, options: CompilerOptions): CompiledResult {
  // 解析模板生成抽象语法树
  const ast = parse(template.trim(), options);
  // 优化语法树
  optimize(ast, options);
  // 生成代码
  const code = generate(ast, options);
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  };
});

编译核心的几个过程

  1. $mount 后,执行 compileToFunctions 函数,compileToFunctions 函数接收三个参数:模板 template,配置 options,Vue 实例 vm
  2. 解析模板字符串,生成 ast 抽象语法树-->parse 过程
  3. 优化语法树-->optimize 过程
  4. 生成代码-->generate 过程

为什么这么编译入口很绕

  1. 因为不同平台下都会有编译过程,依赖的 baseOptions 根据平台不同
  2. 把 baseOption 在每次编译的时候不都通过参数传入 ,使用函数柯里化把参数实现保留
  3. 把编译过程抽象出来,把真正的编译过程、编译处理、缓存处理等各自剥离出来

暂无评论