fs-extra の使い方 — Node.js ファイル操作の決定版パッケージ
一言でいうと
fs-extra は、Node.js 標準の fs モジュールに 再帰的なディレクトリ作成・コピー・削除・JSON読み書き などの便利メソッドを追加し、さらに全メソッドを Promise 対応させたドロップイン置き換えパッケージです。内部で graceful-fs を使用しており、大量ファイル操作時の EMFILE エラーも防止します。
どんな時に使う?
- ディレクトリごと再帰的にコピー・削除したい時 —
fs.cpやfs.rmのrecursiveオプションを毎回指定する煩わしさから解放されます - 存在しない中間ディレクトリを含むパスにファイルを書き出したい時 —
outputFileで親ディレクトリの自動作成とファイル書き込みを一発で実行できます - JSON ファイルの読み書きを簡潔に行いたい時 —
readJson/writeJsonでJSON.parse/JSON.stringifyのボイラープレートが不要になります
インストール
# npm
npm install fs-extra
# yarn
yarn add fs-extra
# pnpm
pnpm add fs-extra
TypeScript で使う場合は型定義もインストールします:
npm install -D @types/fs-extra
基本的な使い方
fs-extra は標準 fs のドロップイン置き換えです。require('fs') を require('fs-extra') に変えるだけで、標準メソッドもすべてそのまま使えます。
import fs from 'fs-extra';
// ディレクトリごと再帰コピー(async/await)
async function main() {
try {
// コピー
await fs.copy('/tmp/source', '/tmp/destination');
console.log('コピー完了');
// 中間ディレクトリを自動作成してファイル書き出し
await fs.outputFile('/tmp/deep/nested/dir/hello.txt', 'Hello, World!');
console.log('ファイル書き出し完了');
// JSON の読み書き
await fs.writeJson('/tmp/config.json', { port: 3000, debug: true }, { spaces: 2 });
const config = await fs.readJson('/tmp/config.json');
console.log(config.port); // 3000
// ディレクトリ・ファイルの削除
await fs.remove('/tmp/destination');
console.log('削除完了');
} catch (err) {
console.error(err);
}
}
main();
ESM での利用
ESM で使う場合、2つのインポート方法があります:
// 方法1: デフォルトインポート(fs標準メソッドも含む)
import fs from 'fs-extra';
await fs.copy('/tmp/a', '/tmp/b');
await fs.readFile('/tmp/a/file.txt', 'utf-8'); // 標準fsメソッドも使える
// 方法2: 名前付きインポート(fs-extra独自メソッドのみ)
import { copy, outputFile, readJson } from 'fs-extra/esm';
// ※ fs-extra/esm では標準fsメソッドは含まれないため、別途importが必要
import { readFileSync } from 'fs';
よく使う API
1. copy / copySync — ファイル・ディレクトリのコピー
ファイルまたはディレクトリを再帰的にコピーします。cp -r に相当します。
import fs from 'fs-extra';
// 基本的なコピー
await fs.copy('/tmp/src', '/tmp/dest');
// オプション付き:既存ファイルを上書きしない
await fs.copy('/tmp/src', '/tmp/dest', { overwrite: false, errorOnExist: true });
// フィルター関数で特定ファイルのみコピー
await fs.copy('/tmp/src', '/tmp/dest', {
filter: (src: string) => {
// node_modules を除外
return !src.includes('node_modules');
},
});
2. ensureDir / ensureDirSync — ディレクトリの確実な作成
ディレクトリが存在しなければ再帰的に作成します。既に存在していてもエラーになりません。mkdirp と mkdirs はエイリアスです。
import fs from 'fs-extra';
// 深いネストのディレクトリも一発で作成
await fs.ensureDir('/tmp/a/b/c/d/e');
// パーミッション指定
await fs.ensureDir('/tmp/my-app/logs', { mode: 0o755 });
// 同期版
fs.ensureDirSync('/tmp/sync-dir/nested');
3. outputFile / outputJson — 親ディレクトリ自動作成付きの書き込み
書き込み先の親ディレクトリが存在しない場合、自動的に作成してからファイルを書き込みます。
import fs from 'fs-extra';
// 親ディレクトリ /tmp/reports/2024/ が存在しなくても自動作成される
await fs.outputFile('/tmp/reports/2024/summary.txt', 'Annual Report');
// JSON を整形して書き出し
await fs.outputJson('/tmp/data/config.json', {
database: {
host: 'localhost',
port: 5432,
},
}, { spaces: 2 });
4. readJson / writeJson — JSON ファイルの読み書き
JSON ファイルの読み込み・書き込みを簡潔に行えます。
import fs from 'fs-extra';
// JSON 読み込み(自動的に JSON.parse される)
interface PackageJson {
name: string;
version: string;
dependencies?: Record<string, string>;
}
const pkg = await fs.readJson('./package.json') as PackageJson;
console.log(pkg.name, pkg.version);
// JSON 書き込み(自動的に JSON.stringify される)
await fs.writeJson('./output.json', { key: 'value', count: 42 }, { spaces: 2 });
// readJson に throws: false を渡すと、パースエラー時に null を返す
const data = await fs.readJson('./maybe-broken.json', { throws: false });
if (data === null) {
console.log('JSONのパースに失敗しました');
}
5. move / remove — ファイルの移動と削除
import fs from 'fs-extra';
// ファイル・ディレクトリの移動(リネームにも使える)
await fs.move('/tmp/old-name', '/tmp/new-name');
// 移動先に同名ファイルがある場合は上書き
await fs.move('/tmp/src/file.txt', '/tmp/dest/file.txt', { overwrite: true });
// ファイル・ディレクトリの再帰削除(rm -rf 相当)
await fs.remove('/tmp/some-directory');
// 存在しないパスを指定してもエラーにならない
await fs.remove('/tmp/does-not-exist'); // OK
補足: pathExists / ensureFile / emptyDir
import fs from 'fs-extra';
// パスの存在確認(fs.access のラッパー)
const exists: boolean = await fs.pathExists('/tmp/myfile.txt');
// ファイルの確実な作成(親ディレクトリも自動作成、ファイルが無ければ空ファイルを作成)
await fs.ensureFile('/tmp/logs/app.log');
// ディレクトリの中身を空にする(ディレクトリ自体は残る)
await fs.emptyDir('/tmp/cache');
類似パッケージとの比較
| 特徴 | fs-extra | Node.js 標準 fs | graceful-fs | shelljs |
|---|---|---|---|---|
| ドロップイン置き換え | ✅ | — | ✅ | ❌ |
| 再帰コピー | copy | cp (v16.7+) | ❌ | cp -r |
| 再帰削除 | remove | rm (v14.14+) | ❌ | rm -rf |
| 再帰 mkdir | ensureDir | mkdir({recursive:true}) | ❌ | mkdir -p |
| JSON 読み書き | readJson/writeJson | ❌ | ❌ | ❌ |
| outputFile(親dir自動作成) | ✅ | ❌ | ❌ | ❌ |
| EMFILE エラー防止 | ✅ | ❌ | ✅ | ❌ |
| Promise 対応 | ✅(全メソッド) | fs/promises (v10+) | ❌ | ❌ |
| 週間DL数 | 約1億+ | 組み込み | 約5,000万+ | 約1,500万+ |
補足: Node.js v16 以降で
fs.cp(再帰コピー)やfs.rm(再帰削除)が安定版として追加されました。標準 fs でもかなりのことができるようになりましたが、outputFileやreadJsonなどの便利メソッド、EMFILE対策が不要であれば標準 fs で十分なケースも増えています。
注意点・Tips
1. fs-extra/esm と fs-extra のインポートの違い
// ✅ デフォルトインポートなら fs-extra を使う(標準fsメソッドも含まれる)
import fs from 'fs-extra';
// ⚠️ fs-extra/esm のデフォルトインポートには標準fsメソッドが含まれない
import fse from 'fs-extra/esm';
// fse.readFileSync は undefined
2. copy のフィルター関数は非同期も可
await fs.copy('/src', '/dest', {
filter: async (src: string) => {
const stat = await fs.stat(src);
// 1MB 以上のファイルはスキップ
return stat.isDirectory() || stat.size < 1024 * 1024;
},
});
3. remove は存在しないパスでもエラーにならない
rimraf と同じ挙動です。存在チェックを事前に行う必要はありません。
4. writeJson / outputJson の spaces オプション
デフォルトでは整形なし(1行)で出力されます。人間が読むファイルには { spaces: 2 } を指定しましょう。
// ❌ 整形なし(デフォルト)
await fs.writeJson('./config.json', data);
// → {"port":3000,"debug":true}
// ✅ 整形あり
await fs.writeJson('./config.json', data, { spaces: 2 });
5. Node.js v24+ での定数エクスポートの変更
Node.js v24.0.0 以降では、fs.F_OK、fs.R_OK、fs.W_OK、fs.X_OK が fs-extra からエクスポートされなくなりました。代わりに fs.constants.F_OK 等を使用してください。
6. walk / walkSync は別パッケージに分離済み
ディレクトリの再帰走査が必要な場合は、klaw(非同期)または klaw-sync(同期)を使用してください。
まとめ
fs-extra は、Node.js のファイル操作における事実上の標準ユーティリティです。標準 fs のドロップイン置き換えとして動作し、copy・remove・ensureDir・readJson・outputFile といった頻出パターンを簡潔に書けるようにしてくれます。Node.js の標準 fs が年々強化されてはいますが、JSON 読み書きや outputFile の利便性、EMFILE エラー防止などは依然として fs-extra ならではの価値であり、多くのプロジェクトで導入する価値があるパッケージです。