Vue 3 魔法揭秘:CSS 解析与 scoped 背后的奇幻之旅

news2024/9/30 7:53:17

文章目录

    • 一、背景
    • 二、源码分析
      • transformMain 返回值
      • transformStyle 方法
      • compileStyleAsync 方法
      • scopedPlugin 方法
      • template 添加 __scopeId
    • 三、总结

一、背景

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

上文分析了 vue3 的编译过程,但是在对其中样式的解析遗留了一些问题:

  • 为什么 genStyleCode 得到了 import 语句 ?我们真正的代码是怎么转化的?
  • 平时vue scoped是怎么实现样式隔离的?
  • 如下图标签/选择器上的唯一属性怎么加上去的?

在这里插入图片描述

带着这些疑问继续进行源码解析。

二、源码分析

书接上回我们发现了 transformMain 方法中 genStyleCode 会处理我们的为import "/Users/zcy/Desktop/毕设/smart-port/src/App.vue?vue&type=style&index=0&lang.less" 那是怎么翻译成具体的 css 的呢 ?

在这里插入图片描述

transformMain 返回值

我们先看一下 transformMain 方法把转码转化为了什么? 下面是转化后格式化完成后的代码:

// 源码script部分
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__
  }
}
import {
  resolveComponent as _resolveComponent, createVNode as _createVNode, toDisplayString as _toDisplayString,
  createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock,
  createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId
} from "vue"
const _withScopeId = n => (_pushScopeId("data-v-7ba5bd90"), n = n(), _popScopeId(), n)
const _hoisted_1 = { id: "nav" }
// 源码template部分
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 */)
  )
}

// 源码样式部分
import "/Users/zcy/Desktop/毕设/smart-port/src/App.vue?vue&type=style&index=0&scoped=true&lang.less"

_sfc_main.__hmrId = "7ba5bd90"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(({ default: updated, _rerender_only }) => { if (_rerender_only) { __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render) } else { __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated) } })
import _export_sfc from 'plugin-vue:export-helper'
export default /*#__PURE__*/_export_sfc(_sfc_main, [['render', _sfc_render], ['__scopeId', "data-v-7ba5bd90"], ['__file', "/Users/zcy/Desktop/毕设/smart-port/src/App.vue"]])

上面这段代码核心三部分 _sfc_render、 styles,_sfc_main,可以看到这里对样式的解析其实只转化为了一个 import 方法,那是怎么会转化真正的 css 的呢 ?这个时候我们在回到 vuePlugin 入口处的 transform 方法中,如下图:

在这里插入图片描述

transformStyle 方法

从上图中可以看在 vue 不存在的时候会进入 transformMain ,否则会进入到 elsetransformStyle 方法,从名字就可以看出这个转化样式的过程,因此我们放开 transformMain 的断点,在 transformStyle 打上断点并进入该方法。

在这里插入图片描述

compileStyleAsync 方法

进入到改方法后我们可以看到内部在执行了 compileStyleAsync 方法, 之后样式就已经加上了隔离,由 #app -> #app[data-v-7ba5bd90], 因此我们深入一下 options.compiler.compileStyleAsync 这个方法,根据我们上一篇文得知,这个方法是在 vue/compiler-sfc 核心包中,我们打个断点进入该方法。

在这里插入图片描述

compileStyleAsync 执行了 doCompileStyle ,接下来我简单摘要一下这个方法:

function doCompileStyle(options) {
    const { filename, id, scoped = false, trim = true, isProd = false, modules = false, modulesOptions = {}, preprocessLang, postcssOptions, postcssPlugins } = options;
    // scoped id 来自于 descriptor.id
    const shortId = id.replace(/^data-v-/, '');
    const longId = `data-v-${shortId}`;
    // 插件数组
    const plugins = (postcssPlugins || []).slice();
    plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));
    // scoped 存在则加入改插件
    if (scoped) {
        plugins.push(scopedPlugin(longId));
    }
    let result;
    let code;
    try {
        // postcss 处理这些插件
        result = _postcss__default(plugins).process(source, postCSSOptions);
        // In async mode, return a promise.
        if (options.isAsync) {
            return result
                .then(result => ({
                code: result.css || '',
                // xxx
            }))
        }
        code = result.css;
    }
    return {
        code: code || ``,
        map: outMap && outMap.toJSON(),
        // xxxx
    };
}

scopedPlugin 方法

这边可以看到使用了 postcss 加载各种插件,其中就有 scopedPlugin ,顾名思义就是给我添加样式隔离的,接下来我们进入这个方法看一眼:

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

  • scopedPlugin 中的 Rule 配置会调用 processRule 方法并传入 scopedId
  • processRule 方法会遍历每个选择器进行执行 rewriteSelector 操作
  • rewriteSelector 方法会处理 v-deep、 >>>、 /deep/、等特殊操作符,最后加上 给选择器加上 scopedId 属性

如上图所示会在该方法中对 css 选择器加上隔离 __scopeId

template 添加 __scopeId

选择器的样式加上了 那我们 template 中的属性什么时候加上呢?

在这里插入图片描述

回到我们最开始 transformMain 中 返回的 code 当中,我们可以看到源码的 template 转化为了 render 函数,并且传入了 __scopeId 我们不难猜到肯定会在 调用 _createElementBlock 等方法的时候会转化为 vdom,最后更新到 dom 属性中, 对于 vdom 的转化过程本文就不过多深入。

三、总结

从上文可以发现 vue3css 的解析其实是分为两次:

  • 第一次先通过 transformMain 得到 import 'xxxx.vue?xxxx' 的方法
  • 第二次因为新增了import 语句插件又会重新执行,再次执行因为 vue 已经存在了,则会进入 transformStyle 方法,在里面进行具体解析,包含 scoped 等各种插件配合使用解析为最终 css 文件。

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

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

相关文章

方舟开发框架(ArkUI)可运行 OpenHarmony、HarmonyOS、Android、iOS等操作系统

ArkUI 是华为开发的一套声明式 UI 开发框架,用于构建分布式应用界面。ArkUI-X 是对 ArkUI 框架的扩展,支持开发者使用一套代码构建支持多平台(包括 OpenHarmony、HarmonyOS、Android、iOS)的应用。 一、方舟开发框架的ArkUI-X Ark…

libcrypto.so.10内容丢失导致sshd无法运行

说明: 我的是centos的服务器,被扫出有ssh漏洞,需要升级到OpenSSH_9.8p1, OpenSSL 3.0.14 4 报错 我的系统和环境升级前的版本 这是升级之后的版本 OpenSSH_9.8p1, OpenSSL 3.0.14 4 解决:我这个的原因是升级的时候把这个文件给删除了, 复制旧服务器上的 libcrypto.so.1…

【C++单调队列】1438. 绝对差不超过限制的最长连续子数组|1672

本文时间知识点 C队列、双向队列 LeetCode1438. 绝对差不超过限制的最长连续子数组 给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。 如…

Python 复制PDF中的页面

操作PDF文档时,复制其中的指定页面可以帮助我们从PDF文件中提取特定信息,如文本、图表或数据等,以便在其他文档中使用。复制PDF页面也可以实现在不同文件中提取页面,以创建一个新的综合文档。 本文将介绍如何使用Python 在同一文档…

Linux 计划任务

1.常见定时计划任务设置方式: at: 突发性的,临时决定只执行一次的任务。 crontab: 定时性的,每隔一定的周期就需要重复执行一次的命令。 用#date 为参考时间 1.1 at 计划任务的使用: 使用…

1.8 软件业务测试

欢迎大家订阅【软件测试】 专栏,开启你的软件测试学习之旅! 文章目录 前言1 概述2 方法3 测试策略4 案例分析 前言 在软件开发生命周期中,业务测试扮演着至关重要的角色。本文详细讲解了业务测试的定义、目的、方法以及测试策略。 本篇文章参…

Apache Iceberg Architecture—Iceberg 架构详解

Apache Iceberg Architecture Apache Iceberg 的架构可以分为三个主要层次:Iceberg Catalog、元数据层和数据层。 一、 Iceberg Catalog(目录) Iceberg Catalog 是 Iceberg 的顶层组件,负责管理所有 Iceberg 表的元数据和元数据操…

828华为云征文 | 智能监控新篇章,Prometheus如何在华为云Flexusx容器环境中大展身手

前言 在数字化转型的浪潮中,智能监控成为企业IT战略的关键环节。部署在华为云Flexus X实例上的Prometheus监控系统,凭借其卓越的性能与灵活性,正开启智能监控的新篇章。Flexus X实例以其强大的计算能力和灵活的资源管理,为Prometh…

I/O中断处理过程

中断优先级包括响应优先级和处理优先级,响应优先级由硬件线路或查询程序的查询顺序决定,不可动态改变。处理优先级可利用中断屏蔽技术动态调整,以实现多重中断。下面来看他们如何运用在中断处理过程中: 中断控制器位于CPU和外设之…

phpstudy简易使用

注意,本文所述的操作步骤均建立在电脑上已经完成php环境变量的配置与vscode的安装之上 、

万界星空科技数字孪生:解锁制造业未来,重塑智慧工厂新纪元

万界星空科技的数字孪生技术是一项创新的技术解决方案,它深度融合了工业大数据、物联网(IoT)、人工智能(AI)等先进技术,为制造业工厂提供了一个高度智能化、可视化的运营管理系统。以下是对万界星空科技数字…

EXEAL无法使用宏处理办法

在打开exeal的时候如果提示无法使用宏,或者不显示宏,可能是因为以下几个权限没有打开 1.随便打开一个exeal选择文件 2.选择更多里面的选项 3.选择信任中心里面的信任中心设置 4.信任中心宏设置启用所有宏 5.如果是网盘等其他路径的文件,点击…

leetcode-链表篇4

leetcode-2 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都…

VUE 开发——Node.js学习

一、认识Node.js Node.js是一个跨平台JavaScript运行环境,使开发者可以搭建服务器端的JavaScript应用程序 使用Node.js编写服务器端程序——编写数据接口、前端工程化; Node.js环境没有BOM和DOM; Node.js安装:下载node-v16.19…

C#基于SkiaSharp实现印章管理(10)

向PDF文件插入印章图片比之前实现的向图片文件插入印章麻烦得多。   最初的想法是使用PDF浏览控件在线打开PDF文件,然后在控件中实现鼠标移动时动态显示印章,点击鼠标时向当前PDF页面的鼠标点击位置插入图片。由于是.net 8的Winform项目,选…

数据结构 ——— C语言实现无哨兵位单向不循环链表

目录 前言 动态顺序表的缺陷 单链表的概念 单链表中节点的结构 单链表逻辑结构示意图​编辑 实现单链表前的准备工作 实现单链表 1. 定义节点的指针 2. 创建节点 3. 打印单链表中的所有数据 4. 在单链表头部插入数据 5. 在单链表尾部插入数据 6. 在单链表头部删除数…

HashMap的实现

Hash的最大容量为什么是2的30次方 问题 static final int *MAXIMUM_CAPACITY* 1 << 30; 回到题目&#xff0c;为什么会是2的30次幂&#xff0c;而不是2的31次幂呢&#xff1f; 首先&#xff1a;JAVA规定了该static final 类型的静态变量为int类型&#xff0c;至于为什…

神经网络在多分类问题中的应用

作者简介:热爱数据分析,学习Python、Stata、SPSS等统计语言的小高同学~个人主页:小高要坚强的博客当前专栏:Python之机器学习本文内容:神经网络在多分类问题中的应用作者“三要”格言:要坚强、要努力、要学习 目录 1. 引言 2.数据构造 3.划分数据集 4.神经网络实现多…

Stable Diffusion绘画 | 来训练属于自己的模型:素材处理与打标篇

纵观整个模型训练流程&#xff0c;图片素材准备和打标环节占据的分量比重&#xff0c;绝对超过60%。 上一篇分享了图片素材准备&#xff0c;这一篇&#xff0c;开始对准备好的图片素材进行处理了。 素材处理 我已经收集了 霉霉 的25张图片&#xff1a; 但是&#xff0c;发现…

4G模组SIM卡电路很简单,但也要注意这些坑

上次水SIM卡相关的文章&#xff0c;还是上一次&#xff1b; 上一篇文章里吹牛说&#xff0c;跟SIM卡相关的问题还有很多&#xff0c;目的是为下一篇文章埋下伏笔&#xff1b;伏笔埋是埋下了&#xff0c;但如果债老是不还&#xff0c;心里的石头就总悬着&#xff0c;搞不好老板…