ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

gulp lastRun() 完全指南:利用任务最近运行时间实现增量构建

gulp lastRun() 完全指南:利用任务最近运行时间实现增量构建 gulp lastRun() 完全指南利用任务最近运行时间实现增量构建【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp导读lastRun()是 gulp 提供的时间戳查询 API用于获取某个任务在当前运行进程中最后一次成功完成的时刻。它是实现增量构建的核心工具——当与src()的since选项结合使用时可以跳过自上次任务成功完成后未发生变化的文件从而显著缩短构建时间。读完本文你将掌握lastRun()的完整签名、参数精度语义、错误处理规则以及如何在 watcher 场景下搭建一套实战可用的增量构建流程。什么是lastRun()lastRun(task)检索指定任务在当前运行进程内最后一次成功完成的时间以毫秒为单位的 Unix 时间戳。它有两个典型特征进程内有效记录只在当前 Node 进程中存在进程退出即清空不跨进程持久化仅记录成功任务失败不会留下有效记录返回值保持为undefined。该 API 在 watcher 运行期间的后续任务触发场景中最为有用监听器因文件变化反复触发任务时每次触发都能拿到上一次成功完成的时间点从而判断哪些文件是新变化的。当它与src()组合使用时就能开启增量构建src(globs, { since: lastRun(task) })只会为自上次任务成功完成以来被修改过的文件创建 Vinyl 对象未变化的文件直接跳过以此加速执行。基本用法与src()的since选项组合官方文档给出的典型场景是图片压缩任务配合文件监听const { src, dest, lastRun, watch } require(gulp); const imagemin require(gulp-imagemin); function images() { return src(src/images/**/*.jpg, { since: lastRun(images) }) .pipe(imagemin()) .pipe(dest(build/img/)); } exports.default function() { watch(src/images/**/*.jpg, images); };这段代码的运行逻辑如下watch(src/images/**/*.jpg, images)监听图片目录任何新增、修改或删除事件都会触发images任务详见 docs/getting-started/8-watching-files.md 中关于 watched events 的说明任务首次运行时lastRun(images)返回undefinedsince选项收到undefined时不会过滤任何文件因此全量构建一次首次任务成功完成后lastRun(images)记录下该完成时刻之后每次 watcher 触发任务since: lastRun(images)都只放行修改时间晚于上次成功完成时刻的文件实现增量构建。src()的since选项在 docs/api/src.md 中有明确说明接受date、timestamp或function类型当设置后只对指定时间之后被修改的文件创建 Vinyl 对象。从 docs/api/src.md 可以看到该选项没有默认值不设置即不过滤。签名与参数lastRun(task, [precision])参数说明参数类型说明task必填functionstring任务函数或已注册任务的字符串别名。precisionnumber时间戳精度用于舍入。默认值Node v0.10 为1000Node v0.12 为0详见下文时间戳精度一节。其中字符串形式指的是通过task()注册的别名。关于任务如何注册、命名函数与displayName别名的机制可参考 docs/api/task.mdconst { task } require(gulp); task(build, function(cb) { // body omitted cb(); }); // 之后即可用字符串别名查询 const last lastRun(build);需要留意的是官方在 docs/api/task.md 中提醒task()注册模式已不再是推荐做法更推荐直接导出任务函数见 docs/getting-started/3-creating-tasks.md。因此在现代 gulpfile 中lastRun(images)这种直接传入命名函数的形式是最常见的。返回值返回与任务最近一次完成时间匹配的毫秒时间戳。若任务从未运行过或运行失败则返回undefined。为避免缓存无效状态任务出错时返回值会保持为undefined——这是刻意设计只有成功完成才会更新记录失败的任务不会污染下一次增量构建的基准时间。错误调用时传入字符串或函数以外的值会抛出错误消息为Only functions can check lastRun当传入一个不可扩展non-extensible的函数、且当前 Node 环境缺少WeakMap时会抛出Only extensible functions can check lastRun时间戳精度Timestamp precision虽然时间戳存在合理的默认精度但你仍可通过precision参数对时间进行舍入。当你的文件系统或 Node 版本在文件时间属性上存在精度损失时这一参数尤其有用。// 假设任务实际完成于 1426000001111 ms lastRun(someTask); // 返回 1426000001111默认精度 lastRun(someTask, 100); // 返回 1426000001100 lastRun(someTask, 1000); // 返回 1426000001000从示例可以看出precision的作用是把时间戳向下舍入到该数值的整数倍毫秒单位。为什么要关心精度lastRun()返回的时间戳会被传给src()的since选项用于和文件的mtime最后修改时间比较。文件mtime的精度因 Node 版本与文件系统而异详见 docs/api/concepts.md#file-system-stats 对fs.Stats的说明若lastRun()的时间戳精度高于文件系统的mtime精度可能出现边界偏差——例如文件系统的 mtime 记录到秒而任务完成时间记录到毫秒导致本应被视为已修改的文件被错误跳过。下表整理了常见环境下的时间精度差异平台精度Node v0.101000msNode v0.121msFAT32 文件系统2000msHFS 或 Ext3 文件系统1000msNTFS Node v0.101sNTFS Node v0.12100msExt4 Node v0.101000msExt4 Node v0.121ms这些表格来自官方文档对应的是较早期的 Node 版本基线。由于本仓库gulp v5.0.1见 package.json要求的 Node 版本为10.13.0见 package.json现代环境下 Node 侧的默认精度基本是 1ms 量级但在 FAT32、HFS、Ext3 等粗精度文件系统上为稳妥起见可以在调用lastRun()时显式传入与文件系统匹配的精度值例如lastRun(images, 1000)。底层实现从源码看lastRun()从何而来查看仓库根目录的 index.js 可以确认lastRun()的实现来源// index.js var Undertaker require(undertaker); // ... function Gulp() { Undertaker.call(this); // ... this.lastRun this.lastRun.bind(this); // ... } util.inherits(Gulp, Undertaker);也就是说gulp 的Gulp类继承自undertaker模块任务注册系统lastRun()是 Undertaker 提供的方法gulp 在构造函数中对其做了bind以便解构使用。仓库依赖undertaker^2.0.0见 package.json。在 docs/api/concepts.md 的模块清单中也可以找到对应关系gulp 由众多小模块拼装而成其中last-run模块专门负责跟踪任务的最后运行时间undertaker模块负责任务注册系统。由此可以推断lastRun()的数据记录底层由last-run模块维护任务完成时写入、任务失败时清空/不写入。从源码结构看lastRun()之所以能接收任务函数或字符串别名与 Undertaker 的任务注册机制密切相关——字符串别名会先被解析为对应的任务函数再查询其运行记录。ESM 导出本仓库同时提供 ESM 入口 index.mjslastRun被显式导出因此现代 ESM 风格的 gulpfile 也可以这样使用import { src, dest, lastRun, watch } from gulp;测试佐证在 test/index.test.js 中仓库通过Object.prototype.hasOwnProperty.call(gulp, lastRun)断言lastRun是 gulp 实例的自有属性验证了该 API 的存在性与可解构性。同时在 test/fixtures/gulpfiles/mjs/gulpfile.mjs 中ESM 示例 gulpfile 也从gulp导入并断言typeof lastRun function验证了 ESM 入口的导出行为。实战模式完整的增量构建示例综合以上知识一个具备容错意识的增量构建任务可以这样组织const { src, dest, lastRun, watch, series } require(gulp); const imagemin require(gulp-imagemin); const uglify require(gulp-uglify); function images() { return src(src/images/**/*.{jpg,png,svg}, { since: lastRun(images), // 在 FAT32 等粗精度文件系统上显式舍入避免误判 }) .pipe(imagemin()) .pipe(dest(build/img/)); } function scripts() { return src(src/scripts/**/*.js, { since: lastRun(scripts) }) .pipe(uglify()) .pipe(dest(build/js/)); } function watchFiles() { watch(src/images/**/*.{jpg,png,svg}, images); watch(src/scripts/**/*.js, scripts); } exports.images images; exports.scripts scripts; exports.watch watchFiles; exports.default series(images, scripts);实践要点先全量后增量首次运行任务时lastRun()返回undefinedsince不过滤文件自动完成一次全量构建因此可以直接把任务本身设计为幂等全量可用每个任务独立计时lastRun()按任务分别记录互不影响适合为图片、脚本等不同资源各自维护增量基准与watch()的默认行为契合watch()自带 200ms 延迟和排队机制见 docs/getting-started/8-watching-files.md多次连续修改会合并触发增量构建恰好只在最终触发时处理真正变化过的文件同步任务不可用与watch()的约束一致被监听的任务必须是异步的返回流、Promise 或调用回调否则无法确定完成时刻任务不会再次运行详见 docs/getting-started/8-watching-files.md注意返回流增量任务返回src()产生的流以正确通知异步完成——这是lastRun()能记录成功时刻的前提。局限与注意事项进程内记录lastRun()不持久化。重启 gulp 进程后首次运行任务仍会全量构建这是预期行为失败不记录任务抛错时返回值保持undefined可避免无效状态被缓存精度匹配在 FAT322000ms、HFS/Ext31000ms等文件系统上应通过precision参数将时间戳舍入到与文件系统一致或更粗的粒度防止因 mtime 精度不足导致漏处理非扩展函数限制传入的函数必须是可扩展的且环境需支持WeakMap否则会抛出 Only extensible functions can check lastRun 错误。延伸阅读src() 文档since选项的完整说明及其他可选参数task() 文档任务注册与字符串别名机制API ConceptsVinyl、任务、glob、文件系统 stats 等前置概念Watching Fileswatch()的延迟、排队、事件等行为Working with Filessrc()/dest()流式管线基础Incremental builds with concatenate需要全量文件集时的增量构建进阶方案【免费下载链接】gulpA toolkit to automate enhance your workflow项目地址: https://gitcode.com/gh_mirrors/gu/gulp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进