前端工程化思維:主題切換架構(gòu)
發(fā)布日期:2022/9/6 9:58:21 瀏覽量:
在前端基礎(chǔ)建設(shè)中,對樣式方案的處理是必不可少的。
在本文中,我們將實現(xiàn)一個工程化主題切換功能,并梳理現(xiàn)代前端樣式的解決方案。
設(shè)計一個主題切換工程架構(gòu)
隨著iOS 13引入深色模式(Dark Mode),各大應(yīng)用和網(wǎng)站也都開始支持深色模式。相比于傳統(tǒng)的頁面配色方案,深色模式具有較好的降噪性,也能讓用戶的眼睛在看內(nèi)容時更舒適。
那么對于前端來說,如何高效地支持深色模式呢?
這里的高效就是指工程化、自動化。在介紹具體方案前,我們先來了解一個必會的前端工程化神器——PostCSS。
▊PostCSS原理和相關(guān)插件能力
簡單來說,PostCSS是一款編譯CSS的工具。PostCSS具有良好的插件性,其插件也是使用JavaScript編寫的,非常有利于開發(fā)者進行擴展。
基于前面內(nèi)容介紹的Babel思想,對比JavaScript的編譯器,我們不難猜出PostCSS的工作原理:PostCSS接收一個CSS文件,并提供插件機制,提供給開發(fā)者分析、修改CSS規(guī)則的能力,具體實現(xiàn)方式也是基于AST技術(shù)實現(xiàn)的。
本文介紹的工程化主題切換架構(gòu)也離不開PostCSS的基礎(chǔ)能力。
▊架構(gòu)思路
對于主題切換,社區(qū)介紹的方案往往是通過CSS變量(CSS自定義屬性)來實現(xiàn)的,這無疑是一個很好的思路,但是作為架構(gòu),使用CSS自定義屬性只是其中一個環(huán)節(jié)。站在更高、更中臺化的視角思考,我們還需要搞清楚以下內(nèi)容。
- 如何維護不同主題色值?
- 誰來維護不同主題色值?
- 在研發(fā)和設(shè)計之間,如何保持不同主題色值的同步溝通?
- 如何最小化前端工程師的開發(fā)量,讓他們不必硬編碼兩份色值?
- 如何使一鍵切換時的性能最優(yōu)?
- 如何配合JavaScript狀態(tài)管理,同步主題切換的信號?
基于以上考慮,以一個超鏈接樣式為例,我們希望做到在開發(fā)時編寫以下代碼。
a {color: cc(GBK05A);
這樣就能一勞永逸,直接支持兩套主題模式(Light/Dark)。也就是說,在應(yīng)用編譯時,上述代碼將被編譯為下面這樣。
a {color: #646464;HTML[data-theme=’dark’] a {color: #808080;
我們來看看在編譯時,構(gòu)建環(huán)節(jié)完成了什么具體操作。
- cc(GBK05A)這樣的聲明被編譯為#646464。cc是一個CSS函數(shù),而GBK05A是一組色值,即一個色組,分別包含了Light和Dark兩種主題模式中的顏色。
- 在HTML根節(jié)點上,添加屬性選擇器data-theme=’dark’,并添加a標(biāo)簽,color色值樣式為#808080。
我們設(shè)想,用戶點擊“切換主題”按鈕時,首先通過JavaScript向HTML根節(jié)點標(biāo)簽內(nèi)添加 data-theme為dark的屬性值,這時CSS選擇器html[data-theme=’dark’] a將發(fā)揮作用,實現(xiàn)樣式切換。
結(jié)合圖1可以輔助理解上述編譯過程。
圖1
回到架構(gòu)設(shè)計中,如何在構(gòu)建時完成CSS的樣式編譯轉(zhuǎn)換呢?
答案指向了PostCSS。
具體架構(gòu)設(shè)計步驟如下。
? 編寫一個名為postcss-theme-colors的PostCSS插件,實現(xiàn)上述編譯過程。
? 維護一個色值,結(jié)合上例(這里以YML格式為例),配置如下。
GBK05A: [BK05, BK06]BK05: ’#808080’BK06: ’#999999’
postcss-theme-colors需要完成以下操作。
- 識別cc函數(shù)。
- 讀取色組配置。
- 通過色值對cc函數(shù)求值,得到兩種顏色,分別對應(yīng)Light和Dark主題模式。
- 原地編譯CSS中的顏色為Light主題模式色值。
- 將Dark主題模式色值寫到HTML根節(jié)點上。
這里需要補充的是,為了將Dark主題模式色值按照html[data-theme=’dark’]方式寫到HTML根節(jié)點上,我們使用了如下兩個PostCSS插件。
- postcss-nested。
- postcss-nesting。
整體架構(gòu)設(shè)計如圖2所示。
圖2
2
主題色切換架構(gòu)實現(xiàn)
有了整體架構(gòu),下面來實現(xiàn)其中的重點環(huán)節(jié)。
首先,我們需要了解PostCSS插件體系。
▊PostCSS插件體系
PostCSS具有天生的插件化體系,開發(fā)者一般很容易上手插件開發(fā),典型的PostCSS插件編寫模板如下。
var postcss = require(’postcss’);module.exports = postcss.plugin(’pluginname’, function (opts) {opts = opts || {};// 處理配置項return function (css, result) {// 轉(zhuǎn)換AST
一個PostCSS就是一個Node.js模塊,開發(fā)者調(diào)用postcss.plugin(源碼鏈接定義在postcss.plugin 中)工廠方法返回一個插件實體,如下。
return {postcssPlugin: ’PLUGIN_NAME’,root (root, postcss) {// 轉(zhuǎn)換ASTDeclaration (decl, postcss) {Declaration: {color: (decl, postcss) {
在編寫PostCSS 插件時,我們可以直接使用postcss.plugin方法完成實際開發(fā),然后就可以開始動手實現(xiàn)postcss-theme-colors插件了。
▊動手實現(xiàn)postcss-theme-colors插件
在PostCSS插件設(shè)計中,我們看到了清晰的AST設(shè)計痕跡,經(jīng)過之前的學(xué)習(xí),我們應(yīng)該對AST 不再陌生。根據(jù)插件代碼骨架加入具體實現(xiàn)邏輯,如下。
const postcss = require(’postcss’)const defaults = {function: ’cc’,groups: {},colors: {},useCustomProperties: false,darkThemeSelector: ’html[data-theme="dark"]’,nestingPlugin: null,const resolveColor = (options, theme, group, defaultValue) => {const [lightColor, darkColor] = options.groups[group] || []const color = theme === ’dark’ ? darkColor : lightColorif (!color) {return defaultValueif (options.useCustomProperties) {return color.startsWith(’--’) ? ’var(${color})’ : ’var(--${color})’return options.colors[color] || defaultValuemodule.exports = postcss.plugin(’postcss-theme-colors’, options => {options = Object.assign({}, defaults, options)// 獲取色值函數(shù)(默認(rèn)為cc)const reGroup = new RegExp(’\\b${options.function}\\(([^)]+)\\)’, ’g’)return (style, result) => {// 判斷PostCSS工作流程中是否使用了某些插件const hasPlugin = name =>name.replace(/^postcss-/, ’’) === options.nestingPlugin ||result.processor.plugins.some(p => p.postcssPlugin === name)// 獲取最終的CSS值const getValue = (value, theme) => {return value.replace(reGroup, (match, group) => {return resolveColor(options, theme, group, match)// 遍歷CSS聲明style.walkDecls(decl => {const value = decl.value// 如果不含有色值函數(shù)調(diào)用,則提前退出if (!value || !reGroup.test(value)) {returnconst lightValue = getValue(value, ’light’)const darkValue = getValue(value, ’dark’)const darkDecl = decl.clone({value: darkValue})let darkRule// 使用插件,生成Dark主題模式if (hasPlugin(’postcss-nesting’)) {darkRule = postcss.atRule({name: ’nest’,params: ’${options.darkThemeSelector} &’,} else if (hasPlugin(’postcss-nested’)) {darkRule = postcss.rule({selector: ’${options.darkThemeSelector} &’,} else {decl.warn(result, ’Plugin(postcss-nesting or postcss-nested) not found’)// 添加Dark主題模式到目標(biāo)HTML根節(jié)點中if (darkRule) {darkRule.append(darkDecl)decl.after(darkRule)const lightDecl = decl.clone({value: lightValue})decl.replaceWith(lightDecl)
上面的代碼中加入了相關(guān)注釋,整體邏輯并不難理解。理解了以上源碼,postcss-theme-colors插件的使用方式也就呼之欲出了。
const colors = {C01: ’#eee’,C02: ’#111’,const groups = {G01: [’C01’, ’C02’],postcss([require(’postcss-theme-colors’)({colors, groups}),]).process(css)
通過上述操作,我們實現(xiàn)了postcss-theme-colors插件,整體架構(gòu)也完成了大半。接下來,我們將繼續(xù)完善,并最終打造出一個更符合基礎(chǔ)建設(shè)要求的方案。
▊架構(gòu)平臺化——色組和色值平臺設(shè)計
在上面的示例中,我們采用了硬編碼(hard coding)方式。
const colors = {C01: ’#eee’,C02: ’#111’,const groups = {G01: [’C01’, ’C02’],
上述代碼聲明了colors和groups兩個變量,并將它們傳遞給了postcss-theme-colors插件。其中,groups變量聲明了色組的概念,比如group1被命名為G01,對應(yīng)了C01(日間色)、C02(夜間色)兩個色值,這樣做的好處顯而易見。
- 可將postcss-theme-colors插件和色值聲明解耦,postcss-theme-colors插件并不關(guān)心具體的色值聲明,而是接收colors和groups變量。
- 實現(xiàn)了色值和色組的解耦。
√colors維護具體色值。
√ groups維護具體色組。
例如,前面提到了如下的超鏈接樣式聲明。
a {color: cc(GBK05A);
在業(yè)務(wù)開發(fā)中,我們直接聲明了“使用GBK05A這個色組”。業(yè)務(wù)開發(fā)者不需要關(guān)心這個色組在Light和Dark主題模式下分別對應(yīng)哪些色值。而設(shè)計團隊可以專門維護色組和色值,最終只提供給開發(fā)者色組。
在此基礎(chǔ)上,我們完全可以抽象出一個色組和色值平臺,方便設(shè)計團隊更新內(nèi)容。這個平臺可以以JSON或YML等任何形式存儲色值和色組的對應(yīng)關(guān)系,方便各個團隊協(xié)作。
在前面提到的主題切換設(shè)計架構(gòu)圖的基礎(chǔ)上,我們擴充其為平臺化解決方案,如圖3所示。
圖3
本文沒有聚焦于CSS樣式的具體用法,而是從更高的視角梳理了現(xiàn)代化前端基礎(chǔ)建設(shè)當(dāng)中的樣式相關(guān)工程方案,并從“主題切換”這一話題入手,聯(lián)動了PostCSS、Webpack,甚至前端狀態(tài)管理流程。
本文節(jié)選自《前端架構(gòu)師:基礎(chǔ)建設(shè)與架構(gòu)設(shè)計思想》一書,更多前端架構(gòu)相關(guān)內(nèi)容,請查看本書!
同時收錄于小程序《互聯(lián)網(wǎng)小兵》,技術(shù)人小程序,收錄前端、后端、移動端、人工智能、算法等優(yōu)質(zhì)技術(shù)文章!
馬上咨詢: 如果您有業(yè)務(wù)方面的問題或者需求,歡迎您咨詢!我們帶來的不僅僅是技術(shù),還有行業(yè)經(jīng)驗積累。
QQ: 39764417/308460098 Phone: 13 9800 1 9844 / 135 6887 9550 聯(lián)系人:石先生/雷先生