Grunt打包之seajs項目【轉】

原文:http://www.cnblogs.com/accordion/p/4508154.htmljavascript

grunt與seajs

grunt是前端流行的自定義任務的腳手架工具,咱們可使用grunt來爲咱們作一些重複度很高的事情,如壓縮,合併,js語法檢查等。經過定義grunt的配置文件Gruntfile.js,配置並註冊grunt的任務,最終咱們能夠經過命令行來執行任務。css

seajs主要用於模塊化,經過define定義一個模塊,能夠經過require加載模塊,exports導出模塊。具體的seajs實現可經過本博客的系列博文--Seajs源碼解析系列來進一步瞭解。html

在實際生產中,若是牢牢定義一系列seajs模塊而並不進行合併壓縮的話,加載性能很低,緣由你們都懂的,seajs在瀏覽器端處理依賴模塊,並進行異步加載,這個過程當中會有多個http請求,大大下降頁面的加載速度。因此結合grunt構建工具,咱們能夠將模塊的依賴處理放到服務端進行,並將全部模塊合併壓縮,完成生產所需的最終文件。前端

在seajs社區中,已經提供了一款npm模塊,即grunt-cmd-transport。咱們經過該模塊給seajs模塊命名,並處理各模塊之間的依賴。這項工做聽起來很簡單,可是在筆者的實踐過程當中出現的問題卻很多,所以本文着重講解transport任務的相關配置。java

grunt的相關文件

grunt相關文件包括了2個,首先是Gruntfile.js,另外一個是package.json文件。Gruntfile進行grunt任務的配置及註冊,package.json用於向Gruntfile提供參數,並設置依賴的npm模塊。node

在下面package.json中,定義spm鍵,設置模塊的別名,在Gruntfile中,經過pkg = grunt.file.readJSON()來讀取package配置文件,並經過<%= pkg.spm.alias %>獲取模塊別名。jquery

package.json
{
 "name" : "HelloSeaJS",  "version" : "1.0.0",  "author" : "yang li",  "spm": {  "alias": {   "jquery": "jquery"  }  },  "devDependencies" : {   "grunt" : "0.4.1",   "grunt-cmd-transport": "~0.2.0",   "grunt-cmd-concat": "~0.2.0",   "grunt-contrib-uglify" : "0.2.0",   "grunt-contrib-clean" : "0.4.0"  } } 

接下來咱們進行設置grunt。Gruntfile.js其實就是一個node模塊,依然使用閉包將全部的邏輯進行包裹,並提供了grunt參數,經過grunt.initConfig進行任務的配置。npm

對於seajs模塊而言,首先須要處理各模塊之間的依賴,咱們經過設置transport任務來完成。seajs遵循的是CMD規範,在定義模塊時不須要制定模塊名和模塊的依賴組,只需設置工廠函數便可。其實在未使用grunt進行合併seajs時(即在瀏覽器端處理模塊依賴),seajs設置模塊id和uri相同,爲絕對路徑。在這個過程當中有些小技巧,在Seajs源碼解析系列中並未提到,如今在這裏着重分析下:json

<script src="../sea-debug.js"></script> <script>  seajs.config({   base: "../gallery/",   alias:{    jquery: 'jquery/jquery-1.11.1'   }  })  seajs.use("../application.js") </script> 

對於上述代碼,application.js並無合併seajs模塊,咱們經過seajs.use建立了一個匿名use模塊,經過瀏覽器

var mod = Module.get(uri, isArray(ids) ? ids : [ids])

來實現,並設置依賴。在此處,依賴爲[‘../application.js’];而後設置use模塊的callback,並調用load函數加載依賴模塊。在load函數中,use模塊調用resolve函數解析出依賴的絕對路徑,即[‘http://localhost:63342/mywork/js/application.js’],並建立一個新的Module表示該模塊,這裏用appMod表示,並以uri爲key保存到modCache中。調用appMod.fetch加載對應的文件 並設置回調函數onRequest ,在application.js中定義了一個匿名模塊define(function(){return {};}),此時模塊的配置信息

meta = { 
id: id, 
uri: Module.resolve(id), // 絕對url 
deps: deps, 
factory: factory 
}

中id=‘undefined’,url=’’,

meta.uri ? Module.save(meta.uri, meta) : 
// Save information for "saving" work in the script onload event 
anonymousMeta = meta

因爲此時meta.uri爲空,所以meta信息保存在全局變量anonymousMeta中,用於後續處理。

紅色字體強調了在調用fetch時設置的回調onRequest函數,當文件加載完畢,執行onRequest,

function onRequest() { 
delete fetchingList[requestUri] 
fetchedList[requestUri] = true

// Save meta data of anonymous module 
if (anonymousMeta) { 
Module.save(uri, anonymousMeta) 
anonymousMeta = null 
}

// Call callbacks 
var m, mods = callbackList[requestUri] 
delete callbackList[requestUri] 
while ((m = mods.shift())) m.load() 
}

此時anonymousMeta已在模塊define時設置,所以將該meta配置文件配置到uri對應的Module對象上,

Module.save = function(uri, meta) { 
var mod = Module.get(uri) 
// Do NOT override already saved modules 
if (mod.status < STATUS.SAVED) { 
mod.id = meta.id || uri 
mod.dependencies = meta.deps || [] 
mod.factory = meta.factory 
mod.status = STATUS.SAVED 

}

因爲meta.id=undefined,所以最終mod.id=uri。

對於經過define(id,deps,function(){})設置了id的具名模塊,是根據id生成uri。在meta中,經過Module.resolve(id)完成,

Module.resolve = function(id, refUri) {
  if (!id) return "" id = parseAlias(id) id = parsePaths(id) id = parseVars(id) id = normalize(id) var uri = addBase(id, refUri) uri = parseMap(uri) return uri }

經過對id的一系列設置(別名解析,路徑修正,變量解析以及添加擴展名,最終添加協議等)生成uri。

transport任務

transport任務是打包seajs模塊的難點,上節提到了seajs模塊的id和uri之間的關係,它們是由seajs來維護的。可是若是經過grunt對seajs進行打包,則模塊之間的關係由transport來維護。經過transport生產的seajs模塊,有一個顯著的變化,即匿名模塊變爲了具名模塊,而且設置了依賴模塊。

下面經過配置項來說解transport任務:

grunt.initConfig({
   pkg: grunt.file.readJSON('package.json'),   transport : {    options : {     path: ['.'],     alias: '<%= pkg.spm.alias %>' // 注意在package.json中jquery的alias設置    },    utils: { // 存放引用的模塊,設定模塊名和依賴,模塊的idleading要與在application中引用的路徑一致     options: {      idleading: '../dist/src/'     },     files: [{      expand: true,      cwd: 'lib/src',      src: '*',      filter: 'isFile',      dest: '.build/lib/src'     }]    },    application : {     options: {      idleading: '../dist/'     },     files: [      {       expand: true,       cwd : 'lib',       src : 'application.js',       filter : 'isFile',       dest : '.build/lib'      }     ]    }   } } 

在options中,設定了路徑爲‘.’,即相對於Gruntfile文件的當前路徑,alias爲package.json中定義的alias;在utils任務中,設置了idleading選項,最終 模塊的id = idleading + 文件名 。值得注意的是idleading路徑的設置,這裏須要當心設置, 它是根據引用最終打包後文件的html的位置決定的 。最後,將lib/src下的全部文件設置完id和依賴後放到.build/lib/src下。application任務和utils任務相似,只是單獨設置application.js文件的id和依賴。

着重講解idleading的設置。咱們計劃將生成的文件(處理完依賴且合併壓縮後的文件)放到dist文件夾下面,最終經過view/hello.html引用,

設置transport:util任務的idleading = ‘../dist/src/’,文件通過transport以後,lib/src/name.js文件會被設置而且保存到.build/lib/src中,此時name.js的模塊名爲’../dist/src/name’,依賴爲[]。同理,lib/application.js保存到.build/lib中,而且模塊名爲’../dist/application’,依賴爲[‘./src/util’,’jquery’,’./src/test’,’./src/name’]。而後通過合併壓縮以後,生產最終的application.js文件,在view/hello.html中引用(開篇提到)。

在hello.html引用的application文件包含了5個模塊,而且每一個模塊都有id和依賴,所以根據上節具名模塊的id與uri的關係,可知道模塊id影響到文件的加載。之因此在設置idleading = 「../dist/」是根據hello.html的位置決定的。在Module.resolve(id)中,有一步驟爲addBase,即有當前相對路徑轉換爲絕對路徑,而當前路徑是相對於html的位置定義的,具體緣由是html引入了seajs,seajs判斷當前html的位置,設置爲當前路徑。這也正是idleading的設置爲」../dist/」的緣由。

下圖能夠印證了上文所述:

固然若是html的路徑有變化,相應的idleading也要改變:

若是在view/layout/hello.html中引用文件,那麼須要改變transport:util的idleading = ‘../../dist/lib/src’,transport:application的idleading = ‘../../dist/lib’,只有保證每一個模塊的uri正確,才能fetch文件而且執行相應的回調函數。

所以,對於transport任務而言,idleading的設置須要十分注意。

concat、uglify、clean任務

這兩個任務很容易定義,並且 grunt官網 上就是以uglify爲例講解Gruntfile的配置,所以,這兩個任務的配置咱們有不少資料能夠參考。咱們使用通配符來匹配文件,使用expand來批量處理,也能夠自定義過濾函數。廢話很少說,呈上這兩個任務的配置:

   concat : {    options : {     separator: '/*-------每一個文件的分割-------*/',     banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +      '<%= grunt.template.today("yyyy-mm-dd") %> */',     footer: '/*-------合併文件的footer-------*/'    },    utils: {     src: ['.build/lib/src/*.js','!.build/lib/src/*-debug.js'],     dest: '.build/util.js'    },    application : {     src: ['.build/lib/application.js','.build/util.js'],     dest: 'dist/application.js'    }   },   uglify : {    main : {     files : {      'dist/application.min.js' : ['dist/application.js'] //對dist/application.js進行壓縮,以後存入dist/application.js文件     }    }   },   clean : {    build : ['.build'] //清除.build文件   } 

最終的Gruntfile文件定義以下:

module.exports = function(grunt){
 grunt.initConfig({   pkg: grunt.file.readJSON('package.json'),   transport : {    options : {     alias: '<%= pkg.spm.alias %>' // 注意在package.json中jquery的alias設置    },    utils: { // 存放引用的模塊,設定模塊名和依賴,模塊的idleading要與在application中引用的路徑一致     options: {      idleading: '../../dist/src/'     },     files: [{      expand: true,      cwd: 'lib/src',      src: '*',      filter: 'isFile',      dest: '.build/lib/src'     }]    },    application : {     options: {      idleading: '../../dist/'     },     files: [      {       expand: true,       cwd : 'lib',       src : 'application.js',       filter : 'isFile',       dest : '.build/lib'      }     ]    }   },   concat : {    options : {     separator: '/*-------每一個文件的分割-------*/',     banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' +      '<%= grunt.template.today("yyyy-mm-dd") %> */',     footer: '/*-------合併文件的footer-------*/'    },    utils: {     src: ['.build/lib/src/*.js','!.build/lib/src/*-debug.js'],     dest: '.build/util.js'    },    application : {     src: ['.build/lib/application.js','.build/util.js'],     dest: 'dist/application.js'    }   },   uglify : {    main : {     files : {      'dist/application.min.js' : ['dist/application.js'] //對dist/application.js進行壓縮,以後存入dist/application.js文件     }    }   },   clean : {    build : ['.build'] //清除.build文件   }  });  grunt.loadNpmTasks('grunt-cmd-transport');  grunt.loadNpmTasks('grunt-cmd-concat');  grunt.loadNpmTasks('grunt-contrib-uglify');  grunt.loadNpmTasks('grunt-contrib-clean');  // 默認被執行的任務列表。  grunt.registerTask('build',['transport','concat','uglify']) }; 

總結

經過對seajs的源碼分析能夠了解模塊id與uri的關係,進而方便對grunt的transport調試。其實之因此用grunt對seajs模塊進行打包會出現各類各樣的問題,歸根結底是路徑錯誤。路徑錯誤包括不少,好比模塊依賴的路徑錯誤,模塊名錯誤,以及package.json的alias設置錯誤,最後,須要十分注意html文件的位置,由於seajs定義的cmd依賴於html文件當前位置。

相關文章
相關標籤/搜索