欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue中CSS?scoped的原理詳細(xì)講解

 更新時(shí)間:2023年01月12日 09:03:41   作者:北海嶼鹿  
在組件中增加的css加了scoped屬性之后,就在會(huì)在當(dāng)前這個(gè)組件的節(jié)點(diǎn)上增加一個(gè)data-v-xxx屬性,下面這篇文章主要給大家介紹了關(guān)于Vue中CSS?scoped原理的相關(guān)資料,需要的朋友可以參考下

前言

在日常的Vue項(xiàng)目開發(fā)過程中,為了讓項(xiàng)目更好的維護(hù)一般都會(huì)使用模塊化開發(fā)的方式進(jìn)行。也就是每個(gè)組件維護(hù)獨(dú)立的template,script,style。主要介紹一下使用<style scoped>為什么在頁面渲染完后樣式之間并不會(huì)造成污染。

示例

搭建一個(gè)簡(jiǎn)單的Vue項(xiàng)目測(cè)試一下:

終端執(zhí)行npx webpack輸出dist目錄,在瀏覽器打開index.html調(diào)試一下看看現(xiàn)象:

  1. 每個(gè)組件都會(huì)擁有一個(gè)[data-v-hash:8]插入HTML標(biāo)簽,子組件標(biāo)簽上也具體父組件[data-v-hash:8];
  2. 如果style標(biāo)簽加了scoped屬性,里面的選擇器都會(huì)變成(Attribute Selector) [data-v-hash:8];
  3. 如果子組件選擇器跟父組件選擇器完全一樣,那么就會(huì)出現(xiàn)子組件樣式被父組件覆蓋,因?yàn)樽咏M件會(huì)優(yōu)先于父組件mounted,有興趣可以測(cè)試一下哦。

webpack.config.js配置

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

vue-loader工作流

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

開啟node調(diào)試模式進(jìn)行查看閱讀,package.json中配置如下:

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

VueLoaderPlugin

先從入口文件lib/index.js開始分析,因?yàn)槲襑ebpack是4.x版本,所以VueLoaderPlugin = require('./plugin-webpack4'),重點(diǎn)來看看這個(gè)lib/plugin-webpack4.js文件:

const qs = require('querystring')
const RuleSet = require('webpack/lib/RuleSet')
 
const id = 'vue-loader-plugin'
const NS = 'vue-loader'
// 很明顯這就是一個(gè)webpack插件寫法
class VueLoaderPlugin {
  apply (compiler) {
    if (compiler.hooks) {
      // 編譯創(chuàng)建之后,執(zhí)行插件
      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
    // 對(duì) rawRules 做 normlized
    const { rules } = new RuleSet(rawRules)
 
    // 從 rawRules 中檢查是否有規(guī)則去匹配 .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 規(guī)則中是否具有 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 規(guī)則,其他規(guī)則調(diào)用 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的配置。總的來說就是對(duì)vue單文件編寫做的一個(gè)擴(kuò)展(比如可以寫less文件,在vue style中也可以寫less)

vue-loader

vue-loader是如何操作.vue文件的,目前只關(guān)心style部分,邏輯在lib/index.js

vue文件解析

// 很明顯這就是一個(gè)loader寫法
module.exports = function (source) {
    const loaderContext = this
    // ...
    const {
        target,
        request, // 請(qǐng)求資源路徑
        minimize,
        sourceMap, 
        rootContext, // 根路徑
        resourcePath, // vue文件的路徑
        resourceQuery // vue文件的路徑 query 參數(shù)
      } = loaderContext
    // ...
    
    // 解析 vue 文件,descriptor 是AST抽象語法樹的描述
    const descriptor = parse({
        source,
        compiler: options.compiler || loadTemplateCompiler(loaderContext),
        filename,
        sourceRoot,
        needMap: sourceMap
    })
    /**
    *
    */
    // hash(文件路徑 + 開發(fā)環(huán)境 ?文件內(nèi)容 : "")生成 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) {
        // 最終生成一個(gè)import依賴請(qǐng)求
        stylesCode = genStylesCode(
            loaderContext,
            descriptor.styles,
            id,
            resourcePath,
            stringifyRequest,
            needsHotReload,
            isServer || isShadow // needs explicit injection?
        )
    }
}

可以看到解析完vue文件的結(jié)果大概就是這樣的:

依賴解析

vue文件解析完之后template,script,style等都有個(gè)依賴的路徑,后續(xù)可以通過配置的loader進(jìn)行解析了,因?yàn)槲覀円呀?jīng)在VuePluginLoader中修改了module.rules的配置,而且依賴的路徑中query中都擁有vue字段,所以會(huì)先走到pitcher-loader,現(xiàn)在來分析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)
        // 調(diào)整loader執(zhí)行順序
        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的結(jié)果大概就是這樣的:

新的依賴解析

分析{tyep:style}的處理流程順序:

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

處理資源的時(shí)候先走的是vue-loader,這時(shí)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標(biāo)簽下的內(nèi)容,然后交給后續(xù)的loader繼續(xù)處理

再來看一下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是這樣的:

總結(jié) 

到此這篇關(guān)于Vue中CSS scoped原理的文章就介紹到這了,更多相關(guān)Vue CSS scoped的原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論