ghtml 1.6.0 → 1.7.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
@@ -4,7 +4,7 @@ Inspired by [html-template-tag](https://github.com/AntonioVdlC/html-template-tag
4
4
 
5
5
  ## Installation
6
6
 
7
- ```shell
7
+ ```sh
8
8
  npm i ghtml
9
9
  ```
10
10
 
@@ -24,9 +24,9 @@ Keep in mind that, in Node.js, all else being equal, streaming a response using
24
24
 
25
25
  ### `htmlAsyncGenerator`
26
26
 
27
- This version of HTML generator should be preferred for asynchronous use cases. The output will be generated as the promise expressions resolve.
27
+ This version of HTML generator should be preferred for asynchronous and streaming use cases. The output will be generated as the promise expressions resolve or stream expressions send data.
28
28
 
29
- **Note:**
29
+ **Minor Note:**
30
30
 
31
31
  Because they return generators instead of strings, a key difference of `htmlGenerator` and `htmlAsyncGenerator` is their ability to recognize and properly handle iterable elements within array expressions. This is to detect nested `htmlGenerator` and `htmlAsyncGenerator` usage, enabling scenarios such as ``${[1, 2, 3].map(i => htmlGenerator`<li>${i}</li>`)}``.
32
32
 
@@ -134,11 +134,12 @@ import { Readable } from "node:stream";
134
134
  import http from "node:http";
135
135
 
136
136
  const asyncGenerator = async function* () {
137
- const helloWorld = await new Promise((resolve) => {
137
+ const helloWorld = new Promise((resolve) => {
138
138
  setTimeout(() => {
139
- resolve("Hello, World!");
140
- }, 1000);
139
+ resolve("<br /><br />Hello, World!");
140
+ }, 2500);
141
141
  });
142
+ yield await readFile("./.gitignore", "utf8");
142
143
  yield helloWorld;
143
144
  };
144
145
 
@@ -146,7 +147,7 @@ http
146
147
  .createServer((req, res) => {
147
148
  const htmlContent = html`<!doctype html>
148
149
  <html>
149
- <p>${asyncGenerator()}</p>
150
+ <p>!${asyncGenerator()}</p>
150
151
  <code>${readFile("./README.md", "utf8")}</code>
151
152
  <code>${createReadStream("./README.md", "utf8")}</code>
152
153
  </html>`;
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.0",
6
+ "version": "1.7.0",
7
7
  "type": "module",
8
8
  "main": "./src/index.js",
9
9
  "exports": {
package/src/html.js CHANGED
@@ -8,11 +8,25 @@ const escapeDictionary = {
8
8
 
9
9
  const escapeRegExp = new RegExp(
10
10
  `[${Object.keys(escapeDictionary).join("")}]`,
11
- "gu",
11
+ "u",
12
12
  );
13
13
 
14
- const escapeFunction = (key) => {
15
- return escapeDictionary[key];
14
+ const escapeFunction = (string) => {
15
+ const stringLength = string.length;
16
+ let start = 0;
17
+ let end = 0;
18
+ let escaped = "";
19
+
20
+ do {
21
+ const escapedCharacter = escapeDictionary[string[end++]];
22
+
23
+ if (escapedCharacter) {
24
+ escaped += string.slice(start, end - 1) + escapedCharacter;
25
+ start = end;
26
+ }
27
+ } while (end !== stringLength);
28
+
29
+ return escaped + string.slice(start, end);
16
30
  };
17
31
 
18
32
  const arrayIsArray = Array.isArray;
@@ -23,8 +37,8 @@ const arrayIsArray = Array.isArray;
23
37
  * @returns {string} The HTML string.
24
38
  */
25
39
  const html = ({ raw: literals }, ...expressions) => {
26
- let accumulator = "";
27
40
  let index = 0;
41
+ let accumulator = "";
28
42
 
29
43
  for (; index !== expressions.length; ++index) {
30
44
  const expression = expressions[index];
@@ -38,16 +52,16 @@ const html = ({ raw: literals }, ...expressions) => {
38
52
  ? expression.join("")
39
53
  : `${expression}`;
40
54
 
41
- if (literal.length && literal.charCodeAt(literal.length - 1) === 33) {
55
+ if (literal && literal.charCodeAt(literal.length - 1) === 33) {
42
56
  literal = literal.slice(0, -1);
43
- } else if (string.length) {
44
- string = string.replace(escapeRegExp, escapeFunction);
57
+ } else if (string && escapeRegExp.test(string)) {
58
+ string = escapeFunction(string);
45
59
  }
46
60
 
47
61
  accumulator += literal + string;
48
62
  }
49
63
 
50
- return (accumulator += literals[index]);
64
+ return accumulator + literals[index];
51
65
  };
52
66
 
53
67
  /**
@@ -70,13 +84,13 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
70
84
  } else {
71
85
  if (expression[Symbol.iterator]) {
72
86
  const isRaw =
73
- literal.length !== 0 && literal.charCodeAt(literal.length - 1) === 33;
87
+ literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
74
88
 
75
89
  if (isRaw) {
76
90
  literal = literal.slice(0, -1);
77
91
  }
78
92
 
79
- if (literal.length) {
93
+ if (literal) {
80
94
  yield literal;
81
95
  }
82
96
 
@@ -96,9 +110,9 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
96
110
 
97
111
  string = `${expression}`;
98
112
 
99
- if (string.length) {
100
- if (!isRaw) {
101
- string = string.replace(escapeRegExp, escapeFunction);
113
+ if (string) {
114
+ if (!isRaw && escapeRegExp.test(string)) {
115
+ string = escapeFunction(string);
102
116
  }
103
117
 
104
118
  yield string;
@@ -111,9 +125,9 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
111
125
  string = `${expression}`;
112
126
  }
113
127
 
114
- if (string.length) {
115
- if (!isRaw) {
116
- string = string.replace(escapeRegExp, escapeFunction);
128
+ if (string) {
129
+ if (!isRaw && escapeRegExp.test(string)) {
130
+ string = escapeFunction(string);
117
131
  }
118
132
 
119
133
  yield string;
@@ -126,18 +140,18 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
126
140
  string = `${expression}`;
127
141
  }
128
142
 
129
- if (literal.length && literal.charCodeAt(literal.length - 1) === 33) {
143
+ if (literal && literal.charCodeAt(literal.length - 1) === 33) {
130
144
  literal = literal.slice(0, -1);
131
- } else if (string.length) {
132
- string = string.replace(escapeRegExp, escapeFunction);
145
+ } else if (string && escapeRegExp.test(string)) {
146
+ string = escapeFunction(string);
133
147
  }
134
148
 
135
- if (literal.length || string.length) {
149
+ if (literal || string) {
136
150
  yield literal + string;
137
151
  }
138
152
  }
139
153
 
140
- if (literals[index].length) {
154
+ if (literals[index]) {
141
155
  yield literals[index];
142
156
  }
143
157
  };
@@ -162,13 +176,13 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
162
176
  } else {
163
177
  if (expression[Symbol.iterator] || expression[Symbol.asyncIterator]) {
164
178
  const isRaw =
165
- literal.length !== 0 && literal.charCodeAt(literal.length - 1) === 33;
179
+ literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
166
180
 
167
181
  if (isRaw) {
168
182
  literal = literal.slice(0, -1);
169
183
  }
170
184
 
171
- if (literal.length) {
185
+ if (literal) {
172
186
  yield literal;
173
187
  }
174
188
 
@@ -191,9 +205,9 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
191
205
 
192
206
  string = `${expression}`;
193
207
 
194
- if (string.length) {
195
- if (!isRaw) {
196
- string = string.replace(escapeRegExp, escapeFunction);
208
+ if (string) {
209
+ if (!isRaw && escapeRegExp.test(string)) {
210
+ string = escapeFunction(string);
197
211
  }
198
212
 
199
213
  yield string;
@@ -206,9 +220,9 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
206
220
  string = `${expression}`;
207
221
  }
208
222
 
209
- if (string.length) {
210
- if (!isRaw) {
211
- string = string.replace(escapeRegExp, escapeFunction);
223
+ if (string) {
224
+ if (!isRaw && escapeRegExp.test(string)) {
225
+ string = escapeFunction(string);
212
226
  }
213
227
 
214
228
  yield string;
@@ -221,18 +235,18 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
221
235
  string = `${expression}`;
222
236
  }
223
237
 
224
- if (literal.length && literal.charCodeAt(literal.length - 1) === 33) {
238
+ if (literal && literal.charCodeAt(literal.length - 1) === 33) {
225
239
  literal = literal.slice(0, -1);
226
- } else if (string.length) {
227
- string = string.replace(escapeRegExp, escapeFunction);
240
+ } else if (string && escapeRegExp.test(string)) {
241
+ string = escapeFunction(string);
228
242
  }
229
243
 
230
- if (literal.length || string.length) {
244
+ if (literal || string) {
231
245
  yield literal + string;
232
246
  }
233
247
  }
234
248
 
235
- if (literals[index].length) {
249
+ if (literals[index]) {
236
250
  yield literals[index];
237
251
  }
238
252
  };
package/test/index.js CHANGED
@@ -174,7 +174,7 @@ test("htmlGenerator works with nested htmlGenerator calls in an array", () => {
174
174
  assert.strictEqual(generator.next().done, true);
175
175
  });
176
176
 
177
- test("htmlGenerator works with other generators", () => {
177
+ test("htmlGenerator works with other generators (raw)", () => {
178
178
  const generator = htmlGenerator`<div>!${generatorExample()}</div>`;
179
179
  let accumulator = "";
180
180
 
@@ -189,6 +189,21 @@ test("htmlGenerator works with other generators", () => {
189
189
  assert.strictEqual(generator.next().done, true);
190
190
  });
191
191
 
192
+ test("htmlGenerator works with other generators (escaped)", () => {
193
+ const generator = htmlGenerator`<div>${generatorExample()}</div>`;
194
+ let accumulator = "";
195
+
196
+ for (const value of generator) {
197
+ accumulator += value;
198
+ }
199
+
200
+ assert.strictEqual(
201
+ accumulator,
202
+ "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
203
+ );
204
+ assert.strictEqual(generator.next().done, true);
205
+ });
206
+
192
207
  test("htmlGenerator works with other generators within an array (raw)", () => {
193
208
  const generator = htmlGenerator`<div>!${[generatorExample()]}</div>`;
194
209
  let accumulator = "";
@@ -247,6 +262,34 @@ test("htmlAsyncGenerator renders unsafe content", async () => {
247
262
  );
248
263
  });
249
264
 
265
+ test("htmlAsyncGenerator works with other generators (raw)", async () => {
266
+ const generator = htmlAsyncGenerator`<div>!${generatorExample()}</div>`;
267
+ let accumulator = "";
268
+
269
+ for await (const value of generator) {
270
+ accumulator += value;
271
+ }
272
+
273
+ assert.strictEqual(
274
+ accumulator,
275
+ "<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></div>",
276
+ );
277
+ });
278
+
279
+ test("htmlAsyncGenerator works with other generators (escaped)", async () => {
280
+ const generator = htmlAsyncGenerator`<div>${generatorExample()}</div>`;
281
+ let accumulator = "";
282
+
283
+ for await (const value of generator) {
284
+ accumulator += value;
285
+ }
286
+
287
+ assert.strictEqual(
288
+ accumulator,
289
+ "<div>&lt;p&gt;This is a safe description.&lt;script&gt;alert(&apos;This is an unsafe description.&apos;)&lt;/script&gt;12345255&lt;/p&gt;</div>",
290
+ );
291
+ });
292
+
250
293
  test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array", async () => {
251
294
  const generator = htmlAsyncGenerator`!${[1, 2, 3].map((i) => {
252
295
  return htmlAsyncGenerator`${i}: <p>${readFile("test/test.md", "utf8")}</p>`;