本文將示範如何合併與壓縮一個基於RequireJS的專案。本文中將用到苦幹個工具,其中就包括Node.js。 因此,如果你手邊還沒有Node.js可以點擊此處下載一個。
動機
關於RequireJS已經有很多文章介紹過了。這個工具可以將你的JavaScript程式碼輕易的分割成苦幹個模組(module)並且保持你的程式碼模組化與易維護性。這樣,你將會獲得一些具有互相依賴關係的JavaScript檔案。只需要在你的HTML文件中引用一個基於RequireJS的腳本文件,所有必須的文件都會被自動引用到這個頁面上.
但是,在生產環境中將所有的JavaScript檔案分離,這是一個不好的做法。這會導致很多次請求(requests),即使這個些文件都很小,也會浪費很多時間。 可以透過合併這些腳本文件,以減少請求的次數達到節省載入時間的目的。
另一個節省載入時間的技巧是縮小這些被載入檔案的大小,相對小一些的檔案會傳輸的更快一些。這個過程叫做最小化 (minification) ,它是透過小心的改變腳本檔案的程式碼結構並且不改變程式碼的形為(behavior)和功能(functionality)來實現的。例如這些:去除不必要的空格,縮短(mangling,或都壓縮)變數(variables)名與函數(methods,或叫方法)名,等等。這種合併並壓縮檔案的過程叫做程式碼優化( optimization)。這種方法除了用來優化(optimization)JavaScript文件,同樣適用於CSS文件的最佳化。
RequireJS有兩個主要方法(method): define()和require()。這兩個方法基本上擁有相同的定義(declaration) 並且它們都知道如何載入的依賴關係,然後執行一個回呼函數(callback function)。與require()不同的是, define()用來儲存程式碼作為一個已命名的模組。 因此define()的回呼函數需要有一個回傳值作為這個模組定義。這些類似被定義的模組叫做AMD (Asynchronous Module Definition,非同步模組定義)。
如果你不大熟悉RequireJS或不太明白我寫的東西 - 不要擔心。下面有一個關於這些的例子。
JavaScript應用程式的最佳化
在本小節我將向大家展示如何最佳化Addy Osmani的TodoMVC Backbone.js RequireJS 專案。 由於TodoMVC專案在不同的框架下包含許多TodoMVC實現,我下載了1.1.0版並提取Backbone.js RequireJS應用程式。 點這裡下載應用程式並解壓縮下載到的zip檔。 todo-mvc的解壓縮目錄將會是我們這個例子的根目錄(root path),從現在起我將把這個目錄引用為
查看
index.html引用腳本檔案的程式碼
<script data-main="js/main" src="js/lib/require/require.js"></script> <!--[if IE]> <script src="js/lib/ie.js"></script> <![endif]-->
其实,整个项目只需要引用require.js这个脚本文件。如果你在浏览器中运行这个项目,并且在你喜欢的(擅长的)调试工具的network标签中, 你就会发现浏览器同时也加载了其它的JavaScript文件:
所有在红线边框里面的脚本文件都是由RequireJS自动加载的。
我们将用RequireJS Optimizer(RequireJS优化器)来优化这个项目。根据已下载的说明文件,找到r.js并将其复制到
RequireJS Optimizer有很多用处。它不仅能够优化单个JavaScript或单个CSS文件,它还可以优化整个项目或只是其中的一部分,甚至多页应用程序(multi-page application)。它还可以使用不同的缩小引擎(minification engines)或者干脆什么都不用(no minification at all),等等。本文无意于涵盖RequireJS Optimizer的所有可能性,在此仅演示它的一种用法。
正如我之前所提到的,我们将用到Node.js来运行优化器(optimizer)。用如下的命令运行它(optimizer):
运行RequireJS Optimizer
$ node r.js -o <arguments>
有两种方式可以将参数传递给optimizer。一种是在命令行上指定参数:
在命令行上指定参数
$ node r.js -o baseUrl=. name=main out=main-built.js
另一种方式是构建一个配置文件(相对于执行文件夹)并包含指定的参数 :
$ node r.js -o build.js
build.js的内容:配置文件中的参数
({ baseUrl: ".", name: "main", out: "main-built.js" })
我认为构建一个配置文件比在命令行中使用参数的可读性更高,因此我将采用这种方式。接下来我们就为项目创建一个
({ appDir: './', baseUrl: './js', dir: './dist', modules: [ { name: 'main' } ], fileExclusionRegExp: /^(r|build)\.js$/, optimizeCss: 'standard', removeCombined: true, paths: { jquery: 'lib/jquery', underscore: 'lib/underscore', backbone: 'lib/backbone/backbone', backboneLocalstorage: 'lib/backbone/backbone.localStorage', text: 'lib/require/text' }, shim: { underscore: { exports: '_' }, backbone: { deps: [ 'underscore', 'jquery' ], exports: 'Backbone' }, backboneLocalstorage: { deps: ['backbone'], exports: 'Store' } } })
弄清楚RequireJS Optimizer的所有配置項並不是本文的目的所在,但我想解釋(描述)一下本文中我所採用的參數:
了解RequireJS Optimizer的更多介紹以及更多高級應用,除了其網頁早先提供的資料,你可以點擊此處查閱所有可用配置選項的詳細的信息。
既然現在已經有了建置檔(build file),那就可以執行優化器(optimizer)了。進入
運行優化器(optimizer)
$ node r.js -o build.js
一個新的資料夾會被產生:
運行最佳化後的項目,它看起來與未最佳化之前的項目完全一樣。再檢查一下該頁面的網路傳輸(network traffic)訊息,會發現只有兩個JavaScript檔案被載入。
RequireJs Optimizer將伺服器上的腳本檔案從13個減少到2個並且將檔案的總大小從164KB減少到58.6KB(require.js與main.js)。
開銷
顯然,在最佳化之後,我們再也沒有必要引用require.js檔了。因為已經沒有被分離的腳本檔案了並且所有具有依賴關係的檔案也已載入。
儘管如此,最佳化過程將我們所有的腳本合併產生了一個最佳化後的腳本文件,其中包含了許多次define() 和require()呼叫。 因此,為了確保應用程式能夠正常運行,define()和require()必須指定並實施到應用程式的某處(即包含這些檔案)。
這會導致一個眾所周知的開銷:我們總是會有一些程式碼實作define()和require()。這些程式碼並不是應用程式的一部分,它們的存在只是為我們的基礎建設考慮(infrastructure considerations)。 當我們開發一個JavaScript函式庫(JavaScript library)時,這個問題變得特別巨大。相較於RequireJS,這些函式庫通常都很小,因此在函式庫中包含它會造成一筆巨大的開銷。
在我寫這篇文章的時候,對於這方面的開銷還沒有一個完整的解決方案,但是我們可以使用almond來緩解這個問題。 Almond是一個極簡單的AMD載入器,它實作了RequireJS介面(API)。因此,可以用來在已最佳化的程式碼中取代RequireJS實現,我們可以在專案中包含almond。
如令,我正致力於開發一個優化器(optimizer),它將能夠優化RequireJS應用程序,而無需開銷,但它仍然是一個新的項目(處於開發的初期階段)因此這裡沒有任何關於它的展示。
下載與總結