Gulp自動(dòng)化構(gòu)建的基本使用

Study Notes

本博主會(huì)持續(xù)更新各種前端的技術(shù),如果各位道友喜歡,可以關(guān)注播掷、收藏猛蔽、點(diǎn)贊下本博主的文章匹厘。

Gulp

用自動(dòng)化構(gòu)建工具增強(qiáng)你的工作流程脚线!

gulp 將開(kāi)發(fā)流程中讓人痛苦或耗時(shí)的任務(wù)自動(dòng)化屎媳,從而減少你所浪費(fèi)的時(shí)間脖捻、創(chuàng)造更大價(jià)值阔逼。

基本使用

安裝 gulp

npm i gulp -D

在項(xiàng)目根目錄下創(chuàng)建gulpfile.js文件

function defaultTask(cb) {
  // 在此處寫(xiě)默認(rèn)任務(wù)的代碼
  cb();
}

function fooTask(cb) {
  // 在此處寫(xiě)自定義任務(wù)的代碼
  cb();
}

exports.default = defaultTask;
exports.foo = fooTask;

package.json文件中配置

{
  "script": {
    "gulp": "gulp"
  }
}

測(cè)試

在項(xiàng)目根目錄下執(zhí)行 gulp 命令:

默認(rèn)執(zhí)行 default 任務(wù)

npm run gulp

執(zhí)行 foo 任務(wù)

npm run gulp foo

<span id="createTask"></span>

創(chuàng)建任務(wù)

每個(gè) gulp 任務(wù)(task)都是一個(gè)異步的 JavaScript 函數(shù),此函數(shù)是一個(gè)可以接收 callback 作為參數(shù)的函數(shù)地沮,或者是一個(gè)返回 stream嗜浮、promise、event emitter摩疑、child process 或 observable (后面會(huì)詳細(xì)講解) 類型值的函數(shù)危融。由于某些平臺(tái)的限制而不支持異步任務(wù),因此 gulp 還提供了一個(gè)漂亮 替代品雷袋。

導(dǎo)出任務(wù)

任務(wù)(tasks)可以是 public(公開(kāi)) 或 private(私有) 類型的吉殃。

  • 公開(kāi)任務(wù)(Public tasks) 從 gulpfile 中被導(dǎo)出(export),可以通過(guò) gulp 命令直接調(diào)用楷怒。
  • 私有任務(wù)(Private tasks) 被設(shè)計(jì)為在內(nèi)部使用寨腔,通常作為 series() 或 parallel() 組合的組成部分。

一個(gè)私有(private)類型的任務(wù)(task)在外觀和行為上和其他任務(wù)(task)是一樣的率寡,但是不能夠被用戶直接調(diào)用迫卢。如需將一個(gè)任務(wù)(task)注冊(cè)為公開(kāi)(public)類型的,只需從 gulpfile 中導(dǎo)出(export)即可冶共。

const { series } = require('gulp');

// `clean` 函數(shù)并未被導(dǎo)出(export)乾蛤,因此被認(rèn)為是私有任務(wù)(private task)每界。
// 它仍然可以被用在 `series()` 組合中。
function clean(cb) {
  // body omitted
  cb();
}

// `build` 函數(shù)被導(dǎo)出(export)了家卖,因此它是一個(gè)公開(kāi)任務(wù)(public task)眨层,并且可以被 `gulp` 命令直接調(diào)用。
// 它也仍然可以被用在 `series()` 組合中上荡。
function build(cb) {
  // body omitted
  cb();
}

exports.build = build;
exports.default = series(clean, build);

組合任務(wù)

Gulp 提供了兩個(gè)強(qiáng)大的組合方法: series() 和 parallel()趴樱,允許將多個(gè)獨(dú)立的任務(wù)組合為一個(gè)更大的操作。這兩個(gè)方法都可以接受任意數(shù)目的任務(wù)(task)函數(shù)或已經(jīng)組合的操作酪捡。series() 和 parallel() 可以互相嵌套至任意深度叁征。

如果需要讓任務(wù)(task)按順序執(zhí)行,請(qǐng)使用 series() 方法逛薇。

const { series } = require('gulp');

function transpile(cb) {
  // body omitted
  cb();
}

function bundle(cb) {
  // body omitted
  cb();
}

exports.build = series(transpile, bundle);

對(duì)于希望以最大并發(fā)來(lái)運(yùn)行的任務(wù)(tasks)捺疼,可以使用 parallel() 方法將它們組合起來(lái)。

const { parallel } = require('gulp');

function javascript(cb) {
  // body omitted
  cb();
}

function css(cb) {
  // body omitted
  cb();
}

exports.build = parallel(javascript, css);

當(dāng) series() 或 parallel() 被調(diào)用時(shí)永罚,任務(wù)(tasks)被立即組合在一起啤呼。這就允許在組合中進(jìn)行改變,而不需要在單個(gè)任務(wù)(task)中進(jìn)行條件判斷呢袱。

const { series } = require('gulp');

function minify(cb) {
  // body omitted
  cb();
}

function transpile(cb) {
  // body omitted
  cb();
}

function livereload(cb) {
  // body omitted
  cb();
}

if (process.env.NODE_ENV === 'production') {
  exports.build = series(transpile, minify);
} else {
  exports.build = series(transpile, livereload);
}

series() 和 parallel() 可以被嵌套到任意深度官扣。

const { series, parallel } = require('gulp');

function clean(cb) {
  // body omitted
  cb();
}

function cssTranspile(cb) {
  // body omitted
  cb();
}

function cssMinify(cb) {
  // body omitted
  cb();
}

function jsTranspile(cb) {
  // body omitted
  cb();
}

function jsBundle(cb) {
  // body omitted
  cb();
}

function jsMinify(cb) {
  // body omitted
  cb();
}

function publish(cb) {
  // body omitted
  cb();
}

exports.build = series(
  clean,
  parallel(cssTranspile, series(jsTranspile, jsBundle)),
  parallel(cssMinify, jsMinify),
  publish,
);

當(dāng)一個(gè)組合操作執(zhí)行時(shí),這個(gè)組合中的每一個(gè)任務(wù)每次被調(diào)用時(shí)都會(huì)被執(zhí)行羞福。例如惕蹄,在兩個(gè)不同的任務(wù)(task)之間調(diào)用的 clean 任務(wù)(task)將被執(zhí)行兩次,并且將導(dǎo)致不可預(yù)期的結(jié)果坯临。因此,最好重構(gòu)組合中的 clean 任務(wù)(task)恋昼。

如果你有如下代碼:

// This is INCORRECT
const { series, parallel } = require('gulp');

const clean = function (cb) {
  // body omitted
  cb();
};

const css = series(clean, function (cb) {
  // body omitted
  cb();
});

const javascript = series(clean, function (cb) {
  // body omitted
  cb();
});

exports.build = parallel(css, javascript);

重構(gòu)為:

const { series, parallel } = require('gulp');

function clean(cb) {
  // body omitted
  cb();
}

function css(cb) {
  // body omitted
  cb();
}

function javascript(cb) {
  // body omitted
  cb();
}

exports.build = series(clean, parallel(css, javascript));

異步執(zhí)行

Node 庫(kù)以多種方式處理異步功能看靠。最常見(jiàn)的模式是 error-first callbacks,但是你還可能會(huì)遇到 streams液肌、promises挟炬、event emitterschild processes, 或 observables嗦哆。gulp 任務(wù)(task)規(guī)范化了所有這些類型的異步功能谤祖。

任務(wù)(task)完成通知

當(dāng)從任務(wù)(task)中返回 stream、promise老速、event emitter粥喜、child process 或 observable 時(shí),成功或錯(cuò)誤值將通知 gulp 是否繼續(xù)執(zhí)行或結(jié)束橘券。如果任務(wù)(task)出錯(cuò)额湘,gulp 將立即結(jié)束執(zhí)行并顯示該錯(cuò)誤卿吐。

當(dāng)使用 series() 組合多個(gè)任務(wù)(task)時(shí),任何一個(gè)任務(wù)(task)的錯(cuò)誤將導(dǎo)致整個(gè)任務(wù)組合結(jié)束锋华,并且不會(huì)進(jìn)一步執(zhí)行其他任務(wù)嗡官。當(dāng)使用 parallel() 組合多個(gè)任務(wù)(task)時(shí),一個(gè)任務(wù)的錯(cuò)誤將結(jié)束整個(gè)任務(wù)組合的結(jié)束毯焕,但是其他并行的任務(wù)(task)可能會(huì)執(zhí)行完衍腥,也可能沒(méi)有執(zhí)行完嘶居。

返回 stream

const { src, dest } = require('gulp');

const streamTask = () => src('src/css/*.css').pipe(dest('dist/css'));

exports.default = streamTask;

返回 promise

const promiseTask = () => Promise.resolve('the value is ignored');

exports.default = promiseTask;

返回 event emitter

const { EventEmitter } = require('events');

const eventEmitterTask = () => {
  const emitter = new EventEmitter();
  // 發(fā)射必須異步發(fā)生歌径,否則gulp尚未監(jiān)聽(tīng)
  setTimeout(() => emitter.emit('finish'), 250);
  return emitter;
};

exports.default = eventEmitterTask;

返回 child process

const { exec } = require('child_process');

const childProcessTask = () => exec('date');

exports.default = childProcessTask;

返回 observable

const { of } = require('rxjs');

const observableTask = () => of(1, 2, 3);

exports.default = observableTask;

使用 callback

如果任務(wù)(task)不返回任何內(nèi)容长赞,則必須使用 callback 來(lái)指示任務(wù)已完成趣席。在如下示例中炸客,callback 將作為唯一一個(gè)名為 cb() 的參數(shù)傳遞給你的任務(wù)(task)剃根。

const callbackTask = (cb) => {
  // `cb()` 應(yīng)該由一些異步工作來(lái)調(diào)用
  cb();
};

exports.default = callbackTask;

如需通過(guò) callback 把任務(wù)(task)中的錯(cuò)誤告知 gulp打毛,請(qǐng)將 Error 作為 callback 的唯一參數(shù)速蕊。

const callbackError = (cb) => {
  // `cb()` 應(yīng)該由一些異步工作來(lái)調(diào)用
  cb(new Error('error'));
};

exports.default = callbackError;

然而物遇,你通常會(huì)將此 callback 函數(shù)傳遞給另一個(gè) API 乖仇,而不是自己調(diào)用它。

const fs = require('fs');

const passingCallback = (cb) => {
  fs.access('gulpfile.js', cb);
};

exports.default = passingCallback;

gulp 不再支持同步任務(wù)(Synchronous tasks)

gulp 不再支持同步任務(wù)(Synchronous tasks)了询兴。因?yàn)橥饺蝿?wù)常常會(huì)導(dǎo)致難以調(diào)試的細(xì)微錯(cuò)誤乃沙,例如忘記從任務(wù)(task)中返回 stream。

當(dāng)你看到 "Did you forget to signal async completion?" 警告時(shí)诗舰,說(shuō)明你并未使用前面提到的返回方式警儒。你需要使用 callback 或返回 stream、promise眶根、event emitter蜀铲、child process、observable 來(lái)解決此問(wèn)題属百。

使用 async/await

如果不使用前面提供到幾種方式记劝,你還可以將任務(wù)(task)定義為一個(gè) async 函數(shù),它將利用 promise 對(duì)你的任務(wù)(task)進(jìn)行包裝族扰。這將允許你使用 await 處理 promise厌丑,并使用其他同步代碼。

const fs = require('fs');

const asyncAwaitTask = async () => {
  const { version } = JSON.parse(fs.readFileSync('package.json'));
  console.log(version, '版本號(hào)');
  await Promise.resolve('some result');
};

exports.default = asyncAwaitTask;

文件處理

gulp 暴露了 src() 和 dest() 方法用于處理計(jì)算機(jī)上存放的文件渔呵。

src() 接受 glob 參數(shù)怒竿,并從文件系統(tǒng)中讀取文件然后生成一個(gè) Node 流(stream)。它將所有匹配的文件讀取到內(nèi)存中并通過(guò)流(stream)進(jìn)行處理扩氢。

由 src() 產(chǎn)生的流(stream)應(yīng)當(dāng)從任務(wù)(task)中返回并發(fā)出異步完成的信號(hào)耕驰,就如 創(chuàng)建任務(wù)(task) 文檔中所述。

流(stream)所提供的主要的 API 是 .pipe() 方法录豺,用于連接轉(zhuǎn)換流(Transform streams)或可寫(xiě)流(Writable streams)耍属。

const { src, dest } = require('gulp');
const babel = require('gulp-babel');

const streamJsTask = () =>
  src('src/js/*.js').pipe(babel()).pipe(dest('dist/js'));
exports.default = streamJsTask;

dest() 接受一個(gè)輸出目錄作為參數(shù)托嚣,并且它還會(huì)產(chǎn)生一個(gè) Node 流(stream),通常作為終止流(terminator stream)厚骗。當(dāng)它接收到通過(guò)管道(pipeline)傳輸?shù)奈募r(shí)示启,它會(huì)將文件內(nèi)容及文件屬性寫(xiě)入到指定的目錄中。gulp 還提供了 symlink() 方法领舰,其操作方式類似 dest()夫嗓,但是創(chuàng)建的是鏈接而不是文件( 詳情請(qǐng)參閱 symlink() )。

大多數(shù)情況下冲秽,利用 .pipe() 方法將插件放置在 src() 和 dest() 之間舍咖,并轉(zhuǎn)換流(stream)中的文件。

向流(stream)中添加文件

src() 也可以放在管道(pipeline)的中間锉桑,以根據(jù)給定的 glob 向流(stream)中添加文件排霉。新加入的文件只對(duì)后續(xù)的轉(zhuǎn)換可用。如果 glob 匹配的文件與之前的有重復(fù)民轴,仍然會(huì)再次添加文件攻柠。

這對(duì)于在添加普通的 JavaScript 文件之前先轉(zhuǎn)換部分文件的場(chǎng)景很有用,添加新的文件后可以對(duì)所有文件統(tǒng)一進(jìn)行壓縮并混淆(uglifying)后裸。

const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');

const streamAddJsTask = () =>
  src('src/js/*.js')
    .pipe(babel())
    .pipe(src('src/vendor/*.js'))
    .pipe(uglify())
    .pipe(dest('dist/js'));

exports.default = streamAddJsTask;

分階段輸出

dest() 可以用在管道(pipeline)中間用于將文件的中間狀態(tài)寫(xiě)入文件系統(tǒng)瑰钮。當(dāng)接收到一個(gè)文件時(shí),當(dāng)前狀態(tài)的文件將被寫(xiě)入文件系統(tǒng)微驶,文件路徑也將被修改以反映輸出文件的新位置浪谴,然后該文件繼續(xù)沿著管道(pipeline)傳輸。

此功能可用于在同一個(gè)管道(pipeline)中創(chuàng)建未壓縮(unminified)和已壓縮(minified)的文件因苹。

const { src, dest } = require('gulp');
const babel = require('gulp-babel');
const uglify = require('gulp-uglify');
const rename = require('gulp-rename');

const streamSegmentJsTask = () =>
  src('src/js/*.js')
    .pipe(babel())
    .pipe(src('src/vendor/*.js'))
    .pipe(dest('dist/js/'))
    .pipe(uglify())
    .pipe(rename('index.min.js'))
    .pipe(dest('dist/js/'));

exports.default = streamSegmentJsTask;

模式:流動(dòng)(streaming)苟耻、緩沖(buffered)和空(empty)模式

src() 可以工作在三種模式下:緩沖(buffering)、流動(dòng)(streaming)和空(empty)模式扶檐。這些模式可以通過(guò)對(duì) src() 的 buffer 和 read 參數(shù) 進(jìn)行設(shè)置凶杖。

緩沖(Buffering)模式是默認(rèn)模式,將文件內(nèi)容加載內(nèi)存中蘸秘。插件通常運(yùn)行在緩沖(buffering)模式下官卡,并且許多插件不支持流動(dòng)(streaming)模式蝗茁。
流動(dòng)(Streaming)模式的存在主要用于操作無(wú)法放入內(nèi)存中的大文件醋虏,例如巨幅圖像或電影。文件內(nèi)容從文件系統(tǒng)中以小塊的方式流式傳輸哮翘,而不是一次性全部加載颈嚼。如果需要流動(dòng)(streaming)模式,請(qǐng)查找支持此模式的插件或自己編寫(xiě)饭寺。
空(Empty)模式不包含任何內(nèi)容阻课,僅在處理文件元數(shù)據(jù)時(shí)有用叫挟。

使用插件

文件監(jiān)控

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市限煞,隨后出現(xiàn)的幾起案子抹恳,更是在濱河造成了極大的恐慌,老刑警劉巖署驻,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奋献,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡旺上,警方通過(guò)查閱死者的電腦和手機(jī)瓶蚂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宣吱,“玉大人窃这,你說(shuō)我怎么就攤上這事≌骱颍” “怎么了杭攻?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)倍奢。 經(jīng)常有香客問(wèn)我朴上,道長(zhǎng),這世上最難降的妖魔是什么卒煞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任痪宰,我火速辦了婚禮,結(jié)果婚禮上畔裕,老公的妹妹穿的比我還像新娘衣撬。我一直安慰自己,他們只是感情好扮饶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布具练。 她就那樣靜靜地躺著,像睡著了一般甜无。 火紅的嫁衣襯著肌膚如雪扛点。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,246評(píng)論 1 308
  • 那天岂丘,我揣著相機(jī)與錄音陵究,去河邊找鬼。 笑死奥帘,一個(gè)胖子當(dāng)著我的面吹牛铜邮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼松蒜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扔茅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起秸苗,我...
    開(kāi)封第一講書(shū)人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤召娜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后惊楼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體萤晴,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年胁后,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了店读。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攀芯,死狀恐怖屯断,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侣诺,我是刑警寧澤殖演,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站年鸳,受9級(jí)特大地震影響趴久,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搔确,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一彼棍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧膳算,春花似錦座硕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至机隙,卻和暖如春蜘拉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背有鹿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工旭旭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人印颤。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓您机,卻偏偏與公主長(zhǎng)得像穿肄,于是被迫代替她去往敵國(guó)和親年局。 傳聞我的和親對(duì)象是個(gè)殘疾皇子际看,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359