Gulp plugin that optimizes build processes by only passing through files that have been modified since the last build
npx @tessl/cli install tessl/npm-gulp-changed@5.0.0gulp-changed is a Gulp plugin that optimizes build processes by only passing through files that have been modified since the last build. It offers multiple comparison strategies including modification time checking and content-based comparison, with built-in comparators for common use cases and support for custom comparison functions.
npm install --save-dev gulp-changedimport gulpChanged, { compareLastModifiedTime, compareContents } from 'gulp-changed';import gulp from 'gulp';
import gulpChanged from 'gulp-changed';
import ngAnnotate from 'gulp-ng-annotate'; // Example processor
const SOURCE = 'src/*.js';
const DESTINATION = 'dist';
export default function build() {
return gulp.src(SOURCE)
.pipe(gulpChanged(DESTINATION))
// ngAnnotate will only get the files that
// changed since the last time it was run
.pipe(ngAnnotate())
.pipe(gulp.dest(DESTINATION));
}Creates a Gulp transform stream that filters out unchanged files based on comparison with destination files.
/**
* Create a gulp plugin that only passes through changed files
* @param destination - Destination directory path or function returning destination path
* @param options - Configuration options for comparison behavior
* @returns Transform stream for use in gulp pipeline
*/
function gulpChanged(destination: string | ((file: Vinyl) => string), options?: ChangedOptions): Transform;
interface ChangedOptions {
/** Working directory the folder is relative to (default: process.cwd()) */
cwd?: string;
/** Function that determines whether the source file is different from the destination file */
hasChanged?: (sourceFile: Vinyl, targetPath: string) => Promise<Vinyl | undefined>;
/** Extension of the destination files (useful if different from source) */
extension?: string;
/** Function to transform the path to the destination file */
transformPath?: (newPath: string) => string;
}Usage Examples:
// Basic usage with destination directory
gulp.src('src/**/*.js')
.pipe(gulpChanged('dist'))
.pipe(babel())
.pipe(gulp.dest('dist'));
// With different file extension
gulp.src('src/**/*.jade')
.pipe(gulpChanged('app', {extension: '.html'}))
.pipe(jade())
.pipe(gulp.dest('app'));
// With custom path transformation
gulp.src('src/content/*.md')
.pipe(gulpChanged('dist', {
transformPath: (newPath) => path.join(
path.dirname(newPath),
path.basename(newPath, '.md'),
'index.html'
)
}))
.pipe(marked())
.pipe(gulp.dest('dist'));
// With function-based destination
gulp.src('src/**/*.js')
.pipe(gulpChanged((file) => `dist/${file.relative.split('/')[0]}`))
.pipe(babel())
.pipe(gulp.dest('dist'));Built-in comparator that compares files based on modification time. This is the default comparison method.
/**
* Compare files based on modification time
* @param sourceFile - Source vinyl file object with stat information
* @param targetPath - Absolute path to target file for comparison
* @returns Promise resolving to sourceFile if changed, undefined if not
*/
async function compareLastModifiedTime(sourceFile: Vinyl, targetPath: string): Promise<Vinyl | undefined>;Usage Example:
// Explicitly use modification time comparison (this is the default)
gulp.src('src/**/*.js')
.pipe(gulpChanged('dist', {hasChanged: compareLastModifiedTime}))
.pipe(babel())
.pipe(gulp.dest('dist'));Built-in comparator that compares files based on actual content differences rather than modification time.
/**
* Compare files based on content differences
* @param sourceFile - Source vinyl file object with contents buffer
* @param targetPath - Absolute path to target file for comparison
* @returns Promise resolving to sourceFile if changed, undefined if not
*/
async function compareContents(sourceFile: Vinyl, targetPath: string): Promise<Vinyl | undefined>;Usage Example:
// Use content-based comparison for more accurate change detection
gulp.src('src/**/*.js')
.pipe(gulpChanged('dist', {hasChanged: compareContents}))
.pipe(babel())
.pipe(gulp.dest('dist'));// Vinyl file object (from vinyl package)
interface Vinyl {
/** Current working directory */
cwd: string;
/** Base directory for relative paths */
base: string;
/** Full file path */
path: string;
/** Relative file path from base */
relative: string;
/** File contents as Buffer or Stream */
contents: Buffer | Stream | null;
/** File system stat object */
stat?: {
/** Modification time in milliseconds */
mtimeMs: number;
[key: string]: any;
};
}
// Transform stream (from Node.js streams)
interface Transform extends Stream.Transform {
/** Write a file to the stream */
write(file: Vinyl): boolean;
/** End the stream */
end(): void;
/** Event emitted when stream ends */
on(event: 'end', callback: () => void): this;
/** Event emitted for each file passing through */
on(event: 'data', callback: (file: Vinyl) => void): this;
}The plugin handles several error conditions:
Error('gulp-changed: \dest` required')` if no destination is providedError('gulp-changed: \options.transformPath` needs to be a function')` if transformPath is not a functionTypeError('\options.transformPath` needs to return a string')` if transformPath doesn't return a stringENOENT errors (file doesn't exist in destination) by passing the file throughYou can provide a custom comparator function for specialized comparison logic:
function customComparator(sourceFile, targetPath) {
// Custom logic here - must return Promise<Vinyl | undefined>
// Return sourceFile if it should be processed, undefined if not
return Promise.resolve(sourceFile);
}
gulp.src('src/**/*.js')
.pipe(gulpChanged('dist', {hasChanged: customComparator}))
.pipe(babel())
.pipe(gulp.dest('dist'));Configure the working directory for relative path resolution:
gulp.src('src/**/*.js')
.pipe(gulpChanged('dist', {
cwd: '/custom/working/directory'
}))
.pipe(babel())
.pipe(gulp.dest('dist'));