成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

了解CSS Module作用域隔離原理

開發 前端
我們知道,Javascript發展到現在出現了眾多模塊化規范,比如AMD、CMD、 Common JS、ESModule等,這些模塊化規范能夠讓我們的JS實現作用域隔離。

CSS Module出現的背景?

我們知道,Javascript發展到現在出現了眾多模塊化規范,比如AMD、CMD、 Common JS、ESModule等,這些模塊化規范能夠讓我們的JS實現作用域隔離。但CSS卻并沒有這么幸運,發展到現在卻一直沒有模塊化規范,由于CSS是 根據選擇器去全局匹配元素的,所以入鍋你在頁面的兩個不同的地方定義了一個相同的類名,先定義的樣式就會被后定義的覆蓋掉。由于這個原因,CSS的命名沖突一直困擾著前端人員。

這種現狀是前端開發者不能接受的,所以CSS社區也誕生了各種各樣的CSS模塊化解決方案(這并不是規范),比如:

  • 「命名方法:」人為約定命名規則
  • 「scoped:」vue中常見隔離方式
  • 「CSS Module:」 每個文件都是一個獨立的模塊
  • 「CSS-in-JS:」這個常見于react、 JSX中

現在來看CSS Module是目前最為流行的一種解決方案,它能夠與CSS預處理器搭配使用在各種框架中。

CSS Module?

CSS Module的流行源于React社區,它獲得了社區的迅速采用,后面由于Vue-cli對其集成后開箱即用的支持,將其推到了一個新高度。

局部作用域

在w3c 規范中,CSS 始終是「全局生效的」。在傳統的 web 開發中,最為頭痛的莫過于處理 CSS 問題。因為全局性,明明定義了樣式,但就是不生效,原因可能是被其他樣式定義所強制覆蓋。

產生局部作用域的唯一方法就是為樣式取一個獨一無二的名字,CSS Module也就是用這個方法來實現作用域隔離的。

在CSS Module中可以使用:local(className)來聲明一個局部作用域的CSS規則。

:local(.qd_btn) {
border-radius: 8px;
color: #fff;
}
:local(.qd_btn):nth(1) {
color: pink;
}

:local(.qd_title) {
font-size: 20px;
}

CSS Module會對:local()包含的選擇器做localIdentName規則處理,也就是為其生成一個唯一的選擇器名稱,以達到作用域隔離的效果。

以上css經過編譯后會生成這樣的代碼:

這里的:export是CSS Module為解決導出而新增的偽類,后面再進行介紹。

全局作用域

當然CSS Module也允許使用:global(className)來聲明一個全局作用域的規則。

:global(.qd_text) {
color: chocolate;
}

而對于:global()包含的選擇器CSS Module則不會做任何處理,因為CSS規則默認就是全局的。

或許很多了會好奇我們在開發過程好像很少使用到:local(),比如在vue中,我們只要在style標簽上加上module就能自動達到作用域隔離的效果。

是的,為了我們開發過程方便,postcss-modules-local-by-default插件已經默認幫我們處理了這一步,只要我們開啟了CSS模塊化,里面的CSS在編譯過程會默認加上:local()。

Composing(組合)

組合的意思就是一個選擇器可以繼承另一個選擇器的規則。

繼承當前文件內容

:local(.qd_btn) {
border-radius: 8px;
color: #fff;
}

:local(.qd_title) {
font-size: 20px;
composes: qd_btn;
}

圖片


繼承其它文件

Composes 還可以繼承外部文件中的樣式

/* a.css */
:local(.a_btn) {
border: 1px solid salmon;
}
/** default.css **/
.qd_box {
border: 1px solid #ccc;
composes: a_btn from 'a.css'
}

編譯后會生成如下代碼:

導入導出

從上面的這些編譯結果我們會發現有兩個我們平時沒用過的偽類::import、:export。

CSS Module 內部通過ICSS來解決CSS的導入導出問題,對應的就是上面兩個新增的偽類。

Interoperable CSS (ICSS) 是標準 CSS 的超集。

:import

語句:import允許從其他 CSS 文件導入變量。它執行以下操作:

  • 獲取并處理依賴項
  • 根據導入的令牌解析依賴項的導出,并將它們匹配到localAlias
  • 在當前文件中的某些地方(如下所述)查找和替換使用localAlias依賴項的exportedValue.

:export

一個:export塊定義了將要導出給消費者的符號。可以認為它在功能上等同于以下 JS:

module.exports = {
"exportedKey": "exportedValue"
}

語法上有以下限制:export:

  • 它必須在頂層,但可以在文件中的任何位置。
  • 如果一個文件中有多個,則將鍵和值組合在一起并一起導出。
  • 如果exportedKey重復某個特定項,則最后一個(按源順序)優先。
  • AnexportedValue可以包含對 CSS 聲明值有效的任何字符(包括空格)。
  • exportedValue不需要引用an ,它已被視為文字字符串。

以下是輸出可讀性所需要的,但不是強制的:

應該只有一個:export塊

它應該位于文件的頂部,但在任何:import塊之后

CSS Module原理?

大概了解完CSS Module語法后,我們可以再來看看它的內部實現,以及它的核心原理 —— 作用域隔離。

一般來講,我們平時在開發中使用起來沒有這么麻煩,比如我們在vue項目中能夠做到開箱即用,最主要的插件就是css-loader,我們可以從這里入手一探究竟。

「這里大家可以思考下,?css-loader主要會依賴哪些庫來進行處理?」

我們要知道,CSS Module新增的這些語法其實并不是CSS 內置語法,那么它就一定需要進行編譯處理

那么編譯CSS我們最先想到的是哪個庫?

postcss對吧?它對于CSS就像Babel對于javascript

可以安裝css-loader來驗證一下:

圖片

跟我們預期的一致,這里我們能看到幾個以postcss-module開頭的插件,這些應該就是實現CSS Module的核心插件。

從上面這些插件應該能看出哪個才是實現作用域隔離的吧

  • Postcss-modules-extract-imports:導入導出功能
  • Postcss-modules-local-by-default:默認局部作用域
  • Postcss-modules-scope:作用域隔離
  • Posts-modules-values:變量功能

編譯流程

整個流程大體上跟Babel編譯javascript類似:parse ——> transform ——> stringier

圖片

與Babel不同的是,PostCSS自身只包括css分析器,css節點樹API,source map生成器以及css節點樹拼接器。

css的組成單元是一條一條的樣式規則(rule),每一條樣式規則又包含一個或多個屬性&值的定義。所以,PostCSS的執行過程是,先css分析器讀取css字符內容,得到一個完整的節點樹,接下來,對該節點樹進行一系列轉換操作(基于節點樹API的插件),最后,由css節點樹拼接器將轉換后的節點樹重新組成css字符。期間可生成source map表明轉換前后的字符對應關系。

CSS在編譯期間也是需要生成AST得,這點與Babel處理JS一樣。

AST

PostCSS的AST主要有以下這四種:

  • rule: 選擇器開頭
#main {
border: 1px solid black;
}
  • atrule: 以@開頭
@media screen and (min-width: 480px) {
body {
background-color: lightgreen;
}
}
  • decl: 具體樣式規則
border: 1px solid black;
  • comment: 注釋
/* 注釋*/

與Babel類似,這些我們同樣可以使用工具來更清晰地了解CSS 的 AST:

圖片


  • Root: 繼承自 Container。AST 的根節點,代表整個 css 文件
  • AtRule: 繼承自 Container。以 @ 開頭的語句,核心屬性為 params,例如:@import url('./default.css'),params 為url('./default.css')
  • Rule: 繼承自 Container。帶有聲明的選擇器,核心屬性為 selector,例如: .color2{},selector為.color2
  • Comment: 繼承自 Node。標準的注釋/* 注釋 */ 節點包括一些通用屬性:
  • type:節點類型
  • parent:父節點
  • source:存儲節點的資源信息,計算 sourcemap
  • start:節點的起始位置
  • end:節點的終止位置
  • raws:存儲節點的附加符號,分號、空格、注釋等,在 stringify 過程中會拼接這些附加符號

安裝體驗

npm i postcss postcss-modules-extract-imports postcss-modules-local-by-default postcss-modules-scope postcss-selector-parser

這些插件的功能我們都可以自己一一去體驗,我們先將這些主要的插件串聯起來試一試效果,再來自行實現一個Postcss-modules-scope插件

(async () => {
const css = await getCode('./css/default.css')
const pipeline = postcss([
postcssModulesLocalByDefault(),
postcssModulesExtractImports(),
postcssModulesScope()
])

const res = pipeline.process(css)

console.log('【output】', res.css)
})()

把這幾個核心插件集成進來,我們會發現,我們的css中的樣式不用再寫:local也能生成唯一hash名稱了,并且也能夠導入其它文件的樣式了。這主要是依靠postcss-modules-local-by-default、postcss-modules-extract-imports兩個插件。

/* default.css */
.qd_box {
border: 1px solid #ccc;
composes: a_btn from 'a.css'
}
.qd_header {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
composes: qd_box;
}
.qd_box {
background: coral;
}

圖片


編寫插件

現在我們就自己來實現一下類似postcss-modules-scope的插件吧,其實原理很簡單,就是遍歷AST,為選擇器生成一個唯一的名字,并將其與選擇器的名稱維護在exports里面。

主要API

說到遍歷AST,與Babel相似Post CSS也同樣提供了很多API用于操作AST:

  • 「walk:」 
  • ??walkAtRules:」  遍歷所有atrule 類型節點
  • 「walkRules:」 遍歷所有rule類型節點
  • walkComments: comment 類型節點
  • 「walkDecls:」 遍歷所有 decl類型節點

(更多內容可在postcss文檔上查看)

有了這些API我們處理AST就非常方便了

插件格式

編寫PostCSS插件與Babel類似,我們只需要按照它的規范進行處理AST就行,至于它的編譯以及目標代碼生成我們都不需要關心。

const plugin = (options = {}) => {
return {
postcssPlugin: 'plugin name',
Once(root) {
// 每個文件都會調用一次,類似Babel的visitor
}
}
}

plugin.postcss = true
module.exports = plugin

核心代碼

const selectorParser = require("postcss-selector-parser");
// 隨機生成一個選擇器名稱
const createScopedName = (name) => {
const randomStr = Math.random().toString(16).slice(2);
return `_${randomStr}__${name}`;
}
const plugin = (options = {}) => {
return {
postcssPlugin: 'css-module-plugin',
Once(root, helpers) {
const exports = {};
// 導出 scopedName
function exportScopedName(name) {
// css名稱與其對應的作用域名城的映射
const scopedName = createScopedName(name);
exports[name] = exports[name] || [];
if (exports[name].indexOf(scopedName) < 0) {
exports[name].push(scopedName);
}
return scopedName;
}
// 本地節點,也就是需要作用域隔離的節點:local()
function localizeNode(node) {
switch (node.type) {
case "selector":
node.nodes = node.map(localizeNode);
return node;
case "class":
return selectorParser.className({
value: exportScopedName(
node.value,
node.raws && node.raws.value ? node.raws.value : null
),
});
case "id": {
return selectorParser.id({
value: exportScopedName(
node.value,
node.raws && node.raws.value ? node.raws.value : null
),
});
}
}
}
// 遍歷節點
function traverseNode(node) {
// console.log('【node】', node)
if(options.module) {
const selector = localizeNode(node.first, node.spaces);
node.replaceWith(selector);
return node
}
switch (node.type) {
case "root":
case "selector": {
node.each(traverseNode);
break;
}
// 選擇器
case "id":
case "class":
exports[node.value] = [node.value];
break;
// 偽元素
case "pseudo":
if (node.value === ":local") {
const selector = localizeNode(node.first, node.spaces);

node.replaceWith(selector);

return;
}else if(node.value === ":global") {

}
}
return node;
}
// 遍歷所有rule類型節點
root.walkRules((rule) => {
const parsedSelector = selectorParser().astSync(rule);
rule.selector = traverseNode(parsedSelector.clone()).toString();
// 遍歷所有decl類型節點 處理 composes
rule.walkDecls(/composes|compose-with/i, (decl) => {
const localNames = parsedSelector.nodes.map((node) => {
return node.nodes[0].first.first.value;
})
const classes = decl.value.split(/\s+/);
classes.forEach((className) => {
const global = /^global\(([^)]+)\)$/.exec(className);
// console.log(exports, className, '-----')
if (global) {
localNames.forEach((exportedName) => {
exports[exportedName].push(global[1]);
});
} else if (Object.prototype.hasOwnProperty.call(exports, className)) {
localNames.forEach((exportedName) => {
exports[className].forEach((item) => {
exports[exportedName].push(item);
});
});
} else {
console.log('error')
}
});

decl.remove();
});

});

// 處理 @keyframes
root.walkAtRules(/keyframes$/i, (atRule) => {
const localMatch = /^:local\((.*)\)$/.exec(atRule.params);

if (localMatch) {
atRule.params = exportScopedName(localMatch[1]);
}
});
// 生成 :export rule
const exportedNames = Object.keys(exports);

if (exportedNames.length > 0) {
const exportRule = helpers.rule({ selector: ":export" });

exportedNames.forEach((exportedName) =>
exportRule.append({
prop: exportedName,
value: exports[exportedName].join(" "),
raws: { before: "\n " },
})
);
root.append(exportRule);
}
},
}
}
plugin.postcss = true
module.exports = plugin

使用

(async () => {
const css = await getCode('./css/index.css')
const pipeline = postcss([
postcssModulesLocalByDefault(),
postcssModulesExtractImports(),
require('./plugins/css-module-plugin')()
])
const res = pipeline.process(css)
console.log('【output】', res.css)
})()


責任編輯:華軒 來源: 前端南玖
相關推薦

2023-09-27 08:33:16

作用域CSS

2011-04-29 10:22:49

CSS高性能Web開發

2021-05-25 10:15:20

JavaScript 前端作用域

2011-09-06 09:56:24

JavaScript

2019-03-13 08:00:00

JavaScript作用域前端

2021-03-09 08:35:51

JSS作用域前端

2017-10-29 06:50:30

前端開發CSSWeb

2010-09-25 16:10:09

添加DHCP作用域

2023-05-05 07:41:42

執行上下文JavaScript

2023-11-06 09:24:14

CSS相對顏色

2010-08-25 15:19:20

DHCP作用域

2021-03-17 08:39:24

作用域作用域鏈JavaScript

2011-04-18 09:31:35

JavaScript

2010-09-29 15:02:23

DHCP作用域

2021-03-16 22:25:06

作用域鏈作用域JavaScript

2025-04-16 08:50:00

信號量隔離線程池隔離并發控制

2010-01-07 16:16:03

VB.NET變量作用域

2013-09-05 10:07:34

javaScript變量

2017-09-14 13:55:57

JavaScript

2022-05-10 08:47:00

JMeter作用域執行順序
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩精品在线播放 | 99成人免费视频 | 欧美 日韩 国产 一区 | 91精品国产综合久久精品 | 久久99深爱久久99精品 | 欧美精品一区二区在线观看 | 国产有码| 插插宗合网 | 精品伊人久久 | 国产成人99久久亚洲综合精品 | 黑人精品欧美一区二区蜜桃 | 91国在线视频 | 91中文字幕在线 | 亚洲精品中文字幕在线观看 | 中文字幕成人在线 | 国产一区二区三区在线 | 亚洲国产一区二区三区, | 午夜爱爱毛片xxxx视频免费看 | 久久精品久久久久久 | 国产精品污www在线观看 | 麻豆av免费观看 | 在线视频99| 午夜免费观看体验区 | 九九综合 | 日韩午夜影院 | 国产精品一区二区在线 | 天天av网 | 精精国产xxxx视频在线野外 | 午夜天堂精品久久久久 | 亚洲成人中文字幕 | 日本黄色影片在线观看 | 日日噜噜夜夜爽爽狠狠 | 亚洲第一天堂无码专区 | 久久免费高清 | 欧美激情综合色综合啪啪五月 | 毛片网站免费观看 | 一级毛片视频在线 | 久久久久久久久久久成人 | 国产一区二区精品在线 | 亚洲国产成人精品女人久久久野战 | 欧美视频二区 |