天天看點

Grunt:任務自動管理工具(收藏+轉載)

   原文:http://javascript.ruanyifeng.com/tool/grunt.html

  • 安裝
  • 指令腳本檔案Gruntfile.js
  • Gruntfile.js執行個體:grunt-contrib-cssmin子產品
  • 常用子產品設定
  • grunt-contrib-jshint
  • grunt-contrib-concat
  • grunt-contrib-uglify
  • grunt-contrib-copy
  • grunt-contrib-watch
  • 其他子產品
  • 參考連結

在Javascript的開發過程中,經常會遇到一些重複性的任務,比如合并檔案、壓縮代碼、檢查文法錯誤、将Sass代碼轉成CSS代碼等等。通常,我們需要使用不同的工具,來完成不同的任務,既重複勞動又非常耗時。Grunt就是為了解決這個問題而發明的工具,可以幫助我們自動管理和運作各種任務。

簡單說,Grunt是一個自動任務運作器,會按照預先設定的順序自動運作一系列的任務。這可以簡化工作流程,減輕重複性工作帶來的負擔。

Grunt基于Node.js,安裝之前要先安裝Node.js,然後運作下面的指令。

sudo npm install grunt-cli -g           

grunt-cli表示安裝的是grunt的指令行界面,參數g表示全局安裝。

Grunt使用子產品結構,除了安裝指令行界面以外,還要根據需要安裝相應的子產品。這些子產品應該采用局部安裝,因為不同項目可能需要同一個子產品的不同版本。

首先,在項目的根目錄下,建立一個文本檔案package.json,指定目前項目所需的子產品。下面就是一個例子。

{
  "name": "my-project-name",
  "version": "0.1.0",
  "author": "Your Name",
  "devDependencies": {
    "grunt": "0.x.x",
    "grunt-contrib-jshint": "*",
    "grunt-contrib-concat": "~0.1.1",
    "grunt-contrib-uglify": "~0.1.0",
    "grunt-contrib-watch": "~0.1.4"
  }
}           

上面這個package.json檔案中,除了注明項目的名稱和版本以外,還在devDependencies屬性中指定了項目依賴的grunt子產品和版本:grunt核心子產品為最新的0.x.x版,jshint插件為最新版本,concat插件不低于0.1.1版,uglify插件不低于0.1.0版,watch插件不低于0.1.4版。

然後,在項目的根目錄下運作下面的指令,這些插件就會被自動安裝在node_modules子目錄。

npm install           

上面這種方法是針對已有package.json的情況。如果想要自動生成package.json檔案,可以使用npm init指令,按照螢幕提示回答所需子產品的名稱和版本即可。

npm init           

如果已有的package.json檔案不包括Grunt子產品,可以在直接安裝Grunt子產品的時候,加上--save-dev參數,該子產品就會自動被加入package.json檔案。

npm install <module> --save-dev           

比如,對應上面package.json檔案指定的子產品,需要運作以下npm指令。

npm install grunt --save-dev
npm install grunt-contrib-jshint --save-dev
npm install grunt-contrib-concat --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-contrib-watch --save-dev           

子產品安裝完以後,下一步在項目的根目錄下,建立腳本檔案Gruntfile.js。它是grunt的配置檔案,就好像package.json是npm的配置檔案一樣。Gruntfile.js就是一般的Node.js子產品的寫法。

module.exports = function(grunt) {

  // 配置Grunt各種子產品的參數
  grunt.initConfig({
    jshint: { /* jshint的參數 */ },
    concat: { /* concat的參數 */ },
    uglify: { /* uglify的參數 */ },
    watch:  { /* watch的參數 */ }
  });

  // 從node_modules目錄加載子產品檔案
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // 每行registerTask定義一個任務
  grunt.registerTask('default', ['jshint', 'concat', 'uglify']);
  grunt.registerTask('check', ['jshint']);

};           

上面的代碼用到了grunt代碼的三個方法:

  • grunt.initConfig:定義各種子產品的參數,每一個成員項對應一個同名子產品。
  • grunt.loadNpmTasks:加載完成任務所需的子產品。
  • grunt.registerTask:定義具體的任務。第一個參數為任務名,第二個參數是一個數組,表示該任務需要依次使用的子產品。default任務名表示,如果直接輸入grunt指令,後面不跟任何參數,這時所調用的子產品(該例為jshint,concat和uglify);該例的check任務則表示使用jshint插件對代碼進行文法檢查。

上面的代碼一共加載了四個子產品:jshint(檢查文法錯誤)、concat(合并檔案)、uglify(壓縮代碼)和watch(自動執行)。接下來,有兩種使用方法。

(1)指令行執行某個子產品,比如

grunt jshint           

上面代碼表示運作jshint子產品。

(2)指令行執行某個任務。比如

grunt check           

上面代碼表示運作check任務。如果運作成功,就會顯示“Done, without errors.”。

如果沒有給出任務名,隻鍵入grunt,就表示執行預設的default任務。

下面通過cssmin子產品,示範如何編寫Gruntfile.js檔案。cssmin子產品的作用是最小化CSS檔案。

首先,在項目的根目錄下安裝該子產品。

npm install grunt-contrib-cssmin --save-dev           

然後,建立檔案Gruntfile.js。

module.exports = function(grunt) {

  grunt.initConfig({
    cssmin: {
      minify: {
        expand: true,
        cwd: 'css/',
        src: ['*.css', '!*.min.css'],
        dest: 'css/',
        ext: '.min.css'
      },
      combine: {
        files: {
          'css/out.min.css': ['css/part1.min.css', 'css/part2.min.css']
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-cssmin');

  grunt.registerTask('default', ['cssmin:minify','cssmin:combine']);

};           

下面詳細解釋上面代碼中的三個方法,下面一個個來看。

(1)grunt.loadNpmTasks

grunt.loadNpmTasks方法載入子產品檔案。

grunt.loadNpmTasks('grunt-contrib-cssmin');           

你需要使用幾個子產品,這裡就要寫幾條grunt.loadNpmTasks語句,将各個子產品一一加載。

如果加載子產品很多,這部分會非常冗長。而且,還存在一個問題,就是凡是在這裡加載的子產品,也同時出現在package.json檔案中。如果使用npm指令解除安裝子產品以後,子產品會自動從package.json檔案中消失,但是必須手動從Gruntfile.js檔案中清除,這樣很不友善,一旦忘記,還會出現運作錯誤。這裡有一個解決辦法,就是安裝load-grunt-tasks子產品,然後在Gruntfile.js檔案中,用下面的語句替代所有的grunt.loadNpmTasks語句。

require('load-grunt-tasks')(grunt);           

這條語句的作用是自動分析package.json檔案,自動加載所找到的grunt子產品。

(2)grunt.initConfig

grunt.initConfig方法用于子產品配置,它接受一個對象作為參數。該對象的成員與使用的同名子產品一一對應。由于我們要配置的是cssmin子產品,是以裡面有一個cssmin成員(屬性)。

cssmin(屬性)指向一個對象,該對象又包含多個成員。除了一些系統設定的成員(比如options),其他自定義的成員稱為目标(target)。一個子產品可以有多個目标(target),上面代碼裡面,cssmin子產品共有兩個目标,一個是“minify”,用于壓縮css檔案;另一個是“combine”,用于将多個css檔案合并一個檔案。

每個目标的具體設定,需要參考該模闆的文檔。就cssmin來講,minify目标的參數具體含義如下:

  • expand:如果設為true,就表示下面檔案名的占位符(即*号)都要擴充成具體的檔案名。
  • cwd:需要處理的檔案(input)所在的目錄。
  • src:表示需要處理的檔案。如果采用數組形式,數組的每一項就是一個檔案名,可以使用通配符。
  • dest:表示處理後的檔案名或所在目錄。
  • ext:表示處理後的檔案字尾名。

除了上面這些參數,還有一些參數也是grunt所有子產品通用的。

  • filter:一個傳回布爾值的函數,用于過濾檔案名。隻有傳回值為true的檔案,才會被grunt處理。
  • dot:是否比對以點号(.)開頭的系統檔案。
  • makeBase:如果設定為true,就隻比對檔案路徑的最後一部分。比如,a?b可以比對/xyz/123/acb,而不比對/xyz/acb/123。

關于通配符,含義如下:

  • *:比對任意數量的字元,不包括/。
  • ?:比對單個字元,不包括/。
  • **:比對任意數量的字元,包括/。
  • {}:允許使用逗号分隔的清單,表示“or”(或)關系。
  • !:用于模式的開頭,表示隻傳回不比對的情況。

比如,foo/*.js比對foo目錄下面的檔案名以.js結尾的檔案,foo/**/*.js比對foo目錄和它的所有子目錄下面的檔案名以.js結尾的檔案,!*.css表示比對所有字尾名不為“.css”的檔案。

使用通配符設定src屬性的更多例子:

{src: 'foo/th*.js'}grunt-contrib-uglify

{src: 'foo/{a,b}*.js'}

{src: ['foo/a*.js', 'foo/b*.js']}           

至于combine目标,就隻有一個files參數,表示輸出檔案是css子目錄下的out.min.css,輸入檔案則是css子目錄下的part1.min.css和part2.min.css。

files參數的格式可以是一個對象,也可以是一個數組。

files: {
        'dest/b.js': ['src/bb.js', 'src/bbb.js'],
        'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
},

// or

files: [
        {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
        {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
],           

如果minify目标和combine目标的屬性設定有重合的部分,可以另行定義一個與minify和combine平行的options屬性。

grunt.initConfig({
    cssmin: {
      options: { /* ... */ },
      minify: { /* ... */ },
      combine: { /* ... */ }
    }
  });           

(3)grunt.registerTask

grunt.registerTask方法定義如何調用具體的任務。“default”任務表示如果不提供參數,直接輸入grunt指令,則先運作“cssmin:minify”,後運作“cssmin:combine”,即先壓縮再合并。如果隻執行壓縮,或者隻執行合并,則需要在grunt指令後面指明“子產品名:目标名”。

grunt # 預設情況下,先壓縮後合并

grunt cssmin:minify # 隻壓縮不合并

grunt css:combine # 隻合并不壓縮           

如果不指明目标,隻是指明子產品,就表示将所有目标依次運作一遍。

grunt cssmin           

grunt的子產品已經超過了2000個,且還在快速增加。下面是一些常用的子產品(按字母排序)。

  • grunt-contrib-clean:删除檔案。
  • grunt-contrib-compass:使用compass編譯sass檔案。
  • grunt-contrib-concat:合并檔案。
  • grunt-contrib-copy:複制檔案。
  • grunt-contrib-cssmin:壓縮以及合并CSS檔案。
  • grunt-contrib-imagemin:圖像壓縮子產品。
  • grunt-contrib-jshint:檢查JavaScript文法。
  • grunt-contrib-uglify:壓縮以及合并JavaScript檔案。
  • grunt-contrib-watch:監視檔案變動,做出相應動作。

子產品的字首如果是grunt-contrib,就表示該子產品由grunt開發團隊維護;如果字首是grunt(比如grunt-pakmanager),就表示由第三方開發者維護。

以下選幾個子產品,看看它們配置參數的寫法,也就是說如何在grunt.initConfig方法中配置各個子產品。

jshint用來檢查文法錯誤,比如分号的使用是否正确、有沒有忘記寫括号等等。它在grunt.initConfig方法裡面的配置代碼如下。

jshint: {
    options: {
        eqeqeq: true,
        trailing: true
    },
    files: ['Gruntfile.js', 'lib/**/*.js']
},           

上面代碼先指定jshint的檢查項目,eqeqeq表示要用嚴格相等運算符取代相等運算符,trailing表示行尾不得有多餘的空格。然後,指定files屬性,表示檢查目标是Gruntfile.js檔案,以及lib目錄的所有子目錄下面的JavaScript檔案。

concat用來合并同類檔案,它不僅可以合并JavaScript檔案,還可以合并CSS檔案。

concat: {
  js: {
    src: ['lib/module1.js', 'lib/module2.js', 'lib/plugin.js'],
    dest: 'dist/script.js'
  }
  css: {
    src: ['style/normalize.css', 'style/base.css', 'style/theme.css'],
    dest: 'dist/screen.css'
  }
},           

js目标用于合并JavaScript檔案,css目标用語合并CSS檔案。兩者的src屬性指定需要合并的檔案(input),dest屬性指定輸出的目标檔案(output)。

uglify子產品用來壓縮代碼,減小檔案體積。

uglify: {
  options: {
    banner: bannerContent,
    sourceMapRoot: '../',
    sourceMap: 'distrib/'+name+'.min.js.map',
    sourceMapUrl: name+'.min.js.map'
  },
  target : {
    expand: true,
    cwd: 'js/origin',
    src : '*.js',
    dest : 'js/'
  }
},           

上面代碼中的options屬性指定壓縮後檔案的檔案頭,以及sourceMap設定;target目标指定輸入和輸出檔案。

copy子產品用于複制檔案與目錄。

copy: {
  main: {
    src: 'src/*',
    dest: 'dest/',
  },
},           

上面代碼将src子目錄(隻包含它下面的第一層檔案和子目錄),拷貝到dest子目錄下面(即dest/src目錄)。如果要更準确控制拷貝行為,比如隻拷貝檔案、不拷貝目錄、不保持目錄結構,可以寫成下面這樣:

copy: {
  main: {
    expand: true,
    cwd: 'src/',
    src: '**',
    dest: 'dest/',
    flatten: true,
    filter: 'isFile',
  },
},           

watch子產品用來在背景運作,監聽指定事件,然後自動運作指定的任務。

watch: {
   scripts: {
    files: '**/*.js',
    tasks: 'jshint',
    options: {
      livereload: true,
    },
   },
   css: {
    files: '**/*.sass',
    tasks: ['sass'],
    options: {
      livereload: true,
    },
   },
},           

設定好上面的代碼,打開另一個程序,運作grunt watch。此後,任何的js代碼變動,檔案儲存後就會自動運作jshint任務;任何sass檔案變動,檔案儲存後就會自動運作sass任務。

需要注意的是,這兩個任務的options參數之中,都設定了livereload,表示任務運作結束後,自動在浏覽器中重載(reload)。這需要在浏覽器中安裝livereload插件。安裝後,livereload的預設端口為localhost:35729,但是也可以用livereload: 1337的形式重設端口(localhost:1337)。

下面是另外一些有用的子產品。

(1)grunt-contrib-clean

該子產品用于删除檔案或目錄。

clean: {
  build: {
    src: ["path/to/dir/one", "path/to/dir/two"]
  }
}           

(2)grunt-autoprefixer

該子產品用于為CSS語句加上浏覽器字首。

autoprefixer: {
  build: {
    expand: true,
    cwd: 'build',
    src: [ '**/*.css' ],
    dest: 'build'
  }
},           

(3)grunt-contrib-connect

該子產品用于在本機運作一個Web Server。

connect: {
  server: {
    options: {
      port: 4000,
      base: 'build',
      hostname: '*'
    }
  }
}           

connect子產品會随着grunt運作結束而結束,為了使它一直處于運作狀态,可以把它放在watch子產品之前運作。因為watch子產品需要手動中止,是以connect子產品也就會一直運作。

(4)grunt-htmlhint

該子產品用于檢查HTML文法。

htmlhint: {
    build: {
        options: {
            'tag-pair': true,
            'tagname-lowercase': true,
            'attr-lowercase': true,
            'attr-value-double-quotes': true,
            'spec-char-escape': true,
            'id-unique': true,
            'head-script-disabled': true,
        },
        src: ['index.html']
    }
}           

上面代碼用于檢查index.html檔案:HTML标記是否配對、标記名和屬性名是否小寫、屬性值是否包括在雙引号之中、特殊字元是否轉義、HTML元素的id屬性是否為唯一值、head部分是否沒有script标記。

(5)grunt-contrib-sass子產品

該子產品用于将SASS檔案轉為CSS檔案。

sass: {
    build: {
        options: {
            style: 'compressed'
        },
        files: {
            'build/css/master.css': 'assets/sass/master.scss'
        }
    }
}           

上面代碼指定輸出檔案為build/css/master.css,輸入檔案為assets/sass/master.scss。

(6)grunt-markdown

該子產品用于将markdown文檔轉為HTML文檔。

markdown: {
    all: {
      files: [
        {
          expand: true,
          src: '*.md',
          dest: 'docs/html/',
          ext: '.html'
        }
      ],
      options: {
        template: 'templates/index.html',
      }
    }
},           

上面代碼指定将md字尾名的檔案,轉為docs/html/目錄下的html檔案。template屬性指定轉換時采用的模闆,模闆樣式如下。

<!DOCTYPE html>
<html>
<head>
    <title>Document</title>
</head>
<body>
 
    <div id="main" class="container">
        <%=content%>
    </div>
 
</body>
</html>