ghtml 1.6.0 → 1.7.1
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 +8 -7
- package/package.json +3 -3
- package/src/html.js +53 -37
- package/test/index.js +44 -1
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
|
-
```
|
|
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 =
|
|
137
|
+
const helloWorld = new Promise((resolve) => {
|
|
138
138
|
setTimeout(() => {
|
|
139
|
-
resolve("Hello, World!");
|
|
140
|
-
},
|
|
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
|
|
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
|
+
"version": "1.7.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@fastify/pre-commit": "^2.1.0",
|
|
24
24
|
"c8": "^9.1.0",
|
|
25
|
-
"grules": "^0.
|
|
26
|
-
"tinybench": "^2.
|
|
25
|
+
"grules": "^0.17.1",
|
|
26
|
+
"tinybench": "^2.8.0"
|
|
27
27
|
},
|
|
28
28
|
"repository": {
|
|
29
29
|
"type": "git",
|
package/src/html.js
CHANGED
|
@@ -8,11 +8,24 @@ const escapeDictionary = {
|
|
|
8
8
|
|
|
9
9
|
const escapeRegExp = new RegExp(
|
|
10
10
|
`[${Object.keys(escapeDictionary).join("")}]`,
|
|
11
|
-
"
|
|
11
|
+
"u",
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
const escapeFunction = (
|
|
15
|
-
|
|
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
|
+
if (escapedCharacter) {
|
|
23
|
+
escaped += string.slice(start, end - 1) + escapedCharacter;
|
|
24
|
+
start = end;
|
|
25
|
+
}
|
|
26
|
+
} while (end !== stringLength);
|
|
27
|
+
|
|
28
|
+
return escaped + string.slice(start, end);
|
|
16
29
|
};
|
|
17
30
|
|
|
18
31
|
const arrayIsArray = Array.isArray;
|
|
@@ -23,10 +36,11 @@ const arrayIsArray = Array.isArray;
|
|
|
23
36
|
* @returns {string} The HTML string.
|
|
24
37
|
*/
|
|
25
38
|
const html = ({ raw: literals }, ...expressions) => {
|
|
26
|
-
|
|
39
|
+
const expressionsLength = expressions.length;
|
|
27
40
|
let index = 0;
|
|
41
|
+
let accumulator = "";
|
|
28
42
|
|
|
29
|
-
for (; index !==
|
|
43
|
+
for (; index !== expressionsLength; ++index) {
|
|
30
44
|
const expression = expressions[index];
|
|
31
45
|
let literal = literals[index];
|
|
32
46
|
let string =
|
|
@@ -38,16 +52,16 @@ const html = ({ raw: literals }, ...expressions) => {
|
|
|
38
52
|
? expression.join("")
|
|
39
53
|
: `${expression}`;
|
|
40
54
|
|
|
41
|
-
if (literal
|
|
55
|
+
if (literal && literal.charCodeAt(literal.length - 1) === 33) {
|
|
42
56
|
literal = literal.slice(0, -1);
|
|
43
|
-
} else if (string.
|
|
44
|
-
string = string
|
|
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
|
|
64
|
+
return accumulator + literals[index];
|
|
51
65
|
};
|
|
52
66
|
|
|
53
67
|
/**
|
|
@@ -56,9 +70,10 @@ const html = ({ raw: literals }, ...expressions) => {
|
|
|
56
70
|
* @yields {string} The HTML strings.
|
|
57
71
|
*/
|
|
58
72
|
const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
73
|
+
const expressionsLength = expressions.length;
|
|
59
74
|
let index = 0;
|
|
60
75
|
|
|
61
|
-
for (; index !==
|
|
76
|
+
for (; index !== expressionsLength; ++index) {
|
|
62
77
|
let expression = expressions[index];
|
|
63
78
|
let literal = literals[index];
|
|
64
79
|
let string;
|
|
@@ -70,13 +85,13 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
70
85
|
} else {
|
|
71
86
|
if (expression[Symbol.iterator]) {
|
|
72
87
|
const isRaw =
|
|
73
|
-
literal
|
|
88
|
+
literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
|
|
74
89
|
|
|
75
90
|
if (isRaw) {
|
|
76
91
|
literal = literal.slice(0, -1);
|
|
77
92
|
}
|
|
78
93
|
|
|
79
|
-
if (literal
|
|
94
|
+
if (literal) {
|
|
80
95
|
yield literal;
|
|
81
96
|
}
|
|
82
97
|
|
|
@@ -96,9 +111,9 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
96
111
|
|
|
97
112
|
string = `${expression}`;
|
|
98
113
|
|
|
99
|
-
if (string
|
|
100
|
-
if (!isRaw) {
|
|
101
|
-
string = string
|
|
114
|
+
if (string) {
|
|
115
|
+
if (!isRaw && escapeRegExp.test(string)) {
|
|
116
|
+
string = escapeFunction(string);
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
yield string;
|
|
@@ -111,9 +126,9 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
111
126
|
string = `${expression}`;
|
|
112
127
|
}
|
|
113
128
|
|
|
114
|
-
if (string
|
|
115
|
-
if (!isRaw) {
|
|
116
|
-
string = string
|
|
129
|
+
if (string) {
|
|
130
|
+
if (!isRaw && escapeRegExp.test(string)) {
|
|
131
|
+
string = escapeFunction(string);
|
|
117
132
|
}
|
|
118
133
|
|
|
119
134
|
yield string;
|
|
@@ -126,18 +141,18 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
126
141
|
string = `${expression}`;
|
|
127
142
|
}
|
|
128
143
|
|
|
129
|
-
if (literal
|
|
144
|
+
if (literal && literal.charCodeAt(literal.length - 1) === 33) {
|
|
130
145
|
literal = literal.slice(0, -1);
|
|
131
|
-
} else if (string.
|
|
132
|
-
string = string
|
|
146
|
+
} else if (string && escapeRegExp.test(string)) {
|
|
147
|
+
string = escapeFunction(string);
|
|
133
148
|
}
|
|
134
149
|
|
|
135
|
-
if (literal
|
|
150
|
+
if (literal || string) {
|
|
136
151
|
yield literal + string;
|
|
137
152
|
}
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
if (literals[index]
|
|
155
|
+
if (literals[index]) {
|
|
141
156
|
yield literals[index];
|
|
142
157
|
}
|
|
143
158
|
};
|
|
@@ -148,9 +163,10 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
148
163
|
* @yields {string} The HTML strings.
|
|
149
164
|
*/
|
|
150
165
|
const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
166
|
+
const expressionsLength = expressions.length;
|
|
151
167
|
let index = 0;
|
|
152
168
|
|
|
153
|
-
for (; index !==
|
|
169
|
+
for (; index !== expressionsLength; ++index) {
|
|
154
170
|
let expression = await expressions[index];
|
|
155
171
|
let literal = literals[index];
|
|
156
172
|
let string;
|
|
@@ -162,13 +178,13 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
162
178
|
} else {
|
|
163
179
|
if (expression[Symbol.iterator] || expression[Symbol.asyncIterator]) {
|
|
164
180
|
const isRaw =
|
|
165
|
-
literal
|
|
181
|
+
literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
|
|
166
182
|
|
|
167
183
|
if (isRaw) {
|
|
168
184
|
literal = literal.slice(0, -1);
|
|
169
185
|
}
|
|
170
186
|
|
|
171
|
-
if (literal
|
|
187
|
+
if (literal) {
|
|
172
188
|
yield literal;
|
|
173
189
|
}
|
|
174
190
|
|
|
@@ -191,9 +207,9 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
191
207
|
|
|
192
208
|
string = `${expression}`;
|
|
193
209
|
|
|
194
|
-
if (string
|
|
195
|
-
if (!isRaw) {
|
|
196
|
-
string = string
|
|
210
|
+
if (string) {
|
|
211
|
+
if (!isRaw && escapeRegExp.test(string)) {
|
|
212
|
+
string = escapeFunction(string);
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
yield string;
|
|
@@ -206,9 +222,9 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
206
222
|
string = `${expression}`;
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
if (string
|
|
210
|
-
if (!isRaw) {
|
|
211
|
-
string = string
|
|
225
|
+
if (string) {
|
|
226
|
+
if (!isRaw && escapeRegExp.test(string)) {
|
|
227
|
+
string = escapeFunction(string);
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
yield string;
|
|
@@ -221,18 +237,18 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
221
237
|
string = `${expression}`;
|
|
222
238
|
}
|
|
223
239
|
|
|
224
|
-
if (literal
|
|
240
|
+
if (literal && literal.charCodeAt(literal.length - 1) === 33) {
|
|
225
241
|
literal = literal.slice(0, -1);
|
|
226
|
-
} else if (string.
|
|
227
|
-
string = string
|
|
242
|
+
} else if (string && escapeRegExp.test(string)) {
|
|
243
|
+
string = escapeFunction(string);
|
|
228
244
|
}
|
|
229
245
|
|
|
230
|
-
if (literal
|
|
246
|
+
if (literal || string) {
|
|
231
247
|
yield literal + string;
|
|
232
248
|
}
|
|
233
249
|
}
|
|
234
250
|
|
|
235
|
-
if (literals[index]
|
|
251
|
+
if (literals[index]) {
|
|
236
252
|
yield literals[index];
|
|
237
253
|
}
|
|
238
254
|
};
|
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><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></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><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></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>`;
|