vue3源码学习api-vue-sfc文件编译

vue 最有代表性质的就是.VUE 的文件,每一个vue文件都是一个组件,那么vue 组件的编译过程是什么样的呢

Vue 单文件组件 (SFC)和指令 ast 语法树

一个 Vue 单文件组件 (SFC),通常使用 *.vue 作为文件扩展名,它是一种使用了类似 HTML 语法的自定义文件格式,用于定义 Vue 组件。一个 Vue 单文件组件在语法上是兼容 HTML 的。

每一个 *.vue 文件都由三种顶层语言块构成:<template><script><style>,以及一些其他的自定义块:

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data() {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

<custom1>
  This could be e.g. documentation for the component.
</custom1>

关于sfc 这里有非常详细的介绍 https://github.com/vuejs/core/tree/main/packages/compiler-sfc

编译解析和转换工作流程

可以在流程图中看到先对整个文件进行解析 识别出 出<template><script><style> 模块 在各自解析

                                  +--------------------+
                                  |                    |
                                  |  script transform  |
                           +----->+                    |
                           |      +--------------------+
                           |
+--------------------+     |      +--------------------+
|                    |     |      |                    |
|  facade transform  +----------->+ template transform |
|                    |     |      |                    |
+--------------------+     |      +--------------------+
                           |
                           |      +--------------------+
                           +----->+                    |
                                  |  style transform   |
                                  |                    |
                                  +--------------------+

1.在facade转换中,使用parse API将源解析为描述符,并基于该描述符生成上述facade模块代码;

2.在脚本转换中,使用“compileScript”处理脚本。这可以处理诸如“<script setup>”和CSS变量注入之类的功能。或者,这可以直接在facade模块中完成(代码内联而不是导入),但需要将“导出默认值”重写为临时变量(为此提供了方便的“重写默认值”API),因此可以将其他选项附加到导出的对象。

3.在模板转换中,使用“compileTemplate”将原始模板编译为渲染函数代码。

4.在样式转换中,使用“compileStyle”编译原始CSS以处理“<style-scoped>”、“<style-module>”和CSS变量注入。

compile 和parse

在 packages/vue/src/index.ts 文件中可以看到
https://github.com/vuejs/core/blob/main/packages/vue/src/index.ts

import { compile,CompilerOptions,CompilerError } from '@vue/compiler-dom'

export { compileToFunction as compile }

vue 到处了一个 compile 方便对 <template> 中的内容进行编译,返回一个渲染函数
到@vue/compiler-dom 中看看

import {
  baseCompile,baseParse,CodegenResult,ParserOptions,RootNode,noopDirectiveTransform,NodeTransform,DirectiveTransform
} from '@vue/compiler-core'
import { parserOptions } from './parserOptions'
import { transformStyle } from './transforms/transformStyle'
import { transformVHtml } from './transforms/vHtml'
import { transformVText } from './transforms/vText'
import { transformModel } from './transforms/vModel'
import { transformOn } from './transforms/vOn'
import { transformShow } from './transforms/vShow'
import { transformTransition } from './transforms/Transition'
import { stringifyStatic } from './transforms/stringifyStatic'
import { ignoreSideEffectTags } from './transforms/ignoreSideEffectTags'
import { extend } from '@vue/shared'

export { parserOptions }

export function compile(  template: string,options: CompilerOptions = {})={

}

export function parse(template: string,options: ParserOptions = {}): RootNode {
  return baseParse(template,extend({},parserOptions,options))
}

export * from './runtimeHelpers'
export { transformStyle } from './transforms/transformStyle'
export { createDOMCompilerError,DOMErrorCodes } from './errors'
export * from '@vue/compiler-core'


我们可以看到很多有用的东西

  • 1 导出了parse,方法,用来对.vue 文件解析
  • 2 导出了compile 方法,用来对<template> 模板进行编译,
  • 3 导入了很多常用的vue 指令,如果想要了解vue 指令是如何实现的就可以顺着进去看看
import { transformVHtml } from './transforms/vHtml'
import { transformVText } from './transforms/vText'
import { transformModel } from './transforms/vModel'
import { transformOn } from './transforms/vOn'
import { transformShow } from './transforms/vShow'

可以简单的写一些代码看看 在nodejs上执行一下 vue 也是支持服务器端渲染的

import { compile,} from 'vue'
import { parse } from '@vue/compiler-dom'
const vuefile="<template><h1>hello</h1></template><style></style><script></script> ";
const templateStr="<template><h1>hello</h1></template> ";
console.log(vuefile)
let RenderFunction = compile(vuefile)
console.log(RenderFunction)
const result = parse(vuefile)
console.log(result)

看看输出

 node test.mjs
<template><h1>hello</h1></template><style></style><script></script> 
[Vue warn]: Template compilation error: Tags with side effect (<script> and <style>) are ignored in client component templates.
1  |  <template><h1>hello</h1></template><style></style><script></script> 
   |                                     ^^^^^^^^^^^^^^^
[Vue warn]: Template compilation error: Tags with side effect (<script> and <style>) are ignored in client component templates.
1  |  <template><h1>hello</h1></template><style></style><script></script> 
   |                                                    ^^^^^^^^^^^^^^^^^
[Function: render] { _rc: true }
{
  type: 0,children: [
    {
      type: 1,ns: 0,tag: 'template',tagType: 0,props: [],isSelfClosing: false,children: [Array],loc: [Object],codegenNode: undefined
    },{
      type: 1,tag: 'style',children: [],tag: 'script',codegenNode: undefined
    }
  ],helpers: Set(0) {},components: [],directives: [],hoists: [],imports: [],cached: 0,temps: 0,codegenNode: undefined,loc: {
    start: { column: 1,line: 1,offset: 0 },end: { column: 69,offset: 68 },source: '<template><h1>hello</h1></template><style></style><script></script> '
  }
}
可以看到compile 返回了一个渲染用的函数
parse 对文件解析返回的数据结构里面包含3个模块

指令

所有的指令都在 transforms 这个文件夹下面
https://github.com/vuejs/core/tree/main/packages/compiler-dom/src/transforms

vue 模板中各种内置的指令都是在这里引入的 看一下最简单 vshow

import { DirectiveTransform } from '@vue/compiler-core'
import { createDOMCompilerError,DOMErrorCodes } from '../errors'
import { V_SHOW } from '../runtimeHelpers'

export const transformShow: DirectiveTransform = (dir,node,context) => {
  const { exp,loc } = dir
  if (!exp) {
    context.onError(
      createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION,loc)
    )
  }

  return {
    props: [],needRuntime: context.helper(V_SHOW)
  }
}

可以看到这里不是对vshow的定义,因为这个模块是编译
指令的定义定义在这个文件夹下
https://github.com/vuejs/core/tree/main/packages/runtime-dom/src/directives
这是vshow
https://github.com/vuejs/core/blob/main/packages/runtime-dom/src/directives/vShow.ts

import { ObjectDirective } from '@vue/runtime-core'

export const vShowOldKey = Symbol('_vod')

interface VShowElement extends HTMLElement {
  // _vod = vue original display
  [vShowOldKey]: string
}

export const vShow: ObjectDirective<VShowElement> = {
  beforeMount(el,{ value },{ transition }) {
    el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display
    if (transition && value) {
      transition.beforeEnter(el)
    } else {
      setDisplay(el,value)
    }
  },mounted(el,{ transition }) {
    if (transition && value) {
      transition.enter(el)
    }
  },updated(el,{ value,oldValue },{ transition }) {
    if (!value === !oldValue) return
    if (transition) {
      if (value) {
        transition.beforeEnter(el)
        setDisplay(el,true)
        transition.enter(el)
      } else {
        transition.leave(el,() => {
          setDisplay(el,false)
        })
      }
    } else {
      setDisplay(el,beforeUnmount(el,{ value }) {
    setDisplay(el,value)
  }
}

function setDisplay(el: VShowElement,value: unknown): void {
  el.style.display = value ? el[vShowOldKey] : 'none'
}

// SSR vnode transforms,only used when user includes client-oriented render
// function in SSR
export function initVShowForSSR() {
  vShow.getSSRProps = ({ value }) => {
    if (!value) {
      return { style: { display: 'none' } }
    }
  }
}

vshow 是指令中最贱的一个主要对 style.display 的修改
vshow 有关的定义主要表现在 beforeMount、mounted、updated、beforeUnmount 这四个生命周期中
而且充分考虑了动画 和没有动画两种情况

可视化的查看编译转换结果play 工具

https://play.vuejs.org/
源码在这里 运维官方的打开很慢
https://github.com/vuejs/repl#readme
可以查看对3个模块编译后的效果

新概念概念 打开新世界的大门

在baseCompile 方法中

const ast = isString(template) ? baseParse(template,options) : template

可以看到这个变量名 ast 那么什么事ast 呢

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。 它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。 之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。

这是一个可以查看一段js对应的语法树的网站
https://astexplorer.net/
如果我们要解析一段内容,先获取这段内容的语法树,往往更容易解析

js 的语法树工具
https://github.com/facebook/jscodeshift

通过语法树工具根号查看一些结构 如果有需求也可以用在其他用途

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件举报,一经查实,本站将立刻删除。

本文链接:https://www.f2er.com/3188649.html

大家都在看

  • 飞码LowCode前端技术系列:如何便捷快速验证实现投产及飞码探索

    本篇文章从数据中心,事件中心如何协议工作、不依赖环境对vue2.x、vue3.x都可以支持、投产页面问题定位三个方面进行分析。
    2023-11-16 博文
  • 如何优雅使用 vuex

    大纲 本文内容更多的是讲讲使用 vuex 的一些心得想法,所以大概会讲述下面这些点: Q1:我为什么会想使用 vuex 来管理数据状态交互? Q2:使用 vuex 框架有哪些缺点或者说副作用? Q3:我是如何在项目里使用 vuex 的? 初识 vuex 对于 vuex,有人喜欢,有人反感 喜欢的人觉
    2023-11-16 博文
  • 第三方组件及计算属性传参的问题解决方式

    1. 前言 唉,好想玩滋嘣。 2. 计算属性直接传参接收不到 表格数据某一列需要用的计算属性时,模板中使用计算属性 fullName 就会直接调用 fullName 函数,而在模板中 fullName(item) 相当于fullName()(item),此处为函数柯里化。 &lt;el-table-
    2023-11-16 博文
  • 记录--Vue3基于Grid布局简单实现一个瀑布流组件

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在学习Grid布局之时,我发现其是CSS中的一种强大的布局方案,它将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,在刷某书和某宝首页时,我们发现其展示方式就是一种瀑布流,是一种流行的网站页面布局,视觉表……
    2023-11-16 博文
  • 用强数据类型保护你的表单数据-基于antd表单的类型约束

    接口数据类型与表单提交数据类型,在大多数情况下,大部分属性的类型是相同的,但很少能做到完全统一。我在之前的工作中经常为了方便,直接将接口数据类型复用为表单内数据类型,在遇到属性类型不一致的情况时会使用any强制忽略类型错误。后来经过自省与思考,这种工作模式会引起各种隐藏bug,一定有更……
    2023-11-16 博文
  • pinia的使用

    前言 最近新开了个项目,以前老项目都是vue2+vuex开发的,都说用vue3+pinia爽得多,那新项目就vue3+pinia吧。这里记录一下pinia的使用。 使用方法 安装pinia: npm i pinia main.js中引入pinia: //main.js import { create
    2023-11-16 博文
  • 记录--让我们来深入了解一下前端“三清”是什么

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前端“三清” 在前端开发中,我们经常听到关于“三清”的说法,即 window、document、Object。这三者分别代表了 BOM(浏览器对象模型)、DOM(文档对象模型)以及 JS 的顶层对象。在这个体系中,我们通过 JavaScr
    2023-11-16 博文
  • 记录--啊?Vue是有三种路由模式的?

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 众所周知,vue路由模式常见的有 history 和 hash 模式,但其实还有一种方式-abstract模式(了解一哈~) 别急,本文我们将重点逐步了解: 路由 + 几种路由模式 + 使用场景 + 思考 + freestyle 路由概念
    2023-11-16 博文