mrkeyoor.com_
Sat 08 Aug 22:01 UTC
npmUtilsupdated 08 Aug 2026

vinyl-fs

vinyl-fs is the filesystem adapter underneath Gulp. Its src() method expands ordered globs and emits Vinyl file objects, while dest() writes those objects back to disk and symlink() creates links. The useful abstraction is that a file carries its path, base directory, contents, stat data, and optional source map through an object-mode stream. That makes it a focused fit for build pipelines that transform many files, but an unnecessarily elaborate replacement for fs/promises when all you need is to copy or edit a few paths.

Verdict

Use vinyl-fs when Vinyl is already the contract between your build steps. For a new standalone file task, direct filesystem promises plus a glob package are easier to type, test, and explain.

API stability4/5The top-level API is only src(), dest(), and symlink(), and those entry points have stayed recognizable across major versions. Version 4 still made behavior-level breaking changes: UTF-8 decoding and encoding became defaults, the stream implementation moved to streamx, Node versions below 10.13 were dropped, and symlink target selection changed. The surface is small, but major upgrades deserve fixture tests around bytes and links.
Docs4/5The README documents every src(), dest(), and symlink() option with types, defaults, mutation behavior, glob-order rules, BOM handling, source maps, and unusually detailed Windows symlink caveats. It is accurate enough to expose the traps that matter. It falls short of a top score because examples are sparse, use older CommonJS style, and TypeScript users must cross-reference separately maintained declaration files.
Maintenance4/5Version 4.0.2 shipped in June 2025 with a fix for globbing before the read stream opens, alongside 4.0.1 fixes for a Node deprecation warning and streaming transcoding. The repository is not archived and reports eight open issues and pull requests. Activity is release-driven rather than constant, but recent fixes address current Node behavior instead of merely republishing old code.
Ecosystem4/5The package recorded 3,397,313 downloads in the measured week and is the filesystem boundary used by the established Vinyl and Gulp plugin ecosystem. Vinyl objects give compatible transforms a shared contract for paths, contents, stats, and source maps. Outside Gulp-style build tooling that contract is much less common, and the lack of bundled declarations or native ESM keeps it from being a general modern default.

Use it if

  • You are building a Gulp-compatible plugin or pipeline that already passes Vinyl file objects between transforms
  • You need ordered positive and negative globs feeding a backpressure-aware file stream
  • You need to preserve file metadata, source maps, or symlink information while transforming files
  • You want one adapter whose destination can be selected separately for every Vinyl file
Skip it if

Setup reality

Install with npm install vinyl-fs. There are no peer dependencies or native compilation steps, and Node 10.13 or newer satisfies the declared engine, but the package is not a tiny fs wrapper: 4.0.2 installs 13 direct runtime dependencies and centers every operation on object-mode Vinyl streams. It is CommonJS, so use require('vinyl-fs') or import it through your runtime's CommonJS interop. TypeScript users also need npm install -D @types/vinyl-fs because the package publishes no declarations. The first surprise is content handling. src() buffers every file by default, removes a UTF-8 BOM, and version 4 decodes with UTF-8; use buffer: false for streams, removeBOM: false when the marker matters, or encoding: false for untouched binary bytes. Glob order matters because a negative pattern only excludes earlier positive matches. dest() overwrites by default and mutates each Vinyl object's cwd, base, path, stat, and sometimes contents before passing it downstream. Error handling is stream error handling, so await stream/promises.pipeline or subscribe before work begins. Symlinks need extra testing: src() resolves them by default, Windows directory links become junctions by default, and a dangling directory link can be guessed as a file. None of this requires credentials or a config file, but production use does require explicit choices about buffering, encoding, overwrite behavior, glob roots, and platform-specific links.

Patterns

Copy files selected by ordered globscopy-matched-files

const vfs = require('vinyl-fs');

vfs.src(['src/**/*', '!src/**/*.test.js'])
  .pipe(vfs.dest('dist'));

Negations must follow the positive glob they filter; reversing these two patterns does not exclude the test files.

Await a pipeline and surface stream errorsawait-copy-completion

const { pipeline } = require('node:stream/promises');
const vfs = require('vinyl-fs');

await pipeline(
  vfs.src('assets/**/*'),
  vfs.dest('public/assets')
);

Awaiting pipeline is safer than listening only for finish because it rejects when either the source or destination errors.

Modify buffered Vinyl contentstransform-buffered-files

const { Transform } = require('node:stream');
const vfs = require('vinyl-fs');

const replaceVersion = new Transform({
  objectMode: true,
  transform(file, _encoding, callback) {
    if (file.isBuffer()) {
      file.contents = Buffer.from(
        file.contents.toString().replaceAll('__VERSION__', '4.2.0')
      );
    }
    callback(null, file);
  },
});

vfs.src('templates/**/*').pipe(replaceVersion).pipe(vfs.dest('dist'));

src() buffers by default, but a transform should still check isBuffer() because Vinyl can also carry streams, null contents, or directories.

Keep large file contents as streamsstream-large-files

const vfs = require('vinyl-fs');

vfs.src('videos/**/*.mp4', { buffer: false, encoding: false })
  .pipe(vfs.dest('archive'));

With buffer: false each file.contents is a paused stream; downstream transforms must explicitly support streaming Vinyl contents.

Inspect matched paths without reading contentslist-without-reading

const vfs = require('vinyl-fs');

const files = vfs.src('src/**/*', { read: false });
files.on('data', (file) => console.log(file.relative));
files.on('error', console.error);

read: false sets file.contents to null, and those files cannot later be written by dest() unless a transform supplies contents.

Process only files changed after a timestampcopy-recent-files

const vfs = require('vinyl-fs');

const lastBuild = new Date('2026-08-01T00:00:00Z');
vfs.src('src/**/*', { since: lastBuild })
  .pipe(vfs.dest('dist'));

Version 4 compares since against the greater of ctime and mtime; keep the build timestamp outside the output tree to avoid feedback loops.

Disable transcoding and BOM removalpreserve-binary-bytes

const vfs = require('vinyl-fs');

vfs.src('fixtures/**/*', {
  encoding: false,
  removeBOM: false,
}).pipe(vfs.dest('copied', { encoding: false }));

Version 4 defaults to UTF-8 decoding and encoding, while src() removes UTF-8 BOMs by default; both switches matter for byte-for-byte copies.

Load and write external source mapswrite-source-maps

const vfs = require('vinyl-fs');

vfs.src('src/**/*.js', { sourcemaps: true })
  .pipe(vfs.dest('dist', { sourcemaps: '.' }));

The source option loads inline maps and resolves linked maps; a destination string writes separate .map files relative to that path.

Choose a destination for each fileroute-by-file

const vfs = require('vinyl-fs');

vfs.src('src/**/*').pipe(vfs.dest((file) => {
  return file.extname === '.css' ? 'dist/styles' : 'dist/assets';
}));

dest() mutates cwd, base, path, stat, and sometimes contents before emitting the file downstream, so retain original paths beforehand if needed.

Leave existing destination files untouchedavoid-overwriting

const vfs = require('vinyl-fs');

vfs.src('defaults/**/*')
  .pipe(vfs.dest('project', { overwrite: false }));

overwrite defaults to true; false is useful for scaffolding but means an existing stale file is silently retained.

Copy links instead of resolving their targetspreserve-symlinks

const vfs = require('vinyl-fs');

vfs.src('tree/**/*', { resolveSymlinks: false })
  .pipe(vfs.dest('copy'));

Windows directory links become junctions by default, and dangling directory links passed through dest() may be guessed as file links.

Create relative symbolic linkscreate-symlinks

const vfs = require('vinyl-fs');

vfs.src('packages/*', { read: false })
  .pipe(vfs.symlink('workspace-links', {
    relativeSymlinks: true,
    useJunctions: false,
  }));

Disabling junctions affects Windows directory links; creating links may still require operating-system permissions or developer mode.

Alternatives

PackageRegistryPick it when
fast-globnpmYou need fast path matching and will perform reads or writes yourself
globbynpmYou want a friendly promise-based glob API with ignore files and modern ESM
fs-extranpmYou need direct promise-based copy, move, remove, and JSON helpers without Vinyl streams