ghtml 1.5.2 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -11
- package/bench/index.js +50 -10
- package/package.json +1 -1
- package/src/html.js +3 -1
package/README.md
CHANGED
|
@@ -18,6 +18,10 @@ The `html` function is designed to tag template literals and automatically escap
|
|
|
18
18
|
|
|
19
19
|
The `htmlGenerator` function acts as the generator version of the `html` function. It facilitates the creation of HTML fragments iteratively, making it ideal for parsing large templates or constructing HTML content dynamically.
|
|
20
20
|
|
|
21
|
+
**Note:**
|
|
22
|
+
|
|
23
|
+
Keep in mind that, in Node.js, all else being equal, streaming a response using synchronous generators will **always** be slower than processing everything directly and sending it at once — [this also applies to TTFB](https://github.com/mcollina/fastify-html/issues/11#issuecomment-2069385895). However, if a template includes promises that do asynchronous operations (I/O, etc.), then `htmlAsyncGenerator` can be used to stream the response as those promises get resolved, which will indeed improve TTFB.
|
|
24
|
+
|
|
21
25
|
### `htmlAsyncGenerator`
|
|
22
26
|
|
|
23
27
|
This version of HTML generator should be preferred for asynchronous use cases. The output will be generated as the promise expressions resolve.
|
|
@@ -79,6 +83,23 @@ const htmlString = html`
|
|
|
79
83
|
`;
|
|
80
84
|
```
|
|
81
85
|
|
|
86
|
+
```js
|
|
87
|
+
import { html } from "ghtml";
|
|
88
|
+
import http from "node:http";
|
|
89
|
+
|
|
90
|
+
http
|
|
91
|
+
.createServer((req, res) => {
|
|
92
|
+
const htmlContent = html`<!doctype html>
|
|
93
|
+
<html>
|
|
94
|
+
<p>You are at: ${req.url}</p>
|
|
95
|
+
</html>`;
|
|
96
|
+
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
|
|
97
|
+
res.write(htmlContent);
|
|
98
|
+
res.end();
|
|
99
|
+
})
|
|
100
|
+
.listen(3000);
|
|
101
|
+
```
|
|
102
|
+
|
|
82
103
|
### `htmlGenerator`
|
|
83
104
|
|
|
84
105
|
```js
|
|
@@ -92,9 +113,10 @@ const generator = function* () {
|
|
|
92
113
|
|
|
93
114
|
http
|
|
94
115
|
.createServer((req, res) => {
|
|
95
|
-
const htmlContent = html
|
|
96
|
-
<
|
|
97
|
-
|
|
116
|
+
const htmlContent = html`<!doctype html>
|
|
117
|
+
<html>
|
|
118
|
+
<p>${generator()}</p>
|
|
119
|
+
</html>`;
|
|
98
120
|
const readableStream = Readable.from(htmlContent);
|
|
99
121
|
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
|
|
100
122
|
readableStream.pipe(res);
|
|
@@ -112,21 +134,22 @@ import { Readable } from "node:stream";
|
|
|
112
134
|
import http from "node:http";
|
|
113
135
|
|
|
114
136
|
const asyncGenerator = async function* () {
|
|
115
|
-
const
|
|
137
|
+
const helloWorld = await new Promise((resolve) => {
|
|
116
138
|
setTimeout(() => {
|
|
117
|
-
resolve("Hello");
|
|
139
|
+
resolve("Hello, World!");
|
|
118
140
|
}, 1000);
|
|
119
141
|
});
|
|
120
|
-
yield
|
|
142
|
+
yield helloWorld;
|
|
121
143
|
};
|
|
122
144
|
|
|
123
145
|
http
|
|
124
146
|
.createServer((req, res) => {
|
|
125
|
-
const htmlContent = html
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
const htmlContent = html`<!doctype html>
|
|
148
|
+
<html>
|
|
149
|
+
<p>${asyncGenerator()}</p>
|
|
150
|
+
<code>${readFile("./README.md", "utf8")}</code>
|
|
151
|
+
<code>${createReadStream("./README.md", "utf8")}</code>
|
|
152
|
+
</html>`;
|
|
130
153
|
const readableStream = Readable.from(htmlContent);
|
|
131
154
|
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
|
|
132
155
|
readableStream.pipe(res);
|
package/bench/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { Buffer } from "node:buffer";
|
|
|
8
8
|
let result = "";
|
|
9
9
|
const bench = new Bench({ time: 500 });
|
|
10
10
|
|
|
11
|
+
// Simple cases
|
|
11
12
|
bench.add("simple HTML formatting", () => {
|
|
12
13
|
result = html`<div>Hello, world!</div>`;
|
|
13
14
|
});
|
|
@@ -16,11 +17,17 @@ bench.add("null and undefined expressions", () => {
|
|
|
16
17
|
result = html`<p>${null} and ${undefined}</p>`;
|
|
17
18
|
});
|
|
18
19
|
|
|
20
|
+
// String expressions
|
|
19
21
|
const username = "User";
|
|
20
|
-
bench.add("string
|
|
22
|
+
bench.add("single string expression", () => {
|
|
23
|
+
result = html`<p>${username}</p>`;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
bench.add("multiple string expressions", () => {
|
|
21
27
|
result = html`<p>${username} and ${username}</p>`;
|
|
22
28
|
});
|
|
23
29
|
|
|
30
|
+
// Array expressions
|
|
24
31
|
const items1 = ["Item 1", undefined, "Item 2", null, 2000, 1500.5];
|
|
25
32
|
bench.add("array expressions", () => {
|
|
26
33
|
result = html`<ul>
|
|
@@ -30,8 +37,25 @@ bench.add("array expressions", () => {
|
|
|
30
37
|
</ul>`;
|
|
31
38
|
});
|
|
32
39
|
|
|
40
|
+
const items2 = ["Item 1", "Item <1.5>", "Item 2", "Item <2.5>", "Item 3"];
|
|
41
|
+
bench.add("array expressions with escapable chars", () => {
|
|
42
|
+
result = html`<ul>
|
|
43
|
+
${items2.map((item) => {
|
|
44
|
+
return html`<li>"${item}" & '${item}'</li>`;
|
|
45
|
+
})}
|
|
46
|
+
</ul>`;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Object expressions
|
|
33
50
|
const user = { id: 1, name: "John Doe" };
|
|
34
|
-
|
|
51
|
+
bench.add("object expressions", () => {
|
|
52
|
+
result = html`
|
|
53
|
+
<div>User: <span>${user.name}</span></div>
|
|
54
|
+
<div>Id: <span>${user.id}</span></div>
|
|
55
|
+
`;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Mixed expressions
|
|
35
59
|
bench.add("multiple types of expressions", () => {
|
|
36
60
|
result = html`
|
|
37
61
|
${undefined}
|
|
@@ -46,19 +70,13 @@ bench.add("multiple types of expressions", () => {
|
|
|
46
70
|
`;
|
|
47
71
|
});
|
|
48
72
|
|
|
73
|
+
// Large strings
|
|
49
74
|
const largeString = Array.from({ length: 1000 }).join("Lorem ipsum ");
|
|
50
75
|
bench.add("large strings", () => {
|
|
51
76
|
result = html`<p>${largeString}${largeString}</p>`;
|
|
52
77
|
});
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
"<script>console.log('This should not execute');</script>";
|
|
56
|
-
bench.add("high iteration count", () => {
|
|
57
|
-
for (let i = 0; i !== 100; i++) {
|
|
58
|
-
result = html`<span>${i}: ${scriptContent}</span>`;
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
79
|
+
// Escaped and unescaped expressions
|
|
62
80
|
const rawHTML = "<em>Italic</em> and <strong>bold</strong>";
|
|
63
81
|
const markup = "<mark>Highlighted</mark>";
|
|
64
82
|
bench.add("unescaped expressions", () => {
|
|
@@ -72,6 +90,28 @@ bench.add("unescaped expressions", () => {
|
|
|
72
90
|
`;
|
|
73
91
|
});
|
|
74
92
|
|
|
93
|
+
bench.add("escaped expressions", () => {
|
|
94
|
+
html`
|
|
95
|
+
<div>${rawHTML}</div>
|
|
96
|
+
<div>${rawHTML}</div>
|
|
97
|
+
<div>${markup}</div>
|
|
98
|
+
<div>${markup}</div>
|
|
99
|
+
<div>${rawHTML}</div>
|
|
100
|
+
<div>${rawHTML}</div>
|
|
101
|
+
`;
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
bench.add("mixed escaped and unescaped expressions", () => {
|
|
105
|
+
html`
|
|
106
|
+
<div>!${rawHTML}</div>
|
|
107
|
+
<div>!${rawHTML}</div>
|
|
108
|
+
<div>${markup}</div>
|
|
109
|
+
<div>${markup}</div>
|
|
110
|
+
<div>!${rawHTML}</div>
|
|
111
|
+
<div>!${rawHTML}</div>
|
|
112
|
+
`;
|
|
113
|
+
});
|
|
114
|
+
|
|
75
115
|
await bench.warmup();
|
|
76
116
|
await bench.run();
|
|
77
117
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Replace your template engine with fast JavaScript by leveraging the power of tagged templates.",
|
|
4
4
|
"author": "Gürgün Dayıoğlu",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.6.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"exports": {
|
package/src/html.js
CHANGED
|
@@ -15,6 +15,8 @@ const escapeFunction = (key) => {
|
|
|
15
15
|
return escapeDictionary[key];
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
const arrayIsArray = Array.isArray;
|
|
19
|
+
|
|
18
20
|
/**
|
|
19
21
|
* @param {{ raw: string[] }} literals Tagged template literals.
|
|
20
22
|
* @param {...any} expressions Expressions to interpolate.
|
|
@@ -32,7 +34,7 @@ const html = ({ raw: literals }, ...expressions) => {
|
|
|
32
34
|
? ""
|
|
33
35
|
: typeof expression === "string"
|
|
34
36
|
? expression
|
|
35
|
-
:
|
|
37
|
+
: arrayIsArray(expression)
|
|
36
38
|
? expression.join("")
|
|
37
39
|
: `${expression}`;
|
|
38
40
|
|