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中编译实现的三个主要逻辑。



相关文章
|
机器学习/深度学习 人工智能 搜索推荐
AIGC工具——PhotoRoom
【1月更文挑战第18天】AIGC工具——PhotoRoom
260 1
AIGC工具——PhotoRoom
DataFrame(13):DataFrame的排序与排名问题(一)
DataFrame(13):DataFrame的排序与排名问题(一)
DataFrame(13):DataFrame的排序与排名问题(一)
|
项目管理 开发工具 git
git push 报错 pre-receive hook declined
git push 报错 pre-receive hook declined
5079 0
git push 报错 pre-receive hook declined
|
11月前
|
负载均衡 网络协议 定位技术
在数字化时代,利用DNS实现地理位置路由成为提升用户体验的有效策略
在数字化时代,利用DNS实现地理位置路由成为提升用户体验的有效策略。通过解析用户请求的来源IP地址,DNS服务器可判断其地理位置,并返回最近或最合适的服务器IP,从而优化网络路由,减少延迟,提高访问速度。示例代码展示了如何基于IP地址判断地理位置并分配相应服务器IP,实际应用中需结合专业地理数据库和动态调整机制,以应对复杂网络环境带来的挑战。
249 6
|
存储 SQL 关系型数据库
OceanBase的架构特点
【8月更文挑战第10天】OceanBase的架构特点
516 66
|
12月前
|
消息中间件 存储 缓存
为什么 Kafka 的吞吐量那么高?
为什么 Kafka 的吞吐量那么高?
303 2
|
机器学习/深度学习 人工智能 自然语言处理
构建智能化编程助手:AI 在软件开发中的新角色
随着AI技术的发展,智能化编程助手正逐渐改变软件开发方式。本文介绍其核心功能,如代码自动补全、智能错误检测等,并探讨如何利用机器学习、自然语言处理及知识图谱等技术构建高效、易用的编程助手,提升开发效率与代码质量,同时讨论面临的技术挑战与未来前景。
|
安全 物联网 API
LabVIEW常用的加密硬件
LabVIEW常用的加密硬件
146 2
MFC基本控件-静态文本的使用
MFC基本控件-静态文本的使用
|
关系型数据库 MySQL 分布式数据库
云原生数据库PolarDB MySQL版的全面深度评测
云原生数据库PolarDB MySQL版的全面深度评测
274 0