Created
May 2, 2020 04:49
-
-
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
This file contains 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
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)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script starts with your
index.html
file and recursively gets every file path in yourdist/
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. Yourindex.html
file is the first file opened and the last file closed.Inside
index.html
the first path might be acss
file, that will be opened, there might be several images, those will be hashed, then thecss
file will be hashed, then the path in theindex.html
will be updated and the script will move on, maybe there are some images in thehtml
, hash those and update paths, move on, last path is ajs
file, open it, find morejs
files, open those, hash, update paths, back up to updating thejs
path in thehtml
, close it up and you're done! 👍😊😃