vue2-编译入口

简介: 前言在前几个月的时候,从0开始学习vue源码。侧重点在于vue的组件化实现,响应式原理,及渲染实现,主要分析的是vue的运行时代码逻辑。从现在开始我们将学习vue编译相关的代码,因为编译相关的代码主要是和AST的生成,转化有关,所以可能比较晦涩和考验JS操作。我们主要是了解其实现过程,知道其大概的实现逻辑和实现流程即可。本篇文章先从入口开始,先简单分析下其入口逻辑

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战


前言


在前几个月的时候,从0开始学习vue源码。侧重点在于vue的组件化实现响应式原理,及渲染实现,主要分析的是vue的运行时代码逻辑。从现在开始我们将学习vue编译相关的代码,因为编译相关的代码主要是和AST的生成,转化有关,所以可能比较晦涩和考验JS操作。我们主要是了解其实现过程,知道其大概的实现逻辑和实现流程即可。

本篇文章先从入口开始,先简单分析下其入口逻辑


入口


我们之前在分析vue实例化的时候知道vue的入口文件是从entry-runtime-with-compiler.js开始的,而我们的render函数也是从那边开始的


if (template) {
  // ...
  const { render, staticRenderFns } = compileToFunctions(template, {
    outputSourceRange: process.env.NODE_ENV !== 'production',
    shouldDecodeNewlines,
    shouldDecodeNewlinesForHref,
    delimiters: options.delimiters,
    comments: options.comments
  }, this)
  options.render = render
  options.staticRenderFns = staticRenderFns
  // ...
}
复制代码


可以发现当我们输入为template时,是通过compileToFunctions来生成render函数的,而在这传入了用户可选配置delimiters分隔符及comments是否保留注释。


compileToFunctions



接下来我们分析下compileToFunctions的实现,其定义在platform/web/compiler


import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
复制代码


其代码看起来很简单,就是引入了createCompiler并传入参数baseOptions。实际上这边就是将与平台相关的baseOptions定义在了platform目录下面,通过柯里化的实现将平台相关的配置分离在不同目录下,最后再调用统一的编译函数

createCompiler


baseOptions中定义了和平台相关的配置,如modules(clsaa style model的编译)directives(text html model的编译)及平台相关的保留标签,特殊行为标签等。


createCompiler


接着我们进入到createCompiler实现的分析


export const createCompiler = createCompilerCreator(function baseCompile (
  template,
  options
) {
  // 1
  const ast = parse(template.trim(), options)
  // 2
  if (options.optimize !== false) {
    optimize(ast, options)
  }
  // 3
  const code = generate(ast, options)
  return {
    ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
})
复制代码

createCompiler函数中实际可以看到我们编译的全貌,编译三步曲


  1. parse:将字符粗模板template解析为AST

  2. optimize:优化AST,实际就是操作AST,对其内容进行转化

  3. generate:生成代码字符串,就是将AST再转化为字符串

实际上这三步也对应着我之前分析过的babel编译原理中的解析->转化->生成

当然,我们分析的是baseCompile中的逻辑,我们最终的函数实际由createCompilerCreator生成的,所以我们还是回到入口的分析上。


createCompilerCreator

export function createCompilerCreator (baseCompile: Function): Function {
  return function createCompiler (baseOptions: CompilerOptions) {
    function compile (
      template,
      options
    ): CompiledResult {
      // 1
      const finalOptions = Object.create(baseOptions)
      const errors = []
      const tips = []
      let warn = (msg, range, tip) => {
        (tip ? tips : errors).push(msg)
      }
      if (options) {
        // ...
        // 2
        // 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
          )
        }
        // 3
        // copy other options
        for (const key in options) {
          if (key !== 'modules' && key !== 'directives') {
            finalOptions[key] = options[key]
          }
        }
      }
      finalOptions.warn = warn
      // 4
      const compiled = baseCompile(template.trim(), finalOptions)
      if (process.env.NODE_ENV !== 'production') {
        detectErrors(compiled.ast, warn)
      }
      compiled.errors = errors
      compiled.tips = tips
      return compiled
    }
    return {
      compile,
      compileToFunctions: createCompileToFunctionFn(compile)
    }
  }
}
复制代码

createCompilerCreator的代码算不上简短,但是逻辑和步骤比较明确且简单,主要是对参数做些预处理,最后再返回新的函数compileToFunctions


  1. 拷贝平台编译的默认配置baseOptions


  2. 合并开发者传入的配置选项modulesdirectives

  3. 开发者配置替代默认配置除modulesdirectives,可以看出不同的配置有不同的策略

  4. 将处理好的最终配置传入baseCompile并进行上面提到的编译三步曲

createCompileToFunctionFn


在上面我们分析的操作都是定义在compile函数中的逻辑,在最终的返回值中,实际还会将compile作为参数传给createCompileToFunctionFn


我们接下来再看看createCompileToFunctionFn的实现


export function createCompileToFunctionFn (compile: Function): Function {
  // 1
  const cache = Object.create(null)
  return function compileToFunctions (
    template: string,
    options?: CompilerOptions,
    vm?: Component
  ): CompiledFunctionResult {
    options = extend({}, options)
    const warn = options.warn || baseWarn
    delete options.warn
    // ...
    // 2
    // check cache
    const key = options.delimiters
      ? String(options.delimiters) + template
      : template
    if (cache[key]) {
      return cache[key]
    }
    // 3
    // compile
    const compiled = compile(template, options)
    // ...
    // 4
    // turn code into functions
    const res = {}
    const fnGenErrors = []
    res.render = createFunction(compiled.render, fnGenErrors)
    res.staticRenderFns = compiled.staticRenderFns.map(code => {
      return createFunction(code, fnGenErrors)
    })
    // ...
    return (cache[key] = res)
  }
}
复制代码


为了避免贴的源码过长,我省略了一些在开发环境中的错误提示代码,但感觉还是有必要说一说省略的三处逻辑



  1. 检查当前运行环境是否能运行new Function,如果不能(配置了无法运行new FunctionCSP)则报错。因为编译将字符串转化成函数就是通过new Function实现的。


  2. 检查compilederrors/tips配置,应该是和sourceMap相关。

  3. 编译中出现错误的抛出,例如Failed to generate render function


说完了被我们打上省略号的步骤,我们再来分析下createCompileToFunctionFn的主要逻辑。


  1. 定义了闭包变量cache用于存储模板编译结果,因为template是实际是不可变的字符串,无论数据如何变化,模板是一样的,所以我们可以存下它的编译结果,只在第一次进行编译就行。

  2. 模板的缓存逻辑,在这可以看到对于同一段模板,我们将用户配置delimiters也存进缓存的key,因为这有可能会影响同一个模板编译结果。

  3. 实际是将上步骤中定义的函数compile在此进行真正的执行
  4. compile的结果字符串通过new Function生成函数


我们最先在入口看到的函数实际就是执行了本步返回值compileToFunctions,通过函数名就能知道。只是vue通过了不断的闭包,函数返回函数,将不同的步骤拆分到不同的函数中执行,所以刚开始分析的时候会感觉藏得挺深的。


梳理


我们前面从入口开始,通过一步步的分析最后纠出最终的编译函数compileToFunctions,有些层层递进的感觉,也有点云里雾里的感觉。那我们再来做个全面的梳理,进一步理解各函数的逻辑关系。


56.png



结语

编译入口的分析比较简单,主要是了解下vue中不同的模块中对入口进行了不同的预处理及配置传入。后面我们将继续分析下vue中编译实现的三个主要逻辑。



相关文章
|
7月前
|
JavaScript 小程序 前端开发
|
JavaScript 算法 中间件
vue3 组件初始化流程(vue3 源码系列)
在ensureRenderer 这个函数中,判断renderer是否存在,不存在则创建,并且传入一系列的api去初始化
vue3 组件初始化流程(vue3 源码系列)
|
2天前
|
JavaScript
【vue】vue2 导入外部js文件如何拿到方法的返回值
【vue】vue2 导入外部js文件如何拿到方法的返回值
18 1
|
2天前
|
小程序 JavaScript
Vue和小程序的区别
Vue和小程序的区别
|
2天前
|
JavaScript 前端开发 编译器
Vue 模板是如何编译的?
Vue 模板是如何编译的?
38 0
|
9月前
|
JavaScript
vue入门之编译项目
vue入门之编译项目
131 0
|
7月前
|
移动开发 JavaScript 小程序
Vue3之程序初始化(createApp)
Vue3之程序初始化(createApp)
113 0
|
11月前
|
JavaScript 前端开发 Go
认识vite_vue3 初始化项目到打包
认识vite_vue3 初始化项目到打包
90 0
|
JavaScript 编译器
从 vue 源码看问题 —— vue 编译器解析的优化
从 vue 源码看问题 —— vue 编译器解析的优化
64 0
|
JavaScript 编译器
从 vue 源码看问题 —— vue 编译器的解析(四)
从 vue 源码看问题 —— vue 编译器的解析
74 0