当前位置:首页 > javascript > vue > 正文内容

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

hxing6411年前 (2023-12-25)vue2810


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,
  CompilerOptions,
  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,
      ns: 0,
      tag: 'style',
      tagType: 0,
      props: [],
      isSelfClosing: false,
      children: [],
      loc: [Object],
      codegenNode: undefined
    },
    {
      type: 1,
      ns: 0,
      tag: 'script',
      tagType: 0,
      props: [],
      isSelfClosing: false,
      children: [],
      loc: [Object],
      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, line: 1, 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, { value }, { 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, value)
    }
  },
  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://xingxinghan.cn/?id=485

分享给朋友:

“vue3源码学习api-vue-sfc文件编译” 的相关文章

Vue3源码之createApp

Vue.js 3中的createApp是用于创建一个Vue应用的函数。它的原理可以简单地解析为以下几个步骤:1. 创建一个应用实例:createApp函数会返回一个应用实例,该实例代表整个Vue应用的根实例。2. 组件注册:通过app.component方法,可以注册全局组件或局部组件。全局组件可以...

Vue过渡动画之CSS过渡

Vue.js 是一个以数据驱动视图的前端框架,它提供了丰富的组件化特性来帮助我们创建交互丰富的 Web 应用程序。Vue 框架内置了一些过渡特效,以及钩子函数,让我们可以在过渡期间添加自定义 CSS 类来实现过渡效果。本文将着重讲解 Vue.js 中的 CSS 过渡,并介绍如何使用它来实现各种有趣的...

uniapp 中 ScrollView 组件上拉分页怎么不滚动到最顶部

实现类似微信聊天页面,上拉加载更多历史聊天记录,每次上拉到顶部,界面自动会滚动到最顶部,我希望ScrollView不要滚动到最顶部,每次就停留在当前位置1,绑定scroll-view中scroll-into-view属性<scroll-view class="scroll-...

Vue中的父子组件通讯及使用sync同步父子组件数据

在Vue.js中,组件通讯是一个非常重要的主题。特别是在处理父子组件之间的通讯时,我们需要了解不同的方式来传递数据和响应事件。本文将介绍Vue中父子组件通讯的几种方式,并重点讨论使用sync属性来实现父子组件数据的双向绑定。父子组件通讯 在Vue中,父组件可以通过prop向子组件传递数据,而子组件则...

Vue.js 系列教程:深入理解组件、Props和Slots

Vue.js 是一款流行的 JavaScript 框架,它提供了一种组件化的开发方式,使得构建复杂的用户界面变得更加简单和高效。 在本篇教程中,我们将深入探讨 Vue.js 中的组件、Props 和 Slots 的概念和用法。 组件...

Vue中Mixin的应用与实践

在Vue.js中,Mixin是一种非常有用的技术,它允许我们将可复用的功能和逻辑抽象出来,并混入到组件中,从而实现代码的复用和组件的扩展。本文将深入探讨Vue中Mixin的应用与实践,包括Mixin的基本概念、实际应用场景以及相关的代码示例,最终总结如何合理地应用和实践Mixin特性。...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。