mirror of
https://github.com/Floriansylvain/compact-portfolio.git
synced 2026-08-19 11:43:19 +02:00
feat: optimize stylesheet loading and implement SRI hash generation for security
This commit is contained in:
+1
-4
@@ -11,10 +11,7 @@
|
|||||||
<meta name="theme-color" content="#0e120e" />
|
<meta name="theme-color" content="#0e120e" />
|
||||||
<title>Portfolio Florian Sylvain - Développeur web fullstack</title>
|
<title>Portfolio Florian Sylvain - Développeur web fullstack</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="style.css" media="print" onload="this.media='all'; this.onload=null;" />
|
<link rel="stylesheet" href="style.css">
|
||||||
<noscript>
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
</noscript>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="top">
|
<body id="top">
|
||||||
|
|||||||
@@ -75,3 +75,12 @@ if (menuBtn && nav) {
|
|||||||
if (e.key === "Escape") closeMenu();
|
if (e.key === "Escape") closeMenu();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const links = document.querySelectorAll("link[data-onload]");
|
||||||
|
links.forEach((link) => {
|
||||||
|
const onloadCode = link.getAttribute("data-onload");
|
||||||
|
link.onload = new Function(onloadCode);
|
||||||
|
link.removeAttribute("data-onload");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -179,6 +179,15 @@ function minifyHTML(src) {
|
|||||||
.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
|
.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (m, css) => {
|
||||||
return m.replace(css, minifyCSS(css));
|
return m.replace(css, minifyCSS(css));
|
||||||
})
|
})
|
||||||
|
.replace(
|
||||||
|
/<link([^>]*)\s+onload=["']([^"']+)["']([^>]*)>/gi,
|
||||||
|
(match, before, onloadCode, after) => {
|
||||||
|
return `<link${before} data-onload="${onloadCode.replace(
|
||||||
|
/"/g,
|
||||||
|
"""
|
||||||
|
)}"${after}>`;
|
||||||
|
}
|
||||||
|
)
|
||||||
.replace(/\s+([\w-]+)=["']([^"'\s<>]+)["']/g, (m, attr, val) => {
|
.replace(/\s+([\w-]+)=["']([^"'\s<>]+)["']/g, (m, attr, val) => {
|
||||||
if (/^[a-zA-Z0-9._:/-]+$/.test(val)) return ` ${attr}=${val}`;
|
if (/^[a-zA-Z0-9._:/-]+$/.test(val)) return ` ${attr}=${val}`;
|
||||||
return m;
|
return m;
|
||||||
@@ -412,6 +421,30 @@ function preCompress(buf) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function injectSRIHashes(html) {
|
||||||
|
const cssEntry = files.get("/style.css");
|
||||||
|
if (cssEntry && cssEntry.sri) {
|
||||||
|
html = html.replace(
|
||||||
|
/<link([^>]*href=["']style\.css["'][^>]*)>/gi,
|
||||||
|
`<link$1 integrity="sha384-${cssEntry.sri}" crossorigin="anonymous">`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsEntry = files.get("/script.js");
|
||||||
|
if (jsEntry && jsEntry.sri) {
|
||||||
|
html = html.replace(
|
||||||
|
/<script([^>]*src=["']script\.js["'][^>]*)>/gi,
|
||||||
|
`<script$1 integrity="sha384-${jsEntry.sri}" crossorigin="anonymous">`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateSRIHash(content) {
|
||||||
|
return crypto.createHash("sha384").update(content, "utf8").digest("base64");
|
||||||
|
}
|
||||||
|
|
||||||
async function loadAndMinify(relPath, minify) {
|
async function loadAndMinify(relPath, minify) {
|
||||||
const abs = path.join(ROOT, relPath);
|
const abs = path.join(ROOT, relPath);
|
||||||
const src = await fs.promises.readFile(abs);
|
const src = await fs.promises.readFile(abs);
|
||||||
@@ -422,6 +455,13 @@ async function loadAndMinify(relPath, minify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = minify ? Buffer.from(minify(originalContent), "utf8") : src;
|
const body = minify ? Buffer.from(minify(originalContent), "utf8") : src;
|
||||||
|
|
||||||
|
let sriHash = null;
|
||||||
|
if (relPath === "style.css" || relPath === "script.js") {
|
||||||
|
sriHash = generateSRIHash(body.toString("utf8"));
|
||||||
|
console.log(`SRI hash for ${relPath}: sha384-${sriHash}`);
|
||||||
|
}
|
||||||
|
|
||||||
const compressed = await preCompress(body);
|
const compressed = await preCompress(body);
|
||||||
const stats = await fs.promises.stat(abs);
|
const stats = await fs.promises.stat(abs);
|
||||||
|
|
||||||
@@ -465,6 +505,7 @@ async function loadAndMinify(relPath, minify) {
|
|||||||
brEtag: compressed.br ? etag(compressed.br) : undefined,
|
brEtag: compressed.br ? etag(compressed.br) : undefined,
|
||||||
gz: compressed.gz,
|
gz: compressed.gz,
|
||||||
gzEtag: compressed.gz ? etag(compressed.gz) : undefined,
|
gzEtag: compressed.gz ? etag(compressed.gz) : undefined,
|
||||||
|
sri: sriHash,
|
||||||
};
|
};
|
||||||
|
|
||||||
files.set("/" + relPath.replace(/\\/g, "/"), entry);
|
files.set("/" + relPath.replace(/\\/g, "/"), entry);
|
||||||
|
|||||||
Reference in New Issue
Block a user