refactor(core): replace cheerio with linkedom to drop deprecated whatwg-encoding (#187)

## Summary

- `cheerio` pulls `encoding-sniffer` → `whatwg-encoding@3.1.1` (deprecated), causing a warning on every `npm install -g hyperframes`
- `linkedom` was already bundled into the CLI via tsup `noExternal` and has zero deprecated transitive deps
- Rewrote `htmlBundler.ts` and `subComposition.ts` to use standard DOM APIs via `linkedom`
- Added a `parseHTMLContent` helper that wraps HTML fragments in a full document structure (required for `linkedom` to populate `document.body`)
- Removed `cheerio` from `cli` dependencies and tsup `external` list
- Replaced `cheerio` with `linkedom` in `core` `optionalDependencies`

## Test plan

- [x] All 411 tests pass (`bun run test` in `packages/core`)
- [x] Full monorepo build succeeds (`bun run build`)
- [x] TypeScript typecheck passes
This commit is contained in:
Miguel Ángel
2026-04-03 16:06:30 +02:00
committed by GitHub
parent 06e3da9ad3
commit e2c8ed5d83
9 changed files with 226 additions and 210 deletions
@@ -1,6 +1,6 @@
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import * as cheerio from "cheerio";
import { parseHTML } from "linkedom";
import { rewriteAssetPaths, rewriteCssAssetUrls } from "../../compiler/rewriteSubCompPaths.js";
/**
@@ -24,21 +24,23 @@ export function buildSubCompositionHtml(
// Extract content from <template> wrapper (compositions are always templates)
const templateMatch = rawComp.match(/<template[^>]*>([\s\S]*)<\/template>/i);
const content = templateMatch?.[1] ?? rawComp;
const $content = cheerio.load(content, {}, false);
const { document: contentDoc } = parseHTML(
`<!DOCTYPE html><html><head></head><body>${content}</body></html>`,
);
rewriteAssetPaths(
$content("[src], [href]").toArray(),
contentDoc.querySelectorAll("[src], [href]"),
compPath,
(el, attr) => $content(el).attr(attr),
(el, attr, value) => {
$content(el).attr(attr, value);
(el: Element, attr: string) => el.getAttribute(attr),
(el: Element, attr: string, value: string) => {
el.setAttribute(attr, value);
},
);
$content("style").each((_, styleEl) => {
$content(styleEl).html(rewriteCssAssetUrls($content(styleEl).html() || "", compPath));
});
for (const styleEl of contentDoc.querySelectorAll("style")) {
styleEl.textContent = rewriteCssAssetUrls(styleEl.textContent || "", compPath);
}
const rewrittenContent = $content.root().html() || content;
const rewrittenContent = contentDoc.body.innerHTML || content;
// Use the project's index.html <head> to preserve all dependencies
const indexPath = join(projectDir, "index.html");