Files
easy-png-tools/scripts/extract-static.mjs
T

145 lines
4.5 KiB
JavaScript

import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
import { join, dirname, basename } from 'node:path';
// Usage: node extract-static.mjs <source-out-dir> <dest-dir>
// e.g. node extract-static.mjs .next\server\app ..\refs-html
const root = process.argv[2];
const dest = process.argv[3];
// --- inject a minimal theme toggle (extraction strips all <script> tags, so the
// ref pages would otherwise have no working dark-mode switch) ---
const THEME_TOGGLE = `
<script>
(function () {
var btn = document.querySelector('button[aria-label="Toggle theme"]');
var shell = document.querySelector('.app-shell');
if (!btn || window.__themeToggleInjected) return;
window.__themeToggleInjected = true;
btn.addEventListener('click', function () {
document.documentElement.classList.toggle('dark-mode');
if (shell) shell.classList.toggle('dark-mode');
});
})();
</script>
`;
function injectThemeToggle(html) {
if (html.includes('__themeToggleInjected')) return html;
const tag = '</body>';
if (html.includes(tag)) return html.replace(tag, THEME_TOGGLE + '\n' + tag);
if (html.includes('</html>')) return html.replace('</html>', THEME_TOGGLE + '\n</html>');
return html + '\n' + THEME_TOGGLE;
}
// --- CSS: find the compiled chunk and pretty-print it ---
const cssChunks = join(root, '..', '..', 'static', 'chunks');
const cssFile = readdirSync(cssChunks)
.filter(f => f.endsWith('.css'))
.sort((a, b) => statSync(join(cssChunks, b)).size - statSync(join(cssChunks, a)).size)[0];
const css = readFileSync(join(cssChunks, cssFile), 'utf8');
const prettyCss = css
.replace(/\r/g, '')
.replace(/\{/g, ' {\n ')
.replace(/;/g, ';\n ')
.replace(/\}/g, '\n}\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
// --- conservative html formatter ---
const VOID = new Set([
'area','base','br','col','embed','hr','img','input',
'link','meta','param','source','track','wbr',
]);
const RAW = new Set(['pre','textarea','script','style']);
function fmtHtml(html) {
const tokens = html.match(
/<!--[\s\S]*?-->|<!doctype[^>]*>|<\/?[a-zA-Z][^>]*>|[^<]+|</gi
) || [];
const out = [];
let depth = 0;
let rawTag = null;
for (const tk of tokens) {
if (rawTag) {
out.push(tk);
if (new RegExp(`</${rawTag}\\s*>`, 'i').test(tk)) {
depth--;
rawTag = null;
}
continue;
}
const isOpen = /^<[a-zA-Z]/.test(tk);
const isClose = /^<\//.test(tk);
let name = '';
if (isOpen || isClose) {
name = (tk.match(/^<\/?([a-zA-Z0-9-]+)/) || [])[1]?.toLowerCase() || '';
}
if (isOpen && RAW.has(name)) {
out.push('\n' + ' '.repeat(depth) + tk);
if (!/\/>$/.test(tk)) { depth++; rawTag = name; }
} else if (isOpen && !RAW.has(name)) {
out.push('\n' + ' '.repeat(depth) + tk);
if (!/\/>$/.test(tk) && !VOID.has(name)) depth++;
} else if (isClose) {
depth = Math.max(0, depth - 1);
out.push('\n' + ' '.repeat(depth) + tk);
} else if (/^</.test(tk)) {
out.push('\n' + ' '.repeat(depth) + tk);
} else {
const t = tk.replace(/\s+/g, ' ');
if (t.trim()) out.push(t);
}
}
return out.join('').replace(/^\n/, '').replace(/\n{3,}/g, '\n\n');
}
// --- auto-discover pages by walking the export dir for *.html ---
function walk(dir, base = '') {
const found = [];
for (const entry of readdirSync(dir)) {
const full = join(dir, entry);
const rel = base ? join(base, entry) : entry;
if (statSync(full).isDirectory()) {
found.push(...walk(full, rel));
} else if (entry.endsWith('.html')) {
found.push(rel);
}
}
return found;
}
const pages = walk(root)
// skip Next internals
.filter((rel) => !basename(rel).startsWith('_') && !basename(rel).startsWith('404'))
.map((rel) => [rel, basename(rel)]);
console.log(`discovered ${pages.length} page(s):`, pages.map((p) => p[1]).join(', '));
for (const [src, out] of pages) {
let html = readFileSync(join(root, src), 'utf8');
// strip scripts — pure static snapshot
html = html.replace(/<script[\s\S]*?<\/script>/g, '');
html = html.replace(/<link[^>]+rel="preload"[^>]+as="script"[^>]*>/g, '');
// inline compiled css instead of linking /_next/...
html = html.replace(
/<link[^>]+rel="stylesheet"[^>]*>/g,
() => '<style>\n' + prettyCss + '\n</style>',
);
html = fmtHtml(html);
html = injectThemeToggle(html);
const file = join(dest, out);
mkdirSync(dirname(file), { recursive: true });
writeFileSync(file, html);
console.log('written:', file);
}