Skip to content

Commit db5ce84

Browse files
authored
chore: Replace jsdom with htmlparser2 collection. (commontoolsinc#1789)
JSDOM was pulling in a large dependency graph, involved clobbering globals, and generally more than we need for CLI rendering and testing. Now using htmlparser2 and friends, updating @commontools/html render to accept a document and options for setting props (or attributes).
1 parent 2589a12 commit db5ce84

File tree

17 files changed

+515
-675
lines changed

17 files changed

+515
-675
lines changed

deno.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@
111111
"@std/http": "jsr:@std/http@^1",
112112
"@std/path": "jsr:@std/path@^1",
113113
"@std/testing": "jsr:@std/testing@^1",
114-
"jsdom": "npm:jsdom",
115114
"lit": "npm:lit@^3.3.0",
116115
"merkle-reference": "npm:merkle-reference@^2.2.0",
117116
"multiformats": "npm:multiformats@^13.3.2",

deno.lock

Lines changed: 127 additions & 286 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/cli/commands/init.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ async function initWorkspace(cwd: string) {
102102
const types = {
103103
"commontools": runtimeModuleTypes.commontools,
104104
"turndown": runtimeModuleTypes.turndown,
105-
"dom-parser": runtimeModuleTypes["dom-parser"],
106105
"ct-env": ctEnv,
107106
"react/jsx-runtime": jsxRuntime,
108107
};

packages/cli/fixtures/3p-modules.tsx

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
str,
99
UI,
1010
} from "commontools";
11-
import { DOMParser, type Element } from "dom-parser";
1211
import TurndownService from "turndown";
1312

1413
const Input = {
@@ -53,14 +52,6 @@ export default recipe(
5352
</ul>
5453
</div>
5554
`;
56-
// test dom-parser
57-
const parser = new DOMParser();
58-
const doc = parser.parseFromString(html, "text/xml");
59-
const root = doc.querySelector("#root");
60-
const ul = doc.getElementsByTagName("ul")[0];
61-
assert(ul.getAttribute("foo") === "bar", "getAttribute() works");
62-
const listitems = doc.getElementsByTagName("li");
63-
assert(listitems.length === 3, "getElementsByTagName() selected 3 items");
6455
const turndown = new TurndownService({
6556
headingStyle: "atx",
6657
codeBlockStyle: "fenced",

packages/cli/lib/charm-render.ts

Lines changed: 52 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,81 @@
11
import { render, VNode } from "@commontools/html";
2-
import { Cell, effect, UI } from "@commontools/runner";
3-
import { inspectCharm, loadManager } from "./charm.ts";
2+
import { Cell, UI } from "@commontools/runner";
3+
import { loadManager } from "./charm.ts";
44
import { CharmsController } from "@commontools/charm/ops";
55
import type { CharmConfig } from "./charm.ts";
66
import { getLogger } from "@commontools/utils/logger";
7+
import { MockDoc } from "@commontools/html/utils";
78

8-
const logger = getLogger("charm-render", { level: "info", enabled: true });
9+
const logger = getLogger("charm-render", { level: "info", enabled: false });
910

1011
export interface RenderOptions {
1112
watch?: boolean;
1213
onUpdate?: (html: string) => void;
1314
}
1415

1516
/**
16-
* Renders a charm's UI to HTML using JSDOM.
17+
* Renders a charm's UI to HTML using htmlparser2.
1718
* Supports both static and reactive rendering with --watch mode.
1819
*/
1920
export async function renderCharm(
2021
config: CharmConfig,
2122
options: RenderOptions = {},
2223
): Promise<string | (() => void)> {
23-
// Dynamically import JSDOM to avoid top-level import issues
24-
const { JSDOM } = await import("npm:jsdom");
25-
26-
// 1. Setup JSDOM environment
27-
const dom = new JSDOM(
24+
const mock = new MockDoc(
2825
'<!DOCTYPE html><html><body><div id="root"></div></body></html>',
2926
);
30-
const { window } = dom;
31-
32-
// Set up global DOM objects needed by the render system
33-
globalThis.document = window.document;
34-
globalThis.Element = window.Element;
35-
globalThis.Node = window.Node;
36-
globalThis.Text = window.Text;
37-
globalThis.HTMLElement = window.HTMLElement;
38-
globalThis.Event = window.Event;
39-
globalThis.CustomEvent = window.CustomEvent;
40-
globalThis.MutationObserver = window.MutationObserver;
27+
const { document, renderOptions } = mock;
4128

42-
try {
43-
// 2. Get charm controller to access the Cell
44-
const manager = await loadManager(config);
45-
const charms = new CharmsController(manager);
46-
const charm = await charms.get(config.charm);
47-
const cell = charm.getCell();
29+
// 2. Get charm controller to access the Cell
30+
const manager = await loadManager(config);
31+
const charms = new CharmsController(manager);
32+
const charm = await charms.get(config.charm);
33+
const cell = charm.getCell();
4834

49-
// Check if charm has UI
50-
const staticValue = cell.get();
51-
if (!staticValue?.[UI]) {
52-
throw new Error(`Charm ${config.charm} has no UI`);
53-
}
35+
// Check if charm has UI
36+
const staticValue = cell.get();
37+
if (!staticValue?.[UI]) {
38+
throw new Error(`Charm ${config.charm} has no UI`);
39+
}
5440

55-
// 3. Get the root container
56-
const container = window.document.getElementById("root");
57-
if (!container) {
58-
throw new Error("Could not find root container");
59-
}
41+
// 3. Get the root container
42+
const container = document.getElementById("root");
43+
if (!container) {
44+
throw new Error("Could not find root container");
45+
}
6046

61-
if (options.watch) {
62-
// 4a. Reactive rendering - pass the Cell directly
63-
const uiCell = cell.key(UI);
64-
const cancel = render(container, uiCell as Cell<VNode>); // FIXME: types
47+
if (options.watch) {
48+
// 4a. Reactive rendering - pass the Cell directly
49+
const uiCell = cell.key(UI);
50+
const cancel = render(container, uiCell as Cell<VNode>, renderOptions); // FIXME: types
6551

66-
// 5a. Set up monitoring for changes
67-
let updateCount = 0;
68-
const unsubscribe = cell.sink((value) => {
69-
if (value?.[UI]) {
70-
updateCount++;
71-
// Wait for all runtime computations to complete
72-
manager.runtime.idle().then(() => {
73-
const html = container.innerHTML;
74-
logger.info(() => `[Update ${updateCount}] UI changed`);
75-
if (options.onUpdate) {
76-
options.onUpdate(html);
77-
}
78-
});
79-
}
80-
});
52+
// 5a. Set up monitoring for changes
53+
let updateCount = 0;
54+
const unsubscribe = cell.sink((value) => {
55+
if (value?.[UI]) {
56+
updateCount++;
57+
// Wait for all runtime computations to complete
58+
manager.runtime.idle().then(() => {
59+
const html = container.innerHTML;
60+
logger.info(() => `[Update ${updateCount}] UI changed`);
61+
if (options.onUpdate) {
62+
options.onUpdate(html);
63+
}
64+
});
65+
}
66+
});
8167

82-
// Return cleanup function
83-
return () => {
84-
cancel();
85-
unsubscribe();
86-
window.close();
87-
};
88-
} else {
89-
// 4b. Static rendering - render once with current value
90-
const vnode = staticValue[UI];
91-
render(container, vnode as VNode); // FIXME: types
68+
// Return cleanup function
69+
return () => {
70+
cancel();
71+
unsubscribe();
72+
};
73+
} else {
74+
// 4b. Static rendering - render once with current value
75+
const vnode = staticValue[UI];
76+
render(container, vnode as VNode, renderOptions); // FIXME: types
9277

93-
// 5b. Return the rendered HTML
94-
return container.innerHTML;
95-
}
96-
} finally {
97-
// Clean up JSDOM only in static mode
98-
if (!options.watch) {
99-
window.close();
100-
}
78+
// 5b. Return the rendered HTML
79+
return container.innerHTML;
10180
}
10281
}

packages/html/deno.jsonc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
{
22
"name": "@commontools/html",
3-
"exports": "./src/index.ts",
43
"tasks": {
5-
"test": "deno test --allow-env --allow-ffi --allow-read"
4+
"test": "deno test --allow-env --allow-ffi --allow-read test/*.test.ts"
65
},
7-
"imports": {},
6+
"exports": {
7+
".": "./src/index.ts",
8+
"./utils": "./src/utils.ts"
9+
},
10+
"imports": {
11+
"htmlparser2": "npm:htmlparser2",
12+
"domhandler": "npm:domhandler",
13+
"dom-serializer": "npm:dom-serializer"
14+
},
15+
816
"compilerOptions": {
917
"jsx": "react",
1018
"jsxFactory": "h",

packages/html/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
export {
22
render,
3+
type RenderOptions,
34
setEventSanitizer,
45
setNodeSanitizer,
6+
type SetPropHandler,
57
vdomSchema,
68
} from "./render.ts";
79
export { debug, setDebug } from "./logger.ts";

0 commit comments

Comments
 (0)