UNPKG

gulp-chef

Version:

Cascading configurable recipes for gulp 4.0. An elegant, intuitive way to reuse gulp tasks.

1,742 lines (1,319 loc) 58.9 kB
# gulp-chef 支援 Gulp 4.0,允許巢狀配置任務及組態。以優雅、直覺的方式,重複使用 gulp 任務。 寫程式的時候你謹守 DRY 原則,那編寫 gulpfile.js 的時候,為什麼不呢? 注意:此專案目前仍處於早期開發階段,因此可能還存有錯誤。請協助回報問題並分享您的使用經驗,謝謝! [![加入在 https://gitter.im/gulp-cookery/gulp-chef 上的討論](https://badges.gitter.im/gulp-cookery/gulp-chef.svg)](https://gitter.im/gulp-cookery/gulp-chef?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## 功能 * 支援 Gulp 4.0, * 自動載入本地通用任務 (recipe), * 支援透過 npm 安裝 plugin, * 支援巢狀任務並且允許子任務繼承組態配置, * 支援向前、向後參照任務, * 透過組態配置即可處理串流:譬如 merge, queue, 或者 concat, * 透過組態配置即可控制子任務的執行: parallel 或者 series, * 支援條件式組態配置, * 支援命令列指令,查詢可用的 recpies 及使用方式,以及 * 支援命令列指令,查詢可用的任務說明及其組態配置。 ## 問與答 ### 問: gulp-chef 違反了 gulp 的『編碼優於組態配置 (preferring code over configuration)』哲學嗎? __答__: 沒有, 你還是像平常一樣寫程式, 並且將可變動部份以組態配置的形式萃取出來。 Gulp-chef 透過簡化以下的工作來提昇使用彈性: * [分割任務到不同的檔案](https://github.com/gulpjs/gulp/blob/master/docs/recipes/split-tasks-across-multiple-files.md),以及 * [讓任務可分享並立即可用](https://github.com/gulpjs/gulp/tree/master/docs/recipes)。 ### 問: 有其它類似的替代方案嗎? __答__: 有,像 [gulp-cozy](https://github.com/lmammino/gulp-cozy), [gulp-load-subtasks](https://github.com/skorlir/gulp-load-subtasks), [gulp-starter](https://github.com/vigetlabs/gulp-starter), [elixir](https://github.com/laravel/elixir), 還有[更多其他方案](https://github.com/search?utf8=%E2%9C%93&q=gulp+recipes&type=Repositories&ref=searchresults)。 ### 問: 那麼,跟其它方案比起來,gulp-chef 的優勢何在? __答__: * Gulp-chef 不是侵入式的。它不強迫也不限定你使用它的 API 來撰寫通用任務 (recipe)。 * Gulp-chef 強大且易用。它提供了最佳實務作法,如:合併串流、序列串流等。這表示,你可以讓任務『[只做一件事並做好 (do one thing and do it well)](https://en.wikipedia.org/wiki/Unix_philosophy)』,然後使用組態配置來組合任務。 * Gulp-chef 本身以及共享任務 (plugin) 都是標準的 node 模組。你可以透過 npm 安裝並管理依賴關係,不再需要手動複製工具程式庫或任務程式碼,不再需要擔心忘記更新某個專案的任務,或者擔心專案之間的任務版本因各自修改而導致不一致的狀況。 * Gulp-chef 提供極大的彈性,讓你依喜好方式決定如何使用它: 『[最精簡 (minimal)](https://github.com/gulp-cookery/example-minimal-configuration)』 或 『[最全面 (maximal)](https://github.com/gulp-cookery/example-recipes-demo)』,隨你選擇。 ## 入門 ### 將 gulp cli 4.0 安裝為公用程式 (全域安裝) Gulp-chef 目前僅支援 gulp 4.0。如果你還沒開始使用 gulp 4.0,你需要先將全域安裝的舊 gulp 版本替換為新的 gulp-cli。 ``` bash npm uninstall -g gulp ``` ``` bash npm install -g "gulpjs/gulp-cli#4.0" ``` 不用擔心,新的 gulp-cli 同時支援 gulp 4.0 與 gulp 3.x。所以你可以在既有的專案中繼續使用 gulp 3.x。 ### 將 gulp 4.0 安裝為專案的 devDependencies ``` bash npm install --save-dev "gulpjs/gulp#4.0" ``` 更詳細的安裝及 Gulp 4.0 入門請參閱 <<[Gulp 4.0 前瞻](http://segmentfault.com/a/1190000002528547)>> 這篇文章。 ### 將 gulp-chef 安裝為專案的 devDependencies ``` bash $ npm install --save-dev gulp-chef ``` ### 根據你的專案的需要,安裝相關的 plugin 為專案的 devDependencies ``` bash npm install --save-dev gulp-ccr-browserify gulp-ccr-postcss browserify-shim stringify stylelint postcss-import postcss-cssnext lost cssnano ``` ### 在專案根目錄建立 gulpfile.js 檔案 ``` javascript var gulp = require('gulp'); var chef = require('gulp-chef'); var ingredients = { src: 'src/', dest: 'dist/', clean: {}, make: { postcss: { src: 'styles.css', processors: { stylelint: {}, import: {}, cssnext: { features: { autoprefixer: { browser: 'last 2 versions' } } }, lost: {}, production: { cssnano: {} } } }, browserify: { bundle: { entry: 'main.js', file: 'scripts.js', transform: ['stringify', 'browserify-shim'], production: { uglify: true } } }, assets: { src: [ 'index.html', 'favicon.ico', 'opensearch.xml' ], recipe: 'copy' } }, build: ['clean', 'make'], default: 'build' }; var meals = chef(ingredients); gulp.registry(meals); ``` ### 執行 Gulp ``` bash $ gulp ``` ## 參考範例 * [example-minimal-configuration](https://github.com/gulp-cookery/example-minimal-configuration) 示範只將 gulp-chef 作為任務的黏合工具,所有的任務都是沒有組態配置、單純的 JavaScript 函數。 * [example-recipes-demo](https://github.com/gulp-cookery/example-recipes-demo) 根據 [gulp-cheatsheet](https://github.com/osscafe/gulp-cheatsheet) 的範例,展示 gulp-chef 的能耐。通常不建議以這裡採用的方式配置任務及組態。 * [example-todomvc-angularjs-browserify](https://github.com/gulp-cookery/example-todomvc-angularjs-browserify) 根據現成完整可運作的範例程式 [angularjs-gulp-example](https://github.com/jhades/angularjs-gulp-example),示範如何將普通的 gulpfile.js 改用 gulp-chef 來撰寫。同時不要錯過了來自範例作者的好文章: "[A complete toolchain for AngularJs - Gulp, Browserify, Sass](http://blog.jhades.org/what-every-angular-project-likely-needs-and-a-gulp-build-to-provide-it/)"。 * [example-webapp-seed](https://github.com/gulp-cookery/example-webapp-seed) 一個簡單的 web app 種子專案。同時也可以當做是一個示範使用本地 recipe 的專案。 ## 用語說明 ### Gulp Task 一個 gulp task 只是普通的 JavaScript 函數,函數可以回傳 Promise, Observable, Stream, 子行程,或者是在完成任務時呼叫 `done()` 回呼函數。從 Gulp 4.0 開始,函數被呼叫時,其執行環境 (context),也就是 `this` 值,是 `undefined```` javascript function gulpTask(done) { assert(this === null); // do things ... done(); } ``` 必須使用 `gulp.task()` 函數註冊之後,函數才會成為 gulp task。 ``` javascript gulp.task(gulpTask); ``` 然後才能在命令列下執行。 ``` bash $ gulp gulpTask ``` ### <a href="#" id="configurable-task"></a> Configurable Task 一個可組態配置的 gulp 任務在 gulp-chef 中稱為 configurable task,其函數參數配置與普通 gulp task 相同。但是被 gulp-chef 呼叫時,gulp-chef 將傳遞一個 `{ gulp, config, upstream }` 物件做為其執行環境 (context)。 ``` javascript // 注意: configurable task 不能直接撰寫,必須透過配置組態的方式來產生。 function configurableTask(done) { done(); } ``` 你不能直接撰寫 configurable task,而是必須透過定義組態配置,並呼叫 `chef()` 函數來產生。 ``` javascript var gulp = require('gulp'); var chef = require('gulp-chef'); var meals = chef({ scripts: { src: 'src/**/*.js', dest: 'dist/' } }); gulp.registry(meals); ``` 在這個範例中,gulp-chef 為你建立了一個名為 "`scripts`" 的 configurable task。注意 `chef()` 函數回傳一個 gulp registry 物件,你可以透過回傳的 gulp registry 物件,以 `meals.get('scripts')` 的方式取得該 configurable task。但是通常你會呼叫 `gulp.registry()` 來註冊所有包含在 registry 之中的任務。 ``` javascript gulp.registry(meals); ``` 一旦你呼叫了 `gulp.registry()` 之後,你就可以在命令列下執行那些已註冊的任務。 ``` bash $ gulp scripts ``` 當 configurable task 被呼叫時,將連同其一起配置的組態,經由其執行環境傳入,大致上是以如下的方式呼叫: ``` javascript scripts.call({ gulp: gulp, config: { src: 'src/**/*.js', dest: 'dist/' } }, done); ``` 另外注意到在這個例子中,在組態中的 "`scripts`" 項目,實際上對應到一個 recipe 或 plugin 的模組名稱。如果是 recipe 的話,該 recipe 模組檔案必須位於專案的 "`gulp`" 目錄下。如果對應的是 plugin 的話,該 plugin 必須先安裝到專案中。更多細節請參考『[撰寫 recipe](#writing-recipes)』及『[使用 plugin](#using-plugins)』的說明。 ### Configurable Recipe 一個支援組態配置,可供 gulp 重複使用的任務,在 gulp-chef 中稱為 configurable recipe <sub>[註]</sub>,其函數參數配置也與普通 gulp task 相同,在被 gulp-chef 呼叫時,gulp-chef 也將傳遞一個 `{ gulp, config, upstream }` 物件做為其執行環境 (context)。這是你真正撰寫,並且重複使用的函數。事實上,前面提到的 "[configurable task](#configurable-task)",就是透過名稱對應的方式,在組態配置中對應到真正的 configurable recipe,然後加以包裝、註冊為 gulp 任務。 ``` javascript function scripts(done) { // 注意:在 configurable recipe 中,你可以直接由 context 取得 gulp 實體。 var gulp = this.gulp; // 注意:在 configurable recipe 中,你可以直接由 context 取得 config 組態。 var config = this.config; // 用力 ... done(); } ``` 註:在 gulp-chef 中,recipe 意指可重複使用的任務。就像一份『食譜』可以用來做出無數的菜餚一樣。 ## 撰寫組態配置 組態配置只是普通的 JSON 物件。組態中的每個項目、項目的子項目,要嘛是屬性 (property),要不然就是子任務。 ### 巢狀任務 任務可以巢狀配置。子任務依照組態的語法結構 (lexically),或稱靜態語彙結構 (statically),以層疊結構 (cascading) 的形式繼承 (inherit) 其父任務的組態。更棒的是,一些預先定義的屬性,譬如 "`src`", "`dest`" 等路徑性質的屬性,gulp-chef 會自動幫你連接好路徑。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', build: { scripts: { src: '**/*.js' }, styles: { src: '**/*.css' } } }); ``` 這個例子建立了__三個__ configurable tasks 任務:`build`, `scripts` 以及 `styles`。 ### 並行任務 在上面的例子中,當你執行 `build` 任務時,它的子任務 `scripts` 和 `styles` 會以並行 (parallel) 的方式同時執行。並且由於繼承的關係,它們將獲得如下的組態配置: ``` javascript scripts: { src: 'src/**/*.js', dest: 'dist/' }, styles: { src: 'src/**/*.css', dest: 'dist/' } ``` ### 序列任務 如果你希望任務以序列 (series) 的順序執行,你可以使用 "`series`" __流程控制器 (flow controller)__,並且在子任務的組態配置中,加上 "`order`" 屬性: ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', build: { series: { scripts: { src: '**/*.js', order: 0 }, styles: { src: '**/*.css', order: 1 } } } }); ``` 記住,你必須使用 "`series`" 流程控制器,子任務才會以序列的順序執行,僅僅只是加上 "`order`" 屬性並不會達到預期的效果。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', build: { scripts: { src: '**/*.js', order: 0 }, styles: { src: '**/*.css', order: 1 } } }); ``` 在這個例子中,`scripts` 和 `styles` 會以並行的方式同時執行。 其實有更簡單的方式,可以使子任務以序列的順序執行:使用陣列。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', build: [{ name: 'scripts', src: '**/*.js' }, { name: 'styles', src: '**/*.css' }] }; ``` 不過,看起來似乎有點可笑?別急,請繼續往下看。 ### 參照任務 你可以使用名稱來參照其他任務。向前、向後參照皆可。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', clean: {}, scripts: { src: '**/*.js' }, styles: { src: '**/*.css' }, build: ['clean', 'scripts', 'styles'] }; ``` 在這個例子中,`build` 任務有三個子任務,分別參照到 `clean`, `scripts` 以及 `styles` 任務。參照任務並不會產生並註冊新的任務,所以,在這個例子中,你無法直接執行 `build` 任務的子任務,但是你可以透過執行 `build` 任務執行它們。 前面提到過,子任務依照組態的語法結構 (lexically),或稱靜態語彙結構 (statically),以層疊結構 (cascading) 的形式繼承 (inherit) 其父任務的組態。既然『被參照的任務』不是定義在『參照任務』之下,『被參照的任務』自然不會繼承『參照任務』及其父任務的靜態組態配置。不過,有另一種組態是執行時期動態產生的,動態組態會在執行時期注入到『被參照的任務』。更多細節請參考『[動態組態](#dynamic-configuration)』的說明。 在這個例子中,由於使用陣列來指定參照 `clean`, `scripts` 及 `styles` 的任務,所以是以序列的順序執行。你可以使用 "`parallel`" 流程控制器改變這個預設行為。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', clean: {}, scripts: { src: '**/*.js' }, styles: { src: '**/*.css' }, build: ['clean', { parallel: ['scripts', 'styles'] }] }; ``` 或者,其實你可以將子任務以物件屬性的方式,放在一個共同父任務之下,這樣它們就會預設以並行的方式執行。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', clean: {}, make: { scripts: { src: '**/*.js' }, styles: { src: '**/*.css' } }, build: ['clean', 'make'] }); ``` 你可以另外使用 "`task`" 關鍵字來引用『被參照的任務』,這樣『參照任務』本身就可以同時擁有其他屬性。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', clean: {}, make: { scripts: { src: '**/*.js' }, styles: { src: '**/*.css' } }, build: { description: 'Clean and make', task: ['clean', 'make'] }, watch: { description: 'Watch and run related task', options: { usePolling: true }, task: ['scripts', 'styles'] } }; ``` ### 純函數 / 內聯函數 任務也可以以普通函數的方式定義並且直接引用,或以內聯匿名函數的形式引用。 ``` javascript function clean() { return del(this.config.dest.path); } var meals = chef({ src: 'src/', dest: 'dist/', scripts: function (done) { }, styles: function (done) { }, build: [clean, { parallel: ['scripts', 'styles'] }] }; ``` 注意在這個例子中,在組態配置中並未定義 `clean` 項目,所以`clean` 並不會被註冊為 gulp task。 另外一個需要注意的地方是,即使只是純函數,gulp-chef 呼叫時,總是會以 `{ gulp, config, upstream }` 做為執行環境來呼叫。 你一樣可以使用 "`task`" 關鍵字來引用函數,這樣任務本身就可以同時擁有其他屬性。 ``` javascript function clean() { return del(this.config.dest.path); } var meals = chef({ src: 'src/', dest: 'dist/', clean: { options: { dryRun: true }, task: clean }, make: { scripts: { src: '**/*.js', task: function (done) { } }, styles: { src: '**/*.css', task: function (done) { } } }, build: ['clean', 'make'], watch: { options: { usePolling: true }, task: ['scripts', 'styles'] } }; ``` 注意到與上個例子相反地,在這裡組態配置中定義了 `clean` 項目,因此 gulp-chef 會產生並註冊 `clean` 任務,所以可以由命令列執行`clean` 任務。 ### 隱藏任務 有時候,某些任務永遠不需要單獨在命令列下執行。隱藏任務可以讓任務不要註冊,同時不可被其它任務引用。隱藏一個任務不會影響到它的子任務,子任務仍然會繼承它的組態配置並且註冊為 gulp 任務。隱藏任務仍然是具有功能的,但是只能透過它的父任務執行。 要隱藏一個任務,可以在項目的組態中加入具有 "`hidden`" 值的 "`visibility`" 屬性。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', scripts: { concat: { visibility: 'hidden', file: 'bundle.js', src: 'lib/', coffee: { src: '**/*.coffee' }, js: { src: '**/*.js' } } } }; ``` 在這個例子中,`concat` 任務已經被隱藏了,然而它的子任務 `coffee` 和 `js` 依然可見。 為了簡化組態配置,你也可以使用在任務名稱前面附加上一個 "`.`" 字元的方式來隱藏任務,就像 UNIX 系統的 [dot-files](https://en.wikipedia.org/wiki/Dot-file) 一樣。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', scripts: { '.concat': { file: 'bundle.js', src: 'lib', coffee: { src: '**/*.coffee' }, js: { src: '**/*.js' } } } }; ``` 這將產出與上一個例子完全相同的結果。 ### 停用任務 有時候,當你在調整 gulpfile.js 時,你可能需要暫時移除某些任務,找出發生問題的根源。這時候你可以停用任務。停用任務時,連同其全部的子任務都將被停用,就如同未曾定義過一樣。 要停用一個任務,可以在項目的組態中加入具有 "`disabled`" 值的 "`visibility`" 屬性。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', scripts: { concat: { file: 'bundle.js', src: 'lib/', coffee: { visibility: 'disabled', src: '**/*.coffee' }, js: { src: '**/*.js' } } } }; ``` 在這個例子中,`coffee` 任務已經被停用了。 為了簡化組態配置,你也可以使用在任務名稱前面附加上一個 "`#`" 字元的方式來停用任務,就像 UNIX 系統的 bash 指令檔的註解一樣。 ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', scripts: { concat: { file: 'bundle.js', src: 'lib', '#coffee': { src: '**/*.coffee' }, js: { src: '**/*.js' } } } }; ``` 這將產出與上一個例子完全相同的結果。 ### 處理命名衝突問題 在使用 gulp-chef 時,建議你為所有的任務,分別取用唯一、容易區別的名稱。 然而,如果你有非常多的任務,那麼將有很高的機率,有一個以上的任務必須使用相同的 recipe 或 plugin。 在預設情況下,任務名稱必須與 recipe 名稱相同,這樣 gulp-chef 才有辦法找到對應的 recipe。那麼,當發生名稱衝突時,gulp-chef 是怎麼處理的呢?gulp-chef 會自動為發生衝突的的任務,在前方附加父任務的名稱,像這樣:"`make:scripts:concat`"。 事實上,你也可以將這個附加名稱的行為變成預設行為:在呼叫 `chef()` 函數時,在 `settings` 參數傳入值為 `true`"`exposeWithPrefix`" 屬性即可。 "`exposeWithPrefix`" 屬性的預設值為 `"auto"```` javascript var ingredients = { ... }; var settings = { exposeWithPrefix: true }; var meals = chef(ingredients, settings); ``` 不是你的菜?沒關係,你也可以使用其他辦法。 #### 引入新的父任務並隱藏名稱衝突的任務 ``` javascript { scripts: { concatScripts: { '.concat': { file: 'bundle.js' } } }, styles: { concatStyles: { '.concat': { file: 'main.css' } } } } ``` #### 使用 `recipe` 關鍵字 ``` javascript { scripts: { concatScripts: { recipe: 'concat', file: 'bundle.js' } }, styles: { concatStyles: { recipe: 'concat', file: 'main.css' } } } ``` 注意:為了盡量避免發生名稱衝突的可能性,並且簡化任務樹,某些特定種類的任務預設是隱藏的。主要是『__串流處理器 (stream processor)__』及『__流程控制器 (flow controller)__』。請參考 [撰寫串流處理器](#writing-stream-processor) and [撰寫流程控制器](#writing-flow-controller) 的說明。 ### 使用 Gulp Plugins 有時候,你所撰寫的任務所做的,只不過是轉呼叫一個 plugin。如果只是這樣的話,事實上你完全可以不用費心寫一個 recipe,你可以直接在組態配置中使用 "`plugin`" 關鍵字做為屬性來引用 plugin。 ``` javascript { concat: { plugin: 'gulp-concat', options: 'bundle.js' } } ``` 這個 "`plugin`" 屬性可以接受 `string``function` 類型的值。當指定的值不是 `function` 而是 `string` 類型時,gulp-chef 將以此字串做為模組名稱,嘗試去 "`require()`" 該模組。使用 "`plugin`" 屬性時,另外還可以指定 "`options`" 屬性,該屬性的值將直接做為唯一參數,用來呼叫 plugin 函數。 任何 gulp plugin,只要它只接受 01 個參數,並且回傳一個 Stream 或 Promise 物件,就可以使用 `plugin`" 關鍵字來加以引用。前提當然是 plugin 已經使用 `npm install` 指令先安裝好了。 千萬不要將 gulp plugin 與 [gulp-chef 專用的 plugin](#using-plugins) 搞混了。gulp-chef 專用的 plugin 稱為 "Cascading Configurable Recipe for Gulp" 或簡稱 "gulp-ccr",意思是『可層疊組態配置、可重複使用的 Gulp 任務』。 ### 傳遞組態值 如同你到目前為止所看到的,在組態配置中的項目,要嘛是任務的屬性,要不然就是子任務。你要如何區別兩者?基本的規則是,除了 "`config`", "`description`", "`dest`", "`name`", "`order`", "`parallel`", "`plugin`", "`recipe`", "`series`", "`spit`", "`src`", "`task`" 以及 "`visibility`" 這些[關鍵字](#keywords)之外,其餘的項目都將被視為子任務。 那麼,你要如何傳遞組態值給你的 recipe 函數呢?其實,"`config`" 關鍵字就是特地為了這個目的而保留的。 ``` javascript { myPlugin: { config: { file: 'bundle.js' } } } ``` 這裡 "`config`" 屬性連同其 "`file`" 屬性,將一起被傳遞給 recipe 函數,而 recipe 函數則透過執行環境依序取得 "`config`" 屬性及 "`file`" 屬性 (在『[撰寫 recipe](#writing-recipes)』中詳細說明)。 ``` javascript function myPlugin(done) { var file = this.config.file; done(); } module.exports = myPlugin; ``` 只為了傳遞一個屬性,就必須特地寫一個 "`config`" 項目來傳遞它,如果你覺得這樣做太超過了,你也可以直接在任意屬性名稱前面附加一個 "`$`" 字元,這樣它們就會被視為是組態屬性,而不再會被當作是子任務。 ``` javascript { myPlugin: { $file: 'bundle.js' } } ``` 這樣 "`$file`" 項目就會被當作是組態屬性,而你在組態配置及 recipe 中,可以透過 "`file`" 名稱來存取它。 (注意,名稱不是 "`$file`",這是為了允許使用者可以交換使用 "`$`" 字元和 "`config`" 項目來傳遞組態屬性。) #### Recipe / Plugin 專屬組態屬性 Recipe 以及 plugin 可以使用 [JSON Schema](http://json-schema.org/) 來定義它們的組態屬性及架構。如果它們確實定義了組態架構,那麼你就可以在組態配置項目中,直接列舉專屬的屬性,而不需要透過 "`$`" 字元和 "`config`" 關鍵字。 舉例,在 "[gulp-ccr-browserify](https://github.com/gulp-cookery/gulp-ccr-browserify)" plugin 中,它定義了 "`bundles`" 及 "`options`" 屬性,因此你可以在組態項目中直接使用這兩個屬性。 原本需要這樣寫: ``` javascript { src: 'src/', dest: 'dest/', browserify: { config: { bundles: { entry: 'main.ts' }, options: { plugins: 'tsify', sourcemaps: 'external' } } } } ``` 現在可以省略寫成這樣: ``` javascript { src: 'src/', dest: 'dest/', browserify: { bundles: { entry: 'main.ts' }, options: { plugins: 'tsify', sourcemaps: 'external' } } } ``` #### 自動識別屬性 為了方便起見,當組態項目中包含有 "`task`", "`series`", "`parallel`" 或 "`plugin`" 關鍵字的時候,這時候除了保留屬性之外,其餘的屬性都將自動認定為組態屬性,而不是子任務。 ### <a href="#" id="dynamic-configuration"></a> 動態組態屬性 / 模板引值 有些『串流處理器』 (譬如 "[gulp-ccr-each-dir](https://github.com/gulp-cookery/gulp-ccr-each-dir)"),會以程序化或動態的方式產生新的組態屬性。這些新產生的屬性,將在執行時期,插入到子任務的的組態中。除了 recipe 及 plugin 可以透過 "`config`" 屬性取得這些值之外,子任務也可以透過使用模板的方式,以 "`{{var}}`" 這樣的語法,直接在組態中引用這些值。 ``` javascript { src: 'src/', dest: 'dist/', 'each-dir': { dir: 'modules/', concat: { file: '{{dir}}', spit: true } } } ``` 這個例子裡,"[each-dir](https://github.com/gulp-cookery/gulp-ccr-each-dir)" plugin 會根據 "`dir`" 屬性指定的內容,也就是 "`modules`" 目錄,找出其下的所有子目錄,然後產生新的 "`dir`" 屬性,透過這個屬性將子目錄資訊傳遞給每個子任務 (這裡只有 "concat" 任務)。子任務可以透過 "`config`" 屬性讀取這個值。使用者也可以使用 "`{{dir}}`" 這樣的語法,在組態配置中引用這個值。 ### 條件式組態配置 Gulp-chef 支援條件式組態配置。可以透過設定執行時期環境的模式來啟用不同的條件式組態配置。這個功能的實作是基於 [json-regulator](https://github.com/amobiz/json-regulator?utm_referer="gulp-chef") 這個模組,可以參考該模組的說明以便獲得更多的相關資訊。 預設提供了 `development`, `production``staging` 三個模式。你可以在組態配置中,將相關的組態內容,分別寫在對應的 `development``dev`, `production``prod` ,或 `staging` 項目之下。 譬如,如果將組態配置寫成這樣: ``` javascript { scripts: { // common configs src: 'src/', development: { // development configs description: 'development mode', dest: 'build/', options: { // development options debug: true }, // sub tasks for development mode lint: { } }, production: { // production configs description: 'production mode', dest: 'dist/', options: { // production options debug: false } }, options: { // common options dev: { // development options description: 'development mode', sourcemap: false }, prod: { // production options description: 'production mode', sourcemap: 'external' } }, // sub tasks pipe: [{ typescript: { src: '**/*.ts' }, js: { src: '**/*.js' } }, { production: { // production configs description: 'production mode', // sub tasks for production mode uglify: { } } }, { production: { // production configs description: 'production mode', // sub tasks for production mode concat: { } } }] } } ``` 當啟用 `development` 模式時,組態配置將被轉換為: ``` javascript { scripts: { src: 'src/', description: 'development mode', dest: 'build/', options: { description: 'development mode', sourcemap: false, debug: true }, lint: { }, pipe: [{ typescript: { src: '**/*.ts' }, js: { src: '**/*.js' } }] } } ``` 而啟用 `production` 模式時,組態配置將被轉換為: ``` javascript { scripts: { src: 'src/', description: 'production mode', dest: 'dist/', options: { description: 'production mode', sourcemap: 'external', debug: false }, pipe: [{ typescript: { src: '**/*.ts' }, js: { src: '**/*.js' } }, { description: 'production mode', uglify: { } }, { description: 'production mode', concat: { } }] } } ``` 超強的! #### 以特定的執行時期環境模式啟動 Gulp ##### 經由命令列參數 ``` bash $ gulp --development build ``` 也可以使用簡寫: ``` bash $ gulp --dev build ``` ##### 經由環境變數 在 Linux/Unix 下: ``` bash $ NODE_ENV=development gulp build ``` 同樣地,若使用簡寫: ``` bash $ NODE_ENV=dev gulp build ``` #### 自訂執行時期環境模式 Gulp-chef 允許你自訂執行時期環境模式。如果你崇尚極簡主義,你甚至可以分別使用 `d`, `p``s` 代表 `development`, `production``staging` 模式。只是要記得,組態配置必須與執行時期環境模式配套才行。 ``` javascript var ingredients = { scripts: { src: 'src/', lint: { }, d: { debug: true }, p: { debug: false, sourcemap: 'external', uglify: { }, concat: { } } } }; var settings = { modes: { production: ['p'], development: ['d'], staging: ['s'], default: 'production' } }; var meals = chef(ingredients, settings); ``` 注意到在 `settings.modes` 之下的 `default` 屬性。這個屬性不會定義新的模式,它是用來指定預設的模式。如果沒有指定 `settings.modes.default` ,那麼,預設模式會成為列在 `settings.modes` 之下的第一個模式。建議最好不要省略。 除了改變模式的代號,你甚至可以設計自己的模式,並且還能一次提供多個代號。 ``` javascript var settings = { modes = { build: ['b', 'build'], compile: ['c', 'compile'], deploy: ['d', 'deploy', 'deployment'], review: ['r', 'review'] default: 'build' } }; ``` 但是要注意的是,不要使用到保留給任務使用的[關鍵字](#keywords)。 ## 內建的 Recipe #### [clean](https://github.com/gulp-cookery/gulp-ccr-clean) 清除 `dest` 屬性指定的目錄。 #### [copy](https://github.com/gulp-cookery/gulp-ccr-copy) 複製由 `src` 屬性指定的檔案,到由 `dest` 屬性指定的目錄,可以選擇是否移除或改變檔案的相對路徑。 #### [merge](https://github.com/gulp-cookery/gulp-ccr-merge) 這是一個串流處理器。回傳一個新的串流,該串流只有在所有的子任務的串流都停止時才會停止。 更多資訊請參考 [merge-stream](https://www.npmjs.com/package/merge-stream) 。 #### [queue](https://github.com/gulp-cookery/gulp-ccr-queue) 這是一個串流處理器。可以匯集子任務所回傳的串流,並回傳一個新的串流,該串流會將子任務回傳的串流,依照子任務的順序排列在一起。 更多資訊請參考 [streamqueue](https://www.npmjs.com/package/streamqueue) 。 #### [pipe](https://github.com/gulp-cookery/gulp-ccr-pipe) 這是一個串流處理器。提供與 [`stream.Readable.pipe()`](https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options) 相同的功能。方便在子任務之間遞送 (pipe) 串流。 #### [parallel](https://github.com/gulp-cookery/gulp-ccr-parallel) 這是一個流程控制器。會以並行 (parallel) 的方式執行子任務,子任務之間不會互相等待。 #### [series](https://github.com/gulp-cookery/gulp-ccr-series) 這是一個流程控制器。會以序列 (series) 的方式執行子任務,前一個子任務結束之後才會執行下一個子任務。 #### [watch](https://github.com/gulp-cookery/gulp-ccr-watch) 這是一個流程控制器。負責監看指定的子任務、以及其所有子任務的來源檔案,當有任何檔案異動時,執行對應的指定任務。 ## <a href="#" id="using-plugins"></a> 使用 Plugin 在你撰寫自己的 recipe 之前,先看一下別人已經做了哪些東西,也許有現成的可以拿來用。你可以使用" `gulp recipe`",或者,更建議使用 "`gulp-ccr`",在 [github.com](https://github.com/search?utf8=%E2%9C%93&q=gulp-ccr) 和 [npmjs.com](https://www.npmjs.com/search?q=gulp-ccr) 上搜尋。這個 "`gulp-ccr`""Cascading Configurable Recipe for Gulp" 的簡寫,意思是『可層疊組態配置、可重複使用的 Gulp 任務』。 一旦你找到了,譬如,[`gulp-ccr-browserify`](https://github.com/gulp-cookery/gulp-ccr-browserify) ,將它安裝為專案的 devDependencies: ``` bash $ npm install --save-dev gulp-ccr-browserify ``` Gulp-chef 會為你移除附加在前面的 "`gulp-ccr-`" 名稱,所以你在使用 plugin 的時候,請移除 "`gulp-ccr-`" 部份。 ``` javascript { browserify: { description: 'Using the gulp-ccr-browserify plugin' } } ``` ## <a href="#" id="writing-recipes"></a> 撰寫 Recipe 斯斯,不是,recipe 有三種: __任務型 (task)__、__串流處理器 (stream processor)__ 以及 __流程控制器 (flow controller)__。 大多數時候,你想要寫的是任務型 recipe。任務型 recipe 負責做苦工,而串流處理器及流程控制器則負責操弄其它 recipe。 更多關於串流處理器及流程控制器的說明,或者你樂於分享你的 recipe,你可以寫成 plugin,請參考 [撰寫 Plugin](#writing-plugins) 的說明。 如果你撰寫的 recipe 只打算給特定專案使用,你可以將它們放在專案根目錄下的特定子目錄下: 類型 |目錄 ---------|------------------ 任務型 |gulp, gulp/tasks 串流處理器 |gulp/streams 流程控制器 |gulp/flows 如果你的 recipe 不需要組態配置,你可以像平常寫 gulp task 一樣的方式撰寫 recipe。知道這代表什麼意思嗎?這代表你以前寫的 gulp task 都可以直接拿來當作 recipe 用。你只需要將它們個別存放到專屬的模組檔案,然後放到專案根目錄下的 "gulp" 目錄下即可。 使用 recipe 的時候,在組態配置中,使用一個屬性名稱與 recipe 模組名稱一模一樣的項目來引用該 recipe。 譬如,假設你有一個 "`my-recipe.js`" recipe 放在 `<your-project>/gulp` 目錄下。可以這樣撰寫組態配置來引用它: ``` javascript var gulp = require('gulp'); var chef = require('gulp-chef'); var meals = chef({ "my-recipe": {} }); gulp.registry(meals); ``` 就是這麼簡單。之後你就可以在命令列下,以 `gulp my-recipe` 指令執行它。 然而,提供組態配置的能力,才能最大化 recipe 的重複使用價值。 要讓 recipe 可以處理組態內容,可以在 recipe 函數中,透過執行環境,也就是 `this` 變數,取得組態。 ``` javascript function scripts(done) { var gulp = this.gulp; var config = this.config; return gulp.src(config.src.globs) .pipe(eslint()) .pipe(concat(config.file)) .pipe(uglify()) .pipe(gulp.dest(config.dest.path)); } module.exports = scripts; ``` 上面的 "`scripts`" recipe,在使用的時候可以像這樣配置: ``` javascript var meals = chef({ src: 'src/', dest: 'dist/', scripts: { src: '**/*.js', file: 'bundle.js' } }); ``` ### Development / Production 模式 Gulp-chef 的 recipe 不需要自行處理條件式組態配置。組態配置在傳遞給 recipe 之前,已經先根據執行環境模式處理完畢。 ## <a href="#" id="writing-plugins"></a> 撰寫 Plugin Gulp-chef 的 plugin,只是普通的 Node.js 模組,再加上一些必要的資訊。 ### Plugin 的類型 在前面 [撰寫 Recipe](#writing-recipes) 的部份提到過,recipe 有三種:__任務型 (task)__、__串流處理器 (stream processor)__ 以及 __流程控制器 (flow controller)__。Gulp-chef 需要知道 plugin 的類型,才能安插必要的輔助功能。由於 plugin 必須使用 `npm install` 安裝,gulp-chef 無法像本地的 recipe 一樣,由目錄決定 recipe 的類型,因此 plugin 必須自行提供類型資訊。 ``` javascript function myPlugin(done) { done(); } module.exports = myPlugin; module.exports.type = 'flow'; ``` 有效的類型為: "`flow`""`stream`" 以及 "`task`"### <a href="#" id="configuration-schema"></a> 組態架構 (Configuration Schema) 為了簡化組態配置的處理過程,gulp-chef 鼓勵使用 [JSON Schema](http://json-schema.org/) 來驗證和轉換組態配置。Gulp-chef 使用 [json-normalizer](https://github.com/amobiz/json-normalizer?utm_referer="gulp-chef") 來為 JSON Schema 提供擴充功能,並且協助將組態內容一般化 (或稱正規化),以提供最大的組態配置彈性。你可以為你的 plugin 定義組態架構,以提供屬性別名、類型轉換、預設值等功能。同時,組態架構的定義內容還可以顯示在命令列中,使用者可以使用指令 `gulp --recipe <recipe-name>` 查詢,不必另外查閱文件,就可以了解如何撰寫組態配置。請參考 [json-normalizer](https://github.com/amobiz/json-normalizer?utm_referer="gulp-chef") 的說明,了解如何定義組態架構,甚至加以擴充。 以下是一個簡單的 plugin,示範如何定義組態架構: ``` javascript var gulpif = require('gulp-if'); var concat = require('gulp-concat'); var sourcemaps = require('gulp-sourcemaps'); var uglify = require('gulp-uglify'); function myPlugin() { var gulp = this.gulp; var config = this.config; var options = this.config.options || {}; var maps = (options.sourcemaps === 'external') ? './' : null; return gulp.src(config.src.globs) .pipe(gulpif(config.sourcemaps, sourcemaps.init()) .pipe(concat(config.file)) .pipe(gulpif(options.uglify, uglify())) .pipe(gulpif(options.sourcemaps, sourcemaps.write(maps))) .pipe(gulp.dest(config.dest.path)); } module.exports = myPlugin; module.exports.type = 'task'; module.exports.schema = { title: 'My Plugin', description: 'My first plugin', type: 'object', properties: { src: { type: 'glob' }, dest: { type: 'path' }, file: { description: 'Output file name', type: 'string' }, options: { type: 'object', properties: { sourcemaps: { description: 'Sourcemap support', alias: ['sourcemap'], enum: [false, 'inline', 'external'], default: false }, uglify: { description: 'Uglify bundle file', type: 'boolean', default: false } } } }, required: ['file'] }; ``` 首先,注意到 "`file`" 被標示為『必須』,plugin 可以利用組態驗證工具自動進行檢查,因此在程式中就不須要再自行判斷。 另外注意到 "`sourcemaps`" 選項允許 "`sourcemap`" 別名,因此使用者可以在組態配置中隨意使用 "`sourcemaps`""`sourcemap`",但是同時在 plugin 中,卻只需要處理 "`sourcemaps`" 即可。 #### 擴充資料型別 Gulp-chef 提供兩個擴充的 JSON Schema 資料型別: "`glob`""`path`"##### glob 一個屬性如果是 "`glob`" 型別,它可以接受一個路徑、一個路徑匹配表達式 (glob),或者是一個由路徑或路徑匹配表達式組成的陣列。另外還可以額外附帶選項資料。 以下都是正確的 "`glob`" 數值: ``` javascript // 一個路徑字串 'src' // 一個由路徑字串組成的陣列 ['src', 'lib'] // 一個路徑匹配表達式 '**/*.js' // 一個由路徑或路徑匹配表達式組成的陣列 ['**/*.{js,ts}', '!test*'] // 非正規化的『物件表達形式』(注意 "glob" 屬性) { glob: '**/*.js' } ``` 上面所有的數值,都會被正規化為所謂的『物件表達形式』: ``` javascript // 一個路徑字串 { globs: ['src'] } // 一個由路徑字串組成的陣列 { globs: ['src', 'lib'] } // 一個路徑匹配表達式 { globs: ['**/*.js'] } // 一個由路徑或路徑匹配表達式組成的陣列 { globs: ['**/*.{js,ts}', '!test*'] } // 正規化之後的『物件表達形式』(注意 "glob" 屬性已經正規化為 "globs") { globs: ['**/*.js'] } ``` 注意到 "`glob`""`globs`" 屬性的別名,在正規化之後,被更正為 "`globs`"。同時,"`glob`" 型別的 "`globs`" 屬性的型態為陣列,因此,所有的值都將自動被轉換為陣列。 當以『物件表達形式』呈現時,還可以使用 "`options`" 屬性額外附帶選項資料。 ``` javascript { globs: ['**/*.{js,ts}', '!test*'], options: { base: 'src', buffer: true, dot: true } } ``` 更多選項資料,請參考 [node-glob](https://github.com/isaacs/node-glob#options) 的說明。 在任務中,任何具有 "`glob`" 型別的組態屬性,都會繼承其父任務的 "`src`" 屬性。這意謂著,當父任務定義了 "`src`" 屬性時,gulp-chef 會為子任務的 "`glob`" 型別的組態屬性,自動連接好父任務的 "`src`" 屬性的路徑。 ``` javascript { src: 'src', browserify: { bundles: { entries: 'main.js' } } } ``` 在這個例子中,"[browserify](https://github.com/gulp-cookery/gulp-ccr-browserify)" plugin 具有一個 "`bundles`" 屬性,"`bundles`" 屬性下又有一個 "`entries`" 屬性,而該屬性為 "`glob`" 型別。這個 "`entries`" 屬性將繼承外部的 "`src`" 屬性,因而變成: `{ globs: "src/main.js" }` 。 如果這不是你要的,你可以指定 "`join`" 選項來覆蓋這個行為。 ``` javascript { src: 'src', browserify: { bundles: { entry: { glob: 'main.js', options: { join: false } } } } } ``` 現在 "`entries`" 屬性的值將成為: `{ globs: "main.js" }` 。 選項 "`join`" 也可以接受字串,用來指定要從哪一個屬性繼承路徑,該屬性必須是 "`glob`""`path`" 型別。 在 plugin 中,也可以透過[組態架構](#configuration-schema)來定義預設要繼承的屬性。請記住,除非有好的理由,請永遠記得同時將 "`options`" 傳遞給呼叫的 API,以便允許使用者指定選項。像這樣: ``` javascript module.exports = function () { var gulp = this.gulp; var config = this.config; return gulp.src(config.src.globs, config.src.options) .pipe(...); } ``` ##### path 一個屬性如果是 "`path`" 型別,它可以接受一個路徑字串。另外還可以額外附帶選項資料。 以下都是正確的 "`path`" 數值: ``` javascript // 一個路徑字串 'dist' // 一個路徑字串 'src/lib/' // 『物件表達形式』 { path: 'maps/' } ``` 上面所有的數值,都會被正規化為所謂的『物件表達形式』: ``` javascript // 一個路徑字串 { path: 'dist' } // 一個路徑字串 { path: 'src/lib/' } // 『物件表達形式』 { path: 'maps/' } ``` 當以『物件表達形式』呈現時,還可以使用 "`options`" 屬性額外附帶選項資料。 ``` javascript { path: 'dist/', options: { cwd: './', overwrite: true } } ``` 更多選項資料,請參考 [gulp.dest()](https://github.com/gulpjs/gulp/blob/4.0/docs/API.md#options-1) 的說明。 在任務中,任何具有 "`path`" 型別的組態屬性,都會繼承其父任務的 "`dest`" 屬性。這意謂著,當父任務定義了 "`dest`" 屬性時,gulp-chef 會為子任務的 "`path`" 型別的組態屬性,自動連接好父任務的 "`dest`" 屬性的路徑。 ``` javascript { dest: 'dist/', scripts: { file: 'bundle.js' } } ``` 假設這裡的 "`file`" 屬性是 "`path`" 型別,它將會繼承外部的 "`dest`" 屬性,而成為: "`{ path: 'dist/bundle.js' }`"。 如果這不是你要的,你可以指定 "`join`" 選項來覆蓋這個行為。 ``` javascript { dest: 'dist/', scripts: { file: { path: 'bundle.js', options: { join: false } } } } ``` 現在 "`file`" 屬性將成為: "`{ path: 'bundle.js' }`"。 選項 "`join`" 也可以接受字串,用來指定要從哪一個屬性繼承路徑,該屬性必須是 "`path`" 型別。 在 plugin 中,也可以透過[組態架構](#configuration-schema)來定義預設要繼承的屬性。請記住,除非有好的理由,請永遠記得同時將 "`options`" 傳遞給呼叫的 API,以便允許使用者指定選項。像這樣: ``` javascript module.exports = function () { var gulp = this.gulp; var config = this.config; return gulp.src(config.src.globs, config.src.options) .pipe(...) .pipe(gulp.dest(config.dest.path, config.dest.options)); } ``` ### <a href="#" id="writing-stream-processor"></a> 撰寫串流處理器 串流處理器負責操作它的子任務輸入或輸出串流。 串流處理器可以自己輸出串流,或者由其中的子任務輸出。串流處理器可以在子任務之間遞送串流;或合併;或串接子任務的串流。任何你能想像得到的處理方式。唯一必要的要求就是:串流處理器必須回傳一個串流。 串流處理器由執行環境中取得 "`tasks`" 屬性,子任務即是經由此屬性,以陣列的方式傳入。 當呼叫子任務時,串流處理器必須為子任務建立適當的執行環境。 ``` javascript module.exports = function () { var gulp = this.gulp; var config = this.config; var tasks = this.tasks; var context, stream; context = { gulp: gulp, // 傳入獲得的組態配置,以便將上層父任務動態插入的組態屬性傳遞給子任務 config: config }; // 如果需要的話,可以額外插入新的組態屬性 context.config.injectedValue = 'hello!'; stream = tasks[0].call(context); // ... return stream; }; ``` 注意父任務可以動態給子任務插入新的組態屬性。只有新的值可以成功插入,若子任務原本就配置了同名的屬性,則新插入的屬性不會覆蓋原本的屬性。 如果要傳遞串流給子任務,串流處理器必須透過 "`upstream`" 屬性傳遞。 ``` javascript module.exports = function () { var gulp = this.gulp; var config = this.config; var tasks = this.tasks; var context, stream, i; context = { gulp: gulp, config: config }; stream = gulp.src(config.src.globs, config.src.options); for (i = 0; i < tasks.length; ++i) { context.upstream = stream; stream = tasks[i].call(context); } return stream; }; ``` 如果串流處理器期望子任務回傳一個串流,然而子任務卻沒有,那麼此時串流處理器必須拋出一個錯誤。 注意:官方關於撰寫 gulp plugin 的 [指導方針](https://github.com/gulpjs/gulp/blob/4.0/docs/writing-a-plugin/guidelines.md) 中提到: "__不要在串流中拋出錯誤 (do not throw errors inside a stream)__"。 沒錯,你不應該在串流中拋出錯誤。但是在串流處理器中,如果不是位於處理串流的程式流程中,而是在處理流程之外,那麼,拋出錯誤是沒有問題的。 你可以使用 [gulp-ccr-stream-helper](https://github.com/gulp-cookery/gulp-ccr-stream-helper) 來協助呼叫子任務,並且檢查其是否正確回傳一個串流。 你可以從 [gulp-ccr-merge](https://github.com/gulp-cookery/gulp-ccr-merge) 以及 [gulp-ccr-queue](https://github.com/gulp-cookery/gulp-ccr-queue) 專案,參考串流處理器的實作。 ### <a href="#" id="writing-flow-controller"></a> 撰寫流程控制器 流程控制器負責控制子任務的執行時機,順序等,而且並不關心子任務的輸出、入串流。 流程控制器沒有什麼特別的限制,唯一的規則是,流程控制器必須正確處理子任務的結束事件。譬如,子任務可以呼叫 "`done()`" 回呼函數;回傳一個串流或 Promise,等等。 你可以從 [gulp-ccr-parallel](https://github.com/gulp-cookery/gulp-ccr-parallel) 、 [gulp-ccr-series](https://github.com/gulp-cookery/gulp-ccr-series) 以及 [gulp-ccr-watch](https://github.com/gulp-cookery/gulp-ccr-watch) 專案,參考流程控制器的實作。 ### 測試 Plugin 建議你可以先寫供專案使用的本地 recipe,完成之後,再轉換為 plugin。大多數的 recipe 測試都是資料導向的,如果你的 recipe 也是這樣,也許你可以考慮使用我的另一個專案: [mocha-cases](https://github.com/amobiz/mocha-cases) 。 ## <a href="#" id="keywords"></a> 任務專用屬性列表 (關鍵字) 以下的關鍵字保留給任務屬性使用,你不能使用這些關鍵字做為你的任務或屬性名稱。 #### config 要傳遞給任務的組態配置。 #### description 描述任務的工作內容。 #### dest 要寫出檔案的路徑。定義在子任務中的路徑,預設會繼承父任務的定義的 dest 路徑。屬性值可以是字串,或者是如下的物件形式: `{ path: '', options: {} }` 。實際傳遞給任務的是後者的形式。 #### name 任務名稱。通常會自動由組態項目名稱獲得。除非任務是定義在陣列中,而你仍然希望能夠在命令列中執行。 #### order 任務的執行順序。只有在你以物件屬性的方式定義子任務時,又希望子任務能夠依序執行時才需要。數值僅用來排序,因此不需要是連續值。需要配合 "`series`" 屬性才能發揮作用。 #### parallel 要求子任務以並行的方式同時執行。預設情形下,以物件屬性的方式定義的子任務才會並行執行。使用此關鍵字時,子任務不論是以陣列項目或物件屬性的方式定義,都將並行執行。 #### plugin 要使用的原生 gulp plugin,可以是模組名稱或函數。 #### recipe 任務所要對應的 recipe 模組名稱。預設與任務名稱 "`name`" 屬性相同。 #### series 要求子任務以序列的方式逐一執行。預設情形下,以陣列項目的方式定義的子任務才會序列執行。使用此關鍵字時,子任務不論是以陣列項目或物件屬性的方式定義,都將序列執行。 #### spit 要求任務寫出檔案。任務允許使用者決定要不要寫出檔案時才有作用。 #### src 要讀入的檔案來源的路徑或檔案匹配表達式。由於預設會繼承父任務的 "`src`" 屬性,通常你會在父任務中定義路徑,在終端任務中才定義檔案匹配表達式。屬性值可以是任意合格的檔案匹配表達式,或由檔案匹配表達式組成的陣列,或者如下的物件形式: `{ globs: [], options: {} }` 呈現。實際傳遞給任務的是後者的形式。 #### task 定義實際執行任務的方式。可以是普通函數的引用、內聯函數或對其它任務的參照。子任務如果以陣列的形式提供,子任務將以序列的順序執行,否則子任務將以並行的方式同時執行。 #### visibility 任務的可見性。有效值為 `normal``hidden` 以及 `disabled`## <a href="#" id="settings"></a> 設定選項 設定選項可以改變 gulp-chef 的預設行為,以及用來定義自訂條件式組態配置的執行時期環境模式。 設定選項是經由 `chef()` 方法的第二個參數傳遞: ``` javascript var config = { }; var settings = { }; var meals = chef(config, settings); ``` ### settings.exposeWithPrefix 開關自動附加任務名稱功能。預設值為 `"auto"`,當發生名稱衝突時,gulp-chef 會自動為發生衝突的的任務,在前方附加父任務的名稱,像這樣:"`make:scripts:concat`"。你可以設定為 `true` 強制開啟。設定為 `false` 強制關閉,此時若遇到名稱衝突時,會拋出錯誤。 ### settings.lookups 設定本地通用任務模組 (recipe) 的查找目錄。預設值為: ``` javascript { lookups: { flows: 'flows', streams: 'streams', tasks: 'tasks' } } ``` #### settings.lookups.flows 設定本地流程控制器的查找目錄。預設值為 `"flows"`#### settings.lookups.streams 設定本地串流處理器的查找目錄。預設值為 `"streams"`#### settings.lookups.tasks 設定本地通用任務的查找目錄。預設值為 `"tasks"`### settings.plugins 傳遞給 "[gulp-load-plugins](https://github.com/jackfranklin/gulp-load-plugins)" 的選項。 Gulp-chef 使用 "gulp-load-plugins" 來載入共享任務模組,或稱為 "gulp-ccr" 模組。 預設情形下,不是以 `"gulp-ccr"` 名稱開頭的共享任務模組將不會被載入。你可以透過更改 "`plugins`" 選項來載入這些模組。 預設選項為: ``` javascript { plugins: { camelize: false, config: process.cwd() + '/package.json', pattern: ['gulp-ccr-*'], replaceString: /^gulp[-.]ccr[-.]/g } } ``` #### settings.plugins.DEBUG 當設定為 `true` 時,"gulp-load-plugins" 將輸出 log 訊息到 console。 #### settings.plugins.camelize 若設定為 `true`,使用 `"-"` 連接的名稱將被改為駝峰形式。 #### settings.plugins.config 由何處查找共享任務模組的資訊。預設為專案的 package.json。 #### settings.plugins.pattern 共享任務模組的路徑匹配表達式 (glob)。預設為 `"gulp-ccr-*"`#### settings.plugins.scope 要查找哪些相依範圍。預設為: ``` javascript ['dependencies', 'devDependencies', 'peerDependencies']. ``` #### settings.plugins.replaceString 要移除的模組附加名稱。預設為: `/^gulp[-.]ccr[-.]/g`#### settings.plugins.lazy 是否延遲載入模組。預設為 `true`#### settings.plugins.rename 指定改名。必須為 hash 物件。鍵為原始名稱,值為改名名稱。 #### settings.plugins.renameFn 改名函數。 ### settings.modes 定義自訂條件式組態配置的執行時期環境模式。 除了 `default` 屬性是用來指定預設模式之外,其餘的屬性名稱定義新的模式,而值必須是陣列,陣列的項目是可用於組態配置及命令列的識別字代號。注意不要使用到保留給任務使用的[關鍵字](#keywords)。預設為: ``` javascript { modes: { production: ['production', 'prod'], development: ['development', 'dev'], staging: ['staging'], default: 'production' } } ``` ## <a href="" id="cli-options"></a> 命令列選項列表 ### --task 查詢任務並顯示其工作內容說明以及組態配置內容。 ``` bash $ gulp --task <task-name> ``` ### --recipe 列舉可用的 recipe,包含內建的 recipe、本地的 recipe 以及已安裝的 plugin 。 你可以任意使用 "`--recipes`""`--recipe`" 以及 "`--r`"``` bash $ gulp --recipes ``` 查詢指定 recipe,顯示其用途說明,以及,如果有定義的話,顯示其[組態架構](#configuration-schema)。 ``` bash $ gulp --recipe <recipe-name> ``` ## 專案建制與貢獻 ``` bash $ git clone https://github.com/gulp-cookery/gulp-chef.git $ cd gulp-chef $ npm install ``` ## 問題提報 [Issues](https://github.com/gulp-cookery/gulp-chef/issues) ## 測試 測試是以 [mocha](https://mochajs.org/) 撰寫,請在命令列下執行下列指令: ``` bash $ npm test ``` ## 授權 [MIT](http://opensource.org/licenses/MIT) ## 作者 [Amobiz](https://github.com/amobiz)