
接收到這個需求的時候有三種思路:
- 打包的時候同時冗余打包多套CSS文件,并在切換主題的時候加載相應(yīng)的樣式文件。
- 在根組件上加上主題類名,并在切換主題的時候改變類名,然后通過CSS類來覆蓋已有樣式。
- 利用 antd-theme-webpack-plugin將less文件加載,并在線解析less文件后覆蓋已有CSS
- 線上解析所有樣式代碼,并根據(jù)新的顏色重新生成樣式,并加入HTML中 下面逐個方案分析:
方案一:打包的時候同時冗余打包多套CSS文件
劣勢相當明顯,第一是打包速度嚴重拖慢,第二是切換主題的時候還需要重新從服務(wù)器上獲取CSS文件,意味著切換個主題需要等上好幾秒之后才能見效。優(yōu)勢的話,估計就是實現(xiàn)起來稍微簡單一點了。但這個很明顯不是我想要的結(jié)果。
方案二:在根組件上加上主題類名
優(yōu)勢在于切換成本比較低,但是劣勢也比較明顯:第一是打包體積嚴重增加(多加一套主題樣式文件幾乎增加一倍),第二是后續(xù)代碼維護困難,需要把所有需要更換主題的樣式文件全部寫進所有主題類里面,工作量太大,而且是個無底洞。
方案三:利用 antd-theme-webpack-plugin
這套方案的可以根據(jù)antd里面的所有l(wèi)ess文件中的變量進行替換,正好我們的項目又是基于React和antd,簡直完美融合。于是欣然試用,最終試用結(jié)果是:我水土不服了。做個簡單的demo的時候運行很好,沒問題,但是移植到已有項目的時候就出現(xiàn)各種不適應(yīng),首先是版本問題,這個好解決。然后又出現(xiàn)其他報錯,一個一個解決完了之后發(fā)現(xiàn)實際運行起來之后還是水土不服。
另外這種模式還有另外的劣勢,必須額外加載less文件,并且在線上解析所有l(wèi)ess文件,然后應(yīng)用到DOM中,對于我們這個光是樣式文件打包(還是minify之后的)起來都有1M左右的項目來說簡直是災難。
方案四:線上解析所有樣式代碼
這種方案其實是比較討巧的做法,將所有的link和style標簽的樣式取出來,然后替換相應(yīng)的變量,再注入到DOM節(jié)點中,完成樣式替換。不需要太多額外的文件引入,是一個比較好的實現(xiàn)思路,唯一的問題就在于前面提到的1M多的樣式文件,考慮到本身項目已經(jīng)比較龐大,在加上這么大的額外開銷還是忍痛拒絕了。
于是主題到了:并非所有的樣式代碼都需要解析的,因為畢竟換膚一般來說只會更換一兩個顏色,而不會更換所有的樣式文件,那么何不在發(fā)布線上之前就將CSS解析好,線上的時候只需要解析這些已經(jīng)處理好的樣式文件即可,實際操作下來之后1M左右的樣式文件只需要處理幾KB的數(shù)據(jù)。在實際線上運行之后發(fā)現(xiàn)也確實比較流暢,也就是文章開頭的GIF的效果了。
這個方案靈感來源于 webpack-theme-color-replacer,該項目通過webpack插件形式將樣式文件解析后將更換主題的代碼注入到每個js頭部,實現(xiàn)思路很漂亮,只是看了代碼之后有幾個問題:
- 樣式加載到每個js頭部,冗余!
- 顏色匹配必須對應(yīng)array中的順序,而且不直觀
- 需要額外再代碼中引入樣式替換的js
綜合起來覺得使用起來比較繁瑣,于是自己動手擼了一個webpack插件:webpack-stylesheet-variable-replacer-plugin,只需要在webpack配置中增加一個plugin,其他的就OK了,支持key--value方式來定義需要替換的變量,還有就是注入文件可控,不會重復注入。下面介紹一下使用方法,簡單三步走:
- 引入插件
const WebpackVariableReplacer = require('webpack-stylesheet-variable-replacer-plugin');
- 定義插件配置信息
new WebpackVariableReplacer({
publicPath: '',
buildPath: 'static/',
nextSupport: true,
specifyEntry: /_app\.js/,
matchVariables: {
main: '#209CEE',
}
}),
- 客戶端調(diào)用替換主題的方法:
window.replaceStyleVariable && window.replaceStyleVariable({main: color});
運行效果還是看文章開頭的GIF,總結(jié)來說,效率高,代碼侵入性低,方便使用。理論上來說,在angular和vue中使用也沒有任何問題。不過本人主要技術(shù)棧還是react,其他沒有測試。 下面解釋一下使用參數(shù):
| 參數(shù) | 默認值 | 詳細說明 |
|---|---|---|
| fileName | webpack-variable-replacer-[hash].js | |
| publicPath | '' | 資源訪問路徑 |
| htmlFileName | '' | 需要注入的HTML文件名 |
| buildPath | ‘’ | 文件打包后存放的位置前綴 |
| nextSupport | false | 是否需要支持next.js,如果后端服務(wù)用next.js搭建,此參數(shù)需傳true |
| injectToEntry | false | 是否需要將生成后的代碼注入到打包的js中,如果穿了htmlFileName,此參數(shù)可傳false,否則必須為true |
| excludeEntry | null | 需要排除的entry文件名,支持傳入正則或者正則字符串 |
| specifyEntry | null | 需要指定的entry文件名,支持傳入正則或者正則字符串,不傳默認所有 |
| matchVariables | {} | 需要匹配的字符串,通過key-value模式傳入,例如:{main:'#123456'}
|
我的主頁:eaTong個人站