一、排完版上線,懸停態集體失效
壓了好幾年沒人動過的樣式表,接手後的第一件事通常是排一遍版。243 字元的輸入美化出來是 291 字元、26 行,每條屬性獨佔一行,看著比原來舒服。上線當天回饋就到:按鈕沒有懸停態,列表項的前綴圖示也不見了。
逐行比對輸出,根因落在 .btn: hover。原始檔案寫的是 .btn:hover,冒號後憑空多出一個空格,選擇器當場失配;同一段的 li::before 出來是 li: : before。只把 .btn:hover{opacity:0.8}li::before{content:""}a:focus{outline:none} 丟進 CSS 美化壓縮,66 字元變成 90 字元、12 行,三個偽類沒有一個倖免——那頁首屏預填的樣例本來就帶著這條偽類,點一次美化就能復現。
二、美化只認字元,不認冒號的身份
CSS 裡的冒號至少有四種身份:偽類與偽元素、屬性與值之間、媒體特徵裡、字串內容裡。美化對四種一視同仁,看見冒號就補一個空格:補在屬性上是可讀性,補在媒體條件裡碰巧仍然合法,補在偽類上是語法事故,補在引號裡直接改了內容。69 字元的 @media (max-width:600px){.a{color:red}.b{padding:calc(100% - 4px) 0}} 美化成 87 字元、11 行,條件冒號被補上空格、左花括號後緊跟著空行。
JS 美化壓縮 那篇的破壞點在分號與換行,識別符號與冒號之間沒有語義綁定;CSS 的偽類偏偏要靠這個緊貼的冒號活著。
圖 1:偽類樣例美化前後的字元與行數
三、縮排那一欄,三檔輸出逐位元組相同
沒有縮排,第一個反應是去動「縮排方式」,那裡確實擺著 2 空格、4 空格、Tab 三檔。可同一段樣例在三檔下都是 291 字元、26 行、每行頂格在 0 列,逐位元組沒有差別:字元迴圈裡算過縮排,隨後一次逐行 trim 全抹掉,收尾再用恆為 0 的層級終值補回去。切到壓縮檔時這一欄只是變淡(實測 opacity 0.5),下拉選單也沒有被停用,改完照樣什麼都不發生。
四、真正保守的是壓縮:calc 的空格它沒動
兩樁先入為主都不成立。一是 calc():.box{width:calc(100% - 20px);padding:calc( 10px - 5px )} 60 字元壓到 56 字元,減號兩側的空格一個沒丟,多寫的會被歸一;反方向也不替你補,無效的 calc(100%-20px) 27 字元壓完一字未改。
二是 IE hack:.ie6{*zoom:1;_height:1%;width:10px} 35 字元壓完原樣,前導的 * 與 _ 沒被當成非法宣告吞掉。!important 同理,red !important 與 none!important 原樣混排出來,47 字元壓完仍是 47 字元。
「去除所有空白」同樣不成立:壓縮只刪花括號、冒號、逗號鄰邊的空白與換行,唯一改動可見內容的是字串,21 字元的 .s{content:"x, y; z"} 壓成 19 字元。
五、/*! */ 沒有特權,去留只在那一個勾選上
打包工具普遍把 /*! */ 當作保留註解,這一版沒有特例:它與普通註解走同一個比對分支,去留只由那個預設勾選決定。/*! keep me: v1.0; license */ 配一條普通註解和 body{color:red} 共 64 字元,取消勾選後壓到 15 字元、少了 76.6%,授權標頭跟著消失;勾著壓是 61 字元、只降 4.7%,註解內部照樣被改寫,keep me: v1.0 成了 keep me:v1.0。這跟 SQL 美化 那篇的「註解去哪了」是同一類口徑:勾選框只決定有無。
六、字串和 url() 裡的分號,被當成宣告分隔符
美化的第二個破壞點在最不該斷的地方。56 字元的 .i{background:url("data:image/png;base64,iVBORw0KGgo=")} 美化成 65 字元:base64 那串在分號處被從中斷開換行,data: 後面也被補出空格。那 49 字元的通訊協定位址也不放過,出來是 url(https: //www.uutools.com/a.png)。內嵌圖示經過美化,不是排版變差,是整筆資源被丟掉。
七、美化不冪等,壓縮冪等
貼回輸出再跑一遍,字元數還會漲:預設樣例第一遍 291、第二遍 310,多出的 19 正是輸出裡的冒號數,第三遍仍是 310;calc 例子 64 到 67 再 67,@media 例子 87 到 93 再 93。壓縮的四組樣例首跑就穩定,243 字元連壓三遍都是 243。
圖 2:縮排三檔與美化兩次迭代的輸出
八、體積收益要另算,統計條也不是位元組表
2000 條規則、91780 字元的樣式表壓完是 89780 字元,只降 2.2%、耗時 0.8 ms;同一份美化到 107779 字元、10000 行,漲 17.4%、耗時 211.6 ms。輸入本就沒什麼多餘空白時,壓縮檔就只有這個量級。
統計條還要換算口徑:數的是 UTF-16 碼元不是位元組,含中文的樣例報 25 字元、UTF-8 實際 33 位元組。美化檔的「變化」是淨增,那 243 字元顯示 +19.8% 並標紅,零變化寫成 +0%,異常時結果 0 字元會渲染成綠色的 -100.0%。
| 現象 | 先查哪一處 | 實測數字 |
|---|---|---|
| 懸停態失效 | 選擇器冒號後多出的空格 | 66 字元變 90 字元 |
| 縮排改了沒反應 | 三檔是否真的改變輸出 | 291 字元、26 行、頂格 |
| 內嵌圖示消失 | data URI 的分號是否被斷行 | 56 字元撐成 65 字元 |
| calc 減法報錯 | 減號空格原本寫沒寫 | 60 壓到 56、27 一字未改 |
Q:怎麼第一時間找出被劈開的偽類?
盯住冒號後面那個多出來的空格:.btn:hover 變 .btn: hover、li::before 變 li: : before。這一版不檢驗合法性,非法輸入照樣走完美化也不提示,行數從 1 漲到 26 不等於結果可用。
Q:壓縮會把 calc 的空格吃掉嗎?
不會:60 字元壓到 56 字元,calc(100% - 20px) 原樣;它也不替你補,無效的 calc(100%-20px) 壓完仍是 27 字元。IE hack 與 !important 的空格同樣不動,會出事的是美化檔。
Q:想要 4 空格縮排,這一頁給得了嗎?
給不了:三檔對同一段樣例都輸出 291 字元、26 行且全部頂格。這一檔只用來看結構,上線前按偽類與 data URI 這兩處自查。