Created
January 24, 2017 23:04
-
-
Save cspotcode/8770b53d674c16206eb91d6f2a367023 to your computer and use it in GitHub Desktop.
Faster Grunt copy task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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