A Volume is one isolated in-memory filesystem. You can spin up as many as you
like, seed them from a plain JSON object, and export their contents back to
JSON --- which makes memfs convenient for fixtures and assertions.
import { Volume } from 'memfs';
const vol = Volume.fromJSON({ '/foo': 'bar' });
vol.readFileSync('/foo', 'utf8'); // 'bar'
There are two JSON shapes. Flat maps full paths to contents:
type DirectoryJSON = { [path: string]: string | Buffer | null };
const vol = Volume.fromJSON(
{
'./README.md': '1',
'./src/index.js': '2',
'./node_modules/debug/index.js': '3',
},
'/app', // cwd: resolves the relative keys above
);
vol.readFileSync('/app/README.md', 'utf8'); // '1'
vol.readFileSync('/app/src/index.js', 'utf8'); // '2'
Nested lets directories nest as objects --- handy for deeper trees:
const vol = Volume.fromNestedJSON({
'/app': {
'index.js': '...',
src: {
'main.ts': '...',
util: { 'log.ts': '...' },
},
},
});
In both shapes the value null means an empty directory and an empty
string '' means an empty file. Values can be string or Buffer
(binary). The instance methods vol.fromJSON(json, cwd?) and
vol.fromNestedJSON(json, cwd?) add files into an existing volume; the static
Volume.fromJSON / Volume.fromNestedJSON create a new one.
const vol = new Volume();
vol.fromJSON({ '/a.txt': 'A' });
vol.fromJSON({ '/b.txt': 'B' }); // merges in
`vol.mountSync(mountpoint, json)` is a legacy alias for adding a flat JSON tree
rooted at a given path. New code should use `fromJSON` with a cwd.
toJSON walks the volume and returns a flat object --- the inverse of
fromJSON. This is the workhorse for test assertions:
vol.writeFileSync('/foo', 'bar');
vol.toJSON(); // {'/foo': 'bar'}
expect(vol.toJSON()).toEqual({ '/foo': 'bar' });
The full signature lets you scope and shape the output:
vol.toJSON(
paths?: PathLike | PathLike[], // restrict to these paths; omit for everything
json?: {}, // object to populate (for merging exports)
isRelative?: boolean, // emit relative instead of absolute paths
asBuffer?: boolean, // emit Buffer contents instead of strings
): DirectoryJSON;
const vol = Volume.fromJSON({ '/dir/a': 'b', '/dir2/a': 'b', '/dir2/c': 'd' });
vol.toJSON('/dir2'); // {'/dir2/a': 'b', '/dir2/c': 'd'}
reset() empties a volume so you can reuse it between tests:
vol.fromJSON({ '/index.js': '...' });
vol.toJSON(); // {'/index.js': '...'}
vol.reset();
vol.toJSON(); // {}
toTree() renders the volume as an ASCII tree (a quick built-in; for arbitrary
fs filesystems and more options see Tree printing):
const { vol } = memfs({
'/src': {
'index.ts': '...',
util: { 'print.ts': '...' },
},
});
console.log(vol.toTree());
// /
// └─ src/
// ├─ index.ts
// └─ util/
// └─ print.ts
toTree(opts?) accepts a sub-folder, a depth, and a separator.
Each Volume is fully independent:
const a = Volume.fromJSON({ '/foo': 'bar' });
const b = Volume.fromJSON({ '/foo': 'baz' });
a.readFileSync('/foo', 'utf8'); // 'bar'
b.readFileSync('/foo', 'utf8'); // 'baz'
Reach for memfs() when you also want the bound, constants-carrying fs
object alongside the volume --- see the Node fs API
page.
unionfsunionfs layers several filesystems
into one. Overlay an in-memory volume on top of the real disk:
import * as realFs from 'fs';
import { ufs } from 'unionfs';
import { Volume } from 'memfs';
const vol = Volume.fromJSON({ '/foo': 'bar' });
ufs.use(realFs).use(vol);
ufs.readFileSync('/foo', 'utf8'); // 'bar', served from the volume
require: fs-monkeyfs-monkey can point Node's module
loader at a volume, so require() resolves modules out of memory:
import { patchRequire } from 'fs-monkey';
vol.writeFileSync('/index.js', 'console.log("hi world")');
patchRequire(vol);
require('/index'); // logs: hi world
fs-monkey also exposes patchFs(vol) to monkey-patch the global fs module
itself.