ghtml 1.2.1 → 1.2.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 +0 -1
- package/.github/workflows/benchmark.yml +93 -0
- package/.github/workflows/{ci.yml → npm-test.yml} +2 -2
- package/README.md +2 -4
- package/bench/index.js +101 -0
- package/package.json +3 -2
- package/src/html.js +7 -7
- package/test/index.js +5 -2
package/.eslintrc.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
name: Benchmark Comparison
|
|
2
|
+
|
|
3
|
+
permissions:
|
|
4
|
+
contents: read
|
|
5
|
+
pull-requests: write
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
pull_request:
|
|
9
|
+
branches:
|
|
10
|
+
- main
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
benchmark-comparison:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
# Checkout PR code
|
|
17
|
+
- name: Checkout PR code
|
|
18
|
+
uses: actions/checkout@v4
|
|
19
|
+
|
|
20
|
+
# Setup Node.js
|
|
21
|
+
- name: Setup Node.js
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: "20"
|
|
25
|
+
|
|
26
|
+
# Install dependencies
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm install
|
|
29
|
+
|
|
30
|
+
# Run benchmark on PR code
|
|
31
|
+
- name: Run benchmark on PR code
|
|
32
|
+
run: node bench/index.js
|
|
33
|
+
id: benchmark_pr
|
|
34
|
+
|
|
35
|
+
# Save PR benchmark results
|
|
36
|
+
- name: Save PR benchmark results
|
|
37
|
+
run: echo "PR_RESULTS=$(cat bench/results.json)" >> "$GITHUB_ENV"
|
|
38
|
+
|
|
39
|
+
# Prepare for main branch benchmark
|
|
40
|
+
- name: Backup PR src directory
|
|
41
|
+
run: mkdir _pr_branch && mv src _pr_branch/src
|
|
42
|
+
|
|
43
|
+
# Checkout main branch src directory
|
|
44
|
+
- name: Checkout main branch src directory
|
|
45
|
+
uses: actions/checkout@v4
|
|
46
|
+
with:
|
|
47
|
+
ref: "main"
|
|
48
|
+
path: "_main_branch"
|
|
49
|
+
|
|
50
|
+
# Replace PR src with main src
|
|
51
|
+
- name: Replace PR src with main src
|
|
52
|
+
run: |
|
|
53
|
+
rm -rf src
|
|
54
|
+
cp -R _main_branch/src src
|
|
55
|
+
|
|
56
|
+
# Run benchmark on main branch src with PR's benchmark tooling
|
|
57
|
+
- name: Run benchmark on main branch src
|
|
58
|
+
run: node bench/index.js
|
|
59
|
+
id: benchmark_main
|
|
60
|
+
|
|
61
|
+
# Save main benchmark results
|
|
62
|
+
- name: Save main benchmark results
|
|
63
|
+
run: echo "MAIN_RESULTS=$(cat bench/results.json)" >> "$GITHUB_ENV"
|
|
64
|
+
|
|
65
|
+
# Comment PR with benchmark results comparison
|
|
66
|
+
- name: Comment PR with benchmark results comparison
|
|
67
|
+
uses: actions/github-script@v7
|
|
68
|
+
with:
|
|
69
|
+
github-token: ${{secrets.GITHUB_TOKEN}}
|
|
70
|
+
script: |
|
|
71
|
+
const prResults = JSON.parse(Buffer.from(process.env.PR_RESULTS, 'base64').toString('utf8'));
|
|
72
|
+
const mainResults = JSON.parse(Buffer.from(process.env.MAIN_RESULTS, 'base64').toString('utf8'));
|
|
73
|
+
|
|
74
|
+
const commentBody = `
|
|
75
|
+
Benchmark Results Comparison (${context.sha}):
|
|
76
|
+
|
|
77
|
+
**PR Branch:**
|
|
78
|
+
\`\`\`json
|
|
79
|
+
${JSON.stringify(prResults, null, 2)}
|
|
80
|
+
\`\`\`
|
|
81
|
+
|
|
82
|
+
**Main Branch:**
|
|
83
|
+
\`\`\`json
|
|
84
|
+
${JSON.stringify(mainResults, null, 2)}
|
|
85
|
+
\`\`\`
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
github.rest.issues.createComment({
|
|
89
|
+
issue_number: context.issue.number,
|
|
90
|
+
owner: context.repo.owner,
|
|
91
|
+
repo: context.repo.repo,
|
|
92
|
+
body: commentBody
|
|
93
|
+
});
|
package/README.md
CHANGED
package/bench/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable no-unused-expressions */
|
|
2
|
+
import { html } from "../src/html.js";
|
|
3
|
+
import { Bench } from "tinybench";
|
|
4
|
+
import { writeFileSync } from "node:fs";
|
|
5
|
+
import { Buffer } from "node:buffer";
|
|
6
|
+
|
|
7
|
+
const bench = new Bench({ time: 500 });
|
|
8
|
+
|
|
9
|
+
bench.add("Simple formatting", () => {
|
|
10
|
+
html`<div>Hello, world!</div>`;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const username = "User";
|
|
14
|
+
bench.add("Using string variable", () => {
|
|
15
|
+
html`<p>${username}</p>`;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const value = null;
|
|
19
|
+
const undef = undefined;
|
|
20
|
+
bench.add("Handling null and undefined", () => {
|
|
21
|
+
html`<p>${value} and ${undef}</p>`;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const user = { id: 1, name: "John Doe" };
|
|
25
|
+
bench.add("Multiple types of expressions", () => {
|
|
26
|
+
html`
|
|
27
|
+
${undefined}
|
|
28
|
+
<div>User: <span>${user.name}</span></div>
|
|
29
|
+
<div>Id: <span>${user.id}</span></div>
|
|
30
|
+
${null}
|
|
31
|
+
`;
|
|
32
|
+
});
|
|
33
|
+
|
|
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
|
+
const largeString = Array.from({ length: 1000 }).join("Lorem ipsum ");
|
|
62
|
+
bench.add("Large strings", () => {
|
|
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
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const scriptContent =
|
|
73
|
+
"<script>console.log('This should not execute');</script>";
|
|
74
|
+
bench.add("Escape HTML", () => {
|
|
75
|
+
html`<div>${scriptContent} ${scriptContent}</div>`;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Render raw HTML
|
|
79
|
+
const rawHTML = "<em>Italic</em> and <strong>bold</strong>";
|
|
80
|
+
const markup = "<mark>Highlighted</mark>";
|
|
81
|
+
bench.add("Unescaped expressions", () => {
|
|
82
|
+
html`
|
|
83
|
+
<div>!${rawHTML}</div>
|
|
84
|
+
<div>!${rawHTML}</div>
|
|
85
|
+
<div>!${markup}</div>
|
|
86
|
+
<div>!${markup}</div>
|
|
87
|
+
<div>!${rawHTML}</div>
|
|
88
|
+
<div>!${rawHTML}</div>
|
|
89
|
+
`;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await bench.warmup();
|
|
93
|
+
await bench.run();
|
|
94
|
+
|
|
95
|
+
const table = bench.table();
|
|
96
|
+
console.table(table);
|
|
97
|
+
|
|
98
|
+
writeFileSync(
|
|
99
|
+
"bench/results.json",
|
|
100
|
+
Buffer.from(JSON.stringify(table), "utf8").toString("base64"),
|
|
101
|
+
);
|
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.
|
|
6
|
+
"version": "1.2.2",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.js",
|
|
9
9
|
"exports": {
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@fastify/pre-commit": "^2.1.0",
|
|
23
|
-
"grules": "^0.
|
|
23
|
+
"grules": "^0.15.0",
|
|
24
|
+
"tinybench": "^2.6.0"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
package/src/html.js
CHANGED
|
@@ -20,12 +20,12 @@ const escapeFunction = (key) => {
|
|
|
20
20
|
* @param {...any} expressions Expressions to interpolate.
|
|
21
21
|
* @returns {string} The HTML string.
|
|
22
22
|
*/
|
|
23
|
-
const html = (literals, ...expressions) => {
|
|
23
|
+
const html = ({ raw: literals }, ...expressions) => {
|
|
24
24
|
let accumulator = "";
|
|
25
25
|
let index = 0;
|
|
26
26
|
|
|
27
27
|
for (; index < expressions.length; ++index) {
|
|
28
|
-
let literal = literals
|
|
28
|
+
let literal = literals[index];
|
|
29
29
|
let expression =
|
|
30
30
|
typeof expressions[index] === "string"
|
|
31
31
|
? expressions[index]
|
|
@@ -44,7 +44,7 @@ const html = (literals, ...expressions) => {
|
|
|
44
44
|
accumulator += literal + expression;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
return (accumulator += literals
|
|
47
|
+
return (accumulator += literals[index]);
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -52,11 +52,11 @@ const html = (literals, ...expressions) => {
|
|
|
52
52
|
* @param {...any} expressions Expressions to interpolate.
|
|
53
53
|
* @yields {string} The HTML strings.
|
|
54
54
|
*/
|
|
55
|
-
const htmlGenerator = function* (literals, ...expressions) {
|
|
55
|
+
const htmlGenerator = function* ({ raw: literals }, ...expressions) {
|
|
56
56
|
let index = 0;
|
|
57
57
|
|
|
58
58
|
for (; index < expressions.length; ++index) {
|
|
59
|
-
let literal = literals
|
|
59
|
+
let literal = literals[index];
|
|
60
60
|
let expression;
|
|
61
61
|
|
|
62
62
|
if (typeof expressions[index] === "string") {
|
|
@@ -114,8 +114,8 @@ const htmlGenerator = function* (literals, ...expressions) {
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
if (literals
|
|
118
|
-
yield literals
|
|
117
|
+
if (literals[index].length) {
|
|
118
|
+
yield literals[index];
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
121
|
|
package/test/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// eslint-disable-next-line n/no-missing-import
|
|
2
1
|
import test from "node:test";
|
|
3
2
|
import assert from "node:assert";
|
|
4
|
-
import { html, htmlGenerator } from "../src/
|
|
3
|
+
import { html, htmlGenerator } from "../src/html.js";
|
|
5
4
|
|
|
6
5
|
const conditionTrue = true;
|
|
7
6
|
const conditionFalse = false;
|
|
@@ -33,6 +32,10 @@ test("renders normal input", () => {
|
|
|
33
32
|
assert.strictEqual(html`Hey, ${username}!`, `Hey, ${username}!`);
|
|
34
33
|
});
|
|
35
34
|
|
|
35
|
+
test("renders undefined and null as empty string", () => {
|
|
36
|
+
assert.strictEqual(html`<p>${null}${undefined}</p>`, "<p></p>");
|
|
37
|
+
});
|
|
38
|
+
|
|
36
39
|
test("renders safe content", () => {
|
|
37
40
|
assert.strictEqual(
|
|
38
41
|
html`<p>${descriptionSafe}</p>`,
|