copy-dir vs copyfiles vs cpx vs fs-extra vs ncp
Strategic File Copying Patterns in Node.js Build Systems
copy-dircopyfilescpxfs-extrancpSimilar Packages:

Strategic File Copying Patterns in Node.js Build Systems

The packages copy-dir, copyfiles, cpx, fs-extra, and ncp all address the fundamental need to move files and directories within Node.js environments, yet they serve distinctly different architectural roles. fs-extra acts as a comprehensive drop-in replacement for the native fs module, offering robust, promise-based file operations with added safety features like move and copy that preserve permissions. cpx and copyfiles are specialized CLI tools designed primarily for build scripts, excelling at glob-based pattern matching to copy specific file types (like assets or configs) from source to destination folders. ncp is an older, asynchronous recursive copier known for speed but lacking modern glob support, while copy-dir provides a simple programmatic interface for recursive directory copying. Understanding the trade-offs between a full library (fs-extra), a CLI utility (cpx, copyfiles), and a legacy tool (ncp) is critical for designing maintainable build pipelines and tooling scripts.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
copy-dir0---6 years agoMIT
copyfiles0422-566 years agoMIT
cpx0526-3510 years agoMIT
fs-extra09,60159.3 kB126 days agoMIT
ncp0681-7811 years agoMIT

Strategic File Copying Patterns in Node.js Build Systems

Moving files and directories is a daily task in frontend engineering, whether you are bundling assets, syncing configuration files, or preparing a deployment package. While the native Node.js fs module provides basic tools, it often requires verbose boilerplate for recursive operations or lacks built-in glob support. The ecosystem offers five distinct solutions: fs-extra, cpx, copyfiles, copy-dir, and ncp. Each solves the problem from a different angleβ€”some as full libraries, others as specialized CLI tools.

Let's break down how they handle real-world engineering scenarios.

πŸ› οΈ Core Philosophy: Library vs. CLI Tool

The first decision is whether you need a library to embed in your code or a command-line tool for your package.json scripts.

fs-extra is a full-featured library. It replaces the native fs module and adds methods like copy, move, and ensureDir. It is designed to be imported and used directly in your JavaScript or TypeScript logic.

// fs-extra: Used inside a script or application
const fs = require('fs-extra');

async function deploy() {
  // Copies recursively, creates destination if missing
  await fs.copy('./src/assets', './dist/assets');
  console.log('Assets deployed');
}

cpx and copyfiles are primarily CLI tools. You rarely import them; instead, you call them in your package.json scripts. They shine when you need to copy files based on patterns (globs) without writing a custom script.

# cpx: CLI usage in package.json
# Copies all .css files from src to dist, preserving structure
"scripts": {
  "build:css": "cpx \"src/**/*.css\" dist"
}
# copyfiles: CLI usage with flat output option
# Copies all .json files to the root of dist, flattening folders
"scripts": {
  "build:config": "copyfiles -u 1 \"src/config/*.json\" dist"
}

copy-dir is a lightweight library focused solely on recursive directory copying. It sits between the heavy fs-extra and the CLI-only tools.

// copy-dir: Simple programmatic recursive copy
const copyDir = require('copy-dir');

copyDir.sync('./src/templates', './dist/templates', {
  utimes: true,  // Keep modification times
  mode: true     // Keep permissions
});

ncp is an older library that provides asynchronous recursive copying. It is minimal and callback-based, lacking the modern Promise API unless wrapped.

// ncp: Legacy callback-based approach
const ncp = require('ncp').ncp;

ncp('./src/legacy', './dist/legacy', function (err) {
  if (err) return console.error(err);
  console.log('Legacy copy done');
});

🎯 Pattern Matching and Glob Support

In modern build systems, you rarely copy entire folders blindly. You usually need to select specific file types (e.g., "all images but not source maps").

cpx has excellent glob support built-in. It uses standard glob syntax, making it easy to include or exclude files directly in the command.

# cpx: Advanced globbing
# Copy all .png and .jpg files, excluding those in 'test' folders
cpx "src/**/*.{png,jpg}" dist --ignore "**/test/**"

copyfiles also supports globs but offers unique flags to control how the directory structure is treated in the output. The -u (up) flag is particularly powerful for flattening paths.

# copyfiles: Flattening directory structures
# Takes src/images/logo.png and outputs dist/logo.png (removes 'images' folder)
copyfiles -u 2 "src/images/*.png" dist

fs-extra does not support globs natively in its copy method. You must pair it with a library like globby or fast-glob to filter files before copying, which adds complexity but offers maximum control.

// fs-extra + globby: Manual glob handling
const fs = require('fs-extra');
const globby = require('globby');

async function copyImages() {
  const files = await globby('src/**/*.{png,jpg}', { ignore: ['**/test/**'] });
  
  for (const file of files) {
    const dest = file.replace('src', 'dist');
    await fs.copy(file, dest);
  }
}

copy-dir and ncp do not support globs. They copy everything in the source directory. If you need filtering, you must implement your own traversal logic or switch tools.

// copy-dir: No glob support, copies EVERYTHING
// Cannot easily exclude specific file types without custom filters
const copyDir = require('copy-dir');
copyDir.sync('./src', './dist'); 

πŸ”„ Watch Mode for Development

During local development, you often need to sync files continuously as you save them. Restarting a build script for every change is too slow.

cpx has a built-in --watch flag. This is its killer feature for frontend devs. It keeps the process running and copies files instantly upon change.

# cpx: Watch mode
# Keeps running, copying TS files to JS folder on every save
cpx "src/**/*.ts" dist --watch

copyfiles also supports a --watch (or -w) flag, providing similar functionality for asset pipelines.

# copyfiles: Watch mode
# Watches for SCSS changes and copies to dist
copyfiles -u 1 "src/**/*.scss" dist --watch

fs-extra, copy-dir, and ncp do not have built-in watch modes. To achieve this, you must combine them with a file watcher like chokidar.

// fs-extra + chokidar: Manual watch implementation
const chokidar = require('chokidar');
const fs = require('fs-extra');

chokidar.watch('src/assets').on('change', async (path) => {
  const dest = path.replace('src', 'dist');
  await fs.copy(path, dest);
  console.log(`Updated: ${dest}`);
});

⚠️ Deprecation and Maintenance Status

A critical architectural decision is avoiding unmaintained dependencies.

ncp is effectively deprecated for new projects. It has not seen significant updates in years, uses an outdated callback-only API, and lacks modern features like promises or globbing. While it still works for simple tasks, it introduces technical debt.

Recommendation: Do not use ncp in new codebases. Replace it with fs-extra for library needs or cpx for CLI scripts.

copy-dir is stable but niche. It receives occasional updates but is not as actively developed as fs-extra. It remains a valid choice for simple, synchronous recursive copies where you don't need the full weight of fs-extra.

fs-extra, cpx, and copyfiles are actively maintained and widely used in the industry. They receive regular updates to support newer Node.js versions and fix security issues.

πŸ“Š Summary of Trade-offs

Featurefs-extracpxcopyfilescopy-dirncp
TypeLibraryCLICLILibraryLibrary
Async/Promiseβœ… YesN/A (CLI)N/A (CLI)βœ… Sync/Async❌ Callback only
Glob Support❌ (Needs helper)βœ… Excellentβœ… Excellent❌ No❌ No
Watch Mode❌ (Needs helper)βœ… Built-inβœ… Built-in❌ No❌ No
Recursiveβœ… Yesβœ… Yes (via glob)βœ… Yes (via glob)βœ… Yesβœ… Yes
Status🟒 Active🟒 Active🟒 Active🟑 StableπŸ”΄ Legacy

πŸ’‘ The Big Picture

Choosing the right tool depends entirely on where and how you are copying files.

fs-extra is your go-to for application logic. If you are writing a build plugin, a CLI tool of your own, or complex server-side logic that needs to move files safely with error handling, this is the industry standard. It replaces fs and never lets you down.

cpx is the winner for simple build scripts. If you just need to copy assets, types, or configs in your package.json and want watch mode for development, cpx offers the best developer experience with minimal configuration. Its glob syntax is intuitive and powerful.

copyfiles is the specialist for complex path manipulation. If your build requires flattening directory trees or moving files with specific structural rules (e.g., "strip the first two folders"), copyfiles provides flags that cpx lacks.

copy-dir serves a niche for lightweight, synchronous scripts where you need to duplicate a whole folder tree without external dependencies like fs-extra. It is simple and effective for that one specific job.

ncp should be left in the past. Its lack of modern features and promise support makes it a poor choice for any new architecture.

Final Thought: For most frontend architectures, a combination of fs-extra (for programmatic tasks) and cpx (for npm script automation) covers 95% of use cases efficiently and safely.

How to Choose: copy-dir vs copyfiles vs cpx vs fs-extra vs ncp

  • copy-dir:

    Choose copy-dir if you need a lightweight, programmatic solution specifically for recursively copying entire directory structures without complex glob patterns. It is suitable for simple internal tooling scripts where you need to duplicate a folder tree exactly as-is, but avoid it for complex build pipelines requiring file filtering or CLI integration.

  • copyfiles:

    Choose copyfiles when your primary requirement is a robust CLI tool for build scripts that supports advanced glob patterns and preserves directory structure flexibility. It is ideal for CI/CD pipelines where you need to move specific assets (like .css or .json files) from deep nested folders to a distribution directory using command-line arguments.

  • cpx:

    Choose cpx if you want a fast, reliable CLI tool specifically optimized for watching and copying files during development or build processes. It excels in scenarios where you need to mirror a subset of files (using globs) repeatedly, such as copying static assets to a dist folder whenever source files change, offering a simpler syntax than copyfiles for standard use cases.

  • fs-extra:

    Choose fs-extra when you need a comprehensive, promise-based file system library that extends the native fs module with safety guarantees and additional methods like move, ensureDir, and copy. It is the best choice for application logic, complex build plugins, or any scenario where you need precise control over file operations, error handling, and recursive copying within JavaScript code rather than just a CLI.

  • ncp:

    Avoid choosing ncp for new projects as it is largely considered legacy; it lacks modern features like glob support and promise-based APIs out of the box. Only consider it if you are maintaining an older codebase that already depends on its specific non-recursive or simple recursive behavior and cannot easily migrate to fs-extra or cpx.

README for copy-dir

copy-dir

Easy used 'copy-dir' lib, even use a filter, copy a file or directory to another path, when target path or parent target path not exists, it will create the directory automatically.

install

npm install copy-dir

grammar

Sync Mode:

copydir.sync(from, to[, options]);

Async Mode:

copydir(from, to, [options, ]callback);

[options]:

  utimes: false,  // Boolean | Object, keep addTime or modifyTime if true
  mode: false,    // Boolean | Number, keep file mode if true
  cover: true,    // Boolean, cover if file exists
  filter: true,   // Boolean | Function, file filter

filter is a function that you want to filter the path, then return true or false.

It can use three arguments named state, filepath, filename

  • state: String, 'file' / 'directory' / 'symbolicLink', marked as the file or path type
  • filepath: String, the file path
  • filename: String, the file name

usage

Sync Mode:

var copydir = require('copy-dir');

copydir.sync('/my/from/path', '/my/target/path', {
  utimes: true,  // keep add time and modify time
  mode: true,    // keep file mode
  cover: true    // cover file when exists, default is true
});

Async Mode:

var copydir = require('copy-dir');

copydir('/my/from/path', '/my/target/path', {
  utimes: true,  // keep add time and modify time
  mode: true,    // keep file mode
  cover: true    // cover file when exists, default is true
}, function(err){
  if(err) throw err;
  console.log('done');
});

add a filter

When you want to copy a directory, but some file or sub directory is not you want, you can do like this:

Sync Mode:

var path = require('path');
var copydir = require('copy-dir');

copydir.sync('/my/from/path', '/my/target/path', {
  filter: function(stat, filepath, filename){
    // do not want copy .html files
    if(stat === 'file' && path.extname(filepath) === '.html') {
      return false;
    }
    // do not want copy .svn directories
    if (stat === 'directory' && filename === '.svn') {
      return false;
    }
    // do not want copy symbolicLink directories
    if (stat === 'symbolicLink') {
      return false;
    }
    return true;  // remind to return a true value when file check passed.
  }
});
console.log('done');

Async Mode:

var path = require('path');
var copydir = require('copy-dir');

copydir('/a/b/c', '/a/b/e', {
  filter: function(stat, filepath, filename) {
    //...
    return true;
  }
}, function(err) {
  //...
});

Update Logs

1.3.0

Bug fix: filter function arguments incorrect, delete the third argument: dirname

Questions?

If you have any questions, please feel free to ask through New Issue.

License

copy-dir is available under the terms of the MIT License.