ghtml 1.2.4 → 1.5.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/.eslintrc.json +4 -1
- package/.github/workflows/benchmark.yml +21 -14
- package/.github/workflows/npm-test.yml +1 -1
- package/README.md +29 -6
- package/bench/index.js +33 -50
- package/package.json +3 -2
- package/src/html.js +137 -19
- package/src/index.js +1 -1
- package/test/index.js +179 -18
- package/test/test.md +1 -0
package/.eslintrc.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
name:
|
|
1
|
+
name: benchmark
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
types: [labeled]
|
|
2
6
|
|
|
3
7
|
permissions:
|
|
4
8
|
contents: read
|
|
5
9
|
pull-requests: write
|
|
6
10
|
|
|
7
|
-
on:
|
|
8
|
-
pull_request:
|
|
9
|
-
branches:
|
|
10
|
-
- main
|
|
11
|
-
|
|
12
11
|
jobs:
|
|
13
|
-
benchmark
|
|
12
|
+
benchmark:
|
|
13
|
+
if: ${{ github.event.label.name == 'benchmark' }}
|
|
14
14
|
runs-on: ubuntu-latest
|
|
15
15
|
steps:
|
|
16
16
|
# Checkout PR code
|
|
@@ -25,11 +25,11 @@ jobs:
|
|
|
25
25
|
|
|
26
26
|
# Install dependencies
|
|
27
27
|
- name: Install dependencies
|
|
28
|
-
run: npm install
|
|
28
|
+
run: npm install --ignore-scripts
|
|
29
29
|
|
|
30
30
|
# Run benchmark on PR code
|
|
31
31
|
- name: Run benchmark on PR code
|
|
32
|
-
run:
|
|
32
|
+
run: npm run benchmark
|
|
33
33
|
id: benchmark_pr
|
|
34
34
|
|
|
35
35
|
# Save PR benchmark results
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
|
|
56
56
|
# Run benchmark on main branch src with PR's benchmark tooling
|
|
57
57
|
- name: Run benchmark on main branch src
|
|
58
|
-
run:
|
|
58
|
+
run: npm run benchmark
|
|
59
59
|
id: benchmark_main
|
|
60
60
|
|
|
61
61
|
# Save main benchmark results
|
|
@@ -70,24 +70,31 @@ jobs:
|
|
|
70
70
|
script: |
|
|
71
71
|
const prResults = JSON.parse(Buffer.from(process.env.PR_RESULTS, 'base64').toString('utf8'));
|
|
72
72
|
const mainResults = JSON.parse(Buffer.from(process.env.MAIN_RESULTS, 'base64').toString('utf8'));
|
|
73
|
-
|
|
74
73
|
const commentBody = `
|
|
75
74
|
Benchmark Results Comparison (${context.sha}):
|
|
76
|
-
|
|
77
75
|
**PR Branch:**
|
|
78
76
|
\`\`\`json
|
|
79
77
|
${JSON.stringify(prResults, null, 2)}
|
|
80
78
|
\`\`\`
|
|
81
|
-
|
|
82
79
|
**Main Branch:**
|
|
83
80
|
\`\`\`json
|
|
84
81
|
${JSON.stringify(mainResults, null, 2)}
|
|
85
82
|
\`\`\`
|
|
86
83
|
`;
|
|
87
|
-
|
|
88
84
|
github.rest.issues.createComment({
|
|
89
85
|
issue_number: context.issue.number,
|
|
90
86
|
owner: context.repo.owner,
|
|
91
87
|
repo: context.repo.repo,
|
|
92
88
|
body: commentBody
|
|
93
89
|
});
|
|
90
|
+
|
|
91
|
+
remove-label:
|
|
92
|
+
needs: benchmark
|
|
93
|
+
runs-on: ubuntu-latest
|
|
94
|
+
steps:
|
|
95
|
+
- name: Remove benchmark label
|
|
96
|
+
uses: octokit/request-action@v2.x
|
|
97
|
+
with:
|
|
98
|
+
route: DELETE /repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/benchmark
|
|
99
|
+
env:
|
|
100
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
package/README.md
CHANGED
|
@@ -18,11 +18,13 @@ 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
|
-
|
|
21
|
+
### `htmlAsyncGenerator`
|
|
22
|
+
|
|
23
|
+
This version of HTML generator should be preferred for asynchronous use cases. The output will be generated as the promise expressions resolve.
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
**Note:**
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
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>`)}``.
|
|
26
28
|
|
|
27
29
|
### `includeFile`
|
|
28
30
|
|
|
@@ -82,12 +84,33 @@ const htmlString = html`
|
|
|
82
84
|
```js
|
|
83
85
|
import { htmlGenerator as html } from "ghtml";
|
|
84
86
|
import { Readable } from "node:stream";
|
|
87
|
+
import http from "node:http";
|
|
88
|
+
|
|
89
|
+
http
|
|
90
|
+
.createServer((req, res) => {
|
|
91
|
+
const htmlContent = html`<html>
|
|
92
|
+
<p>${"...HTML content..."}</p>
|
|
93
|
+
</html>`;
|
|
94
|
+
const readableStream = Readable.from(htmlContent);
|
|
95
|
+
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
|
|
96
|
+
readableStream.pipe(res);
|
|
97
|
+
})
|
|
98
|
+
.listen(3000);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `htmlAsyncGenerator`
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { htmlAsyncGenerator as html } from "ghtml";
|
|
105
|
+
import { readFile } from "node:fs/promises";
|
|
106
|
+
import { Readable } from "node:stream";
|
|
107
|
+
import http from "node:http";
|
|
85
108
|
|
|
86
109
|
http
|
|
87
110
|
.createServer((req, res) => {
|
|
88
|
-
const htmlContent =
|
|
89
|
-
|
|
90
|
-
|
|
111
|
+
const htmlContent = html`<html>
|
|
112
|
+
<code>${readFile("./README.md")}</code>
|
|
113
|
+
</html>`;
|
|
91
114
|
const readableStream = Readable.from(htmlContent);
|
|
92
115
|
res.writeHead(200, { "Content-Type": "text/html;charset=utf-8" });
|
|
93
116
|
readableStream.pipe(res);
|
package/bench/index.js
CHANGED
|
@@ -1,84 +1,67 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
1
2
|
/* eslint-disable no-unused-expressions */
|
|
2
3
|
import { html } from "../src/index.js";
|
|
3
4
|
import { Bench } from "tinybench";
|
|
4
5
|
import { writeFileSync } from "node:fs";
|
|
5
6
|
import { Buffer } from "node:buffer";
|
|
6
7
|
|
|
8
|
+
let result = "";
|
|
7
9
|
const bench = new Bench({ time: 500 });
|
|
8
10
|
|
|
9
|
-
bench.add("
|
|
10
|
-
html`<div>Hello, world!</div>`;
|
|
11
|
+
bench.add("simple HTML formatting", () => {
|
|
12
|
+
result = html`<div>Hello, world!</div>`;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
bench.add("null and undefined expressions", () => {
|
|
16
|
+
result = html`<p>${null} and ${undefined}</p>`;
|
|
11
17
|
});
|
|
12
18
|
|
|
13
19
|
const username = "User";
|
|
14
|
-
bench.add("
|
|
15
|
-
html`<p>${username}</p>`;
|
|
20
|
+
bench.add("string expressions", () => {
|
|
21
|
+
result = html`<p>${username} and ${username}</p>`;
|
|
16
22
|
});
|
|
17
23
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
const items1 = ["Item 1", undefined, "Item 2", null, 2000, 1500.5];
|
|
25
|
+
bench.add("array expressions", () => {
|
|
26
|
+
result = html`<ul>
|
|
27
|
+
${items1.map((item) => {
|
|
28
|
+
return html`<li>${item}</li>`;
|
|
29
|
+
})}
|
|
30
|
+
</ul>`;
|
|
22
31
|
});
|
|
23
32
|
|
|
24
33
|
const user = { id: 1, name: "John Doe" };
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
const items2 = ["Item 1", "Item 2", "Item 3"];
|
|
35
|
+
bench.add("multiple types of expressions", () => {
|
|
36
|
+
result = html`
|
|
27
37
|
${undefined}
|
|
28
38
|
<div>User: <span>${user.name}</span></div>
|
|
29
39
|
<div>Id: <span>${user.id}</span></div>
|
|
30
|
-
${null}
|
|
40
|
+
${null}${123}${456n}
|
|
41
|
+
<ul>
|
|
42
|
+
!${items2.map((item) => {
|
|
43
|
+
return html`<li>${item}</li>`;
|
|
44
|
+
})}
|
|
45
|
+
</ul>
|
|
31
46
|
`;
|
|
32
47
|
});
|
|
33
48
|
|
|
34
|
-
const items = ["Item 1", "Item 2", "Item 3"];
|
|
35
|
-
bench.add("Arrays and iteration", () => {
|
|
36
|
-
html`<ul>
|
|
37
|
-
${items.map((item) => {
|
|
38
|
-
return html`<li>${item}</li>`;
|
|
39
|
-
})}
|
|
40
|
-
</ul>`;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
const items2 = ["Item 1", undefined, "Item 2", null, 2000];
|
|
44
|
-
bench.add("Arrays and iteration with multiple types", () => {
|
|
45
|
-
html`<ul>
|
|
46
|
-
${items2.map((item) => {
|
|
47
|
-
return html`<li>${item}</li>`;
|
|
48
|
-
})}
|
|
49
|
-
</ul>`;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const loggedIn = true;
|
|
53
|
-
bench.add("Complex/nested expressions", () => {
|
|
54
|
-
html`<nav>
|
|
55
|
-
${loggedIn
|
|
56
|
-
? html`<a href="/logout">Logout</a>`
|
|
57
|
-
: html`<a href="/login">Login</a>`}
|
|
58
|
-
</nav>`;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
49
|
const largeString = Array.from({ length: 1000 }).join("Lorem ipsum ");
|
|
62
|
-
bench.add("
|
|
63
|
-
html`<p>${largeString}</p>`;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
bench.add("High iteration count", () => {
|
|
67
|
-
for (let i = 0; i !== 1000; i++) {
|
|
68
|
-
html`<span>${i}</span>`;
|
|
69
|
-
}
|
|
50
|
+
bench.add("large strings", () => {
|
|
51
|
+
result = html`<p>${largeString}${largeString}</p>`;
|
|
70
52
|
});
|
|
71
53
|
|
|
72
54
|
const scriptContent =
|
|
73
55
|
"<script>console.log('This should not execute');</script>";
|
|
74
|
-
bench.add("
|
|
75
|
-
|
|
56
|
+
bench.add("high iteration count", () => {
|
|
57
|
+
for (let i = 0; i !== 100; i++) {
|
|
58
|
+
result = html`<span>${i}: ${scriptContent}</span>`;
|
|
59
|
+
}
|
|
76
60
|
});
|
|
77
61
|
|
|
78
|
-
// Render raw HTML
|
|
79
62
|
const rawHTML = "<em>Italic</em> and <strong>bold</strong>";
|
|
80
63
|
const markup = "<mark>Highlighted</mark>";
|
|
81
|
-
bench.add("
|
|
64
|
+
bench.add("unescaped expressions", () => {
|
|
82
65
|
html`
|
|
83
66
|
<div>!${rawHTML}</div>
|
|
84
67
|
<div>!${rawHTML}</div>
|
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.5.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"node": ">=18"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
|
-
"
|
|
17
|
+
"benchmark": "node bench/index.js",
|
|
18
|
+
"test": "npm run lint && c8 --100 node --test test/*.js",
|
|
18
19
|
"lint": "eslint . && prettier --check .",
|
|
19
20
|
"lint:fix": "eslint --fix . && prettier --write ."
|
|
20
21
|
},
|
package/src/html.js
CHANGED
|
@@ -27,10 +27,10 @@ const html = ({ raw: literals }, ...expressions) => {
|
|
|
27
27
|
for (; index !== expressions.length; ++index) {
|
|
28
28
|
let literal = literals[index];
|
|
29
29
|
let expression =
|
|
30
|
-
|
|
31
|
-
?
|
|
32
|
-
: expressions[index]
|
|
33
|
-
?
|
|
30
|
+
expressions[index] === undefined || expressions[index] === null
|
|
31
|
+
? ""
|
|
32
|
+
: typeof expressions[index] === "string"
|
|
33
|
+
? expressions[index]
|
|
34
34
|
: Array.isArray(expressions[index])
|
|
35
35
|
? expressions[index].join("")
|
|
36
36
|
: `${expressions[index]}`;
|
|
@@ -59,14 +59,14 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
59
59
|
let literal = literals[index];
|
|
60
60
|
let expression;
|
|
61
61
|
|
|
62
|
-
if (
|
|
63
|
-
expression = expressions[index];
|
|
64
|
-
} else if (expressions[index] == null) {
|
|
62
|
+
if (expressions[index] === undefined || expressions[index] === null) {
|
|
65
63
|
expression = "";
|
|
64
|
+
} else if (typeof expressions[index] === "string") {
|
|
65
|
+
expression = expressions[index];
|
|
66
66
|
} else {
|
|
67
67
|
if (typeof expressions[index][Symbol.iterator] === "function") {
|
|
68
68
|
const isRaw =
|
|
69
|
-
literal.length
|
|
69
|
+
literal.length !== 0 && literal.charCodeAt(literal.length - 1) === 33;
|
|
70
70
|
|
|
71
71
|
if (isRaw) {
|
|
72
72
|
literal = literal.slice(0, -1);
|
|
@@ -77,20 +77,138 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
for (const value of expressions[index]) {
|
|
80
|
-
if (
|
|
80
|
+
if (value === undefined || value === null) {
|
|
81
|
+
continue;
|
|
82
|
+
} else if (typeof value === "string") {
|
|
81
83
|
expression = value;
|
|
82
|
-
} else
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
} else {
|
|
85
|
+
if (typeof value[Symbol.iterator] === "function") {
|
|
86
|
+
for (const innerValue of value) {
|
|
87
|
+
if (innerValue === undefined || innerValue === null) {
|
|
88
|
+
continue;
|
|
89
|
+
} else if (typeof innerValue === "string") {
|
|
90
|
+
expression = innerValue;
|
|
91
|
+
} else {
|
|
92
|
+
expression = `${innerValue}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (expression.length) {
|
|
96
|
+
if (!isRaw) {
|
|
97
|
+
expression = expression.replace(
|
|
98
|
+
escapeRegExp,
|
|
99
|
+
escapeFunction,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
yield expression;
|
|
104
|
+
}
|
|
91
105
|
}
|
|
106
|
+
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
expression = `${value}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (expression.length) {
|
|
114
|
+
if (!isRaw) {
|
|
115
|
+
expression = expression.replace(escapeRegExp, escapeFunction);
|
|
92
116
|
}
|
|
117
|
+
|
|
118
|
+
yield expression;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
expression = `${expressions[index]}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (literal.length && literal.charCodeAt(literal.length - 1) === 33) {
|
|
129
|
+
literal = literal.slice(0, -1);
|
|
130
|
+
} else if (expression.length) {
|
|
131
|
+
expression = expression.replace(escapeRegExp, escapeFunction);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (literal.length || expression.length) {
|
|
135
|
+
yield literal + expression;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (literals[index].length) {
|
|
140
|
+
yield literals[index];
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {{ raw: string[] }} literals Tagged template literals.
|
|
146
|
+
* @param {...any} expressions Expressions to interpolate.
|
|
147
|
+
* @yields {string} The HTML strings.
|
|
148
|
+
*/
|
|
149
|
+
const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
150
|
+
let index = 0;
|
|
151
|
+
|
|
152
|
+
for (; index !== expressions.length; ++index) {
|
|
153
|
+
let literal = literals[index];
|
|
154
|
+
let expression;
|
|
155
|
+
|
|
156
|
+
expressions[index] = await expressions[index];
|
|
157
|
+
|
|
158
|
+
if (expressions[index] === undefined || expressions[index] === null) {
|
|
159
|
+
expression = "";
|
|
160
|
+
} else if (typeof expressions[index] === "string") {
|
|
161
|
+
expression = expressions[index];
|
|
162
|
+
} else {
|
|
163
|
+
if (
|
|
164
|
+
typeof expressions[index][Symbol.iterator] === "function" ||
|
|
165
|
+
typeof expressions[index][Symbol.asyncIterator] === "function"
|
|
166
|
+
) {
|
|
167
|
+
const isRaw =
|
|
168
|
+
literal.length !== 0 && literal.charCodeAt(literal.length - 1) === 33;
|
|
169
|
+
|
|
170
|
+
if (isRaw) {
|
|
171
|
+
literal = literal.slice(0, -1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (literal.length) {
|
|
175
|
+
yield literal;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for await (const value of expressions[index]) {
|
|
179
|
+
if (value === undefined || value === null) {
|
|
180
|
+
continue;
|
|
181
|
+
} else if (typeof value === "string") {
|
|
182
|
+
expression = value;
|
|
93
183
|
} else {
|
|
184
|
+
if (
|
|
185
|
+
typeof value[Symbol.iterator] === "function" ||
|
|
186
|
+
typeof value[Symbol.asyncIterator] === "function"
|
|
187
|
+
) {
|
|
188
|
+
for await (const innerValue of value) {
|
|
189
|
+
if (innerValue === undefined || innerValue === null) {
|
|
190
|
+
continue;
|
|
191
|
+
} else if (typeof innerValue === "string") {
|
|
192
|
+
expression = innerValue;
|
|
193
|
+
} else {
|
|
194
|
+
expression = `${innerValue}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (expression.length) {
|
|
198
|
+
if (!isRaw) {
|
|
199
|
+
expression = expression.replace(
|
|
200
|
+
escapeRegExp,
|
|
201
|
+
escapeFunction,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
yield expression;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
94
212
|
expression = `${value}`;
|
|
95
213
|
}
|
|
96
214
|
|
|
@@ -125,4 +243,4 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
125
243
|
}
|
|
126
244
|
};
|
|
127
245
|
|
|
128
|
-
export { html, htmlGenerator };
|
|
246
|
+
export { html, htmlGenerator, htmlAsyncGenerator };
|
package/src/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { html, htmlGenerator } from "./html.js";
|
|
1
|
+
export { html, htmlGenerator, htmlAsyncGenerator } from "./html.js";
|
package/test/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { html, htmlGenerator } from "../src/index.js";
|
|
1
|
+
import { html, htmlGenerator, htmlAsyncGenerator } from "../src/index.js";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
2
4
|
import test from "node:test";
|
|
3
5
|
import assert from "node:assert";
|
|
4
6
|
|
|
@@ -20,6 +22,17 @@ const generatorExample = function* () {
|
|
|
20
22
|
yield "</p>";
|
|
21
23
|
};
|
|
22
24
|
|
|
25
|
+
const generatorPromiseExample = function* () {
|
|
26
|
+
yield [
|
|
27
|
+
new Promise((resolve) => {
|
|
28
|
+
resolve("<p>");
|
|
29
|
+
}),
|
|
30
|
+
null,
|
|
31
|
+
12n,
|
|
32
|
+
];
|
|
33
|
+
yield;
|
|
34
|
+
};
|
|
35
|
+
|
|
23
36
|
test("renders empty input", () => {
|
|
24
37
|
assert.strictEqual(html({ raw: [""] }), "");
|
|
25
38
|
});
|
|
@@ -163,38 +176,186 @@ test("htmlGenerator works with nested htmlGenerator calls in an array", () => {
|
|
|
163
176
|
|
|
164
177
|
test("htmlGenerator works with other generators", () => {
|
|
165
178
|
const generator = htmlGenerator`<div>!${generatorExample()}</div>`;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
let accumulator = "";
|
|
180
|
+
|
|
181
|
+
for (const value of generator) {
|
|
182
|
+
accumulator += value;
|
|
183
|
+
}
|
|
184
|
+
|
|
169
185
|
assert.strictEqual(
|
|
170
|
-
|
|
171
|
-
"<script>alert('This is an unsafe description.')</script>",
|
|
186
|
+
accumulator,
|
|
187
|
+
"<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p></div>",
|
|
172
188
|
);
|
|
173
|
-
assert.strictEqual(generator.next().value, "12345");
|
|
174
|
-
assert.strictEqual(generator.next().value, "255");
|
|
175
|
-
assert.strictEqual(generator.next().value, "</p>");
|
|
176
|
-
assert.strictEqual(generator.next().value, "</div>");
|
|
177
189
|
assert.strictEqual(generator.next().done, true);
|
|
178
190
|
});
|
|
179
191
|
|
|
180
192
|
test("htmlGenerator works with other generators within an array (raw)", () => {
|
|
181
193
|
const generator = htmlGenerator`<div>!${[generatorExample()]}</div>`;
|
|
182
|
-
|
|
194
|
+
let accumulator = "";
|
|
195
|
+
|
|
196
|
+
for (const value of generator) {
|
|
197
|
+
accumulator += value;
|
|
198
|
+
}
|
|
199
|
+
|
|
183
200
|
assert.strictEqual(
|
|
184
|
-
|
|
185
|
-
"<p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p>",
|
|
201
|
+
accumulator,
|
|
202
|
+
"<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p></div>",
|
|
186
203
|
);
|
|
187
|
-
assert.strictEqual(generator.next().value, "</div>");
|
|
188
204
|
assert.strictEqual(generator.next().done, true);
|
|
189
205
|
});
|
|
190
206
|
|
|
191
207
|
test("htmlGenerator works with other generators within an array (escaped)", () => {
|
|
192
208
|
const generator = htmlGenerator`<div>${[generatorExample()]}</div>`;
|
|
193
|
-
|
|
209
|
+
let accumulator = "";
|
|
210
|
+
|
|
211
|
+
for (const value of generator) {
|
|
212
|
+
accumulator += value;
|
|
213
|
+
}
|
|
214
|
+
|
|
194
215
|
assert.strictEqual(
|
|
195
|
-
|
|
196
|
-
"
|
|
216
|
+
accumulator,
|
|
217
|
+
"<div><p>This is a safe description.<script>alert('This is an unsafe description.')</script>1,2,3,4,5255</p></div>",
|
|
197
218
|
);
|
|
198
|
-
assert.strictEqual(generator.next().value, "</div>");
|
|
199
219
|
assert.strictEqual(generator.next().done, true);
|
|
200
220
|
});
|
|
221
|
+
|
|
222
|
+
test("htmlAsyncGenerator renders safe content", async () => {
|
|
223
|
+
const generator = htmlAsyncGenerator`<p>${descriptionSafe}!${descriptionUnsafe}G!${htmlAsyncGenerator`${array1}`}!${null}${255}</p>`;
|
|
224
|
+
let accumulator = "";
|
|
225
|
+
|
|
226
|
+
for await (const value of generator) {
|
|
227
|
+
accumulator += value;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(
|
|
231
|
+
accumulator,
|
|
232
|
+
"<p>This is a safe description.<script>alert('This is an unsafe description.')</script>G12345255</p>",
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("htmlAsyncGenerator renders unsafe content", async () => {
|
|
237
|
+
const generator = htmlAsyncGenerator`<p>${descriptionSafe}${descriptionUnsafe}${htmlAsyncGenerator`${array1}`}${null}${255}</p>`;
|
|
238
|
+
let accumulator = "";
|
|
239
|
+
|
|
240
|
+
for await (const value of generator) {
|
|
241
|
+
accumulator += value;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
assert.strictEqual(
|
|
245
|
+
accumulator,
|
|
246
|
+
"<p>This is a safe description.<script>alert('This is an unsafe description.')</script>12345255</p>",
|
|
247
|
+
);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test("htmlAsyncGenerator works with nested htmlAsyncGenerator calls in an array", async () => {
|
|
251
|
+
const generator = htmlAsyncGenerator`!${[1, 2, 3].map((i) => {
|
|
252
|
+
return htmlAsyncGenerator`${i}: <p>${readFile("test/test.md", "utf8")}</p>`;
|
|
253
|
+
})}`;
|
|
254
|
+
let accumulator = "";
|
|
255
|
+
|
|
256
|
+
for await (const value of generator) {
|
|
257
|
+
accumulator += value;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
assert.strictEqual(
|
|
261
|
+
accumulator.replaceAll("\n", "").trim(),
|
|
262
|
+
"1: <p># test.md></p>2: <p># test.md></p>3: <p># test.md></p>",
|
|
263
|
+
);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("htmlAsyncGenerator renders chunks with promises (escaped)", async () => {
|
|
267
|
+
const generator = htmlAsyncGenerator`<ul>!${[1, 2].map((i) => {
|
|
268
|
+
return htmlAsyncGenerator`${i}: ${readFile("test/test.md", "utf8")}`;
|
|
269
|
+
})}</ul>`;
|
|
270
|
+
const fileContent = readFileSync("test/test.md", "utf8").replaceAll(
|
|
271
|
+
">",
|
|
272
|
+
">",
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
let value = await generator.next();
|
|
276
|
+
assert.strictEqual(value.value, "<ul>");
|
|
277
|
+
|
|
278
|
+
value = await generator.next();
|
|
279
|
+
assert.strictEqual(value.value, `1`);
|
|
280
|
+
|
|
281
|
+
value = await generator.next();
|
|
282
|
+
assert.strictEqual(value.value, `: ${fileContent}`);
|
|
283
|
+
|
|
284
|
+
value = await generator.next();
|
|
285
|
+
assert.strictEqual(value.value, `2`);
|
|
286
|
+
|
|
287
|
+
value = await generator.next();
|
|
288
|
+
assert.strictEqual(value.value, `: ${fileContent}`);
|
|
289
|
+
|
|
290
|
+
value = await generator.next();
|
|
291
|
+
assert.strictEqual(value.value, "</ul>");
|
|
292
|
+
|
|
293
|
+
value = await generator.next();
|
|
294
|
+
assert.strictEqual(value.done, true);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("htmlAsyncGenerator renders chunks with promises (raw)", async () => {
|
|
298
|
+
const generator = htmlAsyncGenerator`<ul>!${[1, 2].map((i) => {
|
|
299
|
+
return htmlAsyncGenerator`${i}: !${readFile("test/test.md", "utf8")}`;
|
|
300
|
+
})}</ul>`;
|
|
301
|
+
const fileContent = readFileSync("test/test.md", "utf8");
|
|
302
|
+
|
|
303
|
+
let value = await generator.next();
|
|
304
|
+
assert.strictEqual(value.value, "<ul>");
|
|
305
|
+
|
|
306
|
+
value = await generator.next();
|
|
307
|
+
assert.strictEqual(value.value, `1`);
|
|
308
|
+
|
|
309
|
+
value = await generator.next();
|
|
310
|
+
assert.strictEqual(value.value, `: ${fileContent}`);
|
|
311
|
+
|
|
312
|
+
value = await generator.next();
|
|
313
|
+
assert.strictEqual(value.value, `2`);
|
|
314
|
+
|
|
315
|
+
value = await generator.next();
|
|
316
|
+
assert.strictEqual(value.value, `: ${fileContent}`);
|
|
317
|
+
|
|
318
|
+
value = await generator.next();
|
|
319
|
+
assert.strictEqual(value.value, "</ul>");
|
|
320
|
+
|
|
321
|
+
value = await generator.next();
|
|
322
|
+
assert.strictEqual(value.done, true);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test("htmlAsyncGenerator redners in chuncks", async () => {
|
|
326
|
+
const generator = htmlAsyncGenerator`<ul>${generatorPromiseExample()}</ul>`;
|
|
327
|
+
|
|
328
|
+
let value = await generator.next();
|
|
329
|
+
assert.strictEqual(value.value, "<ul>");
|
|
330
|
+
|
|
331
|
+
value = await generator.next();
|
|
332
|
+
assert.strictEqual(value.value, "<p>");
|
|
333
|
+
|
|
334
|
+
value = await generator.next();
|
|
335
|
+
assert.strictEqual(value.value, "12");
|
|
336
|
+
|
|
337
|
+
value = await generator.next();
|
|
338
|
+
assert.strictEqual(value.value, "</ul>");
|
|
339
|
+
|
|
340
|
+
value = await generator.next();
|
|
341
|
+
assert.strictEqual(value.done, true);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("htmlAsyncGenerator redners in chuncks (raw)", async () => {
|
|
345
|
+
const generator = htmlAsyncGenerator`<ul>!${generatorPromiseExample()}</ul>`;
|
|
346
|
+
|
|
347
|
+
let value = await generator.next();
|
|
348
|
+
assert.strictEqual(value.value, "<ul>");
|
|
349
|
+
|
|
350
|
+
value = await generator.next();
|
|
351
|
+
assert.strictEqual(value.value, "<p>");
|
|
352
|
+
|
|
353
|
+
value = await generator.next();
|
|
354
|
+
assert.strictEqual(value.value, "12");
|
|
355
|
+
|
|
356
|
+
value = await generator.next();
|
|
357
|
+
assert.strictEqual(value.value, "</ul>");
|
|
358
|
+
|
|
359
|
+
value = await generator.next();
|
|
360
|
+
assert.strictEqual(value.done, true);
|
|
361
|
+
});
|
package/test/test.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# test.md>
|