当前位置: 首页 > news >正文

Vue 3 文件编译流程详解与 Babel 的使用

文章目录

    • 一、背景
    • 二、结论
    • 三、@vitejs/plugin-vue 插件
      • 调试前物料准备
      • vuePlugin 入口
      • buildStart 方法
      • transform 方法
    • 四、@vue/compiler-sfc 核心包
      • parse 方法
      • compileScript、rewriteDefault 方法
      • compileTemplate 方法
    • 五、整体架构
    • 六、总结
    • 参考资料

一、背景

最近正在研究 react jsx 转化为 js 的过程,在学习完成之后,突然想到既然 react 是用过 babel 完成 jsx 的转化,那 vue 是不是也是使用 babel 的呢,带着这些疑问我开始探索 vue 的编译过程,经过一些资料的查询发现好像和 react 不太一样,但是网上却很少有深入的文章,因此我开始对源码的探索。

二、结论

vue 3 组件在编译过程中使用了 babel/parse 处理 script 中 js 代码处理为普通 js 代码,其余解析均未使用;下面我们将从源码层面解读。

三、@vitejs/plugin-vue 插件

调试前物料准备

  • vue 3 + vite
  • vscode

添加入口断点:

在这里插入图片描述

  • 找到入口文件 vite.config.js
  • 找到对应解析插件 vitejs/plugin-vue 插件 并在对应使用处打上断点
  • 在控制台打开 js 调试终端 (调试方法和浏览器类似)
  • 在对应终端启动我们的项目

vuePlugin 入口

进入 vue() 方法调用中,会出现 vuePlugin 方法这个方法暴露了很多配置属性,因为我们目的关注编译打包过程,所以我们这次只需关系核心的 buildStart 和 transform 即可,因此我们在这两个方法中打上断点

// src/index.ts
function vuePlugin(rawOptions = {}) {// xxxx 省略return {name: "vite:vue",handleHotUpdate(ctx) {},config(config) {},configResolved(config) {},configureServer(server) {},// 开始打打包时处理的内容buildStart() {options.compiler = options.compiler || resolveCompiler(options.root);},async resolveId(id) {},load(id, opt) {},// 转化代码时执行的内容transform(code, id, opt) {// xxxx 省略}};
}

buildStart 方法

buildStart 方法主要是在服务启动前拿到编译的配置

在这里插入图片描述

接下来我们在 build 处打上断点看之后的流程,在控制台输入 options 查看其中内容:

在这里插入图片描述

此图可以看出最初 compiler 为空,我们的代码执行了 resolveCompiler 方法,并把结果存入了 options.compiler 中,为了清楚该方法做了什么,我们 step into 看一下这个方法的实现。

在这里插入图片描述

这个代码可以看出其实就是加载了并且返回了 vue/compiler-sfc 这个核心包,那加载这个包是做什么呢,我们可以接着往下看,继续走下一个断点,然后会发现我们项目启动了,并没有走到下一个断点。

在这里插入图片描述

transform 方法

那么是什么时候怎么才能走到我们的 transform 中呢?,(vite 的特点:先启动再根据加载页面按需加载所需要编译的代码),明白这一点之后我们只需要在浏览器访问这个地址:我这里就是 http://localhost:3000/ 访问之后发现我们的断点进入到了 transform 中:

在这里插入图片描述

从这边也可以看出 vite 打包为什么比 webpack 快

继续看 transform 里面的代码,可以看到 return transformMain() 这个就是我们的核心转化方法,我们继续 step into 该方法:

在这里插入图片描述

接着我们看一下这个方法的入参:

  • code: 就是我们的源代码
  • filename:这个文件的路径
  • options:配置项
  • pluginContext:插件的上下文(this)
  • ssr:暂不考虑
  • asCustomElement:暂不考虑

然后我们其实核心要看的就是这个 code -> 原生 js 的转化,因此往下看这个 code 都在哪里使用,首先我们就可以看到 createDescriptor 这个方法,这个时候我们打印一下descriptor 得到如下图:
在这里插入图片描述

发现我们 code 被解析如下三个部分 templatestylesscript。接着看这里的代码发现如下图 3 行代码:

// 解析 descriptor 中的 script 部分
const { code: scriptCode, map } = await genScriptCode(descriptor, options, pluginContext, ssr);// 解析 descriptor 中的 style 部分
const stylesCode = await genStyleCode(descriptor, pluginContext, asCustomElement, attachedProps);// 解析 descriptor 中的 template 部分
const ({ code: templateCode, map: templateMap } = await genTemplateCode(descriptor, options, pluginContext, ssr));

然后在这里其实就比较清晰了,createDescriptor 先对源代码进行了初步解析,然后返回了 descriptor,然后我们进入 genScriptCodegenStyleCodegenTemplateCode 这三个方法进行具体代码转化。

接下来我们看下这四个方法的具体实现(只保留核心代码):

// 得到原始的 descriptor
function createDescriptor(filename, source, { root, isProduction, sourceMap, compiler }) {const { descriptor, errors } = compiler.parse(source, {filename,sourceMap});return { descriptor, errors };
}// 处理 descriptor 中 script 部分
async function genScriptCode(descriptor, options, pluginContext, ssr) {// 只保留核心代码const script = resolveScript(descriptor, options, ssr);scriptCode = options.compiler.rewriteDefault(script.content, "_sfc_main", xxx);return {code: scriptCode,map};
}
// 处理 css 
async function genStyleCode(descriptor, pluginContext, asCustomElement, attachedProps) {//  没有使用 compiler
}
// 处理 template
async function genTemplateCode(descriptor, options, pluginContext, ssr) {const template = descriptor.template;if (!template.lang && !template.src) {return transformTemplateInMain(template.content, descriptor, options, pluginContext, ssr);}
}function transformTemplateInMain(code, descriptor, options, pluginContext, ssr) {const result = compile(code, descriptor, options, pluginContext, ssr);
}function compile(code, descriptor, options, pluginContext, ssr) {const result = options.compiler.compileTemplate(__spreadProps(__spreadValues({}, resolveTemplateCompilerOptions(descriptor, options, ssr)), {source: code}));return result;
}function resolveScript(descriptor, options, ssr) {resolved = options.compiler.compileScript(descriptor, __spreadProps(__spreadValues({}, options.script), {// xxx}));return resolved;
}

genStyleCode 则处理为 import "/Users/zcy/Desktop/毕设/smart-port/src/App.vue?vue&type=style&index=0&lang.less" 后续文章中会介绍为什么这个东西是怎么解析的,本文不会过多讲解。

经过上面代码分析:可以看出核心处理方法为:

options.compiler.compileTemplate、
options.compiler.compileScript、 
options.compiler.rewriteDefault、
options.compiler.parse

此时再看 options.compiler 这个对象是不是很眼熟呢?这个就是在 buildStart 中 获取到的 vue/compiler-sfc 核心包源码如下:

options.compiler = options.compiler || resolveCompiler(options.root);

因此我们要看懂到底是怎么解析 vue 组件的,就需要深入这个包中。

四、@vue/compiler-sfc 核心包

parse 方法

我们在 options.compiler.parse 方法调用处打上断点,然后我们逐层进入方法:

在这里插入图片描述
在这里插入图片描述

我们进入到了 parse 方法这边可以看到有一个 ast 的转化,如下图调用了 compiler.parse 方法,经过分析发现这个方法源于,@vue/compiler-dom ,从这个包的依赖看出并没有使用 babel,略过这个核心包,因此得出结论 parse 方法中没有使用 babel。

在这里插入图片描述

compileScript、rewriteDefault 方法

继续刚才操作我们看 compileScript 方法:

在这里插入图片描述

顺腾摸瓜找到 parser$2 这个变量的源头

var parser$2 = require('@babel/parser');

在这里插入图片描述

rewriteDefault 也是如此:

在这里插入图片描述

因此得出结论在 script 的解析中会使用 @babel/parse。下面为解析后的源码:

import { ref } from 'vue';const _sfc_main = {setup(__props, { expose }) {expose();const state = ref(1)const __returned__ = { state, ref }
Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })
return __returned__
}
}

compileTemplate 方法

同样使用了 @vue/compiler-dom 进行转化,具体转化细节就不进行详细展开了, 结果会转化为一个 render 函数如下:

import { resolveComponent as _resolveComponent, createVNode as _createVNode, toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"const _hoisted_1 = { id: "nav" }function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {const _component_router_link = _resolveComponent("router-link")const _component_router_view = _resolveComponent("router-view")return (_openBlock(), _createElementBlock(_Fragment, null, [_createElementVNode("div", _hoisted_1, [_createVNode(_component_router_link, { to: "/login" }),_createVNode(_component_router_link, { to: "/" }),_createElementVNode("div", null, _toDisplayString($setup.state), 1 /* TEXT */)]),_createVNode(_component_router_view)], 64 /* STABLE_FRAGMENT */))
}ƒ

在这里插入图片描述

五、整体架构

在这里插入图片描述

六、总结

本文只是出于好奇浅浅研究下了一下 vue3 的编译过程,和其中涉及到 babel 使用的点,对于很多细节没有深入研究,因此只做了一个简单的分析,希望对大家有参考价值。

参考资料

  • 原来 vue3 文件编译是这样工作的!看完后更懂vue3了

相关文章:

Vue 3 文件编译流程详解与 Babel 的使用

文章目录 一、背景二、结论三、vitejs/plugin-vue 插件调试前物料准备vuePlugin 入口buildStart 方法transform 方法 四、vue/compiler-sfc 核心包parse 方法compileScript、rewriteDefault 方法compileTemplate 方法 五、整体架构六、总结参考资料 一、背景 最近正在研究 rea…...

Android常用C++特性之std::chrono

声明:本文内容生成自ChatGPT,目的是为方便大家了解学习作为引用到作者的其他文章中。 std::chrono 是 C11 引入的标准库中的时间处理工具,提供了以多种精度进行时间测量、处理和操作的功能。它允许开发者处理时间点(time_point&am…...

[Oracle] ORA-04036: 实例使用的 PGA 内存超出 PGA_AGGREGATE_LIMIT

有说该问题是因为触发了Oracle的BUG导致,最直接的解决方法就是重启数据库实例; Linux下数据库实例重启...

一次 Spring 扫描 @Component 注解修饰的类坑

问题现象 之前遇到过一个问题,在一个微服务的目录下有相同功能 jar 包的两个不同的版本,其中一个版本里面的类有 Component 注解,另外一个版本的类里面没有 Component 注解,且按照加载的顺序,没有 Component 注解的 j…...

深度学习:调整学习率

目录 前言 一、什么是调整学习率? 二、调整学习率的作用 三、怎么调整学习率 1.有序调整 2.自适应调整 3.自定义调整 4.调整示例 前言 在深度学习中,调整学习率是非常重要的,它对模型的训练效果和收敛速度有显著影响。 一、什么是调整…...

Java项目实战II基于Java+Spring Boot+MySQL的厨艺交流平台设计与实现(源码+数据库+文档)

目录 一、前言 二、技术介绍 三、系统实现 四、文档参考 五、核心代码 六、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 在美食文化…...

第二十节:学习Redis缓存数据库实现增删改查(自学Spring boot 3.x的第五天)

这节记录下如何使用redis缓存数据库。 第一步: 先在服务器端安装redis, 下载地址:Releases tporadowski/redis GitHub。 第二步: 安装redis客户端可视化管理软件redisDesktopmanager Redis Desktop Manager - Download 第…...

Android SQLite的基本使用、生成Excel文件保存到本地

1. Android SQLite的基本使用 1.1. SQLiteOpenHelper Android 底层已经通过一个SQLiteOpenHelper的抽象类将数据库的创建,以及修改,更新等都放在了里面。 要使用它必须实现它的OnCreate(SQLiteDatabase db),onUpgrade(SQLiteDatabase db, int…...

记一次因视频编码无法在浏览器播放、编码视频报错问题

起因 ... f cv2.VideoWriter_fourcc(*h264) ...我这边使用h264编码会提示 OpenCV: FFMPEG: tag 0x34363268/h264 is not supported with codec id 27 and format mp4 / MP4 (MPEG-4 Part 14) OpenCV: FFMPEG: fallback to use tag 0x31637661/avc1 [ERROR:02.711] global /i…...

【深度学习】深度卷积神经网络(AlexNet)

在 LeNet 提出后,卷积神经网络在计算机视觉和机器学习领域中很有名气,但并未起到主导作用。 这是因为 LeNet 在更大、更真实的数据集上训练的性能和可行性还有待研究。 事实上,在 20 世纪 90 年代到 2012 年之间的大部分时间里,…...

C语言扫盲

文章目录 C版本C语言特征GCCprintf数据类型函数指针内存管理void指针 Struct结构和Union结构typedef预处理器make工具cmake工具Projectintegral of sinc functionemulator embedded systeman event schedule 补充在线Linux终端安装Linux参考 建议还是国外教材学习…人家的PPT比…...

视频融合共享平台LntonAIServer视频智能分析抖动检测算法和过亮过暗检测算法

LntonAIServer作为一款智能视频监控平台,集成了多种先进的视频质量诊断功能,其中包括抖动检测和过暗检测算法。这些算法对于提升视频监控系统的稳定性和图像质量具有重要意义。 以下是对抖动检测算法和过暗检测算法的应用场景及优势的详细介绍。 一、L…...

【笔记篇】Davinci Configurator OS模块(上)

目录 1 简介1.1 架构概览2 功能描述2.1 特性2.2 规范偏离2.2.1 API 函数的泛型偏离2.2.2 可信函数 API 偏离2.2.3 服务保护偏离2.2.4 代码保护2.2.5 SyncScheduleTable API 偏差2.2.6 CheckTask/ISRMemoryAccess API 偏差2.2.7 中断 API 偏差2.2.8 Cross Core Getter API2.2.9 …...

19.3 打镜像部署到k8s中,prometheus配置采集并在grafana看图

本节重点介绍 : 打镜像,导出镜像,传输到各个节点并导入运行该项目配置prometheus和grafana 打镜像 本地build docker build -t ink8s-pod-metrics:v1 .build过程 导出镜像 docker save ink8s-pod-metrics > ink8s-pod-metrics.tar 传输到各个node…...

如何让系统u盘重新可用

目录 引言开始操作遇到的错误 引言 我们将 u 盘制作为系统 U 盘后,U 盘就没法在电脑中正常识别出了。当装完系统,不再需要 u 盘充当系统 U 盘想要正常使用该 U 盘,这时候就需要有些操作,让这个 U 盘正常化。 上图就是充当系统盘的…...

14.安卓逆向-frida基础-编写hook脚本2

免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 内容参考于:图灵Python学院 本人写的内容纯属胡编乱造,全都是合成造假,仅仅只是为了娱乐,请不要盲目相信。 工…...

车辆零部件检测和分割数据集-车体数据集-yolo格式-yolov5-yolov10可用

这些标签是用于实例分割任务中的类别,通常在汽车图像识别或自动驾驶技术中使用。以下是这些类别: back_bumper - 后保险杠back_glass - 后挡风玻璃back_left_door - 后左车门back_left_light - 后左灯back_right_door - 后右车门back_right_light - 后右…...

甄选范文“论分布式存储系统架构设计”,软考高级论文,系统架构设计师论文

论文真题 分布式存储系统(Distributed Storage System)通常将数据分散存储在多台独立的设备上。传统的网络存储系统采用集中的存储服务器存放所有数据,存储服务器成为系统性能的瓶颈,也是可靠性和安全性的焦点,不能满足大规模存储应用的需要。分布式存储系统采用可扩展的…...

第十四章:html和css做一个心在跳动,为你而动的表白动画

💖 让心跳加速,传递爱意 💖 在这个特别的时刻,让爱在跳动中绽放!🌟 无论是初次相遇的心动,还是陪伴多年的默契,我们的心总在为彼此跳动。就像这颗炙热的爱心,随着每一次的跳动,传递着满满的温暖与期待。 在这个浪漫的季节,让我们一同感受爱的律动!无论你是在…...

poetry安装

文章目录 前言1. 为什么pip install poetry 会造成依赖冲突1.1 全局环境依赖混淆:1.2 工具和项目之间的冲突:1.3 缺乏依赖隔离:1.4 多出很多额外依赖: 2. 不推荐pipx安装3. poetry高级安装3.1 默认安装路径3.2自定义安装 4. 安装p…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言:多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时,​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套:跨云网络构建数据…...

synchronized 学习

学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

uniapp中使用aixos 报错

问题&#xff1a; 在uniapp中使用aixos&#xff0c;运行后报如下错误&#xff1a; AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...

QT: `long long` 类型转换为 `QString` 2025.6.5

在 Qt 中&#xff0c;将 long long 类型转换为 QString 可以通过以下两种常用方法实现&#xff1a; 方法 1&#xff1a;使用 QString::number() 直接调用 QString 的静态方法 number()&#xff0c;将数值转换为字符串&#xff1a; long long value 1234567890123456789LL; …...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...