1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-21 18:55:36 +02:00

fix(singlefile): improve font capture and eliminate ES module viewer errors

Font fidelity:
- --browser-wait-delay=2000: Cloudflare Fonts injects @font-face CSS after
  HTML parse; the font hook needs extra time to see it after networkidle2
- --remove-unused-fonts=false: preserve @font-face rules even when fonts
  haven't rendered yet (font-display:swap, off-screen text)
- --remove-alternative-fonts=false: preserve unicode-range subsets instead
  of stripping them as 'alternatives'

ES module viewer error fix:
- Write a user script (sf-strip-scripts.js) that listens for
  single-file-on-before-capture-start and removes all <script> elements
  (except application/ld+json) from the live DOM before serialization.
  Scripts still execute during capture for CSS fidelity; none end up in
  the saved file, so no data:-URL base ES module resolution errors.
This commit is contained in:
TheGeneralist 2026-06-24 21:30:14 +02:00
parent 852cc45956
commit ab353f9209
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4

View file

@ -48,19 +48,45 @@ fn save_with(
let out_file = temp_dir.join(format!("{timestamp}.html")); let out_file = temp_dir.join(format!("{timestamp}.html"));
// Write a user script that strips <script> elements from the live DOM
// just before SingleFile serializes it. This lets scripts execute during
// capture (so JS-applied CSS is present) without leaving data:-URL ES
// modules in the saved file that would cause "base scheme isn't
// hierarchical" errors in the viewer. JSON-LD structured data is kept.
let user_script_path = temp_dir.join("sf-strip-scripts.js");
std::fs::write(
&user_script_path,
"addEventListener('single-file-on-before-capture-start',()=>{\
document.querySelectorAll('script:not([type=\"application/ld+json\"])')\
.forEach(el=>el.remove());\
});",
)
.context("failed to write single-file user script")?;
let out = Command::new(single_file) let out = Command::new(single_file)
.arg(url) .arg(url)
.arg(&out_file) .arg(&out_file)
.arg(format!("--browser-executable-path={chrome}")) .arg(format!("--browser-executable-path={chrome}"))
.arg("--browser-headless") .arg("--browser-headless")
.arg("--browser-wait-until=networkidle2") .arg("--browser-wait-until=networkidle2")
// Extra delay after networkidle2: Cloudflare Fonts injects @font-face
// CSS after HTML parse, so the font hook needs more time to see it.
.arg("--browser-wait-delay=2000")
// Preserve all CSS: single-file's defaults strip rules it considers // Preserve all CSS: single-file's defaults strip rules it considers
// "unused" (breaking CSS nesting) and remove @media blocks that don't // "unused" (breaks CSS nesting) and remove @media blocks that don't
// match the capture viewport (breaking responsive layout). Allow scripts // match the capture viewport (breaks responsive layout).
// to run so JS-applied classes are present when CSS is evaluated.
.arg("--remove-unused-styles=false") .arg("--remove-unused-styles=false")
.arg("--remove-alternative-medias=false") .arg("--remove-alternative-medias=false")
// Allow scripts to run during capture so JS-applied classes exist in
// the DOM when CSS is evaluated. The user script above strips <script>
// elements before serialization so no broken module imports end up in
// the saved file.
.arg("--block-scripts=false") .arg("--block-scripts=false")
.arg(format!("--browser-script={}", user_script_path.display()))
// Preserve fonts: defaults strip @font-face rules deemed "unused" or
// "alternative" (unicode-range subsets), losing CDN-served fonts.
.arg("--remove-unused-fonts=false")
.arg("--remove-alternative-fonts=false")
.output() .output()
.with_context(|| format!("failed to spawn {single_file} process"))?; .with_context(|| format!("failed to spawn {single_file} process"))?;