ghtml 2.0.0 → 2.0.2
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 +2 -1
- package/README.md +5 -5
- package/package.json +2 -2
- package/src/html.js +27 -21
package/.eslintrc.json
CHANGED
package/README.md
CHANGED
|
@@ -20,11 +20,11 @@ The `htmlGenerator` function acts as the generator version of the `html` functio
|
|
|
20
20
|
|
|
21
21
|
**Note:**
|
|
22
22
|
|
|
23
|
-
Keep in mind that, in Node.js, all else being equal, streaming a response using synchronous generators
|
|
23
|
+
Keep in mind that, in Node.js, all else being equal, streaming a response using synchronous generators is **always** 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 does indeed improve TTFB.
|
|
24
24
|
|
|
25
25
|
### `htmlAsyncGenerator`
|
|
26
26
|
|
|
27
|
-
This version of HTML generator should be preferred for asynchronous and streaming use cases. The output
|
|
27
|
+
This version of HTML generator should be preferred for asynchronous and streaming use cases. The output is generated as the promise expressions resolve or stream expressions send data.
|
|
28
28
|
|
|
29
29
|
**Minor Note:**
|
|
30
30
|
|
|
@@ -42,7 +42,7 @@ Available in Node.js, the `includeFile` function is a wrapper around `readFileSy
|
|
|
42
42
|
import { html } from "ghtml";
|
|
43
43
|
|
|
44
44
|
const username = '<img src="https://example.com/pwned.png">';
|
|
45
|
-
const greeting = html`<h1>Hello, ${username}
|
|
45
|
+
const greeting = html`<h1>Hello, ${username}</h1>`;
|
|
46
46
|
|
|
47
47
|
console.log(greeting);
|
|
48
48
|
// Output: <h1>Hello, <img src="https://example.com/pwned.png"></h1>
|
|
@@ -58,7 +58,7 @@ console.log(container);
|
|
|
58
58
|
// Output: <div><img src="https://example.com/safe.png"></div>
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
When nesting multiple `html` expressions,
|
|
61
|
+
When nesting multiple `html` expressions, make sure to use `!` as the inner calls do their own escaping:
|
|
62
62
|
|
|
63
63
|
```js
|
|
64
64
|
const someCondition = Math.random() >= 0.5;
|
|
@@ -171,4 +171,4 @@ console.log(logo);
|
|
|
171
171
|
|
|
172
172
|
## Security
|
|
173
173
|
|
|
174
|
-
Like [similar](https://handlebarsjs.com/guide/#html-escaping) [tools](https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities), `ghtml`
|
|
174
|
+
Like [similar](https://handlebarsjs.com/guide/#html-escaping) [tools](https://github.com/mde/ejs/blob/main/SECURITY.md#out-of-scope-vulnerabilities), `ghtml` does not prevent all kinds of XSS attacks. It is the responsibility of consumers to sanitize user inputs. Some inherently insecure uses include dynamically generating JavaScript, failing to quote HTML attribute values (especially when they contain expressions), and using unsanitized user-provided URLs.
|
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": "2.0.
|
|
6
|
+
"version": "2.0.2",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@fastify/pre-commit": "^2.1.0",
|
|
24
|
-
"c8": "^
|
|
24
|
+
"c8": "^10.0.0",
|
|
25
25
|
"grules": "^0.17.2",
|
|
26
26
|
"tinybench": "^2.8.0"
|
|
27
27
|
},
|
package/src/html.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
const arrayIsArray = Array.isArray;
|
|
2
|
+
|
|
3
|
+
const symbolIterator = Symbol.iterator;
|
|
4
|
+
|
|
5
|
+
const symbolAsyncIterator = Symbol.asyncIterator;
|
|
6
|
+
|
|
1
7
|
const escapeDictionary = {
|
|
2
8
|
'"': """,
|
|
3
9
|
"&": "&",
|
|
@@ -7,10 +13,7 @@ const escapeDictionary = {
|
|
|
7
13
|
"`": "`",
|
|
8
14
|
};
|
|
9
15
|
|
|
10
|
-
const escapeRegExp = new RegExp(
|
|
11
|
-
`[${Object.keys(escapeDictionary).join("")}]`,
|
|
12
|
-
"u",
|
|
13
|
-
);
|
|
16
|
+
const escapeRegExp = new RegExp(`[${Object.keys(escapeDictionary).join("")}]`);
|
|
14
17
|
|
|
15
18
|
const escapeFunction = (string) => {
|
|
16
19
|
const stringLength = string.length;
|
|
@@ -30,8 +33,6 @@ const escapeFunction = (string) => {
|
|
|
30
33
|
return escaped + string.slice(start, end);
|
|
31
34
|
};
|
|
32
35
|
|
|
33
|
-
const arrayIsArray = Array.isArray;
|
|
34
|
-
|
|
35
36
|
/**
|
|
36
37
|
* @param {{ raw: string[] }} literals Tagged template literals.
|
|
37
38
|
* @param {...any} expressions Expressions to interpolate.
|
|
@@ -85,7 +86,7 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
85
86
|
} else if (expression === undefined || expression === null) {
|
|
86
87
|
string = "";
|
|
87
88
|
} else {
|
|
88
|
-
if (expression[
|
|
89
|
+
if (expression[symbolIterator]) {
|
|
89
90
|
const isRaw =
|
|
90
91
|
literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
|
|
91
92
|
|
|
@@ -105,13 +106,17 @@ const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
|
105
106
|
continue;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
if (expression[
|
|
109
|
+
if (expression[symbolIterator]) {
|
|
109
110
|
for (expression of expression) {
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
}
|
|
111
|
+
if (typeof expression === "string") {
|
|
112
|
+
string = expression;
|
|
113
|
+
} else {
|
|
114
|
+
if (expression === undefined || expression === null) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
string = `${expression}`;
|
|
119
|
+
}
|
|
115
120
|
|
|
116
121
|
if (string) {
|
|
117
122
|
if (!isRaw && escapeRegExp.test(string)) {
|
|
@@ -178,7 +183,7 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
178
183
|
} else if (expression === undefined || expression === null) {
|
|
179
184
|
string = "";
|
|
180
185
|
} else {
|
|
181
|
-
if (expression[
|
|
186
|
+
if (expression[symbolIterator] || expression[symbolAsyncIterator]) {
|
|
182
187
|
const isRaw =
|
|
183
188
|
literal !== "" && literal.charCodeAt(literal.length - 1) === 33;
|
|
184
189
|
|
|
@@ -198,16 +203,17 @@ const htmlAsyncGenerator = async function* ({ raw: literals }, ...expressions) {
|
|
|
198
203
|
continue;
|
|
199
204
|
}
|
|
200
205
|
|
|
201
|
-
if (
|
|
202
|
-
expression[Symbol.iterator] ||
|
|
203
|
-
expression[Symbol.asyncIterator]
|
|
204
|
-
) {
|
|
206
|
+
if (expression[symbolIterator] || expression[symbolAsyncIterator]) {
|
|
205
207
|
for await (expression of expression) {
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
}
|
|
208
|
+
if (typeof expression === "string") {
|
|
209
|
+
string = expression;
|
|
210
|
+
} else {
|
|
211
|
+
if (expression === undefined || expression === null) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
209
214
|
|
|
210
|
-
|
|
215
|
+
string = `${expression}`;
|
|
216
|
+
}
|
|
211
217
|
|
|
212
218
|
if (string) {
|
|
213
219
|
if (!isRaw && escapeRegExp.test(string)) {
|