之前的項目一直采用grunt來構建,然後用requirejs做模塊化,requirejs官方有提供grunt的插件來做壓縮合並。現在的項目切到了gulp,模塊化用起了seajs,自然而然地也想到了模塊合並壓縮的問題。
然後一開始在解決這個問題的時候,並不是很順利,在npm上並沒有那種特別流行的專門用來做seajs合並壓縮的gulp插件,雖然在seajs的github上也看了不少的issue,但是大多數都是只能將所有的模塊文件合並成一個總的文件,這對於單頁面的應用來說肯定沒有問題,但是對於多頁面的應用而言,顯然就違背了模塊化思想中按需加載的核心,所以我想要的是一個能夠根據我每個頁面各自所依賴的模塊來按需合並的方法。
這個按需合並的意思,一方面是只合並一個頁面所依賴的那些模塊,另一方面是,還能過濾掉某些模塊不參與合並,考慮這個的原因在於有些模塊,比如jquery等,都屬於第三方依賴的庫,可能文件比較大,最重要的是你幾乎不會去改動它的代碼,所以這些模塊不合並到頁面的js中,會有助於更好地利用浏覽器緩存。本文介紹一個簡單可行的辦法,來做基於gulp構建的中小型項目中的seajs合並壓縮。
注:為了說明,seajs合並後的效果,本文提供了一個演示demo,它有兩個頁面:login.html和regist.html,分別用來模擬多頁應用中的兩個獨立的文件,可通過以下鏈接來查看:
http://liuyunzhuge.github.io/blog/seajs/dist/html/login.html
http://liuyunzhuge.github.io/blog/seajs/dist/html/regist.html
以login.html為例,查看這個頁面的源文件中,會看到它除了引用seajs以及相關的配置文件common.js外,只引用了app/login作為頁面的main js,這個app/login模塊其實對應的就是js/app/login.js:
但實際上,這個login.js依賴了更多的模塊js,你可以通過chrome的soures來查看該頁面加載的詳細js資源:
在login.js合並之前,它的代碼是這樣的:
但是在前2個截圖中,我們並沒有看到mod/mod1.js , mod/mod2.js , deps/fastclick.js這三個文件,我們除了看到login.js,還看到了lib/bootstrap.js , lib/jquery.js , lib/jquery.validate.js。這就是合並的效果。一方面保持了js/lib文件夾下的模塊都不會參與合並,另一方面保證了頁面的main js所依賴的其它模塊,都合並到頁面的main js文件中來。
該demo相關的代碼可通過以下鏈接進行查看:
https://github.com/liuyunzhuge/blog/tree/master/seajs
1. 合並思路
其實方法還算比較簡單,我最後再介紹。
1)我先說下自己對seajs的模塊進行組織的一種文件夾結構,它是這樣的:
這個結構借鑒於requirejs,盡量讓文件組織扁平化,對於中小型前端項目來說,應該不會太麻煩。其中各個文件夾的作用是:
1)js/app 存放各個頁面的main js,基本上是一個頁面一個js的邏輯
2)js/deps 存放哪些需要被合並到main js的第三方模塊
3)js/lib 存放哪些不需要參與合並的第三方模塊
4)js/mod 存放各個項目中自己寫的一些js模塊
5)common.js 是seajs的配置文件。
2)在common.js中把js/lib下的模塊都配置到了alias選項裡面,因為這些js都不參與合並,需要使用到浏覽器緩存,alias可以方便我們在修改或升級了js/lib下的文件的時候,對這些文件的加載地址做一點更新:
base配置到了js文件夾。在模塊開發中,要require其他模塊時,我的習慣都是直接寫mod/mod1這樣的模塊標識,不用相對標識,即使要定義的這個模塊所依賴的模塊跟它存在於同一個文件夾中,這也是我為啥把base目錄設置到js文件夾的原因,有點類似站點根目錄的感覺。
3)合並的思路:主要是利用gulp-seajs-transport和gulp-seajs-concat這兩個gulp插件。雖然它們在github上不是很熱門,但是已經很好地解決我的問題了,使用起來也非常簡單:
(更多內容需要查看本文開始處提供的源碼鏈接,找到相關的gulpfile.js文件)
gulp-seajs-transport可以幫助你把seajs的模塊文件從匿名模塊,變成具名模塊。比如js/mod/mod1.js在構建前是這樣的:
但是經過transport處理後就會變成:
這個是seajs合並工作中比較關鍵的一點,它不像requirejs,直接做concat即可;它必須先經過一個transport的任務處理,將匿名模塊變成具名模塊,同時用define的第二個參數來描述這個模塊的所有依賴,就像requirejs那樣。只有做完了transport,才能利用gulp-seajs-concat做合並。原因請參考:https://github.com/seajs/seajs/issues/426。
gulp-seajs-concat做合並的時候,就很簡單了,只要告訴它一個base選項即可,這個base選項跟js/common.js中base選項保持一致。因為gulp-seajs-concat根據base和transport之後的模塊,就能找到它所依賴的其它模塊文件。
4)頁面中使用main js時要采用這種方式:
use的參數名稱,必須跟合並之後的main js的主模塊ID保持一致。比如js/app/login.js合並之後是這個樣子:
第一個define對應的模塊就是合並後文件內的主模塊,紅框的內容就是該主模塊的id,seajs use這個模塊的時候,參數名稱必須和這個id一致。否則seajs即使成功的加載到了這個文件,也不會執行任何模塊內的代碼。因為seajs有一個規則:ID 和路徑匹配原則,其中有點跟這個相關,就是:當seajs use到一個文件內包含多個模塊時,會根據use的參數名來尋找這個文件內的主模塊,只有它們完全匹配,才能找得到。
5)壓縮混淆:使用gulp-uglify:
但是要注意那個mangle,必須把require exports module排除掉,否則會引發一些意外的問題。
2. 本文小結
本文內容雖然很簡單,但是在剛切到gulp和seajs的時候,還是費了不少時間才把本文的問題解決,雖然在准備demo的時候進展地比我當時的情況要順利的多…不管怎麼樣,希望本文的內容多多少少能幫助到一些朋友。