Add content-hashed asset manifest and HTML rewrite in dist build

This commit is contained in:
thePR0M3TH3AN committed 2026-02-10 00:40:45 -05:00
1 parent d58cff2eac
commit 8c84f0a052
3 files changed
+95 -9

No files matched your search

+3 -3
View File
@@ -67,7 +67,7 @@
})(); })();
</script> </script>
<link href="css/tailwind.generated.css?v=2024.11.09" rel="stylesheet" /> <link href="css/tailwind.generated.css" rel="stylesheet" />
</head> </head>
<body class="h-screen w-screen overflow-hidden bg-transparent text-text" data-ds="new"> <body class="h-screen w-screen overflow-hidden bg-transparent text-text" data-ds="new">
<main <main
@@ -214,7 +214,7 @@
</section> </section>
</main> </main>
<script type="module" src="js/nostrToolsBootstrap.js?v=2024.11.09"></script> <script type="module" src="js/nostrToolsBootstrap.js"></script>
<script type="module" src="js/embed.js?v=2024.11.09"></script> <script type="module" src="js/embed.js"></script>
</body> </body>
</html> </html>
+6 -6
View File
@@ -107,12 +107,12 @@
<!-- Styles --> <!-- Styles -->
<!-- <!--
Cache busting: keep these query strings in sync with config/asset-version.js Build-time asset hashing rewrites these source paths to content-hashed
so browsers pick up the latest sidebar markup and styling without files in dist/, keeping source templates readable while preserving cache
hard-refreshing around CDN caches. busting semantics.
--> -->
<link href="css/tailwind.generated.css?v=2024.11.09-2" rel="stylesheet" /> <link href="css/tailwind.generated.css" rel="stylesheet" />
<link href="css/docs.css?v=2024.11.09-2" rel="stylesheet" /> <link href="css/docs.css" rel="stylesheet" />
</head> </head>
<body class="sidebar-collapsed" data-ds="new"> <body class="sidebar-collapsed" data-ds="new">
<!-- <!--
@@ -423,7 +423,7 @@
</div> </div>
<!-- Additional Scripts --> <!-- Additional Scripts -->
<script type="module" src="js/index.js?v=2024.11.09"></script> <script type="module" src="js/index.js"></script>
<script src="vendor/marked.min.js"></script> <script src="vendor/marked.min.js"></script>
<script src="vendor/highlight.min.js"></script> <script src="vendor/highlight.min.js"></script>
<div id="uiOverlay" data-overlay-root aria-hidden="true"></div> <div id="uiOverlay" data-overlay-root aria-hidden="true"></div>
+86
View File
@@ -1,6 +1,7 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { execSync } from 'node:child_process'; import { execSync } from 'node:child_process';
import crypto from 'node:crypto';
const DIST = 'dist'; const DIST = 'dist';
const FILES_TO_COPY = [ const FILES_TO_COPY = [
@@ -26,6 +27,15 @@ const DIRS_TO_COPY = [
'vendor', 'vendor',
'views' 'views'
]; ];
const HASHED_ENTRY_HTML_FILES = ['index.html', 'embed.html'];
const HASHED_ASSET_PATHS = [
'css/docs.css',
'css/tailwind.generated.css',
'js/embed.js',
'js/index.js',
'js/nostrToolsBootstrap.js'
];
const ASSET_MANIFEST_PATH = 'asset-manifest.json';
function cleanDist() { function cleanDist() {
if (fs.existsSync(DIST)) { if (fs.existsSync(DIST)) {
@@ -74,6 +84,76 @@ function copyDir(src, dest) {
} }
} }
function hashContent(content) {
return crypto.createHash('sha256').update(content).digest('hex');
}
function createHashedFilePath(assetPath, hash) {
const ext = path.extname(assetPath);
const baseName = path.basename(assetPath, ext);
const dirName = path.dirname(assetPath);
const shortHash = hash.slice(0, 16);
const hashedName = `${baseName}.${shortHash}${ext}`;
return dirName === '.' ? hashedName : path.posix.join(dirName, hashedName);
}
function escapeForRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function generateHashedAssetManifest() {
const sortedAssetPaths = [...HASHED_ASSET_PATHS].sort();
const manifest = {};
for (const logicalAssetPath of sortedAssetPaths) {
const sourcePath = path.join(DIST, logicalAssetPath);
if (!fs.existsSync(sourcePath)) {
throw new Error(`Missing required asset for hashing: ${logicalAssetPath}`);
}
const content = fs.readFileSync(sourcePath);
const hash = hashContent(content);
const hashedRelativePath = createHashedFilePath(logicalAssetPath, hash);
const hashedDistPath = path.join(DIST, hashedRelativePath);
fs.mkdirSync(path.dirname(hashedDistPath), { recursive: true });
fs.writeFileSync(hashedDistPath, content);
manifest[logicalAssetPath] = hashedRelativePath;
}
const sortedManifest = Object.fromEntries(
Object.entries(manifest).sort(([a], [b]) => a.localeCompare(b))
);
fs.writeFileSync(
path.join(DIST, ASSET_MANIFEST_PATH),
`${JSON.stringify(sortedManifest, null, 2)}\n`
);
return sortedManifest;
}
function rewriteEntryHtmlAssetPaths(manifest) {
for (const htmlFile of HASHED_ENTRY_HTML_FILES) {
const htmlPath = path.join(DIST, htmlFile);
if (!fs.existsSync(htmlPath)) {
continue;
}
let htmlContent = fs.readFileSync(htmlPath, 'utf8');
for (const [logicalPath, hashedPath] of Object.entries(manifest)) {
const escapedLogicalPath = escapeForRegExp(logicalPath);
const attributePattern = new RegExp(
`((?:src|href)=["'])${escapedLogicalPath}(?:\\?[^"']*)?(["'])`,
'g'
);
htmlContent = htmlContent.replace(attributePattern, `$1${hashedPath}$2`);
}
fs.writeFileSync(htmlPath, htmlContent);
}
}
function main() { function main() {
console.log('Cleaning dist...'); console.log('Cleaning dist...');
cleanDist(); cleanDist();
@@ -115,6 +195,12 @@ function main() {
copyDir(dir, path.join(DIST, dir)); copyDir(dir, path.join(DIST, dir));
} }
console.log('Generating hashed asset manifest...');
const manifest = generateHashedAssetManifest();
console.log('Rewriting HTML entry points with hashed assets...');
rewriteEntryHtmlAssetPaths(manifest);
console.log('Build complete.'); console.log('Build complete.');
} }