ghtml 1.2.1 → 1.2.3

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 CHANGED
@@ -1,5 +1,4 @@
1
1
  {
2
2
  "root": true,
3
- "env": { "node": true },
4
3
  "extends": ["plugin:grules/all"]
5
4
  }
@@ -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
+ });
@@ -1,4 +1,4 @@
1
- name: CI
1
+ name: npm-test
2
2
 
3
3
  on:
4
4
  push:
@@ -23,4 +23,4 @@ jobs:
23
23
  with:
24
24
  node-version: ${{ matrix.node-version }}
25
25
  - run: npm install
26
- - run: npm run test
26
+ - run: npm test
package/README.md CHANGED
@@ -61,10 +61,8 @@ const htmlString = html`
61
61
  ? html`
62
62
  <p>Data:</p>
63
63
  <ul>
64
- ${Object.values(data).map(
65
- ([key, val]) => `
66
- ${key}: ${val}
67
- `,
64
+ !${Object.values(data).map(
65
+ ([key, val]) => html`<li>${key}: ${val}</li>`,
68
66
  )}
69
67
  </ul>
70
68
  `
package/bench/index.js ADDED
@@ -0,0 +1,101 @@
1
+ /* eslint-disable no-unused-expressions */
2
+ import { html } from "../src/index.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.1",
6
+ "version": "1.2.3",
7
7
  "type": "module",
8
8
  "main": "./src/index.js",
9
9
  "exports": {
@@ -14,13 +14,15 @@
14
14
  "node": ">=18"
15
15
  },
16
16
  "scripts": {
17
- "test": "npm run lint && node --experimental-test-coverage test/index.js",
17
+ "test": "npm run lint && c8 --100 node --test test/**",
18
18
  "lint": "eslint . && prettier --check .",
19
19
  "lint:fix": "eslint --fix . && prettier --write ."
20
20
  },
21
21
  "devDependencies": {
22
22
  "@fastify/pre-commit": "^2.1.0",
23
- "grules": "^0.14.1"
23
+ "c8": "^9.1.0",
24
+ "grules": "^0.15.0",
25
+ "tinybench": "^2.6.0"
24
26
  },
25
27
  "repository": {
26
28
  "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
- for (; index < expressions.length; ++index) {
28
- let literal = literals.raw[index];
27
+ for (; index !== expressions.length; ++index) {
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.raw[index]);
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
- for (; index < expressions.length; ++index) {
59
- let literal = literals.raw[index];
58
+ for (; index !== expressions.length; ++index) {
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.raw[index].length) {
118
- yield literals.raw[index];
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
1
+ import { html, htmlGenerator } from "../src/index.js";
2
2
  import test from "node:test";
3
3
  import assert from "node:assert";
4
- import { html, htmlGenerator } from "../src/index.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>`,