前言
做Web前端開發,總是要考慮頁面的打開速度,如果文件數量越少、文件長度越小,就可以直接的提升網頁的訪問速度。
但在開發的時候,為了保證代碼的可讀性,我們寫的程序文件會很多而且很大,這樣就與部署的要求發生背離,通過UglifyJS2這個工具,我們可以在開發完成時,對代碼文件進行 合並、混淆、壓縮 等的操作,達到最優的訪問性能。
目錄
- UglifyJS介紹
- UglifyJS2介紹
- UglifyJS2安裝
- UglifyJS2命令操作
- UglifyJS2的API使用
1. UglifyJS介紹
開始UglifyJS2介紹之前,我們先要說一下UglifyJS。UglifyJS是UglifyJS2的前身,是一個Javascript開發的通用的語法分析、代碼壓縮、代碼優化的一個工具包。UglifyJS是基於Nodejs環境開發,支持CommonJS模塊系統的任意的Javascript平台。
UglifyJS的實現主要分為2部分:
- 生成JS代碼的抽象語法樹(AST),通過parse-js.js完成。
- 遍歷AST語法樹,做各種操作,比如自動縮進、縮短變量名、刪除塊括號{}、去空格、常量表達式、連續變量聲明、語塊合並、去掉無法訪問的代碼等,通過process.js完成。
2. UglifyJS2介紹
UglifyJS2是作者對UglifyJS的重寫,是完全的重寫,而不僅僅是升級。從UglifyJS2官司方網頁介紹看,UglifyJS2把整個的JS壓縮過程,做了更進一步的細化。
- parser,用於實現抽象語言法樹(AST)。
- the code generator, 通過AST生成JS代碼和source map。
- compressor, 用於JS代碼的壓縮。
- mangler, 用於減少局部變量的命名,用單字母替換。
- scope analyzer, 用來判斷 變量定義范圍和變量引用范圍的工具。
- tree walker, AST樹遍歷工具。
- tree transformer, AST樹轉換工具。
上述所有的功能代碼API是在6500行的左右,比其他的相同功能的開發包都要小。作者還提供了一個在線版本UglifyJS2的JS壓縮工具,http://lisperator.net/uglifyjs/,大家可以測試一下。
3. UglifyJS2安裝
系統環境:
- win7 64bit
- Nodejs:v0.10.5
- Npm:1.2.19
UglifyJS2的安裝非常簡單,和Nodejs的其他包一樣,全局安裝使用如下命令。
- npm install uglify-js -g
也可以通過github下載源代碼安裝。
- git clone git://github.com/mishoo/UglifyJS2.git
- cd UglifyJS2
我們在使用UglifyJS2的時候主要有2種方式,一種是通過命令行操作,對指定的JS文件進行壓縮;另一種是通過程序的API調用,對文件或內存中的JS代碼進行壓縮。下面我將分兩種情況進行介紹。
4. UglifyJS2命令操作
在全局安裝好UglifyJS2以后,我們就可以使用UglifyJS2的命令了。
打印uglifyjs命令行的幫助信息,會打出很長一段說明。
- D:\workspace\javascript\nodejs-uglifyJS2>uglifyjs -h
- D:\toolkit\nodejs\\node.exe D:\toolkit\nodejs\node_modules\uglify-js\bin\uglifyjs input1.js [input2.js ...] [options]
- Use a single dash to read input from the standard input.
- NOTE: by default there is no mangling/compression.
- Without [options] it will simply parse input files and dump the AST
- with whitespace and comments discarded. To achieve compression and
- mangling you need to use `-c` and `-m`.
- Options:
- --source-map Specify an output file where to generate source
- map. [string]
- --source-map-root The path to the original source to be included
- in the source map. [string]
- --source-map-url The path to the source map to be added in //#
- sourceMappingURL. Defaults to the value passed
- with --source-map. [string]
- --source-map-include-sources Pass this flag if you want to include the
- content of source files in the source map as
- sourcesContent property. [boolean]
- --in-source-map Input source map, useful if you're compressing
- JS that was generated from some other original
- code.
- --screw-ie8 Pass this flag if you don't care about full
- compliance with Internet Explorer 6-8 quirks
- (by default UglifyJS will try to be IE-proof).
- [boolean]
- --expr Parse a single expression, rather than a
- program (for parsing JSON) [boolean]
- -p, --prefix Skip prefix for original filenames that appear
- in source maps. For example -p 3 will drop 3
- directories from file names and ensure they are
- relative paths. You can also specify -p
- relative, which will make UglifyJS figure out
- itself the relative paths between original
- sources, the source map and the output file.
- [string]
- -o, --output Output file (default STDOUT).
- -b, --beautify Beautify output/specify output options.
- [string]
- -m, --mangle Mangle names/pass mangler options. [string]
- -r, --reserved Reserved names to exclude from mangling.
- -c, --compress Enable compressor/pass compressor options. Pass
- options like -c
- hoist_vars=false,if_return=false. Use -c with
- no argument to use the default compression
- options. [string]
- -d, --define Global definitions [string]
- -e, --enclose Embed everything in a big function, with a
- configurable parameter/argument list. [string]
- --comments Preserve copyright comments in the output. By
- default this works like Google Closure, keeping
- JSDoc-style comments that contain "@license" or
- "@preserve". You can optionally pass one of the
- following arguments to this flag:
- - "all" to keep all comments
- - a valid JS regexp (needs to start with a
- slash) to keep only comments that match.
- Note that currently not *all* comments can be
- kept when compression is on, because of dead
- code removal or cascading statements into
- sequences. [string]
- --preamble Preamble to prepend to the output. You can use
- this to insert a comment, for example for
- licensing information. This will not be
- parsed, but the source map will adjust for its
- presence.
- --stats Display operations run time on STDERR.
- [boolean]
- --acorn Use Acorn for parsing. [boolean]
- --spidermonkey Assume input files are SpiderMonkey AST format
- (as JSON). [boolean]
- --self Build itself (UglifyJS2) as a library (implies
- --wrap=UglifyJS --export-all) [boolean]
- --wrap Embed everything in a big function, making the
- “exports” and “global” variables available. You
- need to pass an argument to this option to
- specify the name that your module will take
- when included in, say, a browser. [string]
- --export-all Only used when --wrap, this tells UglifyJS to
- add code to automatically export all globals.
- [boolean]
- --lint Display some scope warnings [boolean]
- -v, --verbose Verbose [boolean]
- -V, --version Print version number and exit. [boolean]
- --noerr Don't throw an error for unknown options in -c,
- -b or -m. [boolean]
對命令參數進行解釋:
- –source-map [string],生成source map文件。
- –source-map-root [string], 指定生成source map的源文件位置。
- –source-map-url [string], 指定source map的網站訪問地址。
- –source-map-include-sources,設置源文件被包含到source map中。
- –in-source-map,自定義source map,用於其他工具生成的source map。
- –screw-ie8, 用於生成完全兼容IE6-8的代碼。
- –expr, 解析一個表達式或JSON。
- -p, –prefix [string], 跳過原始文件名的前綴部分,用於指定源文件、source map和輸出文件的相對路徑。
- -o, –output [string], 輸出到文件。
- -b, –beautify [string], 輸出帶格式化的文件。
- -m, –mangle [string], 輸出變量名替換后的文件。
- -r, –reserved [string], 保留變量名,排除mangle過程。
- -c, –compress [string], 輸出壓縮后的文件。
- -d, –define [string], 全局定義。
- -e, –enclose [string], 把所有代碼合並到一個函數中,並提供一個可配置的參數列表。
- –comments [string], 增加注釋參數,如@license、@preserve。
- –preamble [string], 增加注釋描述。
- –stats, 顯示運行狀態。
- –acorn, 用Acorn做解析。
- –spidermonkey, 解析SpiderMonkey格式的文件,如JSON。
- –self, 把UglifyJS2做為依賴庫一起打包。
- –wrap, 把所有代碼合並到一個函數中。
- –export-all, 和–wrap一起使用,自動輸出到全局環境。
- –lint, 顯示環境的異常信息。
- -v, –verbose, 打印運行日志詳細。
- -V, –version, 打印版本號。
- –noerr, 忽略錯誤命令行參數。
通過對命令行各種參數的解釋,我們基本上知道了這些參數都是干什么的了,下面我就試一下。
寫2個簡單地JS文件,demo.js, main.js。
- ~ vi D:\workspace\javascript\nodejs-uglifyJS2\demo.js
- 'use strict';
- function hello(name){
- if(name==='fens.me'){
- return "Long time no see, "+name;
- }
- return "hello "+name;
- }
- console.log(hello('Conan'));
- console.log(hello('fens.me'));
main.js
- ~ vi D:\workspace\javascript\nodejs-uglifyJS2\main.js
- 'use strict';
- function book(){
- return [
- {head:'前言',page:'/views/tpl/book-r1/preface.html',active:false},
- {head:'目錄',page:'/views/tpl/book-r1/contents.html',active:true},
- {head:'代碼',page:'/views/tpl/book-r1/code.html',active:false},
- {head:'試讀',page:'/views/tpl/book-r1/sample.html',active:false},
- {head:'勘誤',page:'/views/tpl/book-r1/mistake.html',active:false}
- ];
- }
- var tab=function(arr,idx){
- for(var i=0;i<arr.length;i++){
- arr[i].active = (idx==i?true:false);
- }
- return arr;
- }
- console.log(tab(book(),3));
接下來,用UglifyJS2命令進行操作,合並兩個文件,對變量名用單字母替換,進行壓縮,所有代碼合並到一個函數,生成source map,指定source map來源網站。
- D:\workspace\javascript\nodejs-uglifyJS2>uglifyjs main.js demo.js -o foo.min.js --source-map foo.min.js.map --source-map-root http://onbook.me -p 5 -c -m --wrap --export-all
在當前目錄生成了2個新文件:foo.min.js.map, foo.min.js,分別查看這兩個文件。
foo.min.js
- !function(e,t){"use strict";function o(){return[{head:"前言",page:"/views/tpl/book-r1/preface.html",active:!1},{head:"目錄",page:"/views/tpl/book-r1/contents.html",active:!0},{head:"代碼",page:"/views/tpl/book-r1/code.html",active:!1},{head:"試讀",page:"/views/tpl/book-r1/sample.html",active:!1},{head:"勘誤",page:"/views/tpl/book-r1/mistake.html",active:!1}]}function n(e){return"fens.me"===e?"Long time no see, "+e:"hello "+e}t["true"]=e,console.log(a(o(),3));var a=function(e,t){for(var o=0;o
foo.min.js.map
- {"version":3,"file":"foo.min.js","sources":["?"],"names":["exports","global","book","head","page","active","hello","name","console","log","tab","arr","idx","i","length","this"],"mappings":"CAAC,SAASA,EAASC,GAAnB,YAEA,SAASC,KACL,QACKC,KAAK,KAAKC,KAAK,kCAAkCC,QAAO,IACxDF,KAAK,KAAKC,KAAK,mCAAmCC,QAAO,IACzDF,KAAK,KAAKC,KAAK,+BAA+BC,QAAO,IACrDF,KAAK,KAAKC,KAAK,iCAAiCC,QAAO,IACvDF,KAAK,KAAKC,KAAK,kCAAkCC,QAAO,IANjE,QAASC,GAAMC,GACd,MAAU,YAAPA,EACK,qBAAqBA,EAEtB,SAASA,EANWN,EAAO,QAAUD,EAY7CQ,QAAQC,IAAIC,EAAIR,IAAO,GADvB,IAAIQ,GAAI,SAASC,EAAIC,GACpB,IAAI,GAAIC,GAAE,EAAEA,EAAEF,EAAIG,OAAOD,IACxBF,EAAIE,GAAGR,OAAUO,GAAKC,GAAE,GAAK,CAE9B,OAAOF,GAGRH,SAAQC,IAAIH,EAAM,UAClBE,QAAQC,IAAIH,EAAM,mBAjBTJ,UAAAI,QASLI,MAX8E,WAAW,MAAOK","sourceRoot":"http://onbook.me"}
通過一條簡單的命令,就實現了對JS代碼的合並、壓縮等的操作,確實非常方便。
下載jquery-2.1.1.js文件自己壓縮,並與官方的壓縮文件進行對比。
- # 下載
- ~ wget http://code.jquery.com/jquery-2.1.1.js
- ~ wget http://code.jquery.com/jquery-2.1.1.min.js
- # 壓縮
- ~ uglifyjs jquery-2.1.1.js -o jquery-2.1.1.min.uglifyjs2.js -p 5 -c -m
- # 比較3個文件大小
- ~ ls -l
- -rwx------ 1 4294967295 mkpasswd 247351 Jul 6 16:26 jquery-2.1.1.js
- -rwx------ 1 4294967295 mkpasswd 84245 Jul 6 16:32 jquery-2.1.1.min.js
- -rwx------ 1 4294967295 mkpasswd 84113 Jul 6 16:28 jquery-2.1.1.min.uglifyjs2.js
我在本地壓縮的文件jquery-2.1.1.min.uglifyjs2.js,與jquery官司網下載的壓縮文件jquery-2.1.1.min.js大小差不多,都在84KB左右。
5. UglifyJS2的API使用
另一種使用方式是,把UglifyJS2包放到程序中,通過API對JS文件或JS代碼進行壓縮。首先,新建一個NPM項目文件package.json,然后在是下載UglifyJS2依賴包。
新建文件package.json
- ~ vi D:\workspace\javascript\nodejs-uglifyJS2\package.json
- {
- "name": "nodejs-uglifyjs2",
- "version": "0.0.1",
- "description": "uglifyjs2",
- "author": "Conan Zhang ",
- "dependencies": {
- }
- }
下載UglifyJS2依賴包
- D:\workspace\javascript\nodejs-uglifyJS2>npm install uglify-js --save
- npm WARN package.json nodejs-uglifyjs2@0.0.1 No readme data!
- npm http GET https://registry.npmjs.org/uglify-js
- npm http 304 https://registry.npmjs.org/uglify-js
- npm http GET https://registry.npmjs.org/async
- npm http GET https://registry.npmjs.org/source-map
- npm http GET https://registry.npmjs.org/optimist
- npm http GET https://registry.npmjs.org/uglify-to-browserify
- npm http 304 https://registry.npmjs.org/uglify-to-browserify
- npm http 304 https://registry.npmjs.org/optimist
- npm http 304 https://registry.npmjs.org/async
- npm http 304 https://registry.npmjs.org/source-map
- npm http GET https://registry.npmjs.org/wordwrap
- npm http GET https://registry.npmjs.org/amdefine
- npm http 304 https://registry.npmjs.org/wordwrap
- npm http 304 https://registry.npmjs.org/amdefine
- uglify-js@2.4.14 node_modules\uglify-js
- ├── uglify-to-browserify@1.0.2
- ├── async@0.2.10
- ├── optimist@0.3.7 (wordwrap@0.0.2)
- └── source-map@0.1.34 (amdefine@0.1.0)
我們新建一個文件uglify2.js,用於編寫程序。
- ~ vi D:\workspace\javascript\nodejs-uglifyJS2\uglify2.js
- 'use strict';
- var UglifyJS = require('uglify-js');
- //代碼壓縮
- var result = UglifyJS.minify("var b = function () {};", {fromString: true});
- console.log("\n===========================");
- console.log(result);
- //文件壓縮
- result = UglifyJS.minify(["demo.js"]);
- console.log("\n===========================");
- console.log(result.code);
- //多文件壓縮,指定source map和網站來源
- result = UglifyJS.minify(["main.js","demo.js"],{
- outSourceMap: "out.js.map",
- sourceRoot: "http://onbook.me",
- mangle:true
- });
- console.log("\n===========================");
- console.log(result.code);
- console.log(result.map);
程序輸出:
- D:\workspace\javascript\nodejs-uglifyJS2>node uglify2.js
- ===========================
- { code: 'var b=function(){};', map: 'null' }
- ===========================
- "use strict";function hello(e){return"fens.me"===e?"Long time no see, "+e:"hello "+e}var tab=function(e,o){for(var n=0;n
- <e.length;n++)e[n].active=o==n?!0:!1;return e};console.log(hello("Conan")),console.log(hello("fens.me"));
- ===========================
- "use strict";function book(){return[{head:"前言",page:"/views/tpl/book-r1/preface.html",active:!1},{head:"目錄",page:"/v
- iews/tpl/book-r1/contents.html",active:!0},{head:"代碼",page:"/views/tpl/book-r1/code.html",active:!1},{head:"試讀",page
- :"/views/tpl/book-r1/sample.html",active:!1},{head:"勘誤",page:"/views/tpl/book-r1/mistake.html",active:!1}]}function he
- llo(e){return"fens.me"===e?"Long time no see, "+e:"hello "+e}console.log(tab(book(),3));var tab=function(e,o){for(var t=
- 0;t<e.length;t++)e[t].active=o==t?!0:!1;return e};console.log(hello("Conan")),console.log(hello("fens.me"));
- //# sourceMappingURL=out.js.map
- {"version":3,"file":"out.js.map","sources":["main.js","demo.js"],"names":["book","head","page","active","hello","name","
- console","log","tab","arr","idx","i","length"],"mappings":"AAAA,YAEA,SAASA,QACL,QACKC,KAAK,KAAKC,KAAK,kCAAkCC,QAAO,IACxD
- F,KAAK,KAAKC,KAAK,mCAAmCC,QAAO,IACzDF,KAAK,KAAKC,KAAK,+BAA+BC,QAAO,IACrDF,KAAK,KAAKC,KAAK,iCAAiCC,QAAO,IACvDF,KAAK,KAAKC
- ,KAAK,kCAAkCC,QAAO,ICNjE,QAASC,OAAMC,GACd,MAAU,YAAPA,EACK,qBAAqBA,EAEtB,SAASA,EDMjBC,QAAQC,IAAIC,IAAIR,OAAO,GCDvB,IAAIQ,
- KAAI,SAASC,EAAIC,GACpB,IAAI,GAAIC,GAAE,EAAEA,EAAEF,EAAIG,OAAOD,IACxBF,EAAIE,GAAGR,OAAUO,GAAKC,GAAE,GAAK,CAE9B,OAAOF,GAGR
- H,SAAQC,IAAIH,MAAM,UAClBE,QAAQC,IAAIH,MAAM","sourceRoot":"http://onbook.me"}
我們看到用操作uglifyJS2包的API,還是挺簡單的,如果對AST樹有遍歷需求,API提供了非常實用的函數支持。
不過我在測試API過程中,發現有2個問題。
- 通過API設置mangle選項,但輸出沒有效果。
- 沒有--wrap和--export-all 命令行參數對應的API。
通過本文的介紹,我們基本上了解了uglifyJS2包的功能和使用方法,然后就可以放心大膽地對JS代碼進行壓縮了。在實際的前端項目中,一般不用自己配置uglifyJS2包,而是通過grunt來調用uglifyJS2進行代碼發布前的壓縮,關於grunt使用,請參考文章:grunt讓Nodejs規范起來。