ghtml 1.2.2 → 1.2.4

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
@@ -12,11 +12,17 @@ npm i ghtml
12
12
 
13
13
  ### `html`
14
14
 
15
- The `html` function is used to tag template literals and escape their expressions. To bypass escaping an expression, prefix it with `!`.
15
+ The `html` function is designed to tag template literals and automatically escape their expressions to prevent XSS attacks. To intentionally bypass escaping for a specific expression, prefix it with `!`.
16
16
 
17
17
  ### `htmlGenerator`
18
18
 
19
- The `htmlGenerator` function is the generator version of the `html` function. It allows for the generation of HTML fragments in a streaming manner, which can be particularly useful for large templates or when generating HTML on-the-fly.
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
+
21
+ **Note:**
22
+
23
+ A key difference of `htmlGenerator` is its ability to recognize and properly handle iterable elements within array expressions. This is to detect nested `htmlGenerator` usage, enabling scenarios such as ``[1,2,3].map(i => htmlGenerator`<li>${i}</li>`)``.
24
+
25
+ As a side effect, an expression like `${[[1, 2, 3], 4]}` (where an element is an array itself) will not be rendered as `"1,2,34"`, which is the case with `html`, but as `"1234"`. This is the intended behavior most of the time anyway.
20
26
 
21
27
  ### `includeFile`
22
28
 
@@ -77,15 +83,16 @@ const htmlString = html`
77
83
  import { htmlGenerator as html } from "ghtml";
78
84
  import { Readable } from "node:stream";
79
85
 
80
- const htmlContent = html`<html>
81
- <p>${"...your HTML content..."}</p>
82
- </html>`;
83
- const readableStream = Readable.from(htmlContent);
84
-
85
- http.createServer((req, res) => {
86
- res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
87
- readableStream.pipe(res);
88
- });
86
+ http
87
+ .createServer((req, res) => {
88
+ const htmlContent = htmlGenerator`<html>
89
+ <p>${"...HTML content..."}</p>
90
+ </html>`;
91
+ const readableStream = Readable.from(htmlContent);
92
+ res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
93
+ readableStream.pipe(res);
94
+ })
95
+ .listen(3000);
89
96
  ```
90
97
 
91
98
  ### `includeFile`
package/bench/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-unused-expressions */
2
- import { html } from "../src/html.js";
2
+ import { html } from "../src/index.js";
3
3
  import { Bench } from "tinybench";
4
4
  import { writeFileSync } from "node:fs";
5
5
  import { Buffer } from "node:buffer";
@@ -64,7 +64,7 @@ bench.add("Large strings", () => {
64
64
  });
65
65
 
66
66
  bench.add("High iteration count", () => {
67
- for (let i = 0; i < 1000; i++) {
67
+ for (let i = 0; i !== 1000; i++) {
68
68
  html`<span>${i}</span>`;
69
69
  }
70
70
  });
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.2.2",
6
+ "version": "1.2.4",
7
7
  "type": "module",
8
8
  "main": "./src/index.js",
9
9
  "exports": {
@@ -14,12 +14,13 @@
14
14
  "node": ">=18"
15
15
  },
16
16
  "scripts": {
17
- "test": "npm run lint && node --experimental-test-coverage test/index.js",
17
+ "test": "npm run lint && c8 --100 node --test test/**",
18
18
  "lint": "eslint . && prettier --check .",
19
19
  "lint:fix": "eslint --fix . && prettier --write ."
20
20
  },
21
21
  "devDependencies": {
22
22
  "@fastify/pre-commit": "^2.1.0",
23
+ "c8": "^9.1.0",
23
24
  "grules": "^0.15.0",
24
25
  "tinybench": "^2.6.0"
25
26
  },
package/src/html.js CHANGED
@@ -24,7 +24,7 @@ const html = ({ raw: literals }, ...expressions) => {
24
24
  let accumulator = "";
25
25
  let index = 0;
26
26
 
27
- for (; index < expressions.length; ++index) {
27
+ for (; index !== expressions.length; ++index) {
28
28
  let literal = literals[index];
29
29
  let expression =
30
30
  typeof expressions[index] === "string"
@@ -55,7 +55,7 @@ const html = ({ raw: literals }, ...expressions) => {
55
55
  const htmlGenerator = function* ({ raw: literals }, ...expressions) {
56
56
  let index = 0;
57
57
 
58
- for (; index < expressions.length; ++index) {
58
+ for (; index !== expressions.length; ++index) {
59
59
  let literal = literals[index];
60
60
  let expression;
61
61
 
@@ -63,8 +63,6 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
63
63
  expression = expressions[index];
64
64
  } else if (expressions[index] == null) {
65
65
  expression = "";
66
- } else if (Array.isArray(expressions[index])) {
67
- expression = expressions[index].join("");
68
66
  } else {
69
67
  if (typeof expressions[index][Symbol.iterator] === "function") {
70
68
  const isRaw =
@@ -79,14 +77,22 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
79
77
  }
80
78
 
81
79
  for (const value of expressions[index]) {
82
- expression =
83
- typeof value === "string"
84
- ? value
85
- : value == null
86
- ? ""
87
- : Array.isArray(value)
88
- ? value.join("")
89
- : `${value}`;
80
+ if (typeof value === "string") {
81
+ expression = value;
82
+ } else if (value == null) {
83
+ expression = "";
84
+ } else if (typeof value[Symbol.iterator] === "function") {
85
+ expression = "";
86
+
87
+ for (const innerValue of value) {
88
+ if (innerValue != null) {
89
+ // At this level, we simply mirror Array.prototype.join
90
+ expression += innerValue;
91
+ }
92
+ }
93
+ } else {
94
+ expression = `${value}`;
95
+ }
90
96
 
91
97
  if (expression.length) {
92
98
  if (!isRaw) {
package/test/index.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { html, htmlGenerator } from "../src/index.js";
1
2
  import test from "node:test";
2
3
  import assert from "node:assert";
3
- import { html, htmlGenerator } from "../src/html.js";
4
4
 
5
5
  const conditionTrue = true;
6
6
  const conditionFalse = false;
@@ -134,18 +134,30 @@ test("htmlGenerator renders safe content", () => {
134
134
  });
135
135
 
136
136
  test("htmlGenerator renders unsafe content", () => {
137
- const generator = htmlGenerator`<p>${descriptionUnsafe}${descriptionUnsafe}${htmlGenerator`${array1}`}${null}${255}</p>`;
138
- assert.strictEqual(
139
- generator.next().value,
140
- "<p>&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;",
141
- );
137
+ const generator = htmlGenerator`<p>${descriptionSafe}${descriptionUnsafe}${htmlGenerator`${array1}`}${null}${255}</p>`;
138
+ let accumulator = "";
139
+
140
+ for (const value of generator) {
141
+ accumulator += value;
142
+ }
143
+
142
144
  assert.strictEqual(
143
- generator.next().value,
144
- "&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;",
145
+ accumulator,
146
+ "<p>This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255</p>",
145
147
  );
146
- assert.strictEqual(generator.next().value, "12345");
147
- assert.strictEqual(generator.next().value, "255");
148
- assert.strictEqual(generator.next().value, "</p>");
148
+ });
149
+
150
+ test("htmlGenerator works with nested htmlGenerator calls in an array", () => {
151
+ const generator = htmlGenerator`<ul>!${[1, 2, 3].map((index) => {
152
+ return htmlGenerator`<li>${index}</li>`;
153
+ })}</ul>`;
154
+ let accumulator = "";
155
+
156
+ for (const value of generator) {
157
+ accumulator += value;
158
+ }
159
+
160
+ assert.strictEqual(accumulator, "<ul><li>1</li><li>2</li><li>3</li></ul>");
149
161
  assert.strictEqual(generator.next().done, true);
150
162
  });
151
163
 
@@ -164,3 +176,25 @@ test("htmlGenerator works with other generators", () => {
164
176
  assert.strictEqual(generator.next().value, "</div>");
165
177
  assert.strictEqual(generator.next().done, true);
166
178
  });
179
+
180
+ test("htmlGenerator works with other generators within an array (raw)", () => {
181
+ const generator = htmlGenerator`<div>!${[generatorExample()]}</div>`;
182
+ assert.strictEqual(generator.next().value, "<div>");
183
+ assert.strictEqual(
184
+ generator.next().value,
185
+ "<p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p>",
186
+ );
187
+ assert.strictEqual(generator.next().value, "</div>");
188
+ assert.strictEqual(generator.next().done, true);
189
+ });
190
+
191
+ test("htmlGenerator works with other generators within an array (escaped)", () => {
192
+ const generator = htmlGenerator`<div>${[generatorExample()]}</div>`;
193
+ assert.strictEqual(generator.next().value, "<div>");
194
+ assert.strictEqual(
195
+ generator.next().value,
196
+ "&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;1,2,3,4,5255&lt;/p&gt;",
197
+ );
198
+ assert.strictEqual(generator.next().value, "</div>");
199
+ assert.strictEqual(generator.next().done, true);
200
+ });