snowpack-plugin-content-hash
Version:
A plugin for snowpack which will add a hash in the file name. The hash is based on the files content.
171 lines (144 loc) • 4.92 kB
JavaScript
const recursive = require('recursive-readdir')
const fs = require('fs');
const path = require('path');
const replace = require('replace');
const locateImports = require('./locate-imports');
const {
createHashFromFile,
extractDirInPath,
extractFileInPath
} = require('./utils.js');
// defaults
let isDev = false;
const defaultExt = ['js', 'jsx']
const htmlFileDefault = '/index.html'
const defaultMount = '__dist__'
const createReference = async (file, opts) => {
const hash = await createHashFromFile(file, opts).then(hash => hash)
// Get file extension
// Create complete new file path
const result = {
file,
hash,
ext: extractFileInPath(file).ext,
}
return result
}
const formatOptions = options => {
const {
exts,
silent,
htmlFile,
} = options || {}
let _exts = Array.isArray(exts) && exts.length > 0 ? exts.map(ext => ext.replace(/\./, '')) : defaultExt
// ToDo: We may find other solutions for other filetypes in the future. But for now, only .js and .jsx are valid.
_exts = _exts.filter(e => defaultExt.find(ext => ext === e))
return {
exts: _exts,
silent: typeof silent === 'boolean' ? silent : true,
htmlFile: typeof htmlFile === 'string' ? htmlFile : htmlFileDefault
}
}
const getMountSrc = mountData => {
for (const key in mountData) {
console.log(key, extractDirInPath(key).dir)
if (extractDirInPath(key).dir === 'src') {
return `${mountData[key]}`
}
}
return ''
}
const plugin = (snowpackConfig, pluginOptions) => {
const snowpackConfigMountSource = snowpackConfig.mount ? getMountSrc(snowpackConfig.mount) : ''
const mountSrc = snowpackConfigMountSource !== '' ? snowpackConfigMountSource.replace(/\/$/, '').replace(/^\//, '') : defaultMount
const { exts, silent, htmlFile } = formatOptions(pluginOptions)
return {
name: "snowpack-plugin-content-hash",
async run(options) {
isDev = !!options.isDev;
},
async optimize({ buildDirectory }) {
if (isDev) {
return
}
/*
* 1. Create reference including content hash of all
* wanted file extensions, within the build dir.
*/
const files = await recursive(buildDirectory).then(files => files)
const promiseReferenceFiles = files.map(file => createReference(file, pluginOptions))
const referenceFiles = await Promise.all(promiseReferenceFiles).then(res => res)
/*
* 2. Locate all imports made within the built dir
*/
const importsInFileList = locateImports(
referenceFiles.filter(ref => exts.find(ext => ext === ref.ext)).map(ref => ref.file),
)
/*
* 3. Verify the imports with reference
*/
const readyForImplementation = []
importsInFileList.forEach(host => {
host.imports.forEach(imp => {
const testImport = path.resolve(extractDirInPath(host.location).str, imp)
const verifiedImport = referenceFiles.find(ref => ref.file === testImport)
if (verifiedImport) {
if (!exts.find(ext => ext === extractFileInPath(imp).ext)) {
return
}
const hash = verifiedImport.hash || ''
readyForImplementation.push({
hostLocation: host.location,
importPath: imp,
hash, // Add content hash for implementation later on
})
}
})
})
/*
* 4. Inject hash into imports
*/
readyForImplementation.forEach(async ({hostLocation, importPath, hash}) => replace({
regex: importPath,
replacement: `${extractDirInPath(importPath).str}/${extractFileInPath(importPath).name}-${hash}.${extractFileInPath(importPath).ext}`,
paths: [hostLocation],
recursive: true,
silent: true,
}))
/*
* 5. Rename files
*/
referenceFiles.forEach(ref => {
if (exts.find(ext => ext === ref.ext)) {
const newPath = `${extractDirInPath(ref.file).str}/${extractFileInPath(ref.file).name}-${ref.hash}.${extractFileInPath(ref.file).ext}`
fs.renameSync( ref.file, newPath)
}
})
/*
* 6. Adjust html file
*/
const htmlContent = path.join(buildDirectory, htmlFile)
const indexJSfile = `${mountSrc}/index.js`
const ref = referenceFiles.find(ref => ref.file === path.join(buildDirectory, indexJSfile))
const print = ref ? `-${ref.hash}` : ''
if (fs.existsSync(htmlContent)) {
replace({
regex: indexJSfile,
replacement: `${mountSrc}/index${print}.js`,
paths: [htmlContent],
recursive: true,
silent: true
})
}
/*
* 7. Log output implementation
*/
if (!silent) {
readyForImplementation.forEach(file => {
console.log(file)
})
}
}
}
}
module.exports = plugin;