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 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`<html>
96
- <p>${generator()}</p>
97
- </html>`;
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 Hello = await new Promise((resolve) => {
137
+ const helloWorld = await new Promise((resolve) => {
116
138
  setTimeout(() => {
117
- resolve("Hello");
139
+ resolve("Hello, World!");
118
140
  }, 1000);
119
141
  });
120
- yield `${Hello}!`;
142
+ yield helloWorld;
121
143
  };
122
144
 
123
145
  http
124
146
  .createServer((req, res) => {
125
- const htmlContent = html`<html>
126
- <p>${asyncGenerator()}</p>
127
- <code>${readFile("./README.md", "utf8")}</code>
128
- <code>${createReadStream("./README.md", "utf8")}</code>
129
- </html>`;
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 expressions", () => {
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
- const items2 = ["Item 1", "Item 2", "Item 3"];
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
- const scriptContent =
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.5.2",
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
- : Array.isArray(expression)
37
+ : arrayIsArray(expression)
36
38
  ? expression.join("")
37
39
  : `${expression}`;
38
40