From 1d931c88b2d5428670fb39458158ce9c5ac242a3 Mon Sep 17 00:00:00 2001 From: Sukka Date: Sun, 11 Feb 2024 02:11:25 +0800 Subject: [PATCH] Refactor `copy` to use `opendir` (#1028) * refactor(copy): backport https://github.com/nodejs/node/pull/41351 * perf(copy): parallel copy * perf(copy): run filter in parallel as well --- lib/copy/copy-sync.js | 12 +++++++++++- lib/copy/copy.js | 33 +++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/copy/copy-sync.js b/lib/copy/copy-sync.js index 8bc60119..40b29793 100644 --- a/lib/copy/copy-sync.js +++ b/lib/copy/copy-sync.js @@ -106,7 +106,17 @@ function mkDirAndCopy (srcMode, src, dest, opts) { } function copyDir (src, dest, opts) { - fs.readdirSync(src).forEach(item => copyDirItem(item, src, dest, opts)) + const dir = fs.opendirSync(src) + + try { + let dirent + + while ((dirent = dir.readSync()) !== null) { + copyDirItem(dirent.name, src, dest, opts) + } + } finally { + dir.closeSync() + } } function copyDirItem (item, src, dest, opts) { diff --git a/lib/copy/copy.js b/lib/copy/copy.js index 6304b021..47fd9836 100644 --- a/lib/copy/copy.js +++ b/lib/copy/copy.js @@ -113,23 +113,28 @@ async function onDir (srcStat, destStat, src, dest, opts) { await fs.mkdir(dest) } - const items = await fs.readdir(src) + const promises = [] // loop through the files in the current directory to copy everything - await Promise.all(items.map(async item => { - const srcItem = path.join(src, item) - const destItem = path.join(dest, item) - - // skip the item if it is matches by the filter function - const include = await runFilter(srcItem, destItem, opts) - if (!include) return - - const { destStat } = await stat.checkPaths(srcItem, destItem, 'copy', opts) + for await (const item of await fs.opendir(src)) { + const srcItem = path.join(src, item.name) + const destItem = path.join(dest, item.name) + + promises.push( + runFilter(srcItem, destItem, opts).then(include => { + if (include) { + // only copy the item if it matches the filter function + return stat.checkPaths(srcItem, destItem, 'copy', opts).then(({ destStat }) => { + // If the item is a copyable file, `getStatsAndPerformCopy` will copy it + // If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively + return getStatsAndPerformCopy(destStat, srcItem, destItem, opts) + }) + } + }) + ) + } - // If the item is a copyable file, `getStatsAndPerformCopy` will copy it - // If the item is a directory, `getStatsAndPerformCopy` will call `onDir` recursively - return getStatsAndPerformCopy(destStat, srcItem, destItem, opts) - })) + await Promise.all(promises) if (!destStat) { await fs.chmod(dest, srcStat.mode)