基於webpack的css sprites實現方案
本文首發於部落格園
作為前端構建工具不可或缺的一個環節,自動生成css sprites圖片不僅僅能夠減少頻繁的人工操作,還能夠避免多人協作時對同一個sprites圖片維護過程中因個人原因引起的圖片不規範問題。58到家前端工程化解決方案boi的自動css sprites功能基於webpack實現,本文記錄一下實現方案的各個細節以及需要注意的地方。
1. 功能需求
css sprites的功能需求簡單說就是將style中引用的雜湊小圖示合併成一張sprites圖片。從功能角度來講比較單一,從實現角度來講需要具備以下幾點:
- 對style檔案進行資源依賴分析,能夠得出style中引用的圖片資源;
- style檔案引用的圖片並非都是圖示,其他的比如背景圖等資源不應該被sprites合併。所以必須有明確的標識可以區分圖示與非圖示資源。
對於第一點,webpack本身就具備依賴分析的功能,所以無需自行實現。那麼如何設計明確的標識以便區分資源型別呢?
2. 使用者至上的設計原則
上文提到的資源標識,我們首先看一下業內的同類產品是如何實現的。以fis為例,請看以下程式碼:
li.list-1::before {
background-image: url('./img/list-1.png?__sprite');
}
li.list-2::before {
background-image: url('./img/list-2.png?__sprite');
}
fis的css sprites功能要求開發者在style程式碼中新增__sprite
標識,fis通過識別這個標識來區分資源型別。這種模式的優點是可以精確地進行定位,而且對圖示檔案的路徑沒有強制要求,可以將圖示檔案與其他資原始檔混合存放。但是,在程式碼中書寫標識,首先需要具體的業務開發人員時刻注意不要遺漏;其次,這種模式實質上是對程式碼的一種“綁架”,程式碼中存在與業務無關的內容並且可移植性不高。
作為框架,所有方案都應該遵循使用者至上的設計原則:
- 配置API語義化,一目瞭然;
- 減少程式碼綁架,減少程式碼中存在與業務無關的內容,以便程式碼的高可移植性;
- 提供高階配置API,方便使用者進行自定義。
基於以上原則,boi在設計配置API時儘量做到了語義化,並且style程式碼中不存在任何與業務無關的內容。以下程式碼是boi配置css sprites功能的demo:
boi.spec('style',{
sprites: true,
spritesConfig: {
dir: 'assets/image/icons',
split: true,
retina: true,
postcssSpritesOpts: null
}
});
與sprites功能相關的配置項細節如下:
sprites
-Boolean
,是否開啟自動sprites功能,預設false
。只有在sprites
為true
時,spritesConfig
才會生效;spritesConfig
-Object
,功能配置細節:dir
-String
,圖示檔案的目錄路徑,預設為undefined
。boi以路徑作為區分圖示與非圖示資源的標識,也就是說參與自動sprites的圖示檔案必須存放於獨立的目錄下,比如'assets/image/icons'
;split
-Boolean
,是否識別子目錄並且每個子目錄分別編譯為sprites圖片,預設為true
。比如上述程式碼對應的專案中存在圖示目錄'assets/image/icons'
,在此目錄下又存在兩個子目錄'assets/image/icons/index'
和'assets/image/icons/admin'
,分別存在index
頁面和admin
頁面的圖示檔案。如果配置split:true
,boi將會編譯輸出兩個sprites圖片sprite.index.png
和sprite.admin.png
;如果配置split:false
,boi只會編譯輸出一個sprites圖片檔案sprite.icons.png
。retina
-Boolean
,是否識別解析度標識,預設為true
。解析度標識指的是類似@2x
的檔名標識,比如存在兩個圖示檔案logo.png
和logo@2x.png
並且style檔案中對兩張圖示都有引用,如果配置retina:true
,boi將把兩種解析度的圖片分別合併為一張sprites圖片,否則會編譯到同一張sprites圖片裡。postcssSpritesOpts
-Object
,預設為null
。boi使用postcss-sprites作為實現css sprites的技術選型。postcssSpritesOpts
是提供給使用者自定義postcss-sprites相關功能的,這個配置項一般情況下是不需要使用者操作的。如果遇到上文提到的配置項不能滿足的應用場景,使用者可以通過此API直接對postcss-sprites進行配置。
3. 技術選型
boi實現css sprites功能的技術選型如下:
- 構建核心: webpack;
- 資源編譯loader:postcss-loader
- sprites功能實現: postcss-sprites
4. 實現方案
上文第二節中提到了boi實現sprites功能的設計原則和工作模式。使用者在配置API中指定圖示檔案的路徑 ,boi以此路徑作為區分圖示與非圖示檔案的標識;並且支援識別解析度標識進行單獨編譯。
在配置postcss時,要注意以下幾點:
- 使用less/sass等css預編譯器時postcss的執行時機問題;
- 通過路徑進行圖示檔案合法性過濾;
- 以子目錄名稱和解析度標識為基礎的sprites圖片命名規則。
下文將分別介紹boi針對上述問題的具體解決方案。
4.1 與css預編譯器綜合使用
postcss並非只支援原始的css語法,同時也支援less和sass等預編譯語法。webpack根據loader的先後順序從右至左依次進行編譯,比如:
{
test: /\.less$/,
loader: 'css!less'
}
webpack對less檔案的編譯順序為:less->css->style。那麼在使用postcss時應該在哪一步執行呢?
雖然postcss支援less和sass,筆者也並不推薦直接使用postcss去編譯less和sass。一方面是因為postcss支援的預編譯器型別有限;另一方面即使postcss支援所有預編譯語言,考慮到使用者配置預編譯器的多樣性,如果對不同編譯器分派不同的postcss外掛勢必會造成boi框架體積的臃腫。
基於上述的考慮,postcss-loader的位置就已經確定了:在預編譯loader之後,css-loader之前。如下:
{
test: /\.less$/,
loader: 'css!postcss!less'
}
之所以在css-loader之前還有另外一個原因, postcss-sprites將雜湊的圖示合併成sprites之後首先要將生成的sprites圖片存放於一個臨時目錄內,然後在通過css-loader進行資源依賴解析並編譯到統一的dest目錄中。所以中間有一個暫存的過程,必須通過css-loader進行依賴解析才能得到最終的結果。
4.2 合法性過濾
boi通過路徑進行圖示合法性標識,首先根據使用者的配置建立驗證正則:
const REG_SPRITES_NAME = new RegExp([
path.posix.normalize(spritesConfig.dir).replace(/^\.*/, '').replace(/\//,
'\\/'),'\\/\.+\\.',
_.isArray(config.image.extType) ? '(' + config.image.extType.join('|') +')' : config.image.extType,
'\$'
].join(''), 'i');
然後配置postcss-sprites的filterBy
鉤子函式進行合法性驗證:
javascript
filterBy: (image) => {
if (!REG_SPRITES_NAME.test(image.url)) {
return Promise.reject();
}
return Promise.resolve();
}
4.3 分組規則
分組的依據有兩個:目錄名稱和解析度標識。首先需要根據使用者的配置建立目錄名稱驗證和解析度標識驗證的正則:
// 合法的雜湊圖path
const REG_SPRITES_PATH = new RegExp([
path.posix.normalize(spritesConfig.dir).replace(/^\.*/, '').replace(/\//, '\\/'),
'\\/(.*?)\\/.*'
].join(''), 'i');
// 合法的retina標識
const REG_SPRITES_RETINA = new RegExp([
'@(\\d+)x\\.',
_.isArray(config.image.extType) ? '(' + config.image.extType.join('|') +')' : config.image.extType,
].join(''), 'i');
然後通過postcss-sprites的groupBy
鉤子函式進行分組規則制定:
groupBy: (image) => {
let groups = null;
let groupName = undefined;
if (spritesConfig && spritesConfig.split) {
groups = REG_SPRITES_PATH.exec(image.url);
groupName = groups ? groups[1] : 'icons';
} else {
groupName = 'icons';
}
if (spritesConfig && spritesConfig.retina) {
image.retina = true;
image.ratio = 1;
let ratio = REG_SPRITES_RETINA.exec(image.url);
if (ratio) {
ratio = ratio[1];
while (ratio > 10) {
ratio = ratio / 10;
}
image.ratio = ratio;
image.groups = image.groups.filter((group) => {
return ('@' + ratio + 'x') !== group;
});
groupName += '@' + ratio + 'x';
}
}
return Promise.resolve(groupName);
}
上述程式碼包括以下邏輯:
* 如果使用者配置split:true
,boi會對子目錄進行正則驗證,如果存在子目錄將會單獨分組;若不存子目錄子預設分組名稱為'icons'
;
* 如果使用者配置retina:true
,boi會驗證圖示檔名是否包含解析度標識,如果存在則將groupName
加上類似'@2x'
的字尾。
各位可能注意到上述程式碼中以下的部分比較怪異:
image.groups = image.groups.filter((group) => {
return ('@' + ratio + 'x') !== group;
});
postcss-sprites識別到圖示存在解析度標識會生成單獨的分組名稱,如果不進行上述過濾的話,最終生成的sprites圖片名稱類似sprites.@2x.icons.png
。以上過濾是為了將@2x
分組刪除,以便編譯後的檔名更具語義化,比如sprites.icons@2x.png
。
5. 開原始碼
各位可以結合原始碼/lib/config/genConfig/mp/style.js理解本文的內容。
相關文章
- 手把手教你如何使用webpack 生成css spritesWebCSS
- css38 CSS Image SpritesCSSS3
- webpack css壓縮方案WebCSS
- 基於webpack搭建前端工程解決方案探索Web前端
- 基於webpack的幾種靜態資源的引入方案Web
- 基於css3動畫實現的旅行的小車CSSS3動畫
- 基於Seata探尋分散式事務的實現方案分散式
- 基於webpack的前端工程化開發解決方案探索(三):webpack-dev-serverWeb前端devServer
- 談談CSS Sprites技術及其優化CSS優化
- 基於 React.js 和 Node.js 的 SSR 實現方案ReactNode.js
- 基於webpack4.x專案實戰Web
- 基於rsync實現海量檔案高速傳輸的解決方案
- 基於jquery實現的ExceljQueryExcel
- 基於JVMTI的Agent實現JVM
- 基於 Webpack5 Module Federation 的業務解耦實踐Web解耦
- CSS預編譯與PostCSS以及Webpack構建CSS綜合方案CSS編譯Web
- webpack基礎–css相關處理WebCSS
- 基於webpack工程化的思考Web
- 基於CSS3實現淡入(fadeIn)淡出(fadeOut)效果CSSS3
- 關鍵 CSS 和 Webpack : 減少阻塞渲染的 CSS 的自動化解決方案CSSWeb
- 基於vue2.0實現的vivo移動端商城(vue+vuex+vue-ruoter+webpack)VueWeb
- 影片直播系統解決方案—是基於聲網SDK實現的
- 技術分享| 基於 Etcd 的分散式鎖實現原理及方案分散式
- 基於Pandas+ECharts的金融大資料視覺化實現方案Echarts大資料視覺化
- 基於多Engine、Navigator2.0實現混合棧管理方案實踐
- Webpack 轉譯 Typescript 現有方案WebTypeScript
- 基於Masstransit實現Eventbus的功能
- 基於 SplPriorityQueue 實現的排序方法排序
- 基於Python的Akka實現Python
- 實現基於角色的授權
- 基於webpack4[.3+]構建可預測的持久化快取方案Web持久化快取
- 基於webpack構建的angular 1.x 工程(一)webpack篇WebAngular
- 基於策略的管理方案
- 翻譯 | 關鍵CSS和Webpack: 減少阻塞渲染的CSS的自動化解決方案CSSWeb
- 基於 webpack 的前後端分離開發環境實踐Web後端開發環境
- 基於Retrofit2實現的LycheeHttpHTTP
- 基於Vue的簡易MVVM實現VueMVVM
- 基於Java Instrument的Agent實現Java