Skip to content

Commit 3f966ac

Browse files
committed
Replace sax with htmlparser2
Sax is a common-js module and doesn't play nicely with bundling by default. htmlparser2 is more forgiving, and comes with TypeScript types.
1 parent ac47a40 commit 3f966ac

File tree

4 files changed

+130
-56
lines changed

4 files changed

+130
-56
lines changed

typescript/package-lock.json

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

typescript/packages/common-html/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"./lib/index.js"
2727
],
2828
"dependencies": {
29-
"sax": "^1.4.1",
29+
"htmlparser2": "^9.1.0",
3030
"vite": "^5.3.3"
3131
},
3232
"devDependencies": {

typescript/packages/common-html/src/parser.ts

Lines changed: 36 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,49 @@
1-
import sax from "sax";
1+
import { Parser } from "htmlparser2";
22
import { parse as parseMustaches, isHole } from "./hole.js";
33
import { create as createVNode, VNode, Props } from "./vnode.js";
44
import * as logger from "./logger.js";
55

66
/** Parse a template into a simple JSON markup representation */
77
export const parse = (markup: string): VNode => {
8-
const strict = false;
9-
const parser = sax.parser(strict, {
10-
trim: true,
11-
lowercase: true,
12-
xmlns: false,
13-
});
14-
158
let root: VNode = createVNode("documentfragment");
169
let stack: Array<VNode> = [root];
1710

18-
parser.onerror = (error: Error) => {
19-
throw error;
20-
};
21-
22-
parser.onopentag = (node) => {
23-
// We've turned off the namespace feature, so node attributes will
24-
// contain only string values, not QualifiedAttribute objects.
25-
const props = parseProps(node.attributes as { [key: string]: string });
26-
const next = createVNode(node.name, props);
27-
const top = getTop(stack);
28-
if (!top) {
29-
throw new ParseError(`No parent tag for ${node.name}`);
30-
}
31-
top.children.push(next);
32-
stack.push(next);
33-
};
34-
35-
parser.onclosetag = (tagName) => {
36-
const node = stack.pop();
37-
if (!node) {
38-
throw new ParseError(`Unexpected closing tag ${tagName}`);
39-
}
40-
};
41-
42-
parser.ontext = (text) => {
43-
const top = getTop(stack);
44-
const parsed = parseMustaches(text);
45-
top.children.push(...parsed);
46-
};
47-
48-
parser.write(markup).close();
49-
50-
if (getTop(stack) !== root) {
51-
throw new ParseError(`Unexpected root node ${root.tag}`);
52-
}
53-
54-
logger.debug("Parsed", root);
11+
const parser = new Parser(
12+
{
13+
onopentag(name, attrs) {
14+
logger.debug("Open", name, attrs);
15+
// We've turned off the namespace feature, so node attributes will
16+
// contain only string values, not QualifiedAttribute objects.
17+
const props = parseProps(attrs as { [key: string]: string });
18+
const next = createVNode(name, props);
19+
const top = getTop(stack);
20+
if (!top) {
21+
throw new ParseError(`No parent tag for ${name}`);
22+
}
23+
top.children.push(next);
24+
stack.push(next);
25+
},
26+
onclosetag(name) {
27+
const vnode = stack.pop();
28+
if (!vnode) {
29+
throw new ParseError(`Unexpected closing tag ${name}`);
30+
}
31+
},
32+
ontext(text) {
33+
const top = getTop(stack);
34+
const parsed = parseMustaches(text.trim());
35+
top.children.push(...parsed);
36+
},
37+
},
38+
{
39+
lowerCaseTags: true,
40+
lowerCaseAttributeNames: true,
41+
xmlMode: false,
42+
},
43+
);
5544

45+
parser.write(markup);
46+
parser.end();
5647
return root;
5748
};
5849

typescript/packages/common-html/src/test/html.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,11 @@ describe("html", () => {
99
const text = state("Hello world!");
1010

1111
const view = html`
12-
<div class="container" hidden="{{hidden}}">
12+
<div class="container">
1313
<button id="foo" onclick=${clicks}>${text}</button>
1414
</div>
1515
`;
1616

17-
// @ts-ignore - ignore for test
18-
assert.strict(hole.isHole(view.template.props.hidden));
19-
2017
// @ts-ignore - ignore for test
2118
assert.strict(hole.isHole(view.template.children[0].props.onclick));
2219

0 commit comments

Comments
 (0)