原文地址:http://www.vinqon.com/codeblog/?detail/11106
前幾天突然想寫一個css的js壓縮工具,於是這兩天研究了一下幾個js、css的壓縮工具並且理清楚了一些概念和原理,下面總結一下。
幾個基本概念
在網站部署前,我們往往要對前端的代碼進行發布,我這里說的“發布”,指的就是精簡、混淆、壓縮、編譯或者還有其他的操作,有些操作很相似,但每個操作的都有其中的意義。
精簡(minify)
對前端代碼精簡的目的很明顯,就是減少代碼體積,減小網絡傳輸時間,提高頁面響應。
而具體到如何精簡,其實也很簡單,下面是其中的一些辦法:
1.刪除代碼注釋
2.刪除無意義或者多余的空白(如空格,制表符,回車,換行)
3.刪除可以省略的符號(如css最后一條規則后面的分號,js塊內最后的一條語句的分號)
4.縮短語句(如果css的簡寫,html中disabled='disabled' 改成disabled , js中縮短局部變量)
對於精簡這個功能,大部分工具都基本實現了上面的方法,包括有yuicompresser,closure complie,jsmin,packer.
混淆(obfuscation)
混淆這個功能主要針對Javascript代碼,它的目的是減低代碼的可讀性,防止被追蹤出程序邏輯。
事實上,對代碼精簡,壓縮,編碼都有混淆的效果。
首先,上面提到精簡的辦法中,刪除注釋,刪除縮進(空格,制表符,換行),縮短局部變量都可以有效減低程序可讀性。除了刪除縮進可以用過js格式化/美化工具還原,其它兩個步驟都是不可逆的。
其次,通過編碼混淆代碼,這篇文章
《javascript常用混淆方法》里面介紹了很多牛X的編碼加密方法。但是這些方法有個明顯缺點,增加代碼體積,而且編碼加密都是可逆的。
最后,通過壓縮的辦法,當然,也是可逆的,下面我們詳細探討一下。
壓縮(compress)
壓縮這一個說法很常被用來概括前面這三種操作,其實上,真正實現壓縮的我目前只看到一種方案:
packer的base64編碼壓縮.
這里可以先看一個簡單的例子:
壓縮前代碼:
1 |
document.getElemntById( "header" ).innerHTML= "This is the header" ; |
壓縮后代碼:
1 |
eval( function (p,a,c,k,e,r){e=String; if (! '' .replace(/^/,String)){ while (c--)r[c]=k[c]||c;k=[ function (e){ return r[e]}];e= function (){ return '\\w+' };c=1}; while (c--) if (k[c])p=p.replace( new RegExp( '\\b' +e(c)+ '\\b' , 'g' ),k[c]); return p}( '1.2("0").3="4 5 6 0";' ,7,7, 'header|document|getElemntById|innerHTML|This|is|the' .split( '|' ),0,{})) |
壓縮后的代碼很惡心,但是認真研究可以發現里面只有三個東西:壓縮后原文,字符表,解壓器。
packer的base64編碼的壓縮率很高,精簡后代碼依然可以減少50%體積以上,因為帶有解壓器,和字符表,上面的例子沒有體現壓縮效果,一般來說,越長的代碼壓縮率更高。
如果對里面構造有興趣的可以直接研究packer的源代碼,算法都是用js寫的,另外,還可以看看這篇文章
《Packer,你對我的JS做了什么!》.
很多地方都把packer這個功能稱為混淆,當然,這的確有混淆的效果,上面也提到。但是,從算法上看,packer base64 encode是一個字典壓縮算法,故這里歸類為壓縮。
另外,需要提醒的是,雖然壓縮有混淆效果,但是過程依然可逆,而且解壓器和字符表明擺在那里,只要把eval四個字母改成alert就可以看到壓縮前的代碼。
因為用了邪惡的eval,packer后的代碼性能會減低...很多,另外,解壓的過程也會消耗一點時間。
特別要注意的是,如果服務器有gzip功能,就不必也不應用packer base64 encode來壓縮。因為packer base64 encode壓縮加gzip壓縮后的體積比源代碼只用gzip壓縮還要大。
原因也可想而知,我們用js進行了一次低效壓縮,gzip壓縮的空間就大大減低了。
編譯(compile)
說到編譯,不得不先提到
google closure compile,它和其他工具不同的是,除了精簡功能,它還可以對Javascript代碼進行優化。
gcc的高級模式會對Javascript進行語義分析,然后會進行刪除無用代碼,刪除沒有使用的變量,優化邏輯關系等比較激進的優化。
雖然編譯前后都是Javascript代碼,但是這個過程已經算得上實際意義上的編譯了。
coffeescript是一個類ruby的語言,書寫起來更加簡潔和優美,coffeescript可以完全編譯成同效的Javascript。
而less和sass是在css上進行語法擴展,在css上實現了變量,作用域,函數之類的功能。
github上有一份
list記錄了所有的這類東東,有興趣可以去研究一下。
談到這些,我們仿佛感覺到了前端發展的一個趨勢,我們原來寫的html,css,Javascript已經開始變成了一個“中間語言”,而且越來越多的團隊也有了自己的一套前端編譯系統更加彰顯了這個趨勢。
李松峰老師博客里面有兩篇文章也對這個話題進行了討論:
《JavaScript是Web的匯編語言(一):語義Web已死!》
《JavaScript是Web的匯編語言(二):瘋狂,亦或只是精神錯亂?》
這是一個有趣的話題,上面很多內容只能貼個鏈接了,也許下次應該單獨做一篇文章來慢慢討論一下。
CSSPacker
這是我研究幾個壓縮工具后,自己突發奇想寫的一個小玩具。
簡單介紹一下,上面提過packer,這是一個真正意義上用Javascript實現的壓縮解壓方案(當然,相對於客戶端的一些壓縮軟件還差很遠),它本來是用來壓縮Javascript的,我把它移植一下,折騰出這個csspacker支持壓縮css。
packer本來有三個功能:精簡代碼,縮短局部變量,base64編碼壓縮,下面簡單介紹一下:
- packer原本精簡代碼是根據Javascript語法精簡的,我把精簡代碼部分重寫了,以適應css語言;
- 縮短局部變量沒用,直接刪掉這個操作;
- base64編碼壓縮適用於任何文本,可以直接保留。但是解壓的操作要修改一下,原來Javascript直接把解壓后的字符串eval一下就好了,css比比較蛋疼,要新建一個style節點,把解壓后css文本插進去。
另外一個比較重大的問題是,圖片路徑問題。css文件上使用的相對路徑是相對於css的位置的,但是js是相對於頁面的位置的,所以,如果css含有相對路徑,要輸入css所在網絡位置,它會自動把里面的相對路徑轉換為URL。
路徑問題讓這個packer不那么方便了,我還考慮其他方案。目前的另外一個想法是,fork一個分支,把csspacker功能弄進去。
好吧,有興趣的歡迎去調戲調戲一下
csspacker。