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

news2024/11/17 11:35:04

文章目录

    • 一、背景
    • 二、结论
    • 三、@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了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2174537.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

企业防泄密妙招有哪些?请记住这8招!超实用,学起来!

在古代,有云:“密者,德之高也;事以密成,语以泄败。” 这些谚语不仅是对忠诚守密的高度赞扬,更是对保密工作重要性的深刻阐述。 在现代企业中,数据泄露已成为不容忽视的严峻挑战。 如何有效防止…

MFC单按钮启停实例

单击按钮启动、停止交替切欣换 1、在1Dlg.h文件中添加代码 public:CMy1Dlg(CWnd* pParent NULL); // standard constructorBOOL m_b;2、在1Dlg.cpp文件中添加代码 CMy1Dlg::CMy1Dlg(CWnd* pParent /*NULL*/): CDialog(CMy1Dlg::IDD, pParent) { m_hIcon AfxGetApp()->Lo…

高中还来得及选择信息学奥赛赛道吗?

随着信息学奥赛(NOI)在升学中的重要性日益凸显,越来越多的学生和家长将其视为进入顶尖高校的一个重要途径。然而,很多学生可能直到高中阶段才意识到信息学奥赛的重要性,或者才开始对编程产生兴趣。于是问题出现了&…

DarkLabel2.4版本导入MOT17数据集

目录 背景导入效果MOT17数据集说明DarkLabel导入视频导入gt文件 背景 做目标追踪,目前找了一圈开源工具,发现DarkLabel还是很好用的,提供自动目标跟踪,标注很方便。 由于目标追踪我用的是bytetrack,官网是用mot17数据…

学校周赛(2)

A.Minimize! 题目 思路 本题只需要遍历c的取值&#xff0c;实时更新答案即可 代码 #include<iostream> #include<algorithm> using namespace std;void todo(){int a,b;cin>>a>>b;int ansINT_MAX;for(int ca;c<b;c){ansmin(ans,(c-a)(b-c));}co…

二叉树的前序遍历,中序遍历,后序遍历以及层次遍历(递归方式+C语言代码)

#include<stdlib.h> #include<stdio.h> #include<assert.h> //定义一个二叉树结点结构体 typedef int ElemTpye; typedef struct TreeNode {ElemTpye data;struct TreeNode* left;struct TreeNode* right; }TreeNode; //创建结点 TreeNode* createTreenode(E…

【qt】QQ仿真项目1

一览全局: QQ仿真项目 一.创建项目添加资源文件二.创建数据库三.自定义标题栏Qt类四.加载样式表标题栏按钮的搭配五.标题栏实现移动窗体六.标题栏按钮连接信号槽七.标题栏双击最大化和还原八.基类窗口实现标题栏按钮信号九.重写基类窗口绘图事件确保设置样式表生效十.用户登录界…

Spring Gateway学习

系列文章目录 JavaSE基础知识、数据类型学习万年历项目代码逻辑训练习题代码逻辑训练习题方法、数组学习图书管理系统项目面向对象编程&#xff1a;封装、继承、多态学习封装继承多态习题常用类、包装类、异常处理机制学习集合学习IO流、多线程学习仓库管理系统JavaSE项目员工…

C++20中头文件concepts的使用

<concepts>是C20中新增加的头文件&#xff0c;此头文件是concepts库的一部分&#xff0c;主要用于模板编程、泛型编程。包括 1.core language concepts&#xff1a; std::same_as&#xff1a;指定一种类型(type)与另一种类型是否相同。 std::derived_from&#xff1a;指定…

学习之什么是装饰器

什么是装饰器 本质&#xff1a;就是一个函数&#xff0c;能为其它的函数增加额外功能 # 目标函数 def power_num(n):"""计算并返回1-n之间每个数的平方和:param n::return:"""total 0for i in range(1, n1):total i ** 2"""…

【网络安全】-访问控制-burp(1~6)

文章目录 前言   1.Lab: Unprotected admin functionality  2.Lab: Unprotected admin functionality with unpredictable URL   3.Lab: User role controlled by request parameter   4.Lab:User role can be modified in user profile  5.Lab: User ID controlled by…

爬虫入门 Selenium使用

爬虫入门 & Selenium使用 特别声明&#x1f4e2;&#xff1a;本教程只用于教学&#xff0c;大家在使用爬虫过程中需要遵守相关法律法规&#xff0c;否则后果自负&#xff01;&#xff01;&#xff01; 项目代码&#xff1a;https://github.com/ziyifast/ziyifast-code_inst…

systemd-analyze命令:Linux系统启动分析

一、命令简介 用途: systemd-analyze ​命令用于分析系统引导过程的时间性能。标签: 系统管理&#xff0c;性能分析。相关命令: systemd-bootchart​, systemd-cgtop​, systemd-resolve​. ‍ 二、命令参数 命令格式 systemd-analyze [OPTIONS...] COMMAND ...选项和参数…

GUI-Guider LVGL 添加自定义代码

添加自定义代码时&#xff0c;分为上线两端 1.上部分可有可无 2.下部分为你触发事件时调用的语句 具体集合下方图片 示例参考

运放模块的选型参数

增益带宽积-----尤其重要&#xff1a; GWB 增益*带宽 压摆率&#xff1a; 高带宽的运放一般都是电流型运放&#xff1a; 注意压摆率计算公式里面的Vopp参数是放大后的电压最大值&#xff1a; 参数&#xff0c;布局一定参考数据手册&#xff01;&#xff01;&#xff01;&…

Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术

&#x1f31f;&#x1f31f; 欢迎来到我的技术小筑&#xff0c;一个专为技术探索者打造的交流空间。在这里&#xff0c;我们不仅分享代码的智慧&#xff0c;还探讨技术的深度与广度。无论您是资深开发者还是技术新手&#xff0c;这里都有一片属于您的天空。让我们在知识的海洋中…

快速理解使用mq(二)——用户、虚拟HOST、Queue的创建

一、用户的创建 直接添加即可 二、虚拟Host创建 创建完成选择所属用户 点进去新建的host 管理对应权限 三、queue 创建 选择对应host 直接添加即可

系统分析师14:需求工程

1 内容概要 2 需求工程概述 需求工程&#xff1a;需求开发【含需求分析】和需求管理系统分析&#xff1a;软件需求分析、硬件需求分析、网络需求分析软件需求是指用户对系统在功能、行为、性能、设计约束等方面的期望考虑“做什么”&#xff0c;而不考虑“怎么做”&#xff0c…

2025 年 IT 前景:机遇与挑战并存,人工智能和云计算成重点

云计算de小白 投资人工智能&#xff1a;平衡潜力与实用性 到 2025 年&#xff0c;人工智能将成为 IT 支出的重要驱动力&#xff0c;尤其是在生成式人工智能领域。人工智能的前景在于它有可能彻底改变业务流程、增强决策能力并开辟新的收入来源。然而&#xff0c;现实情况更加微…

4款工具搞定PDF去水印,线上+软件一应俱全!

作为一名文员&#xff0c;我每天都得和各种文档打交道&#xff0c;其中PDF文件是最常见的。有时候&#xff0c;我们收到的PDF文件会带有水印&#xff0c;这不仅影响了阅读体验&#xff0c;还可能在分享时造成不便。所以&#xff0c;学会如何去除PDF水印对我来说至关重要。今天&…