Skip to content

Instantly share code, notes, and snippets.

@jamessouth
Created May 2, 2020 04:49
Show Gist options
  • Save jamessouth/c752c91a869b81dc3153630d0f208d34 to your computer and use it in GitHub Desktop.
Save jamessouth/c752c91a869b81dc3153630d0f208d34 to your computer and use it in GitHub Desktop.
Node script that hashes the files in a dist directory in the proper order, i.e. hashes the files that a file references before hashing the file itself
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const walk = require('walkdir');
let tree = [];
//hash and rename a file
async function hashFile(filepath) {
const file = fs.createReadStream(filepath);
const hash = crypto.createHash('md5');
const { dir, name, ext } = path.parse(filepath);
return new Promise((res) => {
file.on('readable', () => {
const data = file.read();
if (data) {
hash.update(data);
} else {
const dig = hash.digest('hex');
const newname = `${name}.${dig}${ext}`;
const newpath = path.resolve(dir, newname);
fs.renameSync(filepath, newpath);
res(newname);
}
});
});
}
//loop through paths found in a file, send each path to either be hashed or explore its paths
async function* generateNewName(arr) {
for (let i = 0; i < arr.length; i += 1) {
let newname;
const { base, ext } = path.parse(arr[i]);
const match = tree.find((e) => e.includes(base)); //get absolute path from tree
if (['.png', '.jpg', '.pdf'].includes(ext)) { //files that don't reference other files can be hashed
newname = await hashFile(match);
} else {
newname = await makeNewFile(match); //follow other paths to get their sub-paths and repeat
}
yield [base, newname];
}
}
//get paths from a file, exclude non-hashed files, replace old paths with new hashed names, return either new or original
//file contents
async function updateHashedPaths(data) {
let file = data.replace(/src\//g, ''); //src used in dev, remove for prod
const paths = file.match(/\.\.?\/(\w+\/)?[\w-]+\.(css|m?js|png|jpg|pdf)/g); //adjust as needed to capture paths
if (paths) {
const uniquePaths = [...new Set([...paths.filter((p) => !p.includes('service-worker', 'manifest.webmanifest'))])]; //exclude
for await (const p of generateNewName(uniquePaths)) {
file = file.replace(new RegExp(p[0], 'g'), p[1]); //update paths
}
return file;
}
return data;
}
//opens file for hashing and writes new file
async function makeNewFile(file) {
const resolvedFile = path.resolve(file);
const data = await fs.promises.readFile(resolvedFile, 'utf8');
const newFile = await updateHashedPaths(data);
await fs.promises.writeFile(resolvedFile, newFile, 'utf8');
const newname = resolvedFile.endsWith('html') ? resolvedFile : await hashFile(resolvedFile); //hash file that does reference
//other files, after paths have been updated
return newname;
}
//walk directory to get absolute file paths for easier matching with relative paths ./ and ../
async function getFileTree(file) {
const dirs = [];
const filetree = await walk.async(path.dirname(file), {
filter: (dir, files) => {
dirs.push(dir);
return files.filter((f) => !['icons', 'index.html', 'manifest.webmanifest'].includes(f)); //exclude paths we don't
//want to hash
},
});
return [file, filetree.filter((f) => !dirs.includes(f))];
}
//pass starter file through while updating tree array
function sepArray([file, arr]) {
tree = tree.concat(arr);
return file;
}
//start with node hashFilenames.js ./dist/index.html
getFileTree(process.argv[2])
.then(sepArray)
.then(makeNewFile)
.catch((e) => console.error(e));
@jamessouth
Copy link
Author

This script starts with your index.html file and recursively gets every file path in your dist/ down to your images (and any other files that don't reference other files), then starts hashing, renaming, updating paths, and working back up through your file tree, such that a file is not hashed until the paths it contains have been hashed and updated. Your index.html file is the first file opened and the last file closed.

Inside index.html the first path might be a css file, that will be opened, there might be several images, those will be hashed, then the css file will be hashed, then the path in the index.html will be updated and the script will move on, maybe there are some images in the html, hash those and update paths, move on, last path is a js file, open it, find more js files, open those, hash, update paths, back up to updating the js path in the html, close it up and you're done! 👍😊😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment