网站的排名就是友情链接/关键词优化意见
文章目录
- 一、背景
- 二、结论
- 三、@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 被解析如下三个部分 template
、styles
、script
。接着看这里的代码发现如下图 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
,然后我们进入 genScriptCode
、genStyleCode
、genTemplateCode
这三个方法进行具体代码转化。
接下来我们看下这四个方法的具体实现(只保留核心代码):
// 得到原始的 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…...

Proteus如何添加数码管
1、打开安装好的Proteus,点击上方菜单栏中的“库”,再选择“从库选取零件”,或者在左侧元件列表中单击鼠标右键,再点击右键菜单中的“从库中挑选”选项。 2、之后在元器件库中,点击类别中的“Optoelectronics”&#…...

5 apache poi实现excel的动态下拉框功能
excel下拉框 RequestMapping("xiala")public void xiala(HttpServletResponse response){String fileName "僵尸表";try{response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharact…...

深度对比:etcd、Consul、Zookeeper 和 Nacos 作为注册中心和配置中心的优势与劣势
在现代分布式系统和微服务架构中,服务注册中心 和 配置中心 是系统稳定运行的关键组成部分。服务注册中心负责服务的动态注册与发现,而配置中心用于集中管理配置,确保系统在变化的环境中保持一致性。本文将对比 etcd、Consul、Zookeeper 和 N…...

Android webview拦截H5的接口请求并返回处理好的数据
Android webview拦截H5的接口请求并返回处理好的数据 Android 可以通过 WebView 的 shouldInterceptRequest 方法拦截到 H5 中的网络请求。这是一个 WebViewClient 中的回调方法,允许开发者在 WebView 发起网络请求时对其进行处理和修改。 具体使用方法如下&#…...

vue echarts tooltip使用动态模板
先上代码 tooltip: {// 这里是车辆iconshow: true,// trigger: "item",// backgroundColor: "transparent",appendToBody: true,textStyle: {color: "#ffffff" //设置文字颜色},formatter: (params) > {return formatHtml(params.data)},}, …...

網路本地連接沒有有效的IP配置:原因與解決方法
網路本地連接顯示“沒有有效的IP配置”。這通常意味著你的電腦無法從路由器或其他網路設備獲取有效的IP地址,從而導致無法上網。本文將從原因和解決方法兩個方面,詳細解析這個問題。 一、問題的原因 路由器或數據機問題: 路由器或數據機出…...

如何使用ssm实现基于web的学生就业管理系统的设计与实现+vue
TOC ssm726基于web的学生就业管理系统的设计与实现vue 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上…...

TCP三次握手四次挥手详解
TCP三次握手建立连接的过程: 一次握手:客户端发送带有 SYN(seqx)标志的数据包到服务端,然后客户端进入 SYN_SEND 状态,等待服务端的确认。二次握手:服务端收到 SYN 包后,发送带有 S…...

了解 如何使用同快充充电器给不同设备快速充电
在这科技发展迅速的时代,快充技术已经走进了我们生活,不得不说有了快充技术的对比,传统的充电模式已经满足不了人们对充电速度的要求。就比如用华为输出100 W快充充电器为手机充电大概需要23分钟充满100%电量,而传统的充电器则需要…...

AGI interior designer丨OPENAIGC开发者大赛高校组AI创作力奖
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给…...