Vue 2 阅读理解(三)之 Compile 模板编译

简介: Vue 2 阅读理解(三)之 Compile 模板编译

template compile 模板解析


runtime-with-compiler.ts 中,Vue 构造函数的 $mount 被重写,添加了对于 template 模板的解析部分。


const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
  el = el && query(el)
  if (el === document.body || el === document.documentElement) {
    __DEV__ && warn('')
    return this
  }
  const options = this.$options
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          if (__DEV__ && !template) {
            warn('')
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        __DEV__ && warn('')
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      const { render, staticRenderFns } = compileToFunctions(
        template,
        {
          outputSourceRange: __DEV__,
          shouldDecodeNewlines,
          shouldDecodeNewlinesForHref,
          delimiters: options.delimiters,
          comments: options.comments
        },
        this
      )
      options.render = render
      options.staticRenderFns = staticRenderFns
    }
  }
  return mount.call(this, el, hydrating)
}


上面的源代码省略了性能分析部分。


当然,既然是编译 template,那么肯定是建立在有 template 配置的基础上,但是因为 render() 方法的优先级高于 template,所以这里采用的是判断 不存在 render 属性 才执行模板编译。


这段代码的大致逻辑如下:


  1. 判断 el 挂载节点,会校验 不能挂载到 html 和 body 上。


  1. 判断不存在 render 配置,执行 template compile 模板编译


整个模板编译过程又分为:“模板字符串查询” 和 “render函数转换”


1. 模板字符串查询


模板字符串查询 分为以下两种情况:


1. 存在 template 配置:


  • 如果 template 是字符串,且以 # 开头,则将该属性视为节点 id 进行获取,并将结果转为 html 字符串重新赋值给 template 变量;如果字符串 不以 # 开头 或者 获取结果不存在,则报错并直接退出该函数


  • 如果 template 具有 nodeType 属性,则表示 template 就是一个 dom 节点,就直接使用 innerHTML 获取该节点的 html 字符串并重新赋值给 template 变量


  • template 既不是 id 字符串也不是 dom 节点,则直接退出函数并报错:非法 template 配置


这种情况采用的都是 element.innerHTML 属性,即只取用配置节点内部的子节点


2. 不存在 template 但是有 el 对应的节点:


因为上文已经查询过 el 配置对应的 dom 节点,所以这里的 el 要么是 undefined,要么就是一个真实 dom


  • 如果 el 节点有 outerHTML 属性,则将 template 变量设置为 el.outerHTML


  • 否则创建一个新的 div 节点 container,并在 container 中插入一个 el 节点的复制节点,将 template 变量设置为 container.innerHTML


如果经过上面的步骤之后,查询到了对应的 template 模板字符串,则进行后续的模板编译;否则直接调用缓存的 mount 方法直接进行后续操作(也就是最初定义的 Vue.prototype.$mount,将实例的 render 方法直接绑定为一个创建空节点的方法)


2. render函数转换


这里的逻辑主要在 compileToFunctions 函数中


const { render, staticRenderFns } = compileToFunctions(
  template,
  {
    outputSourceRange: __DEV__,
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
  },
  this
)


compileToFunctions 来自于 createCompiler


import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
// 这里的 baseOptions 包含以下内容
export const baseOptions: CompilerOptions = {
  expectHTML: true,
  modules, // 处理 class,style 绑定 和 v-model 语法糖
  directives, // 处理 v-model,v-text,v-html 指令
  isPreTag, // 判断 pre 标签
  isUnaryTag, // 判断 link,meta,source,input 之类的可闭合标签
  mustUseProp, // 判断 input,textarea,option 之类的标签,只可以使用 prop 绑定特定属性
  canBeLeftOpenTag, // 判断可以只需要开启部分的标签
  isReservedTag, // 判断是不是 html 或者 svg 保留标签
  getTagNamespace, // 获取 svg 类标签和 math 标签
  staticKeys: genStaticKeys(modules) // 返回由 modules 的每个模块的staticKeys组成的字符串
}


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
  }
})


createCompilerCreator 函数接收一个 baseCompile 函数作为参数,返回一个接收 CompilerOptions 参数并返回 compile,compileToFunctions 的对象。而 compileToFunctions 方法则是来自于 createCompileToFunctionFn(compile)


3. baseCompile 函数


该函数接收一个 template 模板字符串(也就是前面通过模板字符串查询得到的结果)和一个编译配置,返回一个包含抽象语法树等属性的对象。


  • ast:由 template 模板得到的 dom 节点关系的 AST 抽象语法树,如果有配置 optimize ,还会调用 **optimize(ast, options)**进行优化


  • render:由 generate 方法生成的渲染方法,主要是通过 遍历 ast 来拼接一个由 _c(xxx) 等方法组成的字符串,这些方法都在定义 Vue 构造函数时被定义的。


  • staticRenderFns:与 render 类似,但是只有在 template 是纯静态节点的时候有返回值


template: "<div class='demo-vm-2'>" +
            "<h2>Demo VM 2</h2>" +
            "<p>demo2 data is: {{ data2 }}</p>" +
            "<p>demo2 props data is: {{ result }}</p>" +
            "<span>test span</span>" +
            "<button @click='demoClick2'>button2</button>" +
            "<br />" +
            "<input :value='changeMessage' />" +
            "<slot name='slot1'></slot>" +
            "<slot></slot>" +
            "<demo-vm-three></demo-vm-three>" +
            "<demo-vm-four></demo-vm-four>" +
            "</div>"
== 编译后 ==>
render:`with(this){return _c('div',{staticClass:"demo-vm-2"},[_c('h2',[_v("Demo VM 2")]),_c('p',[_v("demo2 data is: "+_s(data2))]),_c('p',[_v("demo2 props data is: "+_s(result))]),_c('span',[_v("test span")]),_c('button',{on:{"click":demoClick2}},[_v("button2")]),_c('br'),_c('input',{domProps:{"value":changeMessage}}),_t("slot1"),_t("default"),_c('demo-vm-three'),_c('demo-vm-four')],2)}`
template: "<div class='demo-vm-4'>" +
            "<h2>Demo VM 4</h2>" +
            "</div>"
== 编译后 ==>
staticRenderFns: ["with(this){return _c('div',{staticClass:\"demo-vm-4\"},[_c('h2',[_v(\"Demo VM 4\")])])}"]


这里的 renderstaticRenderFns 返回值都是一个 with 函数,通过内部的字符串编译成 JavaScript 脚本并指定内部环境变量为当前 实例 this


4. compile 函数


compile 函数由 createCompiler 函数创建,接收 template 模板字符串和 baseOptions 编译配置,并通过闭包的方式使用上面的 baseCompile 方法来编译 template 模板。


最终 compile 函数内部核心逻辑就是将 compileToFunctions() 传入的编译配置项和 baseOptions 基础配置项合并为一个配置项 finalOptions,并通过 baseCompile(template, finalOptions) 来得到最终的组件渲染函数。


目录
相关文章
|
2月前
|
JavaScript
vue学习(3)模板语法
vue学习(3)模板语法
62 11
|
16天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
1月前
|
JavaScript 前端开发
|
2月前
|
JavaScript 前端开发 开发者
Vue3:快速生成模板代码
Vue3:快速生成模板代码
|
3月前
|
JavaScript 开发工具 容器
初始Vue、Vue模板语法、数据绑定(2022/7/3)
这篇文章是关于Vue.js基础的介绍,包括Vue的简介、安装使用、开发工具、基础知识、模板语法和数据绑定。文中通过代码实例和页面效果展示了如何创建Vue实例、使用插值和指令语法、以及单向和双向数据绑定的方法。
初始Vue、Vue模板语法、数据绑定(2022/7/3)
|
2月前
|
JavaScript 前端开发 搜索推荐
推荐5款免费、开箱即用的Vue后台管理系统模板
推荐5款免费、开箱即用的Vue后台管理系统模板
116 1
|
3月前
|
JavaScript Linux 开发工具
开源项目:使用 Atom-Electron 和 Vue.js 制作的简单 RSS 阅读器!!
开源项目:使用 Atom-Electron 和 Vue.js 制作的简单 RSS 阅读器!!
|
3月前
|
JavaScript
Vue——Vue v2.7.14 源码阅读之代码目录结构【一】
Vue——Vue v2.7.14 源码阅读之代码目录结构【一】
77 0
|
4月前
|
缓存 JavaScript 前端开发
前端框架与库 - Vue.js基础:模板语法、数据绑定
【7月更文挑战第14天】Vue.js 是渐进式框架,以简洁API和高效数据绑定知名。本文聚焦模板语法与数据绑定,解释常见问题和易错点,助力初学者避坑。模板语法中,{{ expression }} 用于渲染值,v-bind/: 用于动态绑定属性。数据绑定涉及文本、属性和事件,注意v-model适用于表单元素,计算属性有缓存。理解正确用法,借助文档和IDE,可提升开发质量和效率。善用Vue.js,打造响应式UI。
133 4
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的多功能智能手机阅读APP附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的多功能智能手机阅读APP附带文章源码部署视频讲解等
77 1