Skip to content

模板编译入口

Introduction

compile

对于umd类型的打包,内部包括了模板的编译,以及其他所有核心的功能,而对于vue-cli或者webpack的工程,所用的 vue 是不到模板编译的内容的,这种项目中模板编译是通过vue-loader来编译模板为 render 函数,因此我们这里分析的是功能最全的 vue

通过打包命令找到入口文件所在的位置:src/platform/web/runtime-with-compiler.ts

ts
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) {
    __DEV__ &&
      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 (__DEV__ && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            );
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        if (__DEV__) {
          warn("invalid template option:" + template, this);
        }
        return this;
      }
    } else if (el) {
      // @ts-expect-error
      template = getOuterHTML(el);
    }
    if (template) {
      /* istanbul ignore if */
      if (__DEV__ && config.performance && mark) {
        mark("compile");
      }

      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: __DEV__,
          shouldDecodeNewlines, // 是否对一般html元素属性中换行符进行编码
          shouldDecodeNewlinesForHref, // 是否对a标签中换行符进行编码
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      );
      options.render = render;
      options.staticRenderFns = staticRenderFns;

      /* istanbul ignore if */
      if (__DEV__ && config.performance && mark) {
        mark("compile end");
        measure(`vue ${this._name} compile`, "compile", "compile end");
      }
    }
  }
  return mount.call(this, el, hydrating);
};

重写Vue.prototype.$mount

首先将 Vue 原型上面的$mount方法保存下来,接着又重写了该方法

重写Vue.prototype.$mount的逻辑如下:

  1. 兼容el, 最终el为 dom 节点

    TIP

    这就是为何再书写 vue 的时候, el 以传选择器, 也可以传真实的 DOM 节点的原因

    Details
    ts
    el = el && query(el);
    
    /**
     * Query an element selector if it's not an element already.
     */
    export function query(el: string | Element): Element {
      if (typeof el === "string") {
        const selected = document.querySelector(el);
        if (!selected) {
          __DEV__ && warn("Cannot find element: " + el);
          return document.createElement("div");
        }
        return selected;
      } else {
        return el;
      }
    }
  2. 拿到 options,若是没有options.render(render 函数),则会进入模板编译的流程

  3. 用原先原型上的$mount进行挂载

接着进行分析没有options.render(render 函数)时候的模板编译流程