Vue 中 CSS scoped 的原理

news2024/9/24 13:20:23

前言

在日常的Vue项目开发过程中,为了让项目更好的维护一般都会使用模块化开发的方式进行。也就是每个组件维护独立的templatescriptstyle。主要介绍一下使用<style scoped>为什么在页面渲染完后样式之间并不会造成污染。

示例

搭建一个简单的Vue项目测试一下:

终端执行npx webpack输出dist目录,在浏览器打开index.html调试一下看看现象:

  1. 每个组件都会拥有一个[data-v-hash:8]插入HTML标签,子组件标签上也具体父组件[data-v-hash:8];
  2. 如果style标签加了scoped属性,里面的选择器都会变成(Attribute Selector) [data-v-hash:8];
  3. 如果子组件选择器跟父组件选择器完全一样,那么就会出现子组件样式被父组件覆盖,因为子组件会优先于父组件mounted,有兴趣可以测试一下哦。

webpack.config.js配置

先看看在webpack.config.js中的配置:

vue-loader工作流

以下就是vue-loader工作大致的处理流程:

开启node调试模式进行查看阅读,package.json中配置如下:

"scripts": {  
    "debug": "node --inspect-brk ./node_modules/webpack/bin/webpack.js"  
 },

VueLoaderPlugin

先从入口文件lib/index.js开始分析,因为我Webpack是4.x版本,所以VueLoaderPlugin = require('./plugin-webpack4'),重点来看看这个lib/plugin-webpack4.js文件:

const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')

const id = 'vue-loader-plugin'
const NS = 'vue-loader'
// 很明显这就是一个webpack插件写法
class VueLoaderPlugin {
  apply (compiler) {
    if (compiler.hooks) {
      // 编译创建之后,执行插件
      compiler.hooks.compilation.tap(id, compilation => {
        const normalModuleLoader = compilation.hooks.normalModuleLoader
        normalModuleLoader.tap(id, loaderContext => {
          loaderContext[NS] = true
        })
      })
    } else {
      // webpack < 4
      compiler.plugin('compilation', compilation => {
        compilation.plugin('normal-module-loader', loaderContext => {
          loaderContext[NS] = true
        })
      })
    }

    // webpack.config.js 中配置好的 module.rules
    const rawRules = compiler.options.module.rules
    // 对 rawRules 做 normlized
    const { rules } = new RuleSet(rawRules)

    // 从 rawRules 中检查是否有规则去匹配 .vue 或 .vue.html 
    let vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue`))
    if (vueRuleIndex < 0) {
      vueRuleIndex = rawRules.findIndex(createMatcher(`foo.vue.html`))
    }
    const vueRule = rules[vueRuleIndex]
    if (!vueRule) {
      throw new Error(
        `[VueLoaderPlugin Error] No matching rule for .vue files found.\n` +
        `Make sure there is at least one root-level rule that matches .vue or .vue.html files.`
      )
    }
    if (vueRule.oneOf) {
      throw new Error(
        `[VueLoaderPlugin Error] vue-loader 15 currently does not support vue rules with oneOf.`
      )
    }

    // 检查 normlized rawRules 中 .vue 规则中是否具有 vue-loader
    const vueUse = vueRule.use
    const vueLoaderUseIndex = vueUse.findIndex(u => {
      return /^vue-loader|(\/|\\|@)vue-loader/.test(u.loader)
    })

    if (vueLoaderUseIndex < 0) {
      throw new Error(
        `[VueLoaderPlugin Error] No matching use for vue-loader is found.\n` +
        `Make sure the rule matching .vue files include vue-loader in its use.`
      )
    }

    // make sure vue-loader options has a known ident so that we can share
    // options by reference in the template-loader by using a ref query like
    // template-loader??vue-loader-options
    const vueLoaderUse = vueUse[vueLoaderUseIndex]
    vueLoaderUse.ident = 'vue-loader-options'
    vueLoaderUse.options = vueLoaderUse.options || {}

    // 过滤出 .vue 规则,其他规则调用 cloneRule 方法重写了 resource 和 resourceQuery 配置
    // 用于编译vue文件后匹配依赖路径 query 中需要的loader
    const clonedRules = rules
      .filter(r => r !== vueRule)
      .map(cloneRule)

    // 加入全局 pitcher-loader,路径query有vue字段就给loader添加pitch方法
    const pitcher = {
      loader: require.resolve('./loaders/pitcher'),
      resourceQuery: query => {
        const parsed = qs.parse(query.slice(1))
        return parsed.vue != null
      },
      options: {
        cacheDirectory: vueLoaderUse.options.cacheDirectory,
        cacheIdentifier: vueLoaderUse.options.cacheIdentifier
      }
    }

    // 修改原始的 module.rules 配置
    compiler.options.module.rules = [
      pitcher,
      ...clonedRules,
      ...rules
    ]
  }
}

以上大概就是VueLoaderPlugin所做的事情。也就是说VueLoaderPlugin主要就是修改module.rules的配置。总的来说就是对vue单文件编写做的一个扩展(比如可以写less文件,在vue style中也可以写less)

vue-loader

vue-loader是如何操作.vue文件的,目前只关心style部分,逻辑在lib/index.js

vue文件解析

// 很明显这就是一个loader写法
module.exports = function (source) {
    const loaderContext = this
    // ...
    const {
        target,
        request, // 请求资源路径
        minimize,
        sourceMap, 
        rootContext, // 根路径
        resourcePath, // vue文件的路径
        resourceQuery // vue文件的路径 query 参数
      } = loaderContext
    // ...
    
    // 解析 vue 文件,descriptor 是AST抽象语法树的描述
    const descriptor = parse({
        source,
        compiler: options.compiler || loadTemplateCompiler(loaderContext),
        filename,
        sourceRoot,
        needMap: sourceMap
    })
    /**
    *
    */
    // hash(文件路径 + 开发环境 ?文件内容 : "")生成 id
    const id = hash(
        isProduction
          ? (shortFilePath + '\n' + source)
          : shortFilePath
    )
    // descriptor.styles 解析后是否具有 attrs: {scoped: true}
    const hasScoped = descriptor.styles.some(s => s.scoped)
    /**
    *
    */
    let stylesCode = ``
    if (descriptor.styles.length) {
        // 最终生成一个import依赖请求
        stylesCode = genStylesCode(
            loaderContext,
            descriptor.styles,
            id,
            resourcePath,
            stringifyRequest,
            needsHotReload,
            isServer || isShadow // needs explicit injection?
        )
    }
}

可以看到解析完vue文件的结果大概就是这样的:

依赖解析

vue文件解析完之后template,script,style等都有个依赖的路径,后续可以通过配置的loader进行解析了,因为我们已经在VuePluginLoader中修改了module.rules的配置,而且依赖的路径中query中都拥有vue字段,所以会先走到pitcher-loader,现在来分析lib/loaders/pitcher.js中的逻辑:

/**
 *
*/
module.exports = code => code

module.exports.pitch = function (remainingRequest) {
    const options = loaderUtils.getOptions(this)
    const { cacheDirectory, cacheIdentifier } = options
    const query = qs.parse(this.resourceQuery.slice(1))

    let loaders = this.loaders
    if (query.type) {
        if (/\.vue$/.test(this.resourcePath)) {
            // 过滤eslint-loader
            loaders = loaders.filter(l => !isESLintLoader(l))
        } else {
            loaders = dedupeESLintLoader(loaders)
        }
    }
    // 过滤pitcher-loader
    loaders = loaders.filter(isPitcher)
    
    const genRequest = loaders => {
        const seen = new Map()
        const loaderStrings = []

        loaders.forEach(loader => {
          const identifier = typeof loader === 'string'
            ? loader
            : (loader.path + loader.query)
          const request = typeof loader === 'string' ? loader : loader.request
          if (!seen.has(identifier)) {
            seen.set(identifier, true)
            // loader.request contains both the resolved loader path and its options
            // query (e.g. ??ref-0)
            loaderStrings.push(request)
          }
        })

        return loaderUtils.stringifyRequest(this, '-!' + [
          ...loaderStrings,
          this.resourcePath + this.resourceQuery
        ].join('!'))
    }
    
    
    if (query.type === `style`) {
        const cssLoaderIndex = loaders.findIndex(isCSSLoader)
        // 调整loader执行顺序
        if (cssLoaderIndex > -1) {
            const afterLoaders = loaders.slice(0, cssLoaderIndex + 1)
            const beforeLoaders = loaders.slice(cssLoaderIndex + 1)
            const request = genRequest([
                ...afterLoaders, // [style-loader,css-loader]
                stylePostLoaderPath, // style-post-loader
                ...beforeLoaders // [vue-loader]
            ])
            return `import mod from ${request}; export default mod; export * from ${request}`
        }
   }
   /**
   *
   */
   const request = genRequest(loaders)
   return `import mod from ${request}; export default mod; export * from ${request}`
}

可以看到解析带scoped属性的style的结果大概就是这样的:

新的依赖解析

分析{tyep:style}的处理流程顺序:

  • vue-loader、style-post-loader、css-loader、style-loader。

处理资源的时候先走的是vue-loader,这时vue-loader中的处理逻辑与第一次解析vue文件不一样了:

const incomingQuery = qs.parse(rawQuery)
// 拥有{type:style}
if (incomingQuery.type) {
    return selectBlock(
      descriptor,
      loaderContext,
      incomingQuery,
      !!options.appendExtension
    )
 }
 
 
 // lib/select.js
 module.exports = function selectBlock (
  descriptor,
  loaderContext,
  query,
  appendExtension
) {
   // ...
  if (query.type === `style` && query.index != null) {
    const style = descriptor.styles[query.index]
    if (appendExtension) {
      loaderContext.resourcePath += '.' + (style.lang || 'css')
    }
    loaderContext.callback(
      null,
      style.content,
      style.map
    )
    return
  }

可以看到vue-loader处理完后返回的就是style.content,也就是style标签下的内容,然后交给后续的loader继续处理

再来看一下style-post-loader是如何生成data-v-hash:8的,逻辑主要在lib/loaders/stylePostLoaders.js中:

const qs = require('querystring')
const { compileStyle } = require('@vue/component-compiler-utils')

module.exports = function (source, inMap) {
  const query = qs.parse(this.resourceQuery.slice(1))
  const { code, map, errors } = compileStyle({
    source,
    filename: this.resourcePath,
    id: `data-v-${query.id}`,
    map: inMap,
    scoped: !!query.scoped,
    trim: true
  })

  if (errors.length) {
    this.callback(errors[0])
  } else {
    this.callback(null, code, map)
  }
}

处理最终返回的code是这样的:

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

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

相关文章

FreeRTOS开发指南

1&#xff1a;任务模板 //任务优先级 #define XXX_TASK_PRIO 1 //任务堆栈大小 #define XXX_STK_SIZE 128 //任务句柄 TaskHandle_t XXXTask_Handler NULL; /* * 放在开始任务&#xff0c;只需要执行一次为了创建任务 */ void Create_XXX_Task(void) {BaseType_t xR…

编码器-解码器架构

“编码器&#xff0d;解码器”架构可以将长度可变的序列作为输入和输出&#xff0c;因此适用于机器翻译等序列转换问题。 编码器将长度可变的序列作为输入&#xff0c;并将其转换为具有固定形状的编码状态。 解码器将具有固定形状的编码状态映射为长度可变的序列。 机器翻译是…

2022 年我国的对外贸易行业发展如何?

2021年&#xff0c;在各种不确定因素的影响下&#xff0c;中国外贸人依然以其强大的韧性和实力取得了新的进出口成绩。去年进出口总值创历史新高&#xff0c;达到6.05万亿美元&#xff0c;一年内分别突破5万亿美元和6万亿美元。 在2021年成绩和经验的加持下&#xff0c;今年外…

SpringBoot+VUE前后端分离项目学习笔记 - 【08 SpringBoot实现分页查询】

手动实现分页功能 先理解分页查询原理 采用limit语句来实现分页 -- 页码PageNum 每页数据条目PageSize5 -- 第一页 PageNum0, limit 0,5 SELECT * FROM sys_user limit 0,5; -- 第二页 PageNum1, limit 5,5 SELECT * FROM sys_user limit 5,5; -- 公式&#xff1a; limit …

Python压缩模块:bz2

文章目录基本原理调用基本原理 bz2和zlib的功能是基本一致的&#xff0c;只是算法不同。zlib模块此前已经总结了&#xff1a;zlib模块详解 bz2模块用到的压缩算法是bzip2算法&#xff0c;其核心是BW变换和MTF变换&#xff0c;当然最后少不了霍夫曼编码。 BWT&#xff0c;即B…

Qt RSA OpenSSL C++ Qt加密解密签字通信系统窗体源码

程序示例精选 Qt RSA OpenSSL C Qt加密解密签字通信系统窗体 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<Qt RSA OpenSSL C Qt加密解密签字通信系统窗体>>编写代码&#xff0c…

年终回顾 | 小米技术最受欢迎的技术文章TOP20

转眼间&#xff0c;小米技术已经陪伴大家度过了一整个年头。在一年里&#xff0c;我们始终坚持为大家提供有趣好玩的技术科普、硬核前沿的技术干货&#xff0c;带给大家一切有关小米的新鲜技术创新内容。2023年就要到了&#xff0c;欢迎你点击文末左下角的“阅读原文”填写一份…

Vue3:搜索框输入防抖实现整理笔记

目录 场景需求 前言 防抖 & 节流 防抖 节流 输入防抖存在的问题 指令实现 总结 在Vue开发中&#xff0c;遇到了搜索框输入防抖处理&#xff0c;算是防抖的使用场景之一吧&#xff0c;抽象其逻辑记录下来以备后用 场景需求 作为开发人员&#xff0c;一定要先搞清楚…

Android---Material Design

目录 一、什么是Material Design Z轴 Material Design 的一些 theme 一、什么是Material Design Material Design 中文名&#xff1a;材料设计语言&#xff0c;是由 Google 推出的全新的设计语言。Google 表示&#xff0c;这种设计语言旨在为手机、平板、台式机和“其它平台”…

标签平滑(Label Smoothing)详解

一、什么是label smoothing&#xff1f; 标签平滑&#xff08;Label smoothing&#xff09;&#xff0c;像L1、L2和dropout一样&#xff0c;是机器学习领域的一种正则化方法&#xff0c;通常用于分类问题&#xff0c;目的是防止模型在训练时过于自信地预测标签&#xff0c;改善…

spring6笔记2( ioc、bean的作用域、工厂模式、bean的四种实例化方式,生命周期)

第四章、Spring对ioc的实现 4.4 p命名空间注入 目的&#xff1a;简化配置。 使用p命名空间注入的前提条件包括两个&#xff1a; 第一&#xff1a;在XML头部信息中添加p命名空间的配置信息&#xff1a;xmlns:p"http://www.springframework.org/schema/p"第二&…

wanglinrong 程序 环境配置

1、总体要求 我的matlab版本是 r2020b、matconvnet-1.0-beta25、visual studio 2022。笔记本安装&#xff0c;没考虑GPU。建议vs的版本尽量比matlab版本低。 1.1 完美解决方案&#xff1a; 低版本Visual Studio与高版本Matlab&#xff0c;先装vs 后装matlab&#xff01; 比如…

windows下PyTorch安装教程(1.10)

文章目录一.pytorch 1.10版本安装教程一.pytorch 1.10版本安装教程 PyTorch官网 pytorch与cuda版本关系 官网 从官网选择自己对应的conda,python,cuda版本&#xff0c;复制conda命令 在windows搜索框中搜索CMD&#xff0c;选择以管理员身份运行 使用conda新建虚拟环境pyt…

arthas离线包使用说明

arthas离线包使用说明 基于私有化全内网场景&#xff0c;打包了一套arthas离线包&#xff0c;方便后续对服务进行调试和问题的定位。 首先将arthas-bin.zip导入到服务器中 下载连接&#xff1a;https://download.csdn.net/download/Decembetion/87347459 将zip包解压 #解压 unz…

c4d导入大模型以及给建筑上贴图笔记

快捷键普及 h定位 o 鼠标中键 切换视图 鼠标左键移动视图 坐标轴反了&#xff0c;按w切换 alt左键 旋转 alt中键移动 alt右键 缩放 导入超大模型 导入后什么都看不到需要在工程属性里面修改为极大 image.pngshiftf2弹出材质编辑器 点四条杠可以移动选项卡 image.png点新建材质之…

基于GUI界面的yolov5人脸口罩检测项目

文章目录 前言 一、运行环境 二、环境配置 三、yolov5网络结构图介绍 四、 损失函数 五、数据集 六、实验内容 1.实验框架 2.实验环境 3.实验结果 前言 佩戴口罩可以有效降低在和感染者有接触时可能被感染者感染的风险。目前&#xff0c;在一些公共场所&#xff0c…

c++结构体数组sort排序出错?(关于sort排序comp比较器的严格弱排序性质)

文章目录sort的严格弱排序的性质无效的比较器&#xff08;Invalid comparator&#xff09;正确的比较器sort的严格弱排序的性质 我在给结构体数组排序的时候&#xff0c;自定义了sort函数的排序法则&#xff0c;我的结构体如下定义&#xff1a; struct score {int a, b, c;//…

English Learning - L1-8 时态(上) 2022.12.29 周四

English Learning - L1-8 时态&#xff08;上&#xff09; 2022.12.29 周四8 时态为什么时态难学&#xff1f;什么是时态&#xff1f;如何套用时态表8.1 一般时态核心思维&#xff08;一&#xff09; 一般现在时核心思维用法1. 普遍的事实和真理2. 重复活动&#xff08;习惯&am…

166页7万字智慧工厂可视化解决方案

【版权声明】本资料来源网络&#xff0c;仅用于行业知识分享&#xff0c;供个人学习参考&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间进行删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a; 第 一 章 应用…

QT VS移植过程中出现的问题以及解决记录

目录 一、无法定位程序输入点于动态链接库 二、E1696 无法打开 源 文件 “QString“ 三、编译Qt项目提示 error MSB6006: “cmd.exe”已退出 四、禁止显示状态 错误 MSB8036 找不到 Windows SDK 五、E2512 功能测试宏的参数必须是简单标识符 六、Qt VS中双击ui文件无法打…