跳转到内容

Node.js

目录

  • 获取当前工作目录
js
console.log(process.cwd());
  • 获取当前文件所在目录
js
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

console.log(__dirname); // 当前文件所在目录

文件

判断文件或目录是否存在

js
fs.existsSync(path);

判断文件还是目录

js
const fullPath = path.join(dirPath, file);
const stat = fs.statSync(fullPath);
console.log(stat.isDirectory());

获取文件绝对路径

js
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const filePath = path.resolve(__dirname, './file.txt');
// 或者 const filePath = path.join(__dirname, 'file.txt');

获取文件内容

js
const mdData = fs.readFileSync(filePath, 'utf8');

写入文件内容

js
// 写入/覆盖
fs.writeFileSync(STORE_PATH, JSON.stringify({}, null, 2));
// 追加
fs.appendFileSync(STORE_PATH, JSON.stringify(data, null, 2));

读取并解析 JSON 文件

js
import fs from 'fs';
import path from 'path';

const readJsonFile = (filePath) => {
  const fullPath = path.resolve(filePath);
  const content = fs.readFileSync(fullPath, 'utf8');
  return JSON.parse(content);
};

写入 JSON 文件到指定目录

js
import fs from 'fs';
import path from 'path';

const writeJsonFile = (dirPath, fileName, data) => {
  // 确保目录存在
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath, { recursive: true });
  }
  const fullPath = path.join(dirPath, fileName);
  fs.writeFileSync(fullPath, JSON.stringify(data, null, 2), 'utf8');
  return fullPath;
};

递归搜索目录中的文件

js
import fs from 'fs';
import path from 'path';

const findFiles = (dirPath, extensions = [], excludeDirs = []) => {
  const files = [];

  const traverse = (currentPath) => {
    const items = fs.readdirSync(currentPath);

    for (const item of items) {
      const fullPath = path.join(currentPath, item);
      const stat = fs.statSync(fullPath);

      if (stat.isDirectory()) {
        // 跳过排除的目录
        if (!excludeDirs.includes(item)) {
          traverse(fullPath);
        }
      } else {
        // 按扩展名过滤
        if (extensions.length === 0 || extensions.includes(path.extname(item))) {
          files.push(fullPath);
        }
      }
    }
  };

  traverse(dirPath);
  return files;
};

递归搜索目录(带文件信息)

js
import fs from 'fs';
import path from 'path';

const scanDirectory = (dirPath, options = {}) => {
  const {
    extensions = [],      // 文件扩展名过滤,如 ['.js', '.ts']
    excludeDirs = [],     // 排除的目录名
    includeHidden = false // 是否包含隐藏文件
  } = options;

  const results = [];

  const traverse = (currentPath, depth = 0) => {
    const items = fs.readdirSync(currentPath);

    for (const item of items) {
      // 跳过隐藏文件
      if (!includeHidden && item.startsWith('.')) {
        continue;
      }

      const fullPath = path.join(currentPath, item);
      const stat = fs.statSync(fullPath);

      if (stat.isDirectory()) {
        if (!excludeDirs.includes(item)) {
          traverse(fullPath, depth + 1);
        }
      } else {
        // 扩展名过滤
        if (extensions.length === 0 || extensions.includes(path.extname(item))) {
          results.push({
            path: fullPath,
            name: item,
            basename: path.basename(item, path.extname(item)),
            ext: path.extname(item),
            size: stat.size,
            depth
          });
        }
      }
    }
  };

  traverse(dirPath);
  return results;
};

获取文件名和扩展名

js
import path from 'path';

const filePath = '/path/to/file/example.txt';

// 获取完整文件名
const fileName = path.basename(filePath);           // 'example.txt'

// 获取不带扩展名的文件名
const baseName = path.basename(filePath, path.extname(filePath)); // 'example'

// 获取扩展名
const extName = path.extname(filePath);             // '.txt'

// 获取目录名
const dirName = path.dirname(filePath);             // '/path/to/file'

// 解析路径(包含所有信息)
const parsed = path.parse(filePath);
// {
//   root: '/',
//   dir: '/path/to/file',
//   base: 'example.txt',
//   ext: '.txt',
//   name: 'example'
// }

复制文件到目标目录

js
import fs from 'fs';
import path from 'path';

const copyFile = (sourcePath, targetDir) => {
  // 确保目标目录存在
  if (!fs.existsSync(targetDir)) {
    fs.mkdirSync(targetDir, { recursive: true });
  }

  const fileName = path.basename(sourcePath);
  const targetPath = path.join(targetDir, fileName);

  fs.copyFileSync(sourcePath, targetPath);
  return targetPath;
};

删除文件或目录

js
import fs from 'fs';
import path from 'path';

const deletePath = (targetPath) => {
  if (!fs.existsSync(targetPath)) {
    return;
  }

  const stat = fs.statSync(targetPath);

  if (stat.isDirectory()) {
    // 递归删除目录
    fs.rmSync(targetPath, { recursive: true, force: true });
  } else {
    // 删除文件
    fs.unlinkSync(targetPath);
  }
};

重命名/移动文件

js
import fs from 'fs';
import path from 'path';

const moveFile = (sourcePath, targetPath) => {
  // 确保目标目录存在
  const targetDir = path.dirname(targetPath);
  if (!fs.existsSync(targetDir)) {
    fs.mkdirSync(targetDir, { recursive: true });
  }

  fs.renameSync(sourcePath, targetPath);
  return targetPath;
};

获取文件统计信息

js
import fs from 'fs';

const getFileInfo = (filePath) => {
  const stat = fs.statSync(filePath);

  return {
    isFile: stat.isFile(),
    isDirectory: stat.isDirectory(),
    size: stat.size,                    // 字节
    created: stat.birthtime,            // 创建时间
    modified: stat.mtime,               // 修改时间
    accessed: stat.atime                // 访问时间
  };
};

路径拼接与解析

js
import path from 'path';

// 拼接路径(自动处理分隔符)
const joinedPath = path.join('/foo', 'bar', 'baz/asdf', 'quux');
// '/foo/bar/baz/asdf/quux'

// 解析为绝对路径
const resolvedPath = path.resolve('src/file.txt');
// '/current/working/dir/src/file.txt'

// 获取相对路径
const relativePath = path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// '../../impl/bbb'

// 规范化路径(处理 .. 和 .)
const normalizedPath = path.normalize('/foo/bar//baz/asdf/quux/..');
// '/foo/bar/baz/asdf'

文件系统工具函数集合

以下是一个完整的文件系统工具类,整合上述常用功能:

js
import fs from 'fs';
import path from 'path';

class FsUtils {
  /**
   * 读取 JSON 文件
   * @param {string} filePath - 文件路径
   * @returns {Object} 解析后的 JSON 对象
   */
  static readJson(filePath) {
    const content = fs.readFileSync(filePath, 'utf8');
    return JSON.parse(content);
  }

  /**
   * 写入 JSON 文件
   * @param {string} dirPath - 目录路径
   * @param {string} fileName - 文件名
   * @param {Object} data - 要写入的数据
   * @returns {string} 写入文件的完整路径
   */
  static writeJson(dirPath, fileName, data) {
    if (!fs.existsSync(dirPath)) {
      fs.mkdirSync(dirPath, { recursive: true });
    }
    const fullPath = path.join(dirPath, fileName);
    fs.writeFileSync(fullPath, JSON.stringify(data, null, 2), 'utf8');
    return fullPath;
  }

  /**
   * 递归搜索目录
   * @param {string} dirPath - 目录路径
   * @param {Object} options - 配置选项
   * @returns {Array} 文件信息数组
   */
  static scan(dirPath, options = {}) {
    const {
      extensions = [],
      excludeDirs = ['node_modules', '.git'],
      includeHidden = false
    } = options;

    const results = [];

    const traverse = (currentPath, depth = 0) => {
      const items = fs.readdirSync(currentPath);

      for (const item of items) {
        if (!includeHidden && item.startsWith('.')) continue;

        const fullPath = path.join(currentPath, item);
        const stat = fs.statSync(fullPath);

        if (stat.isDirectory()) {
          if (!excludeDirs.includes(item)) {
            traverse(fullPath, depth + 1);
          }
        } else {
          if (extensions.length === 0 || extensions.includes(path.extname(item))) {
            results.push({
              path: fullPath,
              name: item,
              basename: path.basename(item, path.extname(item)),
              ext: path.extname(item),
              size: stat.size,
              depth
            });
          }
        }
      }
    };

    traverse(dirPath);
    return results;
  }

  /**
   * 复制文件
   * @param {string} sourcePath - 源文件路径
   * @param {string} targetDir - 目标目录
   * @returns {string} 目标文件完整路径
   */
  static copy(sourcePath, targetDir) {
    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true });
    }
    const targetPath = path.join(targetDir, path.basename(sourcePath));
    fs.copyFileSync(sourcePath, targetPath);
    return targetPath;
  }

  /**
   * 删除文件或目录
   * @param {string} targetPath - 目标路径
   */
  static delete(targetPath) {
    if (!fs.existsSync(targetPath)) return;
    const stat = fs.statSync(targetPath);
    if (stat.isDirectory()) {
      fs.rmSync(targetPath, { recursive: true, force: true });
    } else {
      fs.unlinkSync(targetPath);
    }
  }

  /**
   * 移动/重命名文件
   * @param {string} sourcePath - 源文件路径
   * @param {string} targetPath - 目标路径
   * @returns {string} 目标路径
   */
  static move(sourcePath, targetPath) {
    const targetDir = path.dirname(targetPath);
    if (!fs.existsSync(targetDir)) {
      fs.mkdirSync(targetDir, { recursive: true });
    }
    fs.renameSync(sourcePath, targetPath);
    return targetPath;
  }

  /**
   * 获取文件信息
   * @param {string} filePath - 文件路径
   * @returns {Object} 文件信息对象
   */
  static getInfo(filePath) {
    const stat = fs.statSync(filePath);
    return {
      isFile: stat.isFile(),
      isDirectory: stat.isDirectory(),
      size: stat.size,
      created: stat.birthtime,
      modified: stat.mtime,
      accessed: stat.atime
    };
  }

  /**
   * 路径解析工具
   * @param {string} filePath - 文件路径
   * @returns {Object} 路径信息对象
   */
  static parsePath(filePath) {
    const parsed = path.parse(filePath);
    return {
      full: filePath,
      dir: parsed.dir,
      base: parsed.base,
      name: parsed.name,
      ext: parsed.ext,
      absolute: path.resolve(filePath)
    };
  }
}

export default FsUtils;

常用路径处理

js
import path from 'path';
import { fileURLToPath } from 'url';

// ES Module 中获取 __dirname 和 __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// 获取项目根目录(假设当前文件在 src/ 目录下)
const rootDir = path.resolve(__dirname, '../');

// 路径拼接示例
const configPath = path.join(rootDir, 'config', 'settings.json');
const outputPath = path.resolve(rootDir, './dist/bundle.js');

// 路径分割
const parts = path.parse('/Users/name/project/src/file.js');
// parts.dir  -> '/Users/name/project/src'
// parts.base -> 'file.js'
// parts.name -> 'file'
// parts.ext  -> '.js'

使用文档

引入方式

将上述 FsUtils 类保存为独立文件,然后在项目中引入:

js
import FsUtils from './utils/FsUtils.js';

功能使用示例

读取 JSON 配置文件

js
// 读取 package.json
const pkg = FsUtils.readJson('./package.json');
console.log(pkg.name);      // 项目名称
console.log(pkg.version);   // 项目版本
console.log(pkg.dependencies); // 依赖列表

写入配置文件

js
// 生成配置文件到 config 目录
const settings = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retry: 3
};

const filePath = FsUtils.writeJson('./config', 'settings.json', settings);
// 返回: '/project/config/settings.json'

扫描项目中的特定文件

js
// 查找所有 JavaScript 文件
const jsFiles = FsUtils.scan('./src', {
  extensions: ['.js', '.jsx']
});
console.log(jsFiles);
// [
//   { path: '/src/index.js', name: 'index.js', basename: 'index', ext: '.js', size: 1024, depth: 0 },
//   { path: '/src/utils/helper.js', name: 'helper.js', basename: 'helper', ext: '.js', size: 2048, depth: 1 }
// ]

// 查找所有文件(不限制扩展名)
const allFiles = FsUtils.scan('./src', {
  excludeDirs: ['node_modules', '.git', 'dist']
});

复制文件到构建目录

js
// 复制静态资源到 dist 目录
const sourceFile = './src/assets/logo.png';
const targetDir = './dist/assets';

const copiedPath = FsUtils.copy(sourceFile, targetDir);
// 返回: '/dist/assets/logo.png'

清理构建目录

js
// 删除旧的构建目录
FsUtils.delete('./dist');
// 删除单个文件
FsUtils.delete('./output.log');

移动临时文件

js
// 将生成的临时文件移动到正式目录
FsUtils.move('./temp/result.json', './output/results.json');

获取文件详细信息

js
// 检查文件信息
const info = FsUtils.getInfo('./src/app.js');

if (info.isFile) {
  console.log(`文件大小: ${(info.size / 1024).toFixed(2)} KB`);
  console.log(`修改时间: ${info.modified}`);
}

解析文件路径

js
// 获取路径的各个组成部分
const parsed = FsUtils.parsePath('/project/src/components/Button.jsx');

console.log(parsed.full);     // '/project/src/components/Button.jsx'
console.log(parsed.dir);      // '/project/src/components'
console.log(parsed.base);     // 'Button.jsx'
console.log(parsed.name);     // 'Button'
console.log(parsed.ext);      // '.jsx'
console.log(parsed.absolute); // '/project/src/components/Button.jsx'

实际应用场景

场景一:构建工具中的文件处理

js
import FsUtils from './utils/FsUtils.js';

// 1. 清理旧构建
FsUtils.delete('./dist');

// 2. 复制静态资源
FsUtils.copy('./src/public/index.html', './dist');
FsUtils.copy('./src/public/favicon.ico', './dist');

// 3. 读取源文件配置
const config = FsUtils.readJson('./src/config.json');

// 4. 扫描所有需要处理的源文件
const sourceFiles = FsUtils.scan('./src', {
  extensions: ['.js', '.css'],
  excludeDirs: ['tests', 'examples']
});

// 5. 处理每个文件
sourceFiles.forEach(file => {
  const relativePath = file.path.replace('/src/', '');
  const outputPath = `./dist/${relativePath}`;
  // 执行编译、压缩等操作...
});

场景二:项目文档生成工具

js
import FsUtils from './utils/FsUtils.js';

// 1. 扫描所有 Markdown 文件
const mdFiles = FsUtils.scan('./docs', {
  extensions: ['.md'],
  excludeDirs: ['node_modules', '.git']
});

// 2. 构建文档树结构
const docTree = {
  title: '项目文档',
  files: mdFiles.map(file => ({
    path: file.path,
    name: file.basename,
    title: file.basename.replace(/-/g, ' ')
  }))
};

// 3. 输出文档索引文件
FsUtils.writeJson('./docs', 'index.json', docTree);

场景三:静态资源管理

js
import FsUtils from './utils/FsUtils.js';

// 1. 扫描图片目录
const images = FsUtils.scan('./src/assets/images', {
  extensions: ['.png', '.jpg', '.svg', '.webp']
});

// 2. 按类型分类
const imagesByType = {
  png: images.filter(img => img.ext === '.png'),
  jpg: images.filter(img => img.ext === '.jpg'),
  svg: images.filter(img => img.ext === '.svg'),
  webp: images.filter(img => img.ext === '.webp')
};

// 3. 生成资源清单
const manifest = {
  images: images.map(img => ({
    src: img.path,
    name: img.basename,
    size: img.size,
    type: img.ext.slice(1)
  }))
};

FsUtils.writeJson('./dist', 'asset-manifest.json', manifest);

场景四:配置文件迁移

js
import FsUtils from './utils/FsUtils.js';

// 读取旧配置
const oldConfig = FsUtils.readJson('./config.old.json');

// 转换配置格式
const newConfig = {
  version: '2.0',
  server: {
    host: oldConfig.hostname,
    port: oldConfig.port
  },
  database: {
    url: oldConfig.dbUrl,
    pool: oldConfig.dbPool
  }
};

// 备份旧配置
FsUtils.move('./config.old.json', './backup/config.old.json');

// 写入新配置
FsUtils.writeJson('./config', 'app.json', newConfig);

场景五:批量文件重命名

js
import FsUtils from './utils/FsUtils.js';

// 扫描需要重命名的文件
const files = FsUtils.scan('./src/components', {
  extensions: ['.js']
});

// 批量重命名(添加前缀)
files.forEach(file => {
  const oldPath = file.path;
  const newPath = file.path.replace(file.name, `Comp_${file.name}`);
  FsUtils.move(oldPath, newPath);
});

注意事项

  1. 路径处理

    • 所有路径参数支持相对路径和绝对路径
    • 相对路径基于当前工作目录(process.cwd())解析
    • Windows 路径分隔符会被自动处理
  2. 目录自动创建

    • writeJsoncopymove 方法会自动创建目标目录
    • 使用 recursive: true 参数确保父目录存在
  3. 文件覆盖

    • writeJsoncopy 操作会覆盖已存在的文件
    • 建议在覆盖前检查文件是否存在
  4. 错误处理

    • 同步方法在出错时会抛出异常
    • 建议使用 try-catch 包裹调用:
    js
    try {
      const data = FsUtils.readJson('./config.json');
    } catch (error) {
      console.error('读取配置失败:', error.message);
    }
  5. 性能考虑

    • 扫描大目录时注意内存使用
    • 可以通过 excludeDirs 排除不必要的目录
    • 深度遍历可能影响性能

Will Try My Best.