锋言锋语

NferZhuang的自留地

vue源码解析准备 — Runtime Only和Runtime + Compiler

如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就将需要加上编译器,即完整版

当使用 vue-loader 或 vueify 的时候,*.vue 文件内部的模板会在构建时预编译成 JavaScript。你在最终打好的包里实际上是不需要编译器的,所以只用运行时版本即可

官方文档

客户端编译模板

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

  <div id="app"></div>

  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      template: '<div>{{ message }}</div>'
    })
  </script>

这种用法就需要在客户端(即浏览器中)编译模板,模版的内容是<div>{{ message }}</div>,模版的数据是message: 'Hello Vue!'

因此,如果使用script引入只有运行时版本的vue.jsvue.runtime.js,就会报错:

vue.runtime.js:593 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

使用渲染函数

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.runtime.js"></script>

  <div id="app"></div>

  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      render (createElement) {
        return createElement('div', this.message)
      }
    })
  </script>

这种用法就是直接给出渲染函数来进行内容输出(具体createElement语法后面再讲),这种情况下不需要进行客户端渲染,直接引用运行时版本的vue.js即可,并不会报错。

预编译模板

简单的页面结构我们可以直接通过createElement函数来手动编写,但是对于复杂的页面结构呢?vue提供了Vue.compile函数用于将模版编译成render函数,示例如下:

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

  <script>
    const result = Vue.compile('<div>{{ message }}</div>');
    console.log(result.render);
  </script>

注意:只有Runtime + Compiler版本的vuejs才有Vue.compile函数,运行时版本的vue.js是没有这个函数的,所以这里也就是所谓的预编译。

输出的结果如下:

ƒ anonymous(
) {
with(this){return _c('div',[_v(_s(message))])}
}

我们用预编译生成的render函数替代上一章的render函数:

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.runtime.js"></script>

  <div id="app"></div>

  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      render (createElement) {
        with(this){return _c('div',[_v(_s(message))])}
      }
    })
  </script>

可以看到这种“开发时预编译,上线使用运行时”的方式既满足了开发需要又减少了线上文件的大小。

注:通过打印,可以看到createElement以及this._c, this._vthis._s的值:

      render (createElement) {
        console.log(createElement, this._c, this._v, this._s)
        with(this){return _c('div',[_v(_s(message))])}
      }
ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, true); }
ƒ (a, b, c, d) { return createElement(vm, a, b, c, d, false); }
ƒ createTextVNode (val) {...}
ƒ toString (val) {...}

所以可以看出,我们最开始手写的渲染函数return createElement('div', this.message)只是上面预编译生成的一个简版。

基于 HTML 的模板语法

除了上面几种基于js代码的形式来创建模版,vuejs也支持基于 HTML 的模板语法,用法如下:

  <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
</head>
<body>
  <div id="app"><div>{{ message }}</div></div>

  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      }
    })
  </script>

注意,这种写法也必须要使用Runtime + Compiler版本的vuejs才可以允许,因为其实质上还是在客户端进行模版编译,因为上面的写法实质上等同于下面的写法:

  <div id="app"></div>
  <template id="tpl">
    <div>{{ message }}</div>
  </template>

  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      template: '#tpl'
    })
  </script>

vuejs源码中的 template 解析

在vuejs源码中,关于获取 template 的关键代码如下:

    var template = options.template;
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template);
        }
      } else if (template.nodeType) {
        template = template.innerHTML;
      } else {
        {
          warn('invalid template option:' + template, this);
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el);
    }

逻辑主要步骤如下:

  • 先判断是否有template属性
  • 如果没有,则直接通过el中的html代码作为模版
  • 如果有,判断是否是字符串(非字符串的形式暂不讨论)
  • 是字符串的情况下,是否以#字符开头
  • 如果是,则获取对应id的innerHTML作为模版
  • 如果不是以#字符开头,则直接作为作为模版

总结

一句话来讲就是:如果有render函数就可以使用运行时版本的vuejs,否则必须使用Runtime + Compiler版本的vuejs。

文 / nfer
LEAVE A REPLY

loading