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

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

文章目录

    • 一、背景
    • 二、源码分析
      • 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.idconst 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 文件。

相关文章:

  • 长沙某公司.Net高级开发面试题
  • 实战C++手写线程池
  • 【自用软件】IDM下载器 Internet Download Manager v6.42 Build 10
  • 黑马头条day5- 延迟任务精准发布文章
  • 前端框架对比与选择
  • Flink 性能优化的高频面试题及答案
  • Android 简单实现联系人列表+字母索引效果
  • py-mmcif包pdbx_struct_oper_list对象介绍
  • Windows安装启动apache httpd 2.4 web服务器
  • 机械键盘驱动调光DIY--【DAREU】
  • C++手动实现栈、和队列
  • 如何修改Nuget包的缓存路径
  • 零工市场小程序的未来发展趋势
  • kubevirt基于CDI创建虚拟机
  • 如何在openEuler上安装和配置openGauss数据库
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • CentOS7简单部署NFS
  • css属性的继承、初识值、计算值、当前值、应用值
  • flask接收请求并推入栈
  • js作用域和this的理解
  • Python学习之路13-记分
  • SQLServer之创建显式事务
  • 从重复到重用
  • 实习面试笔记
  • 探索 JS 中的模块化
  • 我与Jetbrains的这些年
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 积累各种好的链接
  • ​低代码平台的核心价值与优势
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • $forceUpdate()函数
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (Python) SOAP Web Service (HTTP POST)
  • (备忘)Java Map 遍历
  • (附源码)springboot优课在线教学系统 毕业设计 081251
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (贪心) LeetCode 45. 跳跃游戏 II
  • (原创)可支持最大高度的NestedScrollView
  • (原創) 物件導向與老子思想 (OO)
  • (转)VC++中ondraw在什么时候调用的
  • .NET版Word处理控件Aspose.words功能演示:在ASP.NET MVC中创建MS Word编辑器
  • .php文件都打不开,打不开php文件怎么办
  • []使用 Tortoise SVN 创建 Externals 外部引用目录
  • [20170713] 无法访问SQL Server
  • [AHOI2009]中国象棋 DP,递推,组合数
  • [Angular] 笔记 16:模板驱动表单 - 选择框与选项
  • [Angular] 笔记 9:list/detail 页面以及@Output
  • [ARC066F]Contest with Drinks Hard
  • [AWS]CodeCommit的创建与使用
  • [caffe(二)]Python加载训练caffe模型并进行测试1
  • [CISCN2019 华东南赛区]Web11