打包的目的和意義就不用敘述了直接上乾貨 http://www.gruntjs.net/getting-started裡面的教程也太簡單了,需要下一番功夫去研究才行。本文將grunt打包的常用方法都用實例描述,更加清晰易懂。 1. 第一個簡單的grunt打包 1)需要安裝nodejs:http://w ...
打包的目的和意義就不用敘述了直接上乾貨
http://www.gruntjs.net/getting-started裡面的教程也太簡單了,需要下一番功夫去研究才行。本文將grunt打包的常用方法都用實例描述,更加清晰易懂。
1. 第一個簡單的grunt打包
1)需要安裝nodejs:http://www.cnblogs.com/chuaWeb/p/nodejs-npm.html
本人的nodejs工程目錄為F:\chuaNodejs(後續所有相對路徑都是相對於這個目錄)
2)命令行到nodejs目錄(需要系統管理員許可權,不然後續過程中會報錯)執行npm install -g grunt-cli
3)在nodejs工程目錄下建一個package.json,內容為
{ "name": "my-first-grunt", "file": "jquery", "version": "0.1.0", "devDependencies": { "grunt": "~0.4.5", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-nodeunit": "~0.4.1", "grunt-contrib-uglify": "~0.5.0" } }
其中file屬性在Gruntfile.js中要用到的屬性,打包源名稱
然後將devDependencies下麵的包給下載下來,比如下載grunt: npm install grunt --save-dev 其他包下載方式一樣
需要註意的是package.json中的數據必須是嚴格的json數據格式(連添加註釋都是不可以的)。
4) 在nodejs工程目錄下建一個Gruntfile.js,內容為
1 module.exports = function (grunt) { 2 // 項目配置 3 grunt.initConfig({ 4 pkg: grunt.file.readJSON('package.json'),//獲取解析package.json將內容保存在pkg中 5 uglify: { 6 options: { 7 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 8 }, 9 build: { 10 src: 'src/<%=pkg.file %>.js', 11 dest: 'dest/<%= pkg.file %>.min.js' 12 } 13 } 14 }); 15 // 載入提供"uglify"任務的插件 16 grunt.loadNpmTasks('grunt-contrib-uglify'); 17 // 預設任務 18 grunt.registerTask('default', ['uglify']); 19 }
裡面pkg讀取package.json文件,然後可以訪問其內容中的屬性,如pkg.file。build屬性src是待壓縮的文件,dest是壓縮重定向到該文件。
所以現在我要建立一個src目錄,目錄下麵保存了一個叫做jquery.js的文件
5)命令行運行:grunt
輸出
Running “uglify:build” (uglify) task File dest/jquery.min.js created: 277.14 kB ->93.35 kB >> 1 file created. Done.
創建了dest/jquery.min.js
註意:grunt命令和grunt uglify等價。當然也可以帶上任務名稱default:grunt default能等到同樣結果。
6)總結:
grunt.initConfig:
為當前項目初始化一個配置對象,其中傳入的 configObject 參數可以用在後續的task中,可以通過grunt.config 方法訪問。幾乎每個項目的 Gruntfile 都會調用此方法。
註意,任何 <% %> 模板字元串只會在取到配置數據後才被處理。
不同的插件有各自的參數,比如例子中的uglify插件用到的參數有options和build。詳細信息需要參考各個插件。
initConfig詳情參考:http://www.gruntjs.net/api/grunt.config#grunt.config.init
grunt.loadNpmTasks:
grunt打包主要是用到各種插件,比如上面的壓縮用的插件uglify。載入插件需要使用該方法。
從指定的 Grunt 插件中載入任務。此插件必須通過npm安裝到本地,並且是參照 Gruntfile.js 文件的相對路徑。
詳情參考:http://www.gruntjs.net/api/grunt.task#grunt.task.loadnpmtasks
grunt.registerTask:
註冊 "別名任務" 或 任務函數。此方法支持以下兩種類型:
別名任務(如果指定了一個任務列表,那麼,新註冊的任務將會是這一個或多個任務的別名(alias)。當此"別名任務"執行時,taskList中的所有任務都將按指定的順序依次執行。taskList 參數必須是一個任務數組。):grunt.task.registerTask(taskName, taskList)
上面的例子就是別名任務。定義一個"default" 任務,當執行 Grunt 且不通過參數指定任務時,第二個參數中的任務將依序執行
任務函數(如果傳入description和taskFunction,每當任務運行時,指定的函數(taskFunction)都會被執行。此外,當執行 grunt --help時,前面指定的描述(description)就會顯示出來。特定任務的屬性和方法在任務函數內部通過this對象的屬性即可訪問。如果任務函數返回false表示任務失敗。):grunt.task.registerTask(taskName, description, taskFunction)
下一個例子我們就試一下這個任務函數,現在剛纔例子每一個步驟目的就清晰了。
詳情參考:http://www.gruntjs.net/api/grunt.task#grunt.task.registertask
2. 使用任務函數方式實現剛纔的那個打包。
其他的東西都沒有變,Gruntfile.js變成了
1 module.exports = function (grunt) { 2 // 載入提供"uglify"任務的插件 3 grunt.loadNpmTasks('grunt-contrib-uglify'); 4 // 任務函數 5 grunt.registerTask('build', 'uglify 使用 任務函數版', function () { 6 //任務列表 7 var tasks = ['uglify']; 8 // 項目配置 9 grunt.initConfig({ 10 pkg: grunt.file.readJSON('package.json'),//獲取解析package.json將內容保存在pkg中 11 }); 12 var uglifyTask = { 13 options: { 14 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 15 }, 16 build: { 17 src: 'src/<%=pkg.file %>.js', 18 dest: 'dest/<%= pkg.file %>.min.js' 19 } 20 } 21 //將一個或多個任務放入隊列中 22 grunt.task.run(tasks); 23 //給當前項目的 Grunt 配置中的uglify屬性設置一個值。 24 grunt.config.set("uglify", uglifyTask); 25 }); 26 }
然後去命令行運行: grunt bulid
和第一個不同的是運行命令必須帶上了任務名稱build。
註意前面提到的任何 <% %> 模板字元串只會在取到配置數據後才被處理。
3. 合併壓縮
有些時候為了減少請求需要合併多個文件,且需要壓縮。使用uglify即可做到這一點。
Gruntfile.js代碼
1 module.exports = function (grunt) { 2 // 項目配置 3 grunt.initConfig({ 4 pkg: grunt.file.readJSON('package.json'),//獲取解析package.json將內容保存在pkg中 5 uglify: { 6 options: { 7 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',//壓縮目標文件頭部註釋 8 mangle: false//變數和函數名稱不壓縮 9 }, 10 my_target: {//該對象名稱隨意 11 files: { 12 'dest/libs.min.js': ['src/jquery.js', 'src/router.js'] 13 } 14 } 15 } 16 }); 17 // 載入提供"uglify"任務的插件 18 grunt.loadNpmTasks('grunt-contrib-uglify'); 19 // 預設任務 20 grunt.registerTask('default', ['uglify']); 21 }
文件壓縮的順序按照files中定義的數組的順序壓縮。我們可以通過options.mangle來設置變數和函數名稱是否壓縮。還可以有下麵的用法
1 options: { 2 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',//壓縮目標文件頭部註釋 3 mangle: { 4 except: ["jQuery"]//不壓縮的關鍵字列表 5 } 6 }
最終jquery.js和router.js合併壓縮成libs.min.js
詳情參考:https://www.npmjs.com/package/grunt-contrib-uglify
4. 合併壓縮require配置的文件。
首先需要下載grunt-contrib-requirejs插件:npm install grunt-contrib-requirejs --save-dev
比如我們的執行文件main.js使用的是require來模塊化載入,代碼如下
1 require.config({ 2 baseUrl: "", 3 paths: { 4 "jquery": "src/jquery", 5 "bootstrap": "src/bootstrap.min", 6 "validator": "src/bootstrapValidator.min" 7 }, 8 shim: { 9 "bootstrap":["jquery"], 10 "beyond": ["bootstrap"], 11 "validator": ["bootstrap"] 12 } 13 }); 14 requirejs(['validator'], function (v) { 15 //代碼 16 });
我們希望jquery/bootstrap/validator幾個文件合併成一個,避免多次請求。
我們新建一個grunt配置文件gruntCfg.json將main.js的require文件配置代碼段拷貝到這個配置文件。並寫入輸入輸出目標。源碼如下
1 { 2 "requirejs": { 3 "main": { 4 "options": { 5 "baseUrl": "", 6 "paths": { 7 "jquery": "src/jquery", 8 "bootstrap": "src/bootstrap.min", 9 "validator": "src/bootstrapValidator.min" 10 }, 11 "shim": { 12 "bootstrap":["jquery"], 13 "validator": ["bootstrap"] 14 }, 15 "include":["validator","jquery","bootstrap"], 16 "out":"dest/libs.js" 17 } 18 } 19 } 20 }
上面include中我打亂了順序,為了測試壓縮順序我在三個庫的前面都添加了console.log(“我是” + 模塊名稱)。
修改Gruntfile.js
1 module.exports = function (grunt) { 2 grunt.loadNpmTasks('grunt-contrib-requirejs'); 3 //為了介紹自定義任務搞了一個這個 4 grunt.registerTask('build', 'require', function () { 5 6 //設置requireJs的信息 7 var taskCfg = grunt.file.readJSON('gruntCfg.json'); 8 var requireTask = taskCfg.requirejs; 9 10 //添加人任務列表 11 grunt.task.run(['requirejs']); 12 grunt.config.set("requirejs", requireTask); 13 }); 14 }
好了現在看一下相關文件的文件結構如下
執行build任務:grunt build
文件夾dest下多了一個libs.js文件
打開來看
第一行代碼就有我剛纔添加的console。繼續找我添加的標誌的話發現壓縮順序是:jQuery->bootstrap->bootstrapValidator。和我們的shim依賴順序一致。說明合併壓縮的順序沒有問題。
更多詳情請查看:https://www.npmjs.com/package/grunt-contrib-requirejs
5. 多任務
我們先看一下使用別名任務來執行多個任務的情況,Gruntfile.js源碼如下
1 module.exports = function (grunt) { 2 // 項目配置 3 grunt.initConfig({ 4 pkg: grunt.file.readJSON('package.json'),//獲取解析package.json將內容保存在pkg中 5 uglify: { 6 options: { 7 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 8 }, 9 build: { 10 src: 'src/<%=pkg.file %>.js', 11 dest: 'dest/<%= pkg.file %>.min.js' 12 }, 13 my_target1: { 14 files: { 15 'dest/libs.min.js': ['src/jquery.js', 'src/router.js'] 16 } 17 }, 18 my_target2: { 19 files: { 20 'dest/libs.min2.js': ['src/jquery.js', 'src/router.js'] 21 } 22 } 23 }, 24 requirejs: { 25 "main": { 26 "options": { 27 "baseUrl": "", 28 "paths": { 29 "jquery": "src/jquery", 30 "bootstrap": "src/bootstrap.min", 31 "validator": "src/bootstrapValidator.min" 32 }, 33 "shim": { 34 "bootstrap":["jquery"], 35 "validator": ["bootstrap"] 36 }, 37 "include":["validator","jquery","bootstrap"], 38 "out":"dest/libs.js" 39 } 40 }, 41 "main2": { 42 "options": { 43 "baseUrl": "", 44 "paths": { 45 "jquery": "src/jquery", 46 "bootstrap": "src/bootstrap.min", 47 "validator": "src/bootstrapValidator.min" 48 }, 49 "shim": { 50 "bootstrap":["jquery"], 51 "validator": ["bootstrap"] 52 }, 53 "include":["validator","jquery","bootstrap"], 54 "out":"dest/libs2.js" 55 } 56 } 57 } 58 59 }); 60 // 載入提供"uglify"任務的插件 61 grunt.loadNpmTasks('grunt-contrib-uglify'); 62 grunt.loadNpmTasks('grunt-contrib-requirejs'); 63 // 預設任務 64 grunt.registerTask('default', ['uglify',"requirejs"]); 65 }
執行:grunt
dest文件夾下多出了5個文件
源碼解析是:任務列表有兩個任務'uglify'和"requirejs",'uglify'有三個子任務:build/ my_target1/ my_target2, "requirejs"有兩個子任務main/main2。最終這五個子任務完成後生成的結果就是dest下多出的5個文件。
還可以使用任務函數方式註冊多個任務。Gruntfile.js源碼如下
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 log: { 4 l1: [1,2,3], 5 l2: 'hello world', 6 l3: false, 7 l4: {name: "chua"}, 8 l5: 1, 9 //這個是不行的l6: undefined, 10 //這個是不行的l7: null 11 l8: function(){alert("test")} 12 } 13 }); 14 15 grunt.registerMultiTask('log','log stuff.', function(){ 16 grunt.log.writeln(this.target + ': ' + this.data); 17 }); 18 }
執行:grunt log
我們使用自定義方式實現先前的多任務Gruntfile.js源碼如下
1 module.exports = function (grunt) { 2 // 項目配置 3 grunt.initConfig({ 4 pkg: grunt.file.readJSON('package.json'),//獲取解析package.json將內容保存在pkg中 5 log: { 6 uglify: { 7 options: { 8 banner: '/*! <%= pkg.file %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 9 }, 10 build: { 11 src: 'src/<%=pkg.file %>.js', 12 dest: 'dest/<%= pkg.file %>.min.js' 13 }, 14 my_target1: { 15 files: { 16 'dest/libs.min.js': ['src/jquery.js', 'src/router.js'] 17 } 18 }, 19 my_target2: { 20 files: { 21 'dest/libs.min2.js': ['src/jquery.js', 'src/router.js'] 22 } 23 } 24 }, 25 requirejs: { 26 "main": { 27 "options": { 28 "baseUrl": "", 29 "paths": { 30 "jquery": "src/jquery", 31 "bootstrap": "src/bootstrap.min", 32 "validator": "src/bootstrapValidator.min" 33 }, 34 "shim": { 35 "bootstrap":["jquery"], 36 "validator": ["bootstrap"] 37 }, 38 "include":["validator","jquery","bootstrap"], 39 "out":"dest/libs.js" 40 } 41 }, 42 "main2": { 43 "options": { 44 "baseUrl": "", 45 "paths": { 46 "jquery": "src/jquery", 47 "bootstrap": "src/bootstrap.min", 48 "validator": "src/bootstrapValidator.min" 49 }, 50 "shim": { 51 "bootstrap":["jquery"], 52 "validator": ["bootstrap"] 53 }, 54 "include":["validator","jquery","bootstrap"], 55 "out":"dest/libs2.js" 56 } 57 } 58 } 59 } 60 }); 61 // 載入提供"uglify"任務的插件 62 grunt.loadNpmTasks('grunt-contrib-uglify'); 63 grunt.loadNpmTasks('grunt-contrib-requirejs'); 64 65 grunt.registerMultiTask('log','log stuff.', function(){ 66 if(this.target == "uglify"){ 67 //添加人任務列表 68 grunt.task.run(['uglify']); 69 grunt.config.set("uglify", this.data); 70 }else if(this.target == "requirejs"){ 71 //添加人任務列表 72 grunt.task.run(['requirejs']); 73 grunt.config.set("requirejs", this.data); 74 } 75 }); 76 }
執行:grunt log
照樣得到先前的結果
說到底和先前的別名任務沒有什麼不同。
6. 壓縮整個文件夾下的js文件
壓縮src下的所有js文件,Gruntfile.js源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 uglify: { 4 my_target: { 5 files: [{ 6 expand: true, 7 cwd: 'src', 8 src: '**/*.js', 9 dest: 'dest' 10 }] 11 } 12 } 13 }); 14 grunt.loadNpmTasks('grunt-contrib-uglify'); 15 grunt.registerTask('default', ['uglify']); 16 }
源碼文件結構(註意src/router下麵還有一個js文件)
執行:grunt
目標文件結構
所以這個方法還是比較方便的,他會遍歷源文件夾下的所有文件(包括子文件夾)然後壓縮。
剛纔上面的方式沒有拷貝src文件夾,下麵這種方式拷貝了src文件夾
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 uglify: { 4 my_target: { 5 files: [{ 6 expand: true, 7 //cwd: 'src', 8 src: 'src/**/*.js', 9 dest: 'dest' 10 }] 11 } 12 } 13 }); 14 grunt.loadNpmTasks('grunt-contrib-uglify'); 15 grunt.registerTask('default', ['uglify']); 16 }
執行:grunt
目標文件結構變成
src下所有的js都被壓縮並保持原來的目錄結構。
7. grunt-contrib-requirejs插件的更詳細使用
我們用實例來說明。現在我們的文件結構是這樣子的
main.js是我們使用require的真實配置文件,源碼如下
1 require.config({ 2 baseUrl: "", 3 paths: { 4 "jquery": "src/jquery", 5 "bootstrap": "src/bootstrap.min", 6 "validator": "src/bootstrapValidator.min" 7 }, 8 shim: { 9 "bootstrap":["jquery"], 10 "beyond": ["bootstrap"], 11 "validator": ["bootstrap"] 12 } 13 }); 14 requirejs(['validator'], function (v) { 15 //代碼 16 });
Gruntfile.js的源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 "requirejs": { 4 "main": { 5 "options": { 6 "baseUrl": ".", 7 "mainConfigFile": "main.js",//指定配置文件 8 "name": "test",//會將對應的文件壓縮到main.js依賴的文件的前面壓縮。但是會在main.js非依賴文件後面壓縮(比如router.js) 9 "include":["jquery","bootstrap","validator","src/router/router.js"],//要壓縮在一起的文件(會包括name對應的文件) 10 "out":"dest/libs.js" 11 } 12 } 13 } 14 }); 15 grunt.loadNpmTasks('grunt-contrib-requirejs'); 16 grunt.registerTask('default', ['requirejs']); 17 }
需要說明的是name指定的文件內容會在main.js配置的依賴文件壓縮之前壓縮,但是如果include中還包含其他非main.js依賴的文件,則會在main.js非依賴文件後面壓縮。
我們的test.js源碼為
1 console.log("這是我隨便測試用的一個js");
然後我們執行:grunt
結果在dest下生成一個libs.js壓縮文件
並且這個文件裡面包含的壓縮文件的順序是:router.js->test.js->jquery.js-> bootstrap.min.js-> bootstrapValidator.min.js。
所以說配置include中文件會依序壓縮到libs.js,但是main.js依賴的文件會放在最後(依賴會逐層往上查找依賴),而name指定的文件會在非依賴文件之後依賴文件之前壓縮。
require模板文件打包
模板文件使用到require工程上的text.js文件,路徑:http://github.com/requirejs/text
我們這裡建立一個模板文件test.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>test</title> 5 </head> 6 <body> 7 </body> 8 </html>
實際上現在test.html沒有用到任何模板相關的運用,我們只是做打包例子,所以不管其他,我們只需要知道模板文件一般是html文件即可
現在工程的文件結構是
Gruntfile.js的源碼為
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 "requirejs": { 4 "main": { 5 "options": { 6 baseUrl: ".", 7 paths: { 8 "jquery": "src/jquery", 9 "bootstrap": "src/bootstrap.min", 10 "validator": "src/bootstrapValidator.min", 11 "text": "text"//引入requirejs項目下解析模板引擎 12 }, 13 shim: { 14 "bootstrap":["jquery"], 15 "beyond": ["bootstrap"], 16 "validator": ["bootstrap"] 17 }, 18 "include":["jquery","bootstrap","validator","text!test.html"], "out":"dest/libs.js" 19 } 20 } 21 } 22 }); 23 grunt.loadNpmTasks('grunt-contrib-requirejs'); 24 grunt.registerTask('default', ['requirejs']); 25 }
執行命令行:grunt
dest文件下多了一個libs.js,查看其中是否有模板文件test.html中的內容
說明模板被一同壓縮進去了。關鍵是"text!test.html"這句話。test會將模板test.html解析成js腳本形式。
8. cssmin打包樣式文件
需要用npm下載cssmin插件:npm install grunt-contrib-cssmin --save-dev
Gruntfile.js文件源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 cssmin: { 4 compress: { 5 files: { 6 'dest/t.min.css': ["src/t1.css","src/t2.css"] 7 } 8 } 9 } 10 }); 11 12 grunt.loadNpmTasks('grunt-contrib-cssmin'); 13 14 }
命令行執行:grunt cssmin
兩個css文件壓縮打包到dest/t.min.css
9. htmlmin打包html文件
需要用npm下載cssmin插件:npm install grunt-contrib-htmlmin --save-dev
Gruntfile.js源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 htmlmin: { 4 main: { 5 options:{ 6 removeComments: true, 7 collapseWhitespace: true 8 }, 9 files:{ 10 "dest/test.html": "test.html" 11 } 12 } 13 } 14 }); 15 16 grunt.loadNpmTasks('grunt-contrib-htmlmin'); 17 grunt.registerTask("default",["htmlmin"]); 18 }
執行命令:grunt
test.html壓縮到dest/test.html
10. copy拷貝文件
需要下載copy插件npm install grunt-contrib-copy --save-dev
將src文件夾(包括內容和子文件內容)拷貝到dest目錄下,Gruntfile.js源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 copy: { 4 main: { 5 expand: true, 6 src: 'src/**', 7 dest: 'dest/' 8 } 9 } 10 }); 11 12 grunt.loadNpmTasks('grunt-contrib-copy'); 13 grunt.registerTask("default",["copy"]); 14 }
運行:grunt
src被完美的拷貝到dest下
只拷貝src的第一層子文件,Gruntfile.js源碼
1 module.exports = function (grunt) { 2 grunt.initConfig({ 3 copy: { 4 main: { 5 expand: true, 6 src: 'src/*', 7 dest: 'dest/' 8 } 9 } 10 }); 11 12 grunt.loadNpmTasks('grunt-contrib-copy'); 13 grunt.registerTask("default",["copy"]); 14 }
運行結果
這個時候router是空文件夾。
所以這裡我們需要明白路徑中“*”只表示當前這一層,“**”表示需要遞歸子文件夾
如果只是拷貝src中的內容而不用src包