Skip to content

Instantly share code, notes, and snippets.

@jonathanpv
Created September 24, 2024 10:49
Show Gist options
  • Save jonathanpv/54044cd3ea02896c7875710495cfdfa4 to your computer and use it in GitHub Desktop.
Save jonathanpv/54044cd3ea02896c7875710495cfdfa4 to your computer and use it in GitHub Desktop.
better
(async function downloadCompleteHTML() {
// Helper function to fetch content of external files (CSS, JS, images)
async function fetchResource(url, isBinary = false) {
try {
const response = await fetch(url);
if (isBinary) {
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
} else {
return await response.text();
}
} catch (error) {
console.warn('Failed to fetch resource:', url);
return '';
}
}
// Helper function to inline external CSS and convert relative URLs to absolute
async function fetchAndProcessCSS(linkElement) {
const href = linkElement.href;
const cssContent = await fetchResource(href);
// Resolve relative URLs within CSS (for images, fonts, etc.)
return cssContent.replace(/url\((?!['"]?(?:data|https?|ftp):)['"]?([^'")]+)['"]?\)/g, function(match, relativeUrl) {
const absoluteUrl = new URL(relativeUrl, href).href;
return `url(${absoluteUrl})`;
});
}
// Helper function to convert images to base64-encoded data URIs
async function inlineImages(element) {
const images = element.querySelectorAll('img');
for (let img of images) {
if (img.src.startsWith('http')) {
const dataUri = await fetchResource(img.src, true);
img.src = dataUri; // Replace the src with base64-encoded data URI
}
}
}
// Helper function to parse CSS and apply styles inline
function applyStyles(element, styleSheet) {
for (let rule of styleSheet.cssRules) {
if (rule instanceof CSSStyleRule) {
try {
const elements = element.querySelectorAll(rule.selectorText);
elements.forEach(el => {
const computedStyle = getComputedStyle(el);
for (let prop of rule.style) {
if (prop.startsWith('--')) continue; // Skip CSS variables
const value = computedStyle.getPropertyValue(prop);
const priority = rule.style.getPropertyPriority(prop);
el.style.setProperty(prop, value, priority);
}
});
} catch (e) {
console.warn('Failed to apply styles for selector:', rule.selectorText, e);
}
}
}
}
// Remove CSS variable definitions from style attributes
function removeCSSVariableDefinitions(element) {
if (element.style) {
for (let i = element.style.length - 1; i >= 0; i--) {
const propName = element.style[i];
if (propName.startsWith('--')) {
element.style.removeProperty(propName);
}
}
}
for (let child of element.children) {
removeCSSVariableDefinitions(child);
}
}
// Fetch and process all external CSS
const linkElements = [...document.querySelectorAll('link[rel="stylesheet"]')];
const allCSS = await Promise.all(linkElements.map(fetchAndProcessCSS));
const combinedCSS = allCSS.join('\n');
// Create a style sheet from the combined CSS
const styleSheet = new CSSStyleSheet();
await styleSheet.replace(combinedCSS);
// Apply styles inline to all elements in the document
applyStyles(document, styleSheet);
// Remove all <link> and <style> elements
document.querySelectorAll('link[rel="stylesheet"], style').forEach(el => el.remove());
// Inline all images as base64 data URIs
await inlineImages(document);
// Remove CSS variable definitions from style attributes
removeCSSVariableDefinitions(document.documentElement);
// Get the final HTML including the modified DOM
const finalHTML = document.documentElement.outerHTML;
// Create a downloadable HTML file
const downloadHTML = (content, fileName) => {
const a = document.createElement("a");
const file = new Blob([content], { type: "text/html" });
a.href = URL.createObjectURL(file);
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
// Download the final HTML file
downloadHTML(finalHTML, "index_inline_css.html");
// Hide loading overlay (assuming it exists in your HTML)
const loadingOverlay = document.getElementById('loadingOverlay');
if (loadingOverlay) {
loadingOverlay.style.display = 'none';
}
console.log("Download complete. The HTML file contains inline CSS from all external stylesheets.");
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment