黑人生命也是命

設定任務

本指南說明如何使用 Gruntfile 為專案設定任務。如果您不知道 Gruntfile 是什麼,請閱讀 入門 指南,並查看 範例 Gruntfile

Grunt 設定

任務設定在 Gruntfile 中透過 grunt.initConfig 方法指定。此設定大部分會在以任務為名的屬性中,但可能包含任何任意資料。只要屬性不會與任務所需的屬性衝突,否則它們將會被忽略。

此外,由於這是 JavaScript,您不限於 JSON;您可以在這裡使用任何有效的 JavaScript。必要時,您甚至可以透過程式產生設定。

grunt.initConfig({
  concat: {
    // concat task configuration goes here.
  },
  uglify: {
    // uglify task configuration goes here.
  },
  // Arbitrary non-task-specific properties.
  my_property: 'whatever',
  my_src_files: ['foo/*.js', 'bar/*.js'],
});

任務設定和目標

當任務執行時,Grunt 會在同名屬性中尋找其設定。多重任務可以有多個設定,使用任意命名的「目標」定義。在以下範例中,concat 任務有 foobar 目標,而 uglify 任務只有 bar 目標。

grunt.initConfig({
  concat: {
    foo: {
      // concat task "foo" target options and files go here.
    },
    bar: {
      // concat task "bar" target options and files go here.
    },
  },
  uglify: {
    bar: {
      // uglify task "bar" target options and files go here.
    },
  },
});

同時指定任務和目標,例如 grunt concat:foogrunt concat:bar,將只處理指定的目標設定,而執行 grunt concat 則會逐一處理所有目標。請注意,如果任務已使用 grunt.task.renameTask 重新命名,Grunt 將會在設定物件中尋找新的任務名稱屬性。

選項

在任務設定中,可以指定 options 屬性來覆寫內建預設值。此外,每個目標都可以有特定於該目標的 options 屬性。目標層級的選項將會覆寫任務層級的選項。

options 物件是選用的,如果不需要,可以省略。

grunt.initConfig({
  concat: {
    options: {
      // Task-level options may go here, overriding task defaults.
    },
    foo: {
      options: {
        // "foo" target options may go here, overriding task-level options.
      },
    },
    bar: {
      // No options specified; this target will use task-level options.
    },
  },
});

檔案

由於大多數工作任務都執行檔案操作,因此 Grunt 具有強大的抽象功能,可宣告工作任務應操作哪些檔案。有數種方式可定義src-dest(來源-目的地)檔案對應,提供不同程度的詳細資料和控制。任何多重工作任務都將了解以下所有格式,因此請選擇最符合您需求的格式。

所有檔案格式都支援 srcdest,但 精簡檔案陣列 格式支援一些額外屬性

  • filter 有效的 fs.Stats 方法名稱 或傳遞已配對 src 檔案路徑並傳回 truefalse 的函數。 請參閱範例
  • nonull 如果設定為 true,則操作將包含不符合的模式。結合 Grunt 的 --verbose 旗標,此選項可協助除錯檔案路徑問題。
  • dot 允許模式與以句點開頭的檔名相符,即使模式在該位置並未明確包含句點。
  • matchBase 如果設定,則不含斜線的模式將與路徑的基礎檔名相符(如果路徑包含斜線)。例如,a?b 將與路徑 /xyz/123/acb 相符,但與 /xyz/acb/123 不相符。
  • expand 處理動態 src-dest 檔案對應,請參閱 "動態建立檔案物件" 以取得更多資訊。
  • 其他屬性將傳遞到基礎函式庫中,作為配對選項。請參閱 node-globminimatch 文件,以取得更多選項。

Grunt 和工作任務選項之間的差異

大多數工作任務都執行檔案操作,因此 Grunt 提供內建基礎架構,以擷取工作任務應處理的檔案。優點是工作任務作者不必再次實作此邏輯。為了讓使用者指定這些檔案,Grunt 提供 nonullfilter 等選項。

除了要處理的檔案之外,每個工作任務都有其特定需求。工作任務作者可能希望允許其使用者設定一些選項,以覆寫預設行為。這些工作任務特定選項不應與前面說明的 Grunt 選項混淆。

為了進一步釐清此差異,讓我們來看一個使用 grunt-contrib-jshint 的範例

grunt.initConfig({
  jshint: {
    ignore_warning: {
      options: {
        '-W015': true,
      },
      src: 'js/**',
      filter: 'isFile'
    }
  }
});

此設定使用 Grunt 選項 srcfilter 來指定要處理的檔案。它也使用 grunt-contrib-jshint 工作任務特定選項 -W015 來忽略特定警告(代碼為 W015 的警告)。

精簡格式

此格式允許每個目標有一個 src-dest(來源-目的地)檔案對應。它最常使用於唯讀工作任務,例如 grunt-contrib-jshint,其中需要單一 src 屬性,且沒有相關的 dest 鍵。此格式也支援每個 src-dest 檔案對應的額外屬性。

grunt.initConfig({
  jshint: {
    foo: {
      src: ['src/aa.js', 'src/aaa.js']
    },
  },
  concat: {
    bar: {
      src: ['src/bb.js', 'src/bbb.js'],
      dest: 'dest/b.js',
    },
  },
});

檔案物件格式

此表單支援每個目標的多個 src-dest 對應,其中屬性名稱為目的地檔案,其值為來源檔案。可以這樣指定任意數量的 src-dest 檔案對應,但不能為每個對應指定額外的屬性。

grunt.initConfig({
  concat: {
    foo: {
      files: {
        'dest/a.js': ['src/aa.js', 'src/aaa.js'],
        'dest/a1.js': ['src/aa1.js', 'src/aaa1.js'],
      },
    },
    bar: {
      files: {
        'dest/b.js': ['src/bb.js', 'src/bbb.js'],
        'dest/b1.js': ['src/bb1.js', 'src/bbb1.js'],
      },
    },
  },
});

檔案陣列格式

此表單支援每個目標的多個 src-dest 檔案對應,同時也允許每個對應有額外的屬性。

grunt.initConfig({
  concat: {
    foo: {
      files: [
        {src: ['src/aa.js', 'src/aaa.js'], dest: 'dest/a.js'},
        {src: ['src/aa1.js', 'src/aaa1.js'], dest: 'dest/a1.js'},
      ],
    },
    bar: {
      files: [
        {src: ['src/bb.js', 'src/bbb.js'], dest: 'dest/b/', nonull: true},
        {src: ['src/bb1.js', 'src/bbb1.js'], dest: 'dest/b1/', filter: 'isFile'},
      ],
    },
  },
});

舊格式

dest-as-target 檔案格式是多任務和目標出現之前的遺留物,其中目的地檔案路徑實際上是目標名稱。不幸的是,由於目標名稱是檔案路徑,執行 grunt task:target 可能很尷尬。此外,您不能為每個 src-dest 檔案對應指定目標級別選項或額外的屬性。

請考慮將此格式標記為已棄用,並盡可能避免使用。

grunt.initConfig({
  concat: {
    'dest/a.js': ['src/aa.js', 'src/aaa.js'],
    'dest/b.js': ['src/bb.js', 'src/bbb.js'],
  },
});

自訂過濾函式

filter 屬性可以幫助您更詳細地鎖定檔案。只需使用有效的 fs.Stats 方法名稱。以下內容將僅在模式與實際檔案相符時進行清理

grunt.initConfig({
  clean: {
    foo: {
      src: ['tmp/**/*'],
      filter: 'isFile',
    },
  },
});

或建立您自己的 filter 函式並傳回 truefalse,表示是否應比對檔案。例如,以下內容將僅清理為空的資料夾

grunt.initConfig({
  clean: {
    foo: {
      src: ['tmp/**/*'],
      filter: function(filepath) {
        return (grunt.file.isDir(filepath) && require('fs').readdirSync(filepath).length === 0);
      },
    },
  },
});

另一個範例,它利用 globexpand: true 功能,讓您避免覆寫已存在於目的地中的檔案

grunt.initConfig({
  copy: {
    templates: {
      files: [{
        expand: true,
        cwd: ['templates/css/'],     // Parent folder of original CSS templates
        src: '**/*.css',             // Collects all `*.css` files within the parent folder (and its subfolders)
        dest: 'src/css/',            // Stores the collected `*.css` files in your `src/css/` folder
        filter: function (dest) {    // `dest`, in this instance, is the filepath of each matched `src`
          var cwd = this.cwd,        // Configures variables (these are documented for your convenience only)
              src = dest.replace(new RegExp('^' + cwd), '');
              dest = grunt.task.current.data.files[0].dest;
          return (!grunt.file.exists(dest + src));    // Copies `src` files ONLY if their destinations are unoccupied
        }
      }]
    }
  }
});

請記住,上述技術在檢查目的地是否存在時不考慮 rename 屬性

Glob 模式

逐一指定所有來源檔案路徑通常不切實際,因此 Grunt 支援透過內建的 node-globminimatch 函式庫進行檔名擴充 (也稱為 glob)。

雖然這不是 glob 模式的完整教學,但請知道在檔案路徑中

  • * 匹配任意數量的字元,但不匹配 /
  • ? 匹配單一字元,但不匹配 /
  • ** 匹配任意數量的字元,包括 /,只要它是路徑部分中唯一的內容即可
  • {} 允許使用逗號分隔的「或」表達式清單
  • 模式開頭的 ! 會否定比對結果

大多數人只需要知道 foo/*.js 會比對 foo/ 子目錄中所有以 .js 結尾的檔案,而 foo/**/*.js 會比對 foo/ 子目錄及其所有子目錄中所有以 .js 結尾的檔案。

此外,為了簡化原本複雜的 glob 比對模式,Grunt 允許指定檔案路徑或 glob 比對模式陣列。模式會依序處理,以 ! 為字首的比對會將比對到的檔案從結果集中排除。結果集會唯一化。

例如

// You can specify single files:
{src: 'foo/this.js', dest: ...}
// Or arrays of files:
{src: ['foo/this.js', 'foo/that.js', 'foo/the-other.js'], dest: ...}
// Or you can generalize with a glob pattern:
{src: 'foo/th*.js', dest: ...}

// This single node-glob pattern:
{src: 'foo/{a,b}*.js', dest: ...}
// Could also be written like this:
{src: ['foo/a*.js', 'foo/b*.js'], dest: ...}

// All .js files, in foo/, in alpha order:
{src: ['foo/*.js'], dest: ...}
// Here, bar.js is first, followed by the remaining files, in alpha order:
{src: ['foo/bar.js', 'foo/*.js'], dest: ...}

// All files except for bar.js, in alpha order:
{src: ['foo/*.js', '!foo/bar.js'], dest: ...}
// All files in alpha order, but with bar.js at the end.
{src: ['foo/*.js', '!foo/bar.js', 'foo/bar.js'], dest: ...}

// Templates may be used in filepaths or glob patterns:
{src: ['src/<%= basename %>.js'], dest: 'build/<%= basename %>.min.js'}
// But they may also reference file lists defined elsewhere in the config:
{src: ['foo/*.js', '<%= jshint.all.src %>'], dest: ...}

有關 glob 模式語法的更多資訊,請參閱 node-globminimatch 文件。

動態建立 files 物件

當您要處理許多個別檔案時,可以使用幾個額外屬性來動態建立檔案清單。這些屬性可以在 精簡檔案陣列 對應格式中指定。

expand 設定為 true 會啟用下列屬性

  • cwd 所有 src 比對都相對於此路徑(但不包含此路徑)。
  • src 相對於 cwd 的比對模式。
  • dest 目標路徑字首。
  • ext 在產生的 dest 路徑中,用此值取代任何現有副檔名。
  • extDot 用來指出表示副檔名的句點位於何處。可以採用 'first'(副檔名從檔名中的第一個句點開始)或 'last'(副檔名從最後一個句點開始),預設設定為 'first' [在 0.4.3 中新增]
  • flatten 從產生的 dest 路徑中移除所有路徑部分。
  • rename 嵌入自訂函式,此函式會傳回包含新目標和檔名的字串。此函式會針對每個比對到的 src 檔案呼叫(在副檔名重新命名和扁平化之後)。更多資訊

在以下範例中,uglify 任務會看到 static_mappingsdynamic_mappings 目標的相同 src-dest 檔案對應清單,因為 Grunt 會在任務執行時自動將 dynamic_mappings 檔案物件擴充為 4 個個別的靜態 src-dest 檔案對應(假設找到 4 個檔案)。

可以指定靜態 src-dest 和動態 src-dest 檔案對應的任何組合。

grunt.initConfig({
  uglify: {
    static_mappings: {
      // Because these src-dest file mappings are manually specified, every
      // time a new file is added or removed, the Gruntfile has to be updated.
      files: [
        {src: 'lib/a.js', dest: 'build/a.min.js'},
        {src: 'lib/b.js', dest: 'build/b.min.js'},
        {src: 'lib/subdir/c.js', dest: 'build/subdir/c.min.js'},
        {src: 'lib/subdir/d.js', dest: 'build/subdir/d.min.js'},
      ],
    },
    dynamic_mappings: {
      // Grunt will search for "**/*.js" under "lib/" when the "uglify" task
      // runs and build the appropriate src-dest file mappings then, so you
      // don't need to update the Gruntfile when files are added or removed.
      files: [
        {
          expand: true,     // Enable dynamic expansion.
          cwd: 'lib/',      // Src matches are relative to this path.
          src: ['**/*.js'], // Actual pattern(s) to match.
          dest: 'build/',   // Destination path prefix.
          ext: '.min.js',   // Dest filepaths will have this extension.
          extDot: 'first'   // Extensions in filenames begin after the first dot
        },
      ],
    },
  },
});

rename 屬性

rename 屬性是唯一的,因為它唯一有效的值是 JavaScript 函數。儘管函數會傳回字串,但您不能僅將字串用作 rename 的值(這麼做會產生錯誤:物件 # 的 'rename' 屬性不是函數)。在以下範例中,copy 任務會建立 README.md 的備份。

grunt.initConfig({
  copy: {
    backup: {
      files: [{
        expand: true,
        src: ['docs/README.md'],    // The README.md file has been specified for backup
        rename: function () {       // The value for rename must be a function
          return 'docs/BACKUP.txt'; // The function must return a string with the complete destination
        }
      }]
    }
  }
});

當函數被呼叫時,會傳入 dest 和匹配的 src 路徑,可用於傳回輸出字串。在以下範例中,檔案會從 dev 資料夾複製到 dist 資料夾,並重新命名為移除「beta」字樣的檔案。

grunt.initConfig({
  copy: {
    production: {
      files: [{
        expand: true,
        cwd: 'dev/',
        src: ['*'],
        dest: 'dist/',
        rename: function (dest, src) {          // The `dest` and `src` values can be passed into the function
          return dest + src.replace('beta',''); // The `src` is being renamed; the `dest` remains the same
        }
      }]
    }
  }
});

如果多個匹配的 src 路徑重新命名為相同的目的地(也就是說,如果兩個不同的檔案重新命名為同一個檔案),每個輸出都會新增到它的來源陣列中。

範本

使用 <% %> 分隔符指定的範本會在任務從設定檔中讀取它們時自動展開。範本會遞迴展開,直到沒有更多範本為止。

整個設定檔物件是解析屬性的內容。此外,grunt 及其方法在範本中可用,例如 <%= grunt.template.today('yyyy-mm-dd') %>

  • <%= prop.subprop %> 展開到設定檔中 prop.subprop 的值,不論類型為何。像這樣的範本可用於參照不只是字串值,還有陣列或其他物件。
  • <% %> 執行任意的內嵌 JavaScript 程式碼。這對於控制流程或迴圈很有用。

給定以下範例 concat 任務設定,執行 grunt concat:sample 會產生一個名為 build/abcde.js 的檔案,方法是將標語 /* abcde */ 與所有符合 foo/*.js + bar/*.js + baz/*.js 的檔案串接。

grunt.initConfig({
  concat: {
    sample: {
      options: {
        banner: '/* <%= baz %> */\n',   // '/* abcde */\n'
      },
      src: ['<%= qux %>', 'baz/*.js'],  // [['foo/*.js', 'bar/*.js'], 'baz/*.js']
      dest: 'build/<%= baz %>.js',      // 'build/abcde.js'
    },
  },
  // Arbitrary properties used in task configuration templates.
  foo: 'c',
  bar: 'b<%= foo %>d', // 'bcd'
  baz: 'a<%= bar %>e', // 'abcde'
  qux: ['foo/*.js', 'bar/*.js'],
});

匯入外部資料

在以下 Gruntfile 中,專案的元資料會從 package.json 檔案匯入到 Grunt 設定檔,而 grunt-contrib-uglify 外掛程式uglify 任務會設定為縮小來源檔案並使用該元資料動態產生標語註解。

Grunt 有 grunt.file.readJSONgrunt.file.readYAML 方法,用於匯入 JSON 和 YAML 資料。

grunt.initConfig({
  pkg: grunt.file.readJSON('package.json'),
  uglify: {
    options: {
      banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'
    },
    dist: {
      src: 'src/<%= pkg.name %>.js',
      dest: 'dist/<%= pkg.name %>.min.js'
    }
  }
});