Skip to content

Commit 6e85f46

Browse files
committed
(feat) escape HTML by default, use trust() for raw
Signed-off-by: Muthu Kumar <muthukumar@thefeathers.in>
1 parent 19c1dde commit 6e85f46

5 files changed

Lines changed: 53 additions & 16 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Hyperactive is a suite of tools to build smart webapps. As of 1.0, only server-s
77
```TypeScript
88
import { elements, renderHTML } from "https://deno.land/x/hyperactive/mod.ts";
99

10+
const [ div, p, h1 ] = elements("div", "p", "h1");
11+
1012
assertEquals(
1113
renderHTML(
1214
div(

src/Node.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,13 @@
11
import { Element } from "./elements.ts";
2-
3-
export type Falsy = false | "" | 0 | 0n | undefined | null;
4-
5-
export const Falsy = new Set([false, "", 0, 0n, undefined, null]);
6-
// deno-lint-ignore no-explicit-any
7-
export const isFalsy = (n: any): n is Falsy => Falsy.has(n);
2+
import { Falsy, isFalsy } from "./util.ts";
83

94
export type Attr = Record<string, string>;
105

116
export type TextNode = string;
127

13-
export type Nodeish<Tag extends Element = Element> =
14-
| Node<Tag>
15-
| TextNode
16-
| Falsy;
8+
export class HTMLNode {
9+
constructor(public htmlString: string) {}
10+
}
1711

1812
export class Node<Tag extends Element = Element, Attrs extends Attr = Attr> {
1913
constructor(
@@ -23,9 +17,15 @@ export class Node<Tag extends Element = Element, Attrs extends Attr = Attr> {
2317
) {}
2418
}
2519

20+
export type Nodeish<Tag extends Element = Element> =
21+
| Node<Tag>
22+
| TextNode
23+
| HTMLNode
24+
| Falsy;
25+
2626
// deno-lint-ignore no-explicit-any
27-
export const isNode = (n: any): n is Node | TextNode =>
28-
n instanceof Node || typeof n === "string";
27+
export const isNode = (n: any): n is Node | HTMLNode | TextNode =>
28+
n instanceof Node || n instanceof HTMLNode || typeof n === "string";
2929

3030
export function h<Tag extends Element = Element, Attrs extends Attr = Attr>(
3131
elem: Tag,
@@ -71,7 +71,7 @@ export function h(
7171
export type hElement<Tag extends Element = Element> =
7272
//
7373
((props?: Attr) => Node<Tag>) &
74-
(<TNodeish extends Nodeish>(childNode: TNodeish) => Node<Tag>) &
74+
((...childNodes: Nodeish[]) => Node<Tag>) &
7575
((props: Attr, ...childNodes: Nodeish[]) => Node<Tag>);
7676

7777
export const elements = <Elems extends Element[]>(...elems: Elems) => {
@@ -86,3 +86,7 @@ export const elements = <Elems extends Element[]>(...elems: Elems) => {
8686
>;
8787
};
8888
};
89+
90+
export function trust(html: string) {
91+
return new HTMLNode(html);
92+
}

src/render.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { Nodeish, isFalsy } from "./Node.ts";
1+
import { Nodeish, HTMLNode } from "./Node.ts";
2+
import { isFalsy, escapeHTML } from "./util.ts";
23

34
export function renderHTML(node: Nodeish) {
4-
if (typeof node === "string") return node;
55
if (isFalsy(node)) return "";
6+
if (typeof node === "string") return escapeHTML(node);
7+
if (node instanceof HTMLNode) return node.htmlString;
68

79
let stringified = "<" + node.tag;
810

src/util.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,16 @@
1+
const escapables = {
2+
"<": "&lt;",
3+
">": "&gt;",
4+
"&": "&amp;",
5+
"'": "&#39;",
6+
'"': "&quot;",
7+
};
8+
9+
export const escapeHTML = (s: string) =>
10+
s.replace(/<|>|&|"|'/g, r => escapables[r as keyof typeof escapables] || r);
11+
112
export type Falsy = false | "" | 0 | 0n | undefined | null;
13+
14+
export const Falsy = new Set([false, "", 0, 0n, undefined, null]);
15+
// deno-lint-ignore no-explicit-any
16+
export const isFalsy = (n: any): n is Falsy => Falsy.has(n);

test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { assertEquals } from "https://deno.land/std@0.99.0/testing/asserts.ts";
22

3-
import { elements, renderHTML } from "./mod.ts";
3+
import { elements, trust, renderHTML } from "./mod.ts";
44

55
const [div, p, h1, br] = elements("div", "p", "h1", "br");
66

@@ -28,3 +28,17 @@ Deno.test({
2828
);
2929
},
3030
});
31+
32+
Deno.test({
33+
name: "renderHTML with HTML characters",
34+
fn: () => {
35+
assertEquals(renderHTML(p("<test />")), `<p>&lt;test /&gt;</p>`);
36+
},
37+
});
38+
39+
Deno.test({
40+
name: "renderHTML with trusted HTML",
41+
fn: () => {
42+
assertEquals(renderHTML(p(trust("<test />"))), `<p><test /></p>`);
43+
},
44+
});

0 commit comments

Comments
 (0)