重新构想原子化 CSS
感謝印記中文的 QC-L[1] 對本文進(jìn)行翻譯,英文原文: English Version[2]。
本文會比往期文章相對長些。這是我個人的一個重大的工具發(fā)布,有許多內(nèi)容我想談?wù)摗H绻隳芑ㄐr間讀完,不勝感激,希望能對你有所幫助 :)
推薦訪問?https://antfu.me/posts/reimagine-atomic-css-zh 以獲得最好的閱讀體驗什么是原子化 CSS?
首先,讓我們?yōu)?原子化 CSS (Atomic CSS) 給出適當(dāng)?shù)亩x:
John Polacek 在 文章 Let’s Define Exactly What Atomic CSS is[3] 中寫道:
Atomic CSS is the approach to CSS architecture that favors small, single-purpose classes with names based on visual function.
譯文:
原子化 CSS 是一種 CSS 的架構(gòu)方式,它傾向于小巧且用途單一的 class,并且會以視覺效果進(jìn)行命名。
有些人可能會稱其為函數(shù)式 CSS,或者 CSS 實用工具。本質(zhì)上,你可以將原子化的 CSS 框架理解為這類 CSS 的統(tǒng)稱:
.m-0?{margin:?0; } .text-red?{color:?red; } /*?...?*/市面上有不少實用至上的 CSS 框架,如 Tailwind CSS[4],Windi CSS[5] 以及 Tachyons[6] 等。
同時有些 UI 庫也會附帶一些 CSS 工具類作為框架的補充,如 Bootstrap[7] 和 Chakra UI[8]。
這篇文章并不打算和你討論使用原子化 CSS 的優(yōu)缺點,相信你己經(jīng)在各種地方聽到了許多討論。今天我們將從框架作者的角度來聊聊如何權(quán)衡設(shè)計以構(gòu)建出那些你喜歡的框架,它們的局限性,以及如何能將它們做得更好以使得我們的日常工作受惠。
背景
在正式開始前,先來聊聊背景。如果你還不認(rèn)識我,我叫 Anthony Fu,是 Vite[9] 團隊的成員,也是 Vitesse[10] (Vite 社區(qū)最受歡迎的起手模板之一) 的作者。我享受原子化 CSS 帶來的快速開發(fā)體驗,而因此選擇了 Tailwind CSS[11] 作為 Vitesse 的默認(rèn) UI 框架。雖然 Vite 較 Webpack 等工具相比,在加載速度上有了大幅提升,但由于 Tailwind 生成了數(shù) MB 的 CSS,使得加載與更新 CSS 成為了整個 Vite 應(yīng)用的性能瓶頸。我曾以為這是使用為了原子式 CSS 的一種權(quán)衡,直到我發(fā)現(xiàn)了 Windi CSS[12]。
Windi CSS[13] 是從零開始編寫的 Tailwind CSS 的替代方案。它的零依賴,也不要求用戶安裝 PostCSS 和 Autoprefixer。更為重要的是,它支持 按需生成。Windi CSS 不會一次生成所有的 CSS,而是只會生成你在代碼中實際使用到的原子化 CSS。這與 Vite 按需使用的理念不謀而合,也因此,我為它編寫了 一個 Vite 插件[14]。不出所料,從一個簡單的測試上可以看到它比 Tailwind 要快了 20~100 倍[15]。
項目進(jìn)展相當(dāng)順利,Windi CSS 也快速成長為一個團隊,我們也引入了許多創(chuàng)新,如 自動值推導(dǎo)[16],可變修飾組[17],Shortcuts[18],在 DevTools 中進(jìn)行設(shè)計[19],屬性化模式[20] 等。作為結(jié)果,Tailwind 也 因此[21] 使用了同樣的技術(shù)并推出了自己的 JIT 按需引擎[22]。
剖析原子化 CSS
在文章開始前,我們來聊聊原子化 CSS 的工作原理。
傳統(tǒng)方案
制作原子化 CSS 的傳統(tǒng)方案其實就是提供所有你可能需要用到的 CSS 工具。例如,你可能會用預(yù)處理器(這里選用的是 SCSS)生成如下代碼:
// style.scss@for $i from 1 through 10 {.m-#{$i} {margin: $i / 4 rem;} }編譯結(jié)果為:
.m-1?{?margin:?0.25?rem;?} .m-2?{?margin:?0.5?rem;?} /*?...?*/ .m-10?{?margin:?2.5?rem;?}現(xiàn)在你可以直接使用 class="m-1" 來設(shè)置邊距。但正如你所見,用這種方法的情況下,你不能使用除了 1 到 10 之外的邊距,而且,即使你只使用了其中一條 CSS 規(guī)則,但還是要為其余幾條規(guī)則的文件體積買單。如果之后你還想支持不同的 margin 方向,使用比如 mt 代表 margin-top,mb 代表 margin-bottom 等,加上這 4 個方向以后,你的 CSS 大小會變成原來的 5 倍。如果再有使用到像 :hover 和 :focus 這樣的偽類時,體積還會得更變大。以此類推,每多加一個工具類,往往意味著你 CSS 文件的大小也會隨之增加。這也就是為什么傳統(tǒng)的 Tailwind 生成的 CSS 文件會有數(shù) MB 的大小。
為了解決這個問題,Tailwind 通過使用 PurgeCSS[23] 來掃描你的大包產(chǎn)物并刪除你不需要的規(guī)則。這得以使其在生產(chǎn)環(huán)境中 CSS 文件縮減為幾 KB。然而,請注意,這個清除操作僅在生成構(gòu)建下有效,而開發(fā)環(huán)境下仍要使用包含了所有規(guī)則巨大的 CSS 文件。這在 Webpack 中表現(xiàn)可能并不明顯,但在 Vite 中卻有著巨大的影響,畢竟其他內(nèi)容的加載都非常迅捷。
既然生成再清除的方法存在局限性,那是否有更好的解決方案?
按需生成
"按需生成" 的想法引入了一種全新的思維方式。讓我們先來對比下這些方案:
傳統(tǒng)的方式不僅會消耗不必要的資源(生成了但未使用),甚至有時更是無法滿足你的需求,因為總會有部分需求無法包含在內(nèi)。
通過調(diào)換 "生成" 和 "掃描" 的順序,"按需" 會為你節(jié)省浪費的計算開銷和傳輸成本,同時可以靈活地實現(xiàn)預(yù)生成無法實現(xiàn)的動態(tài)需求。另外,這種方法可以同時在開發(fā)和生產(chǎn)中使用,提供了一致的開發(fā)體驗,使得 HMR (Hot Module Replacement, 熱更新) 更加高效。
為了實現(xiàn)這一點,Windi CSS 和 Tailwind JIT 都采用了預(yù)先掃描源代碼的方式。下面是一個簡單示例:
import?glob?from?'fast-glob' import?{?promises?as?fs?}?from?'fs'//?通常這個是可以配置的 const?include?=?['src/**/*.{jsx,tsx,vue,html}']async?function?scan()?{const?files?=?await?glob(include)for?(const?file?of?files)?{const?content?=?await?fs.readFile(file,?'utf8')//?將文件內(nèi)容傳遞給生成器并配對?class?的使用情況} }await?scan() //?掃描會在構(gòu)建/服務(wù)器啟動前完成 await?buildOrStartDevServer()為了在開發(fā)期間提供 HMR,通常會啟動一個 文件系統(tǒng)監(jiān)聽器[24]:
import?chokidar?from?'chokidar'chokidar.watch(include).on('change',?(event,?path)?=>?{//?重新讀取文件const?content?=?await?fs.readFile(file,?'utf8')//?將新的內(nèi)容重新傳遞給生成器//?清除?CSS?模塊的緩存并觸發(fā)?HMR?事件 })因此,通過按需生成方式,Windi CSS 獲得了比傳統(tǒng)的 Tailwind CSS 快 100 倍左右[25] 的性能。
痛癢
我現(xiàn)在在我?guī)缀跛械膽?yīng)用中都在使用 Windi CSS,而且效果顯著,性能優(yōu)異,HMR 瞬間完成幾乎無法察覺。自動值推導(dǎo)[26] 和 屬性化模式[27] 更是提升了我的開發(fā)體驗。到這里,我本該可以睡上一個好覺去想想其他事情了,但是有時候,它還是會瘙你癢癢打擾你的美夢。
我發(fā)現(xiàn)最令人討厭的是,和很多時候我不清楚我得到的結(jié)果是什么,以及怎么樣做才能讓它生效。對我而言,最好最理想的原子化 CSS 應(yīng)該是直覺性的。一旦學(xué)會,它應(yīng)該非常直觀易懂,并且可以推導(dǎo)出其他已知情況。如果你知道 mt-1 是上邊距的一倍單位,你就會直覺地認(rèn)為 mb-2 是下邊距的兩倍單位。當(dāng)它正常工作時,是直覺使然,但當(dāng)它不起作用時,會令人沮喪和困惑。
例如,我們知道 Tailwind 中的 border-2 標(biāo)識邊框?qū)挾葹?2px,4 表示 4px,6 表示 6px,8 表示 8px,但當(dāng)你使用 border-10 卻不起作用(你甚至需要一些時間來發(fā)現(xiàn)這件事!)。你可能會說這是故意而為之,以使得設(shè)計系統(tǒng)具有一致性。不如這樣,我們來做個小測驗,如果想要讓 border-10 正常工作,應(yīng)該如何做?
在你的全局樣式中的某個位置添加這樣一個工具類?
.border-10?{border-width:?10px; }快速且直觀,最重要的是,它的確解決了你的需求。但是,如果什么都需要我自己手動添加,那我們?yōu)槭裁催€需要使用 Tailwind ?
如果你對 Tailwind 了解深入一些,那你可能知道它可以進(jìn)行額外配置。所以你需要花 5 分鐘,檢索他們的文檔。你將得到如下方案[28]:
//?tailwind.config.js module.exports?=?{theme:?{borderWidth:?{DEFAULT:?'1px','0':?'0','2':?'2px','3':?'3px','4':?'4px','6':?'6px','8':?'8px','10':?'10px'?//?<--?here}} }這似乎很合理,我可以把我需要的情況都列出來,回去繼續(xù)工作了...等一下,我剛剛進(jìn)行到哪里了?因為這樣一個工具的丟失而被打斷,除了配置,我們還會需要時間重新找回原本正在進(jìn)行的工作的上下文。接著,如果我想設(shè)置邊框顏色,我還需要查詢文檔,然后如何進(jìn)行配置。也許有人喜歡這樣的工作流程,但這并不適合我,我并不享受被本該直覺性工作的工具打斷的我的工作流程。
Windi CSS 對規(guī)則相對寬松一些,會盡可能地根據(jù)你使用的 class 提供相應(yīng)的實用工具類。在這種情況下,border-10 在 Windi 中可以做到開箱即用。但是,由于 Windi 需要與 Tailwind 兼容,它還必須使用與 Tailwind 完全相同的配置項。盡管數(shù)字推斷的問題得到了解決,但如果你想添加一些自定義的工具,這將是一場噩夢。下面是一個來自 Tailwind 文檔[29] 的示例:
//?tailwind.config.js const?_?=?require('lodash') const?plugin?=?require('tailwindcss/plugin')module.exports?=?{theme:?{rotate:?{'1/4':?'90deg','1/2':?'180deg','3/4':?'270deg',}},plugins:?[plugin(function({?addUtilities,?theme,?e?})?{const?rotateUtilities?=?_.map(theme('rotate'),?(value,?key)?=>?{return?{[`.${e(`rotate-${key}`)}`]:?{transform:?`rotate(${value})`}}})addUtilities(rotateUtilities)})] }將產(chǎn)生如下代碼:
.rotate-1\/4?{transform:?rotate(90deg); } .rotate-1\/2?{transform:?rotate(180deg); } .rotate-3\/4?{transform:?rotate(270deg); }產(chǎn)生的 CSS 代碼甚至比結(jié)果還要長。并且難以閱讀和維護(hù),同時,它破壞了按需應(yīng)變的能力。
Tailwind 的 API 和插件系統(tǒng)沿用了舊的思維方式進(jìn)行設(shè)計,并不能適應(yīng)新的按需方式。其核心工具是在生成器中鍛造出來的,而且其定制化功能相當(dāng)有限。因此,我開始思考,如果我們可以放棄這些歷史包袱,并以隨需應(yīng)變思想重新設(shè)計它,我們可以得到什么?
向你介紹 UnoCSS
**UnoCSS**[30] - 具有高性能且極具靈活性的即時原子化 CSS 引擎。
該項目誕生于我在國慶期間的做的一些隨機實驗。從使用者的角度出發(fā)去探索靈活性和直觀性的最佳平衡,加上按需生成的思想,這些實驗的最終結(jié)果在不少方面甚至超出了我的預(yù)期。接下來讓我為你逐一介紹:
引擎
UnoCSS 是一個引擎,而非一款框架,因為它并未提供核心工具類,所有功能可以通過預(yù)設(shè)和內(nèi)聯(lián)配置提供。
我們設(shè)想 UnoCSS 能夠通過預(yù)設(shè)模擬大多數(shù)已有原子化 CSS 框架的功能。也有可能會被用作創(chuàng)建一些新的原子化 CSS 框架的引擎。例如:
import?UnocssPlugin?from?'@unocss/vite'//?以下預(yù)設(shè)目前還不存在, //?歡迎大家踴躍貢獻(xiàn)! import?PresetTachyons?from?'@unocss/preset-tachyons' import?PresetBootstrap?from?'@unocss/preset-bootstrap' import?PresetTailwind?from?'@unocss/preset-tailwind' import?PresetWindi?from?'@unocss/preset-windi' import?PresetAntfu?from?'@antfu/oh-my-cool-unocss-preset'export?default?{plugins:?[UnocssPlugin({presets:?[//?PresetTachyons,PresetBootstrap,//?PresetTailwind,//?PresetWindi,//?PresetAntfu//?選擇其中一個...或多個!]})] }讓我們來看看如何使它們成為可能:
直觀且完全可定制
UnoCSS 的主要目標(biāo)是直觀性和可定制性。它可以讓你在數(shù)十秒內(nèi),定義你自己的 CSS 工具。
靜態(tài)規(guī)則
原子化 CSS 可能數(shù)量相當(dāng)龐大。因此,規(guī)則定義直接了當(dāng)對于閱讀和維護(hù)非常重要。如需為 UnoCSS 創(chuàng)建一個自定義規(guī)則,你可以這樣寫:
rules:?[['m-1',?{?margin:?'0.25rem'?}] ]當(dāng)在用戶代碼庫中檢測到 m-1 時,就會產(chǎn)生如下 CSS:
.m-1?{?margin:?0.25rem;?}動態(tài)規(guī)則
想要使其動態(tài)化,可以將匹配器修改為正則表達(dá)式,將主體改為一個函數(shù):
rules:?[[/^m-(\d)$/,?([,?d])?=>?({?margin:?`${d?/?4}rem`?})],[/^p-(\d)$/,?(match)?=>?({?padding:?`${match[1]?/?4}rem`?})], ]其中,主題函數(shù)的第一個參數(shù)為匹配結(jié)果,所以你可以對它進(jìn)行解構(gòu)以獲得正則表達(dá)式的匹配組。
例如,當(dāng)你使用:
<div?class="m-100"><button?class="m-3"><icon?class="p-5"?/>My?Button</button> </div>就會產(chǎn)生相應(yīng)的 CSS:
.m-100?{?margin:?25rem;?} .m-3?{?margin:?0.75rem;?} .p-5?{?padding:?1.25rem;?}這樣就行了。而現(xiàn)在,你只需要使用相同的模式添加更多的實用工具類,你就擁有了屬于自己的原子化 CSS!
可變修飾
可變修飾 (Variants)[31] 在 UnoCSS 中也是簡單且強大的。這里有幾個示例:
variants:?[//?支持所有規(guī)則的?`hover:`{match:?s?=>?s.startsWith('hover:')???s.slice(6)?:?null,selector:?s?=>?`${s}:hover`,},//?支持?`!`?前綴,使規(guī)則優(yōu)先級更高{match:?s?=>?s.startsWith('!')???s.slice(1)?:?null,rewrite:?(entries)?=>?{//?在所有?CSS?值中添加?`?!important`entries.forEach(e?=>?e[1]?+=?'?!important')return?entries},} ],你可以參考 文檔[32] 了解更多細(xì)節(jié)。
預(yù)設(shè)
你可以將自己的自定義規(guī)則和可變修飾打包成預(yù)設(shè),與他人分享,或是使用 UnoCSS 作為引擎創(chuàng)建你自己的原子化 CSS 框架!
同時,我們在發(fā)布時也提供了 一些預(yù)設(shè)[33] 供你快速上手。
值得一提的是,默認(rèn)的 `@unocss/preset-uno`[34] 預(yù)設(shè)(實驗階段)是一系列流行的原子化框架的 通用超集,包括了 Tailwind CSS,Windi CSS,Bootstrap,Tachyons 等。
例如,ml-3(Tailwind),ms-2(Bootstrap),ma4(Tachyons),mt-10px(Windi CSS)均會生效。
.ma4?{?margin:?1rem;?} .ml-3?{?margin-left:?0.75rem;?} .ms-2?{?margin-inline-start:?0.5rem;?} .mt-10px?{?margin-top:?10px;?}了解更多關(guān)于默認(rèn)預(yù)設(shè)的信息[35]。
靈活性
截止目前為止,我們都在向你展示如何使用 UnoCSS 來模仿 Tailwind 和其他原子化框架的行為,即便 UnoCSS 讓這件事變得十分容易,但僅此一點可能也不會在最終使用者的方面產(chǎn)生太大影響。
一起來見識下 UnoCSS 真正的威力:
屬性化模式
屬性化模式 (Attributify Mode)[36] 是 Windi CSS 最受歡迎的特性之一。它能幫助你通過使用屬性更好地組織和分組你的實用工具類。
它會把你的冗長的 Tailwind 代碼(難以閱讀與編輯):
<button?class="bg-blue-400?hover:bg-blue-500?text-sm?text-white?font-mono?font-light?py-2?px-4?rounded?border-2?border-blue-200?dark:bg-blue-500?dark:hover:bg-blue-600">Button </button>變成:
<button?bg="blue-400?hover:blue-500?dark:blue-500?dark:hover:blue-600"text="sm?white"font="mono?light"p="y-2?x-4"border="2?rounded?blue-200" >Button </button>在更好的按類型進(jìn)行組織的同時,也節(jié)省了重復(fù)輸入相同前綴的時間。
在 UnoCSS 中,我們也實現(xiàn)了屬性化模式,只使用 **一個可變修飾**[37] 和 **一個提取器**[38],總共 代碼行數(shù)不超過 100!更重要的是,它直接適用于你自定義的任何規(guī)則!
除了 Windi CSS 的屬性化模式,僅需改動幾行代碼,我們還實現(xiàn)了無值的屬性的支持:
<div?class="m-2?rounded?text-teal-400"?/>現(xiàn)在變?yōu)?#xff1a;
<div?m-2?rounded?text-teal-400?/>整個屬性化模式是通過 `@unocss/preset-attributify`[39] 預(yù)設(shè)提供的,詳細(xì)的使用方法請參考其文檔。
純 CSS 圖標(biāo)
如果你讀過我之前的文章 Journey with Icons Continues[40],你一定知道我對圖標(biāo)非常熱衷,并且在積極研究圖標(biāo)的各種解決方案。這次,憑借 UnoCSS 的靈活性,我們甚至可以擁有純 CSS 的圖標(biāo)。你沒看錯,它是純 CSS 的圖標(biāo),不需要任何 JavaScript!讓我們來看看它長什么樣子:
<!--?A?basic?anchor?icon?from?Phosphor?icons?--> <div?class="i-ph-anchor-simple-thin"?/> <!--?An?orange?alarm?from?Material?Design?Icons?--> <div?class="i-mdi-alarm?text-orange-400?hover:text-teal-400"?/> <!--?A?large?Vue?logo?--> <div?class="i-logos-vue?text-3xl"?/> <!--?Sun?in?light?mode,?Moon?in?dark?mode,?from?Carbon?--> <button?class="i-carbon-sun?dark:i-carbon-moon"?/> <!--?Twemoji?of?laugh,?turns?to?tear?on?hovering?--> <div?class="i-twemoji-grinning-face-with-smiling-eyes?hover:i-twemoji-face-with-tears-of-joy"?/>與可變修飾結(jié)合,你甚至可以根據(jù)懸停狀態(tài)或顏色模式來切換圖標(biāo)。得益于 Iconify[41] 項目,你可以從一百余個熱門圖標(biāo)集合(Material Design Icons, Ant Design Icons 等等)中獲得 超過一萬個圖標(biāo) 供你按需使用。
同樣的,這個功能的實現(xiàn)代碼并未超過 100 行。具體請參考 `@unocss/preset-icons`[42] 預(yù)設(shè)的實現(xiàn)了解其中的魔法。
希望這些預(yù)設(shè)可以讓你對 UnoCSS 的靈活性有一個大致的了解。它還處于一個非常早期的階段,有很多可能性等待我們?nèi)ヌ剿鳌?/p>
CSS 作用域
在使用 Tailwind / Windi 時,我遇到的另一個問題就是 樣式預(yù)檢 (Preflight)[43]。預(yù)檢功能重置了原生元素,并為 CSS 變量提供了一些兜底方案,在開發(fā)一個只使用 Tailwind/Windi 的新應(yīng)用時,效果很棒。但當(dāng)你想讓它們與其他 UI 框架一起工作,或者使用 Tailwind 編寫一些共享組件時,預(yù)檢往往會引入許多沖突,破壞你現(xiàn)有的 UI。
因此,UnoCSS 采取了另一個霸氣的操作,不支持預(yù)檢。相反,它將 CSS 重置的控制權(quán)完全留給了用戶 (或 UnoCSS 上層框架),讓他們使用適合自己的方案 - Normalize.css,Reset.css 或者 UI 框架自帶的CSS 重置等。
這也使得 UnoCSS 在 CSS 作用域上有了更多可能性。例如,我們在 Vite 插件上有一個實驗性的 scoped-vue 模式,可以為每個組件生成作用域樣式,你可以安全地使用原子化 CSS 作為組件庫,而無需擔(dān)心與用戶的 CSS 發(fā)生沖突。比如:
<template><div?class="m-2?rounded"><slot></div> <template><!--?以下內(nèi)容將被注入?bundler?中?--> <style?scoped> .m-2{margin:0.5rem;} .rounded{border-radius:0.25rem;} </style>我們還在嘗試更多的可能性,比如支持 Web Component,MPA 情況下的 CSS 代碼分割,以及模塊級別的 CSS 作用域等。
性能
考慮到 UnoCSS 帶來的靈活性和想象力,坦率地說,我認(rèn)為性能可能不是那么重要的事情。出于好奇,我寫了一個 簡單的 benchmark[44] 來比較性能。結(jié)果令人驚訝:
10/21/2021,?2:17:45?PM 1656?utilities?|?x50?runsnone????????????????????????????8.75?ms?/????0.00?ms? unocss???????v0.0.0????????????13.72?ms?/????4.97?ms?(x1.00) windicss?????v3.1.9???????????980.41?ms?/??971.66?ms?(x195.36) tailwindcss??v3.0.0-alpha.1??1258.54?ms?/?1249.79?ms?(x251.28)從結(jié)果來看,UnoCSS 可以比 Tailwind 的 JIT 引擎快 200 倍!說實話,在按需生成的情況下,Windi 和 Tailwind JIT 都已經(jīng)算是超快了,UnoCSS 的性能提升感知度可能沒有那么高。然而,幾乎為零的開銷意味著你可以將 UnoCSS 整合到你現(xiàn)有的項目中,作為一個增量解決方案與其他框架一同協(xié)作,而不需要擔(dān)心性能損耗。
在開發(fā)時,UnoCSS 做了很多性能上的優(yōu)化。如果你感興趣,可以參考:
跳過解析,不使用 AST
從內(nèi)部實現(xiàn)上看,Tailwind 依賴于 PostCSS 的 AST 進(jìn)行修改,而 Windi 則是編寫了一個自定義解析器和 AST。考慮到在開發(fā)過程中,這些工具 CSS 的并不經(jīng)常變化,UnoCSS 通過非常高效的字符串拼接來直接生成對應(yīng)的 CSS 而非引入整個編譯過程。同時,UnoCSS 對類名和生成的 CSS 字符串進(jìn)行了緩存,當(dāng)再次遇到相同的實用工具類時,它可以繞過整個匹配和生成的過程。
單次迭代
正如前文所述,Windi CSS 和 Tailwind JIT 都依賴于對文件系統(tǒng)的預(yù)掃描,并使用文件系統(tǒng)監(jiān)聽器來實現(xiàn) HMR。文件 I/O 不可避免地會引入開銷,而你的構(gòu)建工具實際上需要再次加載它們。那么我們?yōu)槭裁床恢苯永靡呀?jīng)被工具讀取過的內(nèi)容呢?
除了獨立的生成器核心以外,UnoCSS 有意只提供了 Vite 插件(以后可能考慮其他的集成),這使得它能夠?qū)W⒂谂c Vite 的最佳集成。
在 Vite 中,transform 的鉤子將與所有的文件及其內(nèi)容一起被迭代。因此,我們可以寫一個插件來收集它們,比如:
export?default?{plugins:?[{name:?'unocss',transform(code,?id)?{//?過濾掉無需掃描的文件if?(!filter(id))?return//?掃描代碼(同時也可以處理開發(fā)中的無效內(nèi)容)scan(code,?id)//?我們只需要內(nèi)容,所以不需要對代碼進(jìn)行轉(zhuǎn)換return?null},resolveId(id)?{return?id?===?VIRTUAL_CSS_ID???id?:?null},async?load(id)?{//?生成的?css?會作為一個虛擬模塊供后續(xù)使用if?(id?===?VIRTUAL_CSS_ID)?{return?{?code:?await?generate()?}}}}] }由于 Vite 也會處理 HMR,并在文件變化時再次執(zhí)行 transform 鉤子,這使得 UnoCSS 可以在一次加載中就完成所有的工作,沒有重復(fù)的文件 I/O 和文件系統(tǒng)監(jiān)聽器。此外,通過這種方式,掃描會依賴于模塊圖而非文件 glob。這意味著只有構(gòu)建在你應(yīng)用程序中的模塊才會影響生成的 CSS,而并非你文件夾下的任何文件。
我們還做了一些小技巧來提高性能。我可能會在以后再寫一篇關(guān)于它們的文章,但在此之前,你可以通過閱讀代碼來弄清它們 :)
現(xiàn)在能用嗎?
簡而言之:可以,但有注意事項。
UnoCSS 仍處于實驗階段,但由于其精簡的設(shè)計,生成的結(jié)果已經(jīng)非常可靠了。需要注意的一點是,API 還沒有最終定案,雖然我們會遵循 semver 的進(jìn)行版本發(fā)布,但是還請為破壞性改動做好準(zhǔn)備。
注意:它并非被設(shè)計成 Windi CSS 或 Tailwind 的替代品(考慮等待 Windi CSS v4)。我們不建議將現(xiàn)有項目完全遷移到 UnoCSS。你可以在新的項目中試用它,或者將它作為你現(xiàn)有 CSS 框架的補充(例如,禁用默認(rèn)預(yù)設(shè),只使用純 CSS 圖標(biāo)的預(yù)設(shè),或者自定義你的規(guī)則)。
順便說一句,目前 你正在閱讀的網(wǎng)站[45] 就構(gòu)建于 UnoCSS 之上,供你參考 :P。
同時,歡迎分享你正在制作的預(yù)設(shè)或幫助我們貢獻(xiàn)默認(rèn)的預(yù)設(shè)。期待看到你能夠蹦出什么新想法!
關(guān)于 Windi CSS?
作為 Windi CSS 的團隊成員,我與 Windi CSS 的創(chuàng)作者 Voorjaar[46] 緊密合作。你可以認(rèn)為 UnoCSS 是 Windi CSS 團隊的一個激進(jìn)的實驗,如果進(jìn)展順利,它可能成為 Windi CSS v4 的新引擎。
Windi CSS 作為一個框架,將填補 UnoCSS 作為一個引擎有意不提供的 @apply 預(yù)處理,IDE 智能提示,預(yù)處理等功能的缺失。而且它還將利用 UnoCSS 為核心工具為用戶配置提供高性能和靈活性。
在我們?yōu)?Windi v4 嵌入新引擎之前,一個使用 UnoCSS 作為 Windi CSS 擴展的 npm 包(例如,擁有純 CSS 圖標(biāo))將很快發(fā)布。敬請關(guān)注 :)
結(jié)束語
非常感謝你的閱讀!如果你對它感興趣,請記得查看 `antfu/unocss`[47] 倉庫以了解更多細(xì)節(jié),也可以通過 **在線 Playground**[48] 進(jìn)行嘗試。
歡迎評論或轉(zhuǎn)發(fā) 此推文[49],讓我知道你的想法!🙌
參考資料
[1]
QC-L: https://github.com/QC-L
[2]English Version: /posts/reimagine-atomic-css
[3]文章 Let’s Define Exactly What Atomic CSS is: https://css-tricks.com/lets-define-exactly-atomic-css/
[4]Tailwind CSS: https://tailwindcss.com/
[5]Windi CSS: https://cn.windicss.org/
[6]Tachyons: https://tachyons.io/
[7]Bootstrap: https://getbootstrap.com/docs/5.1/utilities/api/
[8]Chakra UI: https://chakra-ui.com/docs/features/style-props
[9]Vite: https://vitejs.dev/
[10]Vitesse: https://github.com/antfu/vitesse
[11]Tailwind CSS: https://tailwindcss.com/
[12]Windi CSS: https://cn.windicss.org
[13]Windi CSS: https://cn.windicss.org
[14]一個 Vite 插件: https://github.com/windicss/vite-plugin-windicss
[15]20~100 倍: https://twitter.com/antfu7/status/1361398324587163648
[16]自動值推導(dǎo): https://cn.windicss.org/features/value-auto-infer.html
[17]可變修飾組: https://windicss.org/features/variant-groups.html
[18]Shortcuts: https://windicss.org/features/shortcuts.html
[19]在 DevTools 中進(jìn)行設(shè)計: https://twitter.com/antfu7/status/1372244287975387145
[20]屬性化模式: https://twitter.com/windi_css/status/1387460661135896577
[21]因此: https://twitter.com/adamwathan/status/1371542711086559237?s=20
[22]JIT 按需引擎: https://tailwindcss.com/docs/just-in-time-mode
[23]PurgeCSS: https://purgecss.com/
[24]文件系統(tǒng)監(jiān)聽器: https://github.com/paulmillr/chokidar
[25]快 100 倍左右: https://twitter.com/antfu7/status/1361398324587163648
[26]自動值推導(dǎo): https://cn.windicss.org/features/value-auto-infer.html
[27]屬性化模式: https://twitter.com/windi_css/status/1387460661135896577
[28]如下方案: https://tailwindcss.com/docs/border-width#border-widths
[29]Tailwind 文檔: https://tailwindcss.com/docs/plugins#escaping-class-names
[30]UnoCSS: https://github.com/antfu/unocss
[31]可變修飾 (Variants): https://cn.windicss.org/utilities/variants.html#variants
[32]文檔: https://github.com/antfu/unocss#custom-variants
[33]一些預(yù)設(shè): https://github.com/antfu/unocss#presets
[34]@unocss/preset-uno: https://github.com/antfu/unocss/tree/main/packages/preset-uno
[35]了解更多關(guān)于默認(rèn)預(yù)設(shè)的信息: https://github.com/antfu/unocss/tree/main/packages/preset-uno
[36]屬性化模式 (Attributify Mode): https://windicss.org/posts/v30.html#attributify-mode
[37]一個可變修飾: https://github.com/antfu/unocss/blob/main/packages/preset-attributify/src/variant.ts
[38]一個提取器: https://github.com/antfu/unocss/blob/main/packages/preset-attributify/src/extractor.ts
[39]@unocss/preset-attributify: https://github.com/antfu/unocss/blob/main/packages/preset-attributify
[40]Journey with Icons Continues: /posts/journey-with-icons-continues
[41]Iconify: https://iconify.design/
[42]@unocss/preset-icons: https://github.com/antfu/unocss/blob/main/packages/preset-icons
[43]樣式預(yù)檢 (Preflight): https://tailwindcss.com/docs/preflight
[44]簡單的 benchmark: https://github.com/antfu/unocss/tree/main/bench
[45]你正在閱讀的網(wǎng)站: https://github.com/antfu/antfu.me
[46]Voorjaar: https://github.com/voorjaar
[47]antfu/unocss: https://github.com/antfu/unocss
[48]在線 Playground: https://unocss.antfu.me/
[49]此推文: https://twitter.com/antfu7/status/1452802545118711812
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的重新构想原子化 CSS的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript学习系列(25):数
- 下一篇: 前端入门之路