Skip to content

Instantly share code, notes, and snippets.

@cspotcode
Created January 24, 2017 23:04
Show Gist options
  • Save cspotcode/8770b53d674c16206eb91d6f2a367023 to your computer and use it in GitHub Desktop.
Save cspotcode/8770b53d674c16206eb91d6f2a367023 to your computer and use it in GitHub Desktop.
Faster Grunt copy task
import {unique} from 'lodash';
import * as path from 'path';
/*
* Grunt copy task that's faster by eliminating unnecessary or redundant fs calls.
*
* Things it does *not* do:
* - set or copy file mode / permissions
* - "process"ing files
* - "process"ing files as raw buffers
* - recursively copying directories
* - tally created files and directories identically to grunt-contrib-copy
* - verbose logging identical to grunt-contrib-copy
* - catch case where src is directory and dest is a pre-existing file (silently succeeds instead)
*/
grunt.registerMultiTask('copy', function() {
const {target, data, files} = this;
const destFiles = [];
files.forEach(fileMapping => {
const {src: srcArray, dest} = fileMapping;
if(srcArray.length !== 1) throw new Error('Only supports one-to-one file mappings.');
destFiles.push(dest);
});
const dirs = unique(destFiles.map(f => path.dirname(f)));
addAllParentDirs(dirs);
// Ensure that parent directories are always created before children
dirs.sort();
let createdDirectories = 0;
dirs.forEach(dir => {
if(mkDir(dir)) createdDirectories++;
});
let copiedFiles = 0;
files.forEach(({src: [src], dest}) => {
switch(copyFile(src, dest, true)) {
case 1:
copiedFiles++;
break;
case 2:
createdDirectories++;
break;
}
});
console.log(`Created ${ createdDirectories } directories, copied ${ copiedFiles } files`);
});
/**
* Given an array of paths, recursively adds paths to all containing directories.
* For example, for path '/foo/bar/baz', adds '/foo/bar' and '/foo'.
*/
function addAllParentDirs(dirs) {
for(let i = 0; i < dirs.length; i++) {
const dir = dirs[i];
const dirOfDir = path.dirname(dir);
if(dirOfDir !== '.' && dirs.indexOf(dirOfDir) === -1) dirs.push(dirOfDir);
}
}
/**
* Copies src to dest. If src is a directory, then creates target directory.
* Returns 0 if nothing was done, 1 if file was created, 2 if directory was created.
*/
function copyFile(src, dest, createDir = false) {
let content;
try {
content = fs.readFileSync(src);
} catch(e) {
// If we tried to read a directory, that's ok. Just skip it.
if(e.code === 'EISDIR') {
if(createDir && mkDir(dest)) return 2;
return 0;
}
throw e;
}
fs.writeFileSync(dest, content);
return 1;
}
/**
* Create directory at given path if a file or directory does not already exist at that path.
* Does not indicate if a file existed at that path even though that might be unexpected.
* Returns true if directory was created, false otherwise.
*/
function mkDir(dir) {
try {
fs.mkdirSync(dir);
} catch(e) {
// If the directory already exists, that's ok.
if(e.code !== 'EEXIST') throw e;
return false;
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment