.vue 怎么变成 .js,我们来试一试!看完会更懂 Vue 吗?

2023-05-16

本文是基于Vite+AntDesignVue打造业务组件库[1]专栏第 12 篇,坚持就是胜利!

接着上篇说,交付一个 vue 组件不仅需要解析 DSL,还要处理 JS/TS,样式,类型声明等内容。我们先研究一下将 .vue 转换成 .js 的问题,这个是关键。

谁能处理 .vue 文件?

要想把 .vue 变成 .js,首先要知道什么工具能处理 .vue 文件,是不是很容易想到我们熟悉的 Webpack 和 Vite?

但是 Webpack 它本身也不能处理 .vue 文件,背后起作用的是 vue-loader[2]

同样地,Vite 也不是天生支持 vue,它能处理 .vue 文件是因为有了 @vitejs/plugin-vue[3] 和 @vitejs/plugin-vue2[4] 这两个插件,前者提供 Vue3 支持,后者提供 Vue2 支持。

vue-loader, @vitejs/plugin-vue, @vitejs/plugin-vue2 三者都依赖了一个核心模块 vue/compiler-sfc[5]

这里要注意 vue/compiler-sfc 和 @vue/compiler-sfc,前者相当于是一个调用入口和版本锁的作用,保证 vue 和 compiler-sfc 的版本一致性;后者是真正的代码实现。

a9a3ed9232f28bcc69dd63dc3ebd3649.png

@vue/compiler-sfc 才是真正具备处理 .vue 文件能力的源头,毕竟这种底层能力还是得看官方。@vue/compiler-sfc 具备两大能力,一个是 parse,一个是 compile。

.vue 被 parse 成了什么?

由于本文的焦点不是源码分析,所以也没必要逐行去分析 @vue/compiler-sfc 的源码,只要把握好 parse 的主干流程即可。

我们把 Vue 的源码下载下来。

git clone https://github.com/vuejs/core.git

然后运行 pnpm install 把依赖装好,保证一些依赖代码,TS 类型能看得更清楚。

我们打开 compiler-sfc 的 parse.ts 源码瞧一瞧,这里有一个很显眼的parse方法。

d10f2335fa73018bae75c51d9ee7573e.png

了解一个函数的作用,最直接的就是看它的输入和输出,我们可以直接观察入参类型和返回值类型,做到窥一斑而知全豹。

parse的入参有两个,一个是 string 类型的source,它代表 .vue 文件的源码字符串;第二个参数类型是SFCParseOptions,是一些条件参数,先不用关注。

parse函数的返回值类型是SFCParseResult,它下面有一个属性descriptor

b9b6ecac94bb9198f6de53ba6ebc95dc.png

descriptor 描述了 parse 的结果,比如 .vue 的 template 部分(也就是模板部分)被 parse 成SFCTemplateBlock了。

0198766a8952f6cf955934c5ae18f524.png

SFCTemplateBlock继承了SFCBlock,并且有ast属性,ast就是抽象语法树,可以用结构化的数据描述模板的结构。

b88d33e7c4db0168d2c6f353c5d3124b.png

.vue 的 script 部分分为两块来看,一个是不带setup属性的常规script,一个是带setup属性的 scriptSetupscriptscriptSetup 的 AST 结构是不一样的。

bb9f63463809dacbc7e23c36a4472fee.png

.vue 的 style 部分则是被 parse 成一个数组styles,它的类型是SFCStyleBlock[]。为什么 style 的 parse 结果会是一个数组呢?这是因为我们可以在 .vue 文件中写多个 style 块。

df7b08b7b10e206e63e3155b3482cff7.png

SFCStyleBlock包含了两个标记类的属性scoped, module,分别代表有没有启用 scoped 和 css module 特性。

7f3eacf1ff4e98a4f7e06affbec9e6ee.png

parse 过程还会得到cssVars,它通过分析样式中的v-bind()得到。

slotted代表你有没有使用 scoped 样式块,并且在这个 scoped 样式块中用了 slot 样式穿透语法:slotted()

了解了大致结构后,我们可以动手测试一下,加深印象。

我们准备一个简单的 test.vue 作为测试的源文件。

970a7b71f18d8097765867af7d6e70a3.png

然后新建一个 test-compiler-sfc.js 用于编写测试逻辑,主要就是读取 test.vue 文件内容,然后调用parse函数得到解析结果。可以断点调试,浏览一下parseResult到底是什么样的数据,包含了哪些内容。

5d07de13b8e326d531bf5b467e88bc7f.png

compiler 主要做了什么?

@vue/compiler-sfc 中有几个以 compile 开头的函数,分别是compileScirpt, compileTemplate, compileStyle,我们挑一些重点看一看。

compileScript

compileScript负责编译脚本,它的第一个参数是descriptor,这是因为 Vue3 支持同时写 script 块和 setupScript 块,编译时需要把两个 block 整合,而 parse 得到的descriptor中可以拿到这些信息。

4d9e805af986b67c74c8d63abcdc813d.png

由于 script 不仅支持langt="js",还支持 ts, jsx, tsx,所以compileScript还会根据情况引入相关 babel 插件。

考虑到 Vue3 支持 v-bind in CSS 特性,编译脚本时还要考虑注入useCssVars

81ef8ac77329575915e2b1ba2f801bfa.png

如果用到了 setup 语法糖,则会有更复杂的分析过程,因为 setup 语法糖带来了一些编译时特性(比如编译宏 defineProps 等),这里不再深入研究。

compileTemplate

compileTemplate负责编译模板,先看看它的参数类型和返回值类型。

88d6196cc41b1a06ab0c1104c138cbff.png

scoped是依据 styles 中有没有 scoped 属性来决定的,用于给标签加data-v-xxx属性。

slotted来源于parse过程中得到的slotted,这个是跟插槽有关的。

compileTemplate通过preprocessLang支持模板预处理,基于此可以支持类似 pug 之类的模板语法。

compileTemplate.ts 代码量不多,其编译的核心工作是交给了 @vue/compiler-dom 和 @vue/compiler-core 处理(这里暂时不讨论 ssr)。

沿着调用链路找,可以观察到整体过程是一个比较标准的 parse -> transform -> generate 三部曲。

02418ff2227c58fb1feb480c5ccc996f.png

template 最终是被编译为渲染函数了,所以其实也是 js,应该不会感到意外吧?

60b32dbd725903401f74f4fbaec7e60c.png

compileStyle

compileStyle是编译样式的,同样地,我们先看看它的参数类型和返回值类型。

7d5bc4a8edb44fec8a2eb6fd220024f3.png

我们注意到SFCStyleCompileOptions中有一个 id,它是组件的一个 uuid,可以用hash-sum[6]生成,具体生成时与一些路径和文件名信息有关。这个 id 也是我们在观察 DOM 结构时看到的 data-v-xxxxx 的源头。

3904e478b648c65fa12b1a6fd91384a8.png

了解了以上关键信息后,我们可以在 test-compiler-sfc.js 中补充一些简单的调用。

export async function test() {
    const fileName = "test.vue"

    const filePath = resolve(__dirname, fileName)

    const fileContent = await readFileSync(filePath)

    const { descriptor } = parse(fileContent.toString(), { filename: fileName })

    const rawShortFilePath = relative(process.cwd(), filePath)
        .replace(/^(\.\.[\/\\])+/, '')
    const shortFilePath = rawShortFilePath.replace(/\\/g, '/')

    const id = hash(shortFilePath)

    descriptor.id = id;

    const styles = descriptor.styles.map(block => compileStyleBlock(block, descriptor))

    console.log(styles)

    const script = compileScript(descriptor, { id })

    console.log(script)

    const hasScoped = descriptor.styles.some((s) => s.scoped);

    const template = compileTemplate({ source: descriptor.template.content, filename: descriptor.filename, id, scoped: hasScoped, slotted: descriptor.slotted })

    console.log(template)
}

也可以打断点调试一下过程。

97e6384ab2ed983e8744138542b5e81c.png

有了以上信息,相信你大概知道 .vue 源码是怎么变成 .js 文件了,只要合理地将一些编译结果组合起来,缝缝补补,最后加一个fs.writeFile写入文件的操作,这应该难不倒你吧?

涉及的源码可在vue-pro-components c10 分支[7]找到,欢迎 star 支持!

小结

我们大致了解了 @vue/compiler-sfc 做的事情,知道了是 @vue/compiler-sfc 提供了核心的 API,但是把这些 API 真正用到生产实践中还要考虑很多细节,需要合理地串起流程,同时考虑一些特殊场景。这些在 vue-loader 和 Vite 的插件 @vitejs/plugin-vue, @vitejs/plugin-vue2 中也有体现,感兴趣的朋友可以再去研究相关代码,这里不再展开。

我们做组件库,不到万不得已肯定是不会去开发底层工具链的,所以只要知道怎么用这些工具即可。

参考资料

[1]

基于Vite+AntDesignVue打造业务组件库: https://juejin.cn/column/7140103979697963045

[2]

vue-loader: https://vue-loader.vuejs.org/

[3]

@vitejs/plugin-vue: https://github.com/vitejs/vite-plugin-vue/tree/main/packages/plugin-vue

[4]

@vitejs/plugin-vue2: https://github.com/vitejs/vite-plugin-vue2

[5]

vue/compiler-sfc: https://github.com/vuejs/core/tree/main/packages/compiler-sfc

[6]

hash-sum: https://www.npmjs.com/package/hash-sum

[7]

vue-pro-components c10 分支: https://github.com/cumt-robin/vue-pro-components/tree/c10

END

47a710b2869435ffa39af6154a799cec.png

如果觉得这篇文章还不错

点击下面卡片关注我

来个【分享、点赞、在看】三连支持一下吧3a7cf70a4dca4db931c7f6ba23ea299e.png

   “分享、点赞、在看” 支持一波 94414e39002799a707350ff0ac6e268b.png 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

.vue 怎么变成 .js,我们来试一试!看完会更懂 Vue 吗? 的相关文章

  • Jupyter notebook读取大型csv文件遇到The kernel appears to have died. It will restart automatically的错误原因排查

    在用pandas读取大型 xff08 都几乎是超1G xff09 的csv文件 xff0c 发生了如下报错 xff1a 搜了一下 xff0c 第一种发生原因 xff1a 安装包版本问题 所以解决办法 xff1a pip install U
  • 打开Jupyter notebook出现了大大的500:Internal Server Error的解决办法

    前提 xff1a 云服务器操作系统为 CentOS 7 5 64位Jupyter notebook 作为容器远程运行在Docker中 起因 xff1a 今天打开Jupyter notebook 打算进行数据分析的时候 xff0c 突然出现这
  • 如何根据自相关(ACF)图和偏自相关(PACF)图选择ARIMA模型的p、q值

    这两天有朋友在之前 x1f449 这篇文章 x1f448 的时候在下面评论询问如何通过自相关 xff08 ACF xff09 和偏自相关 xff08 PACF xff09 图找到p q值 xff1f 这里掌柜就详细阐述一下 PS xff1a
  • 一个人做饭简单食谱_通过这5条简单食谱学习SQL

    一个人做饭简单食谱 SQL Structured Query Language is a powerful and expressive language for dealing with data from relational data
  • 利用SARIMA模型对季节周期性的时序案例进行分析(一)

    看过掌柜前几篇关于时序文章 xff1a 利用ARIMA模型对时间序列进行分析的经典案例 xff08 详细代码 xff09 利用ARIMA模型对时间序列进行分析的经典案例 xff08 详细代码 xff09 一文中会遇到的问题总结ARIMA模型
  • 数据分析面试、笔试题汇总+解析(六)

    xff08 接上篇 xff09 面试题 xff08 MySQL篇 xff09 3 如何提高MySQL的查询速度 xff1f 考点解析 xff1a 考察面试者对MySQL查询优化的理解 参考答案 xff1a xff08 因为这个问题如果回答的
  • 力扣584.寻找用户推荐人 - 你应该知道的五种MySQL解法

    题目链接 xff1a 584 寻找用户推荐人 解题思路 此题思路其实很简单 xff1a 只需要在条件查询中筛选推荐人编号都不是2的用户就能返回正确答案 但是这里有一个坑大家要注意 xff1a customer表中referee id列是包含
  • 浅析“04.23王者荣耀KPL比赛因出现硬件异常导致比赛延期”这一事件

    背景 不知道朋友们有没有看昨天晚上八点多的王者荣耀KPL比赛 xff08 成都AG超玩会VS广州TTG xff09 这一场 xff0c 当时比赛进行到快15分钟的时候出现了红方请求暂停的情况 xff0c 后来比赛直播界面就一直提示如下 xf
  • 初识网络基础《网络七层模型详解》

    坚持原创输出 xff0c 点击蓝字关注我吧 图片来自网络 目录 一 初识网络基础二 网络七层模型详解 1 物理层2 数据链路层3 网络层4 传输层5 会话层6 表示层7 应用层三 总结 一 初识网络基础 其实网络刚刚开始发展的时候 xff0
  • winScp连接Ubuntu 连接不上问题解决

    今天把服务器重装了之后就一直连不上 显示 网络连接超时 xff0c 可以尝试以下几种方法 xff1a 1 把linux的防火墙关了 xff1a 查看防火墙的信息 xff1a service iptables status 开启防火墙 xff
  • ctf中pwn题目总结

    pwntools工具使用 1 安装 pip install pwntools xff08 python2 xff09 pip3 install pwntools xff08 python3 xff09 2 使用 Context设置 cont
  • python学习--for循环详解

    最近开始整理python的资料 xff0c 博主建立了一个qq群 xff0c 希望给大家提供一个交流的平台 78486745 一 使用for循环实现简单功能 span class token triple quoted string str
  • 字符串排序-C语言实例

    1 简单的字符串排序 给定有限个字符串 将其按照大小顺序排列 此时的大小顺序简单来说我们可以先认为是ASCLL码的大小顺序 由此我们只需要比较字符串的ASCLL码的大小即可 2 算法设计 1 输入10个字符串 2 任选一种排序算法对字符串排
  • C语言最全入门笔记

    c语言入门 C语言一经出现就以其功能丰富 表达能力强 灵活方便 应用面广等特点迅速在全世界普及和推广 C语言不但执行效率高而且可移植性好 xff0c 可以用来开发应用软件 驱动 操作系统等 C语言也是其它众多高级语言的鼻祖语言 xff0c
  • bios uefi 区别_UEFI vs BIOS:有何区别?

    bios uefi 区别 So you might have heard the acronyms BIOS and UEFI thrown around especially when trying to switch Operating
  • mysql查看使用的配置文件路径

    mysql怎么查看使用的配置文件路径 xff1f 结论 mysql help grep 39 my cnf 39 或 mysqld verbose help grep A 1 39 Default options 39 通过以上两个命令就可
  • 家庭宽带光猫改桥接模式教程-淇云博客

    目前大多数家庭宽带默认是在光猫直接拨号上网的 xff0c 如果你想要改为自己的路由器拨号比较麻烦 xff0c 需要光猫的超级管理员账号才可以进后台修改 xff0c 但是光猫的超级管理员账号会被运营商远程修改 xff0c 一般也不会告诉用户
  • WSL 下的Linux删除文件夹

    WSL 下的Linux删除文件夹 问题导出 最近在学习Tengine框架 xff0c 下载源码编译成功 xff0c 改了几行代码后准备重新编译一下 xff0c 于是便在文件系统里 xff0c 鼠标右键删除了build文件夹 xff0c 之后
  • CSDN博客代码高亮显示

    今天分享下在CSDN写博客的时候如何设置代码高亮 一 首先进入个人博客界面 xff1a 三 设置选择你的博客标题 描述和想要的皮肤并选择代码显示格式 三 进入管理界面后进入博客设置 那么走到这里 xff0c 你肯定已经开始迫不及待的去实验了
  • Python - 对象=属性+方法,实例化是从类变成实例对象的过程

    Python面向对象三大特征 xff1a 封装 继承 多态 类的封装 xff1a 封装之后 xff0c 可以直接调用类的对象 xff0c 来操作内部的一些类方法 xff0c 不需要让使用者看到代码工作的细节 类的继承 xff1a 类可以从其

随机推荐