今年三月我接手過一個同事留下的郵件範本。檔案是建置產物,整個 body 裡幾萬個字元全擠在一行,編輯器裡要橫向捲四十多屏才能看到一個閉合標籤。我要改的東西很小:某個 div 上的一個 class 名。結果找了將近二十分鐘,中間還誤刪過一段被吞進同一行的註解。那天之後我給自己定了條規矩——拿到壓縮過的原始碼,第一件事不是讀,是先把它攤開;而反過來,要上線的頁面我捨不得把結構縮排留著不動。
美化和壓縮不是同一個動作的兩面
很多人把這兩個方向理解成「加換行」和「刪換行」,好像來回做就能回到原樣。實際上只有單向是可逆的:美化在標籤嵌套關係上重新插入換行和縮排,壓縮把所有空白和換行刪掉。壓縮後的檔案裡沒有留下「原本在哪裡換行、原來縮排幾個空格」這些資訊,你再美化一次,得到的是工具按嵌套層級重新排的版本,而不是原作者手寫的那份。
這件事在版本控制裡很要命。假設倉庫裡存的是壓縮版,某次你把它攤開提交,diff 會把整份檔案當成「一行刪除、幾百行新增」,誰也無法審。更常見的是另一種:前一個人用 2 空格,你用 4 空格重新格式化一遍再提交,於是每一行都被標記成改動,真正那兩行改了什麼完全被淹沒。我做過的處理是——倉庫裡統一儲存一種形態(多數時候是美化版),壓縮只發生在建置產物裡,不進倉庫;並且格式化時先選定縮排寬度,之後不隨意切換。
<div class="wrap"><p>第一段</p><p>第二段</p><ul><li>條目</li></ul></div>
攤開以後是下面這樣。我關心的是層級一眼能看出來,閉合標籤和開標籤在同一個縮排深度:
<div class="wrap">
<p>第一段</p>
<p>第二段</p>
<ul>
<li>條目</li>
</ul>
</div>
2 空格、4 空格還是 Tab,我的選擇依據
用 HTML 美化壓縮 時可以先選縮排方式,三個選項是 2 空格、4 空格、Tab。這個選擇本身沒有優劣,只有「和別人一致」的價值。我一般按嵌套深度來估:如果頁面結構經常到第四第五層,2 空格在窄編輯器裡不會把程式碼擠到螢幕右邊,4 空格則看得更清楚。定下來之後有個可核對的規律——同一段結構換縮排寬度,每層的字元數是可以算的。
| 標籤嵌套層級 | 2 空格縮排的前導空格數 | 4 空格縮排的前導空格數 | Tab 縮排 |
|---|---|---|---|
| 第 1 層 | 0 | 0 | 0 個 |
| 第 2 層 | 2 | 4 | 1 個 |
| 第 3 層 | 4 | 8 | 2 個 |
| 第 4 層 | 6 | 12 | 3 個 |
| 第 5 層 | 8 | 16 | 4 個 |
之所以我會專門去數這個:一旦從 2 空格切到 4 空格,第 5 層的行前面就多了 8 個空格,那一行的內容變了,diff 必然認為它被改過。反過來,如果兩個人一個用 Tab 一個用空格,同一個檔案在兩種編輯器設定下看起來「錯位」,其實是顯示寬度不同,不是程式碼不同。
壓縮刪掉的那些空白,有一部分是渲染要用的
HTML 解析時會把原始碼裡連續的空白和換行摺疊成一個空格。所以平時標籤之間換行、縮排,頁面上看不出差別,這是美化的安全邊界。但一旦涉及行內元素,那個被摺疊出來的空格就存在了:兩個用空格分隔的行內區塊之間,原始碼裡換成回車,渲染出來通常仍然留著一個空格的寬度;靠把字級設成 0 來消掉間隙的寫法,一旦空白處理發生變化,間距就跟著變。
更直接的是 pre 和 textarea 這類內容:裡面的換行和空格本身就是要顯示的東西,不能當成「格式雜訊」刪掉。HTML 美化壓縮 的壓縮模式對 pre、textarea 這類標籤的內容是保留的,我自己壓完也還是會 grep 一遍這兩處,確認程式碼範例、簽名框沒被壓平。
郵件範本是我最不願意隨手格式化的地方
回到開頭那份範本。郵件的 HTML 和網頁不是一套脾氣:各家郵件用戶端對空白的處理不一致,行內元素之間的換行在某些用戶端會真的變成一個空格寬度;表格儲存格 display 設成行內區塊來排版時,原始碼裡加個換行就可能把版面頂歪。所以我處理郵件範本的順序是:先看是否需要攤開閱讀,攤開只在「要讀懂結構」那一步用,改完再壓回去提交;如果這個檔案本身是要上線的範本,我不會把美化後的版本直接塞進寄送流程,而是先在自己這裡寄一封測試郵件,看那幾處依賴空白的間距有沒有變化。
另一條經驗是別把註解留在壓縮後的行裡。壓縮之後一段註解可能把緊跟的程式碼吞掉——這是我那天誤刪註解之後才想明白的,範本裡我用註解標記可編輯區域,壓完必須確認這些標記還在。
什麼時候美化,什麼時候壓縮
| 我遇到的情形 | 方向 | 理由 |
|---|---|---|
| 建置產物、線上抓下來的原始碼,全在一行 | 美化 | 要讀結構、要定位某個標籤 |
| 手寫的 HTML 要發布,體積敏感 | 壓縮 | 去掉換行與縮排,減少傳輸字元 |
| 要提交進倉庫、要別人 review | 美化 | diff 只顯示真正改動的行 |
| 郵件範本、含程式碼範例的頁面 | 美化後檢查、壓縮前檢查 | 空白參與渲染,pre、textarea 內容不能動 |
| 排查標籤沒閉合、層級混亂 | 美化 | 靠縮排深度發現錯位的閉合標籤 |
壓縮這件事我會看工具給出的原始字元數、結果字元數和壓縮率,但我不把它當效能結論用——一次壓縮省的往往只是幾百到幾千個空白字元,頁面快不快主要不是由這點空白決定的。這幾個數對我真正的用處是「量級感」:壓縮率異常高說明原來有大量空白,壓縮率幾乎為 0 說明這檔案本來就是壓縮過的,再壓一次只是白做。
幾件常被問到的
Q:美化會不會改變頁面顯示效果?
絕大多數情況下不會,因為標籤之間的換行會被摺疊成空白,瀏覽器忽略多餘空白。會出問題的地方是 pre、textarea,以及依賴行內元素間距的寫法,這兩類要單獨看。
Q:壓縮以後出問題,能還原回去嗎?
不能精確還原。原來的換行位置和縮排寬度沒有被儲存,壓縮後再美化得到的是按層級重排的版本。所以我從不在唯一一份副本上做壓縮。
Q:接手一行式的檔案,我該整份攤開還是只攤片段?
整份攤開更省事,因為讀結構要看全局;但要提交回去時,我會用壓縮版覆蓋,只把改動的那幾處複製過去,避免格式化波及全檔案。
Q:Tab 和空格哪個「更規範」?
沒有哪個更規範,只有一個專案裡只能用一種。Tab 的問題是顯示寬度取決於編輯器設定,同一份檔案兩個人看到的對齊不一樣;空格則永遠一致。要混用時我傾向於統一轉成空格。
Q:CSS 和 HTML 分在兩個檔案裡,要不要一起處理?
我不會混著做。樣式表的換行規則、影響面和標籤結構不同,我一般把樣式那邊單獨走一遍,改完先看樣式有沒有塌陷,再動 HTML。
那個一行的郵件範本最後是改完了,我用的時間比預期長,不是因為找不到 class,而是因為攤開以後我發現結構本身有兩處閉合標籤位置不對——壓縮的版本把這些都藏在一行裡看不出來。現在我的判斷標準是:要讀懂、要審、要 debug,一律美化;要發布、要減體積,才壓縮;而只要檔案是別人要接著改的,倉庫裡那份就必須是可讀的形態,壓縮留在建置裡。