摘要:單元測試秉承測試驅(qū)動開發(fā)的開發(fā)理念,單元測試的任務(wù)是必不可少的。維護一份按照建議,也將更新歷史等數(shù)據(jù)放在了一個名為文件上,并采用語義化的版本號。
本文原始來源:http://devework.com/postcss-p...。轉(zhuǎn)載請?zhí)峁┰紒碓矗x謝!
前陣子為了滿足工作上的一個需求開發(fā)了一個PostCSS 插件,后來也將這個插件提交給PostCSS 官方并得到認可。在這篇文章中筆者將記錄開發(fā)過程中遇到的一些問題,且斗膽將之稱為“最佳實踐”,希望對有興趣嘗試PostCSS 插件開發(fā)的您有所幫助。
簡介篇 開發(fā)成果展示首先先上成果:https://github.com/Jeff2Ma/postcss-lazyimagecss (歡迎給個star 哦~)
postcss-lazyimagecss 插件實現(xiàn)的功能是為 CSS 中的background-image 對應(yīng)的圖片自動添加width 與height 屬性。簡單形象化的效果展示如下:
/* Input ./src/index.css */ .icon-close { background-image: url(../slice/icon-close.png); //icon-close.png - 16x16 } .icon-new { background-image: url(../slice/icon-new@2x.png); //icon-new@2x.png - 16x16 } /* Output ./dist/index.css */ .icon-close { background-image: url(../slice/icon-close.png); width: 16px; height: 16px; } .icon-new { background-image: url(../slice/icon-new@2x.png); width: 8px; height: 8px; background-size: 8px 8px; }為什么重復(fù)造一個輪子
開發(fā)這個PostCSS 插件的起因是原先工作流中使用的gulp-lazyimagecss 插件在加入SourceMap 功能后運行不正常,多次嘗試修復(fù)均告失敗。后來筆者想到,PostCSS 本身天然支持SourceMap,那如果將這個功能開發(fā)成PostCSS 插件豈不是也完美支持SourceMap 了?
于是筆者便在gulp-lazyimagecss 的基礎(chǔ)上開發(fā)出了這么一個輪子。在此也感謝原開發(fā)者hzlzh 與littledu 的大力幫助與支持。對筆者而言,更像是站在巨人的肩膀上開發(fā)出來這個插件。
準(zhǔn)備篇 原理關(guān)于PostCSS 的原理,官方有這么一個圖:
簡單解釋,PostCSS 會將上一步傳入的 CSS 按照一條條樣式規(guī)則(rule)進行解析(Parser)得到一個節(jié)點樹;然后借助一系列插件在節(jié)點樹上進行轉(zhuǎn)換操作,并最終通過Stringifier 進行拼接。source map則記錄了前后的對應(yīng)關(guān)系。
當(dāng)然,在實際的開發(fā)中其實不必深究原理,最重要的是看其提供的API 來調(diào)用即可。
工欲善其事必先利其器開發(fā)一個PostCSS 插件也是開發(fā)一個Node 模塊,想到后面要發(fā)布到NPM 跟PostCSS 官方,那么作為一個開源項目的可維護性、可擴展性也是很重要的。因此在進入正式的開發(fā)之前,筆者做了如下的工作:
1、配置 editorconfigeditorconfig 作為一套統(tǒng)一代碼格式的解決方案,已經(jīng)在團隊不少項目中使用,其很好地解決了因為團隊協(xié)作中因不同代碼編輯器及不同的代碼習(xí)慣產(chǎn)生的潛在風(fēng)險。這里是最終的配置文件。
2、基礎(chǔ)的開發(fā)工作流在整個開發(fā)插件過程前,筆者根據(jù)需求配了個基于Gulp 的開發(fā)工作流,主要配備如下功能(任務(wù)):
代碼質(zhì)量監(jiān)控ESlint
優(yōu)秀的開源代碼必然是有著標(biāo)準(zhǔn)化的JavaScript 代碼風(fēng)格,因此在整個開發(fā)過程中借助ESlint 來嚴(yán)格控制自己的代碼質(zhì)量。這里是本項目的ESlint 配置文件。
var eslint = require("gulp-eslint"); gulp.task("lint", function () { return gulp.src(files) .pipe(eslint()) .pipe(eslint.format()) .pipe(eslint.failAfterError()); });
基礎(chǔ)的CSS 轉(zhuǎn)換
這個任務(wù)其實就是本PostCSS 插件實現(xiàn)的功能,之所以在開發(fā)過程中也要配置是為了下面的單元測試任務(wù)的調(diào)用。
單元測試
秉承TDD(測試驅(qū)動開發(fā))的開發(fā)理念,單元測試的任務(wù)是必不可少的。
gulp.task("test", function () { return gulp.src("test/*.js", { read: false }) .pipe(mocha({ timeout: 1000000 })); });
watch 任務(wù)
gulp watch 任務(wù)是上面任務(wù)的集體調(diào)用,實現(xiàn)的功能是在開發(fā)過程中,每當(dāng)按下保存鍵就自動運行ESlint 代碼質(zhì)量監(jiān)控及進行單元測試任務(wù)。有效保障了整個開發(fā)過程中的質(zhì)量。
3、托管到 Github 并配置Travis-ci 持續(xù)集成整個開發(fā)過程使用Github 托管源代碼并通過Travis-ci 持續(xù)集成。PostCSS 官方建議最低需要支持Node.js 0.12 的版本,所以整個Travis-ci 的配置文件如下:
sudo: false language: node_js node_js: - "0.12" - "4" - "5" - "6" - "stable" before_script: - npm install -g mocha
相應(yīng)的在Travis-ci 管理后臺配置push 操作作為動作鉤子,這樣每次有commit push 上去就會自動進行測試并在log 上展示出結(jié)果:
開發(fā)篇 從最小開始一個PostCSS 插件最基礎(chǔ)的構(gòu)成如下:
var postcss = require("postcss"); module.exports = postcss.plugin("PLUGIN_NAME", function (opts) { opts = opts || {}; // 傳入配置相關(guān)的代碼 return function (root, result) { // 轉(zhuǎn)化CSS 的功能代碼 }; });
然后就是不同的需求情況來決定是否引入第三方模塊,是否有額外配置項,然后在包含root,result 的匿名函數(shù)中進行最為核心的轉(zhuǎn)換代碼功能編寫。
root(css),rule, nodes, decl, prop, value如本文一開頭的PostCSS 原理解析,CSS 文件在經(jīng)過Parser 轉(zhuǎn)化后的遞歸單個子單位可以歸為如下:
root(css) :也是整個CSS 代碼段,包含多個rule。
rule: 包含一個CSS class 范圍內(nèi)的代碼段
.icon-close { background-image: url(../slice/icon-close.png); font-size: 14px; }
nodes: 代指rule 中{}中間的多個 decl 部分。
decl: 單行CSS ,即有屬性與值的部分
background-image: url(../slice/icon-close.png);
prop,value
相應(yīng)的CSS 屬性與值,如上面 prop為background-image,value為url(../slice/icon-close.png)
偽代碼實現(xiàn)根據(jù)postcss-lazyimagecss 插件要實現(xiàn)的內(nèi)容,涉及到CSS 轉(zhuǎn)化的有如下情景:
增加 width 屬性及獲取到真實值
增加 height 屬性及獲取到真實值
二倍圖情況下增加 background-size 屬性并計算出值
結(jié)合上一小節(jié),可以先寫出如下簡潔版?zhèn)未a:
css.walkRules(function (rule) { // 遍歷所有 CSS rule.walkDecls(/^background(-image)?$/, function (decl) { // 遍歷每條 CSS 規(guī)則,找出目標(biāo) rule // 一些傳參等代碼 nodes.forEach(function (node) { // 遍歷其它 rules ... }); ... // 其它代碼實現(xiàn),如找出圖片真實width 等 rule.append({prop: "width", value: valueWidth}); // 在該decl 追加width 屬性 }); });細化代碼
接下來就是考慮不同情況增加一些邏輯判斷:
判斷url 中是否為網(wǎng)絡(luò)地址或Base64 的data 形式:imageRegex.exec(value).indexOf("data:")
判斷該rule 下是否已經(jīng)有width 等屬性,在nodes 循環(huán)中:
if (node.prop === "width") { CSSWidth = true; }
判斷2倍圖圖片寬高是否為偶數(shù):
value.indexOf("@2x") > -1 && (info.width % 2 !== 0 || info.height % 2 !== 0
再具體的不再詳述,完整的代碼實現(xiàn)可以見這里。
難點解決postcss-lazyimagecss 插件使用了第三方模塊fast-image-size 來進行圖片數(shù)據(jù)(文件類型、寬高)的獲取,大大提高了開發(fā)效率。然而在尋找圖片絕對路徑的這個實現(xiàn)上還是繞了不少彎路。
插件的思路是需要獲取CSS 中background-image屬性對應(yīng)值中url()的相對圖片路徑,以此來找到圖片的絕對路徑,之后用fast-image-size 模塊獲取到相應(yīng)的數(shù)據(jù)。
然而在一些特殊情況并不能準(zhǔn)確找到絕對路徑。
在CSS 預(yù)處理器(如Less 或Sass)中,常借助@import來組件化CSS 代碼,然而在層層@import 下路徑可能已經(jīng)被產(chǎn)生變化。舉個例子,有如下結(jié)構(gòu):
. ├── css ├── html ├── img │?? └── icon.png └── scss ├── index.scss └── second └── _import.scss
上面的文件樹中展示的 scss/index.scss @import 了二級目錄下的 _import.scss,在_import.scss中有一個類需要用到img/icon.png。
因為同時也配置了local server(以上面的./目錄作為server 的根目錄),那么在 url 中可以寫成../../img/icon.png 或../img/icon.png,甚至寫成../../../../../img/icon.png(N個../)——這些情況下Sass 編譯后的index.css 均可正常讀取。原因相信也知道,因為root url的存在,上面的路徑寫法均相當(dāng)于/img/icon.png。
在這個情況下于用戶而言是感受不到錯誤的,但在插件中可就找不到真實絕對路徑了。筆者對于這個情況是采用了如下方式進行解決:
借助Node.js 中的fs.existsSync 函數(shù)檢測絕對路徑對應(yīng)的文件是否存在。第一次為正常fs.existsSync,如果找到就跳出;如果沒有則先對路徑的字符串執(zhí)行replace("../", "");然后再次執(zhí)行fs.existsSync。如果兩次均沒有找到則在終端進行提示,但這種情況下并不會報錯破壞進程的運行。
function fixAbsolutePath(dir, relative) { // find the first time var absolute = path.resolve(dir, relative); // check if is a image file var reg = /.(jpg|jpeg|png|gif|svg|bmp)/i; if (!reg.test(absolute)) { pluginLog("Not a image file: ", absolute); return; } if (!fs.existsSync(absolute) && (relative.indexOf("../") > -1)) { relative = relative.replace("../", ""); // find the second time absolute = path.resolve(dir, relative); } return absolute; }
不敢說這是一種最好的處理方式,但至少是一種可行的處理方式。
單元測試單元測試上采用Mocha 測試工具, should.js 做斷言庫。在筆者看來,結(jié)合TDD 進行開發(fā),單元測試僅作為一種開發(fā)的輔助手段,規(guī)避開發(fā)過程中一些產(chǎn)生致命的報錯。本文不展開如何寫單元測試,具體實現(xiàn)可點擊這里。
優(yōu)化篇在Postcss 官方Github Repo,有一個Plugin Guidelines。對于其提倡的“Do one thing, and do it well” 深感認同,因此在基本完成插件功能后筆者又做了如下優(yōu)化工作。
更友好的log 提示官方其實是建議用內(nèi)置的result.warn來代替console.log或console.warn來展示log 信息(原因據(jù)說是一些PostCSS 處理器會忽略這類console log 輸出)。不過筆者嘗試后發(fā)現(xiàn)官方函數(shù)下提示的信息會非常長,后面采用了借助chalk 模塊封裝了console.log的形式增加了高亮態(tài)信息展示。
錦上添花 “找不到圖片文件”的場景處理用戶在寫CSS 代碼的時候,background-image 的url 可能會有如下情況:
輸入的是目錄
輸入的非圖片路徑
輸入了一半就保存了
根本就是瞎輸入
場景很多,但對于插件而言僅僅是能否找到與否的結(jié)果。在處理這些錯誤場景的情況下也給出的細分到“File does not exist” 或 “Not a image file”的情況,讓這類錯誤提醒更加友好一些。
提示二倍圖不正確如果用戶引用的二倍圖(類似xxx@2x.png)的寬度高度為非偶數(shù)的話,也會有相應(yīng)的提醒。
以上的報錯提示在實際運行效果如下:
英文版 READMEPostCSS 官方建議是README.md用英文寫,其余語種采用類似README.zh.md的方式。
維護一份 changelog按照建議,也將更新歷史等數(shù)據(jù)放在了一個名為CHANGELOG.md文件上,并采用語義化的版本號。
其它根據(jù)自己的開發(fā)習(xí)慣,在Github 上的Repo 也放置了一份LICENSE 文件。
發(fā)布篇 發(fā)布到NPM 官方發(fā)布到NPM 官方的步驟在這里就不再詳述。僅分享一個不錯的版本號增加方式(告別packup.json 的手動改版本數(shù)字)。
npm version patch => z+1 npm version minor => y+1 && z=0 npm version major => x+1 && y=0 && z=0
與上文所講的語義化的版本號相關(guān),vX.Y.Z(主版本號.次版本號.修訂號)三個選項分別對應(yīng)三部分的版本號,每次運行命令會導(dǎo)致相應(yīng)的版本號遞增一,同時子版本號清零。記得運行上面命令前先將文件變動提交到git 上去。
之后運行npm publish命令即可。
發(fā)布到PostCSS 官方Postcss 官方主頁上有個plugin list 文件展示了所有的第三方插件,提交的話Fork 一份然后在該文件增加自己的插件詳細然后提交合并,等作者允許即可。
發(fā)布到postcss.partpostcss.parts 是一個非官方的PostCSS 插件搜索平臺。提交自己插件可按照這個說明。其實本質(zhì)也是Fork 然后加信息在Pull request 的方式,在此不累述。
結(jié)束篇 效果在開發(fā)完postcss-lazyimagecss 插件后,筆者按照上面的發(fā)布方式提交了給官方。后面效果還不錯,PostCSS 作者也提了個star 跟issue。PostCSS 官方推特上的推薦也帶來了第一批Stargazers。
因為這個緣故,在第三屆中國CSS 大會上也有幸與PostCSS 作者ai 大神勾搭了下,并得到了大神贈送的俄羅斯巧克力。
思考在筆者看來,PostCSS 的作為一個CSS 轉(zhuǎn)換引擎,其不參與細分功能實現(xiàn)僅交于第三方插件的設(shè)計理念,讓其產(chǎn)生了一個非常的開放的生態(tài)。但對于個開放機制下的一些情況筆者并不是很贊同,如一些用中文寫CSS 的插件(當(dāng)然這個更多是for fun),一些自定義CSS 屬性如用size: 10px 2px 等代替width/height的插件——在筆者看來PostCSS 插件應(yīng)該更多在遵從CSS 標(biāo)準(zhǔn)語法的基礎(chǔ)上進行擴展。
但無論如何,還是挺佩作者開發(fā)出了這么個造福前端屆的工具;也因為認同作者,筆者寫了這篇文章為推廣PostCSS 做了一點微小的工作;也希望對看到文末的您有所幫助,積極參與到開源創(chuàng)作的事業(yè)中。
參考文章:
http://ai.github.io/postcss-way/
https://github.com/postcss/po...
https://css-tricks.com/want-m...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/86675.html
摘要:這里要介紹的是工作流中的一種很普遍的代碼加工流程正常的業(yè)務(wù)邏輯開發(fā)流程需要經(jīng)過預(yù)處理器如或,然后再經(jīng)過后處理器如進行深加工。 還未看的,可以點擊查看上兩篇文章喲:Webpack 最佳實踐總結(jié)(一)、Webpack 最佳實踐總結(jié)(二) 好了,這篇是第三篇,也是完結(jié)篇,我感覺這一篇是最亂的一篇,湊合著看吧,不會讓你失望的 整合 CSS 加工流 有時候,前端項目中除了 JavaScript ...
摘要:這里要介紹的是工作流中的一種很普遍的代碼加工流程正常的業(yè)務(wù)邏輯開發(fā)流程需要經(jīng)過預(yù)處理器如或,然后再經(jīng)過后處理器如進行深加工。 還未看的,可以點擊查看上兩篇文章喲:Webpack 最佳實踐總結(jié)(一)、Webpack 最佳實踐總結(jié)(二) 好了,這篇是第三篇,也是完結(jié)篇,我感覺這一篇是最亂的一篇,湊合著看吧,不會讓你失望的 整合 CSS 加工流 有時候,前端項目中除了 JavaScript ...
摘要:無論是早期工具,還是現(xiàn)在流行的配合這類構(gòu)建工具而產(chǎn)生的雪碧圖插件。 本文原文鏈接:https://devework.com/postcss-...,轉(zhuǎn)載請注明原始來源,謝謝! showImg(https://segmentfault.com/img/bVPmaC?w=1692&h=754); postcss-lazysprite 是一個基于PostCSS 開發(fā)的用于生成雪碧圖圖片及其C...
摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...
摘要:正在失業(yè)中的課多周刊第期我們的微信公眾號,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。是一種禍害譯本文淺談了在中關(guān)于的不好之處。淺談超時一運維的排查方式。 正在失業(yè)中的《課多周刊》(第3期) 我們的微信公眾號:fed-talk,更多精彩內(nèi)容皆在微信公眾號,歡迎關(guān)注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的...
閱讀 3969·2021-11-23 10:09
閱讀 1338·2021-11-23 09:51
閱讀 2939·2021-11-23 09:51
閱讀 1585·2021-09-07 09:59
閱讀 2354·2019-08-30 15:55
閱讀 2292·2019-08-30 15:55
閱讀 2949·2019-08-30 15:52
閱讀 2560·2019-08-26 17:04