diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 001354b..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "root": true, - "extends": ["plugin:grules/all"], - "rules": { - "no-await-in-loop": "off", - "require-unicode-regexp": "off" - } -} diff --git a/LICENSE b/LICENSE index f55fa3b..fdc9ce7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Gürgün Dayıoğlu +Copyright (c) 2023 Gürgün Dayıoğlu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bench/index.js b/bench/index.js index d944dc2..321f573 100644 --- a/bench/index.js +++ b/bench/index.js @@ -1,10 +1,12 @@ /* eslint-disable no-unused-vars */ + import { html } from "../src/index.js"; import { Bench } from "tinybench"; import { writeFileSync } from "node:fs"; import { Buffer } from "node:buffer"; let result = ""; + const bench = new Bench({ time: 500 }); bench.add("simple HTML formatting", () => { @@ -112,7 +114,7 @@ await bench.warmup(); await bench.run(); const table = bench.table(); -console.table(table); +globalThis.console.table(table); writeFileSync( "bench/results.json", diff --git a/bin/example/.eslintrc.json b/bin/example/.eslintrc.json deleted file mode 100644 index ebcdcca..0000000 --- a/bin/example/.eslintrc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rules": { - "require-await": "off", - "n/no-missing-import": "off" - } -} diff --git a/bin/example/assets/script.js b/bin/example/assets/script.js index 7e64951..cdf6b44 100644 --- a/bin/example/assets/script.js +++ b/bin/example/assets/script.js @@ -1 +1 @@ -console.warn("Hello World!"); +globalThis.console.warn("Hello World!"); diff --git a/bin/example/package.json b/bin/example/package.json index 3f9d280..bcd88c3 100644 --- a/bin/example/package.json +++ b/bin/example/package.json @@ -6,8 +6,8 @@ "build": "ghtml --roots=assets/ --refs=routes/ --prefix=/p/assets/" }, "dependencies": { - "@fastify/static": "^7.0.1", - "fastify": "^4.26.1", + "@fastify/static": "^8.0.1", + "fastify": "^5.0.0", "ghtml": "file:../../" } } diff --git a/bin/example/routes/index.js b/bin/example/routes/index.js index 0919685..041ae89 100644 --- a/bin/example/routes/index.js +++ b/bin/example/routes/index.js @@ -1,3 +1,5 @@ +/* eslint-disable n/no-missing-import, require-await */ + import { html } from "ghtml"; export default async (fastify) => { diff --git a/bin/example/server.js b/bin/example/server.js index 681b000..0f2e03d 100644 --- a/bin/example/server.js +++ b/bin/example/server.js @@ -1,10 +1,12 @@ +/* eslint-disable n/no-missing-import */ + import Fastify from "fastify"; const fastify = Fastify(); // Plugins await fastify.register(import("@fastify/static"), { - root: new URL("assets/", import.meta.url).pathname, + root: new globalThis.URL("assets/", import.meta.url).pathname, prefix: "/p/assets/", wildcard: false, index: false, @@ -20,5 +22,5 @@ fastify.listen({ port: 5050 }, (err, address) => { throw err; } - console.warn(`Server listening at ${address}`); + globalThis.console.warn(`Server listening at ${address}`); }); diff --git a/bin/src/index.js b/bin/src/index.js index ff651c8..bdebf92 100755 --- a/bin/src/index.js +++ b/bin/src/index.js @@ -18,7 +18,7 @@ const parseArguments = (args) => { } if (!roots || !refs) { - console.error( + globalThis.console.error( 'Usage: npx ghtml --roots="base/path/to/scan/assets/1/,base/path/to/scan/assets/2/" --refs="views/path/to/append/hashes/1/,views/path/to/append/hashes/2/" [--prefix="/optional/prefix/"]', ); process.exit(1); @@ -31,10 +31,10 @@ const main = async () => { const { roots, refs, prefix } = parseArguments(process.argv.slice(2)); try { - console.warn(`Generating hashes and updating file paths...`); - console.warn(`Scanning files in: ${roots}`); - console.warn(`Updating files in: ${refs}`); - console.warn(`Using prefix: ${prefix}`); + globalThis.console.warn(`Generating hashes and updating file paths...`); + globalThis.console.warn(`Scanning files in: ${roots}`); + globalThis.console.warn(`Updating files in: ${refs}`); + globalThis.console.warn(`Using prefix: ${prefix}`); await generateHashesAndReplace({ roots, @@ -42,9 +42,11 @@ const main = async () => { prefix, }); - console.warn("Hash generation and file updates completed successfully."); + globalThis.console.warn( + "Hash generation and file updates completed successfully.", + ); } catch (error) { - console.error(`Error occurred: ${error.message}`); + globalThis.console.error(`Error occurred: ${error.message}`); process.exit(1); } }; diff --git a/bin/src/utils.js b/bin/src/utils.js index 23e45c0..c538e3d 100644 --- a/bin/src/utils.js +++ b/bin/src/utils.js @@ -1,3 +1,5 @@ +/* eslint-disable no-await-in-loop */ + import { Glob } from "glob"; import { createHash } from "node:crypto"; import { readFile, writeFile } from "node:fs/promises"; @@ -44,12 +46,12 @@ const updateFilePathsWithHashes = async ( for (const [path, hash] of fileHashes) { const fullPath = prefix + path; const escapedPath = fullPath.replace( - /[$()*+.?[\\\]^{|}]/g, + /[$()*+.?[\\\]^{|}]/gu, String.raw`\$&`, ); const regex = new RegExp( `(?${escapedPath})(\\?(?[^#"'\`]*))?`, - "g", + "gu", ); content = content.replace( diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..1f1b2d3 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,3 @@ +import grules from "grules"; + +export default [...grules]; diff --git a/package.json b/package.json index 7227fff..0ae0a36 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,9 @@ "devDependencies": { "@fastify/pre-commit": "^2.1.0", "c8": "^10.1.2", - "grules": "^0.23.3", + "grules": "^0.24.2", "tinybench": "^2.9.0", - "typescript": ">=5.5.4" + "typescript": ">=5.6.2" }, "repository": { "type": "git", diff --git a/src/html.js b/src/html.js index 8c4c383..5f7c37f 100644 --- a/src/html.js +++ b/src/html.js @@ -1,3 +1,5 @@ +/* eslint-disable no-await-in-loop, require-unicode-regexp */ + const escapeRegExp = /["&'<=>]/g; const escapeFunction = (string) => { @@ -35,29 +37,23 @@ const escapeFunction = (string) => { }; /** - * The `html` function is designed to tag template literals and automatically escape their expressions. - * @param {TemplateStringsArray} literals Tagged template literals. - * @param {...any} expressions Expressions to interpolate. - * @returns {string} The processed HTML string. + * @param {TemplateStringsArray} literals literals + * @param {...any} expressions expressions + * @returns {string} string */ export const html = (literals, ...expressions) => { let accumulator = ""; for (let i = 0; i !== expressions.length; ++i) { let literal = literals.raw[i]; - let string = - typeof expressions[i] === "string" - ? expressions[i] - : expressions[i] == null - ? "" - : Array.isArray(expressions[i]) - ? expressions[i].join("") - : `${expressions[i]}`; + let string = Array.isArray(expressions[i]) + ? expressions[i].join("") + : String(expressions[i] ?? ""); if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); } else { - string &&= escapeFunction(string); + string = escapeFunction(string); } accumulator += literal + string; @@ -67,11 +63,10 @@ export const html = (literals, ...expressions) => { }; /** - * The `htmlGenerator` function acts as the generator version of the `html` function. - * @param {TemplateStringsArray} literals Tagged template literals. - * @param {...any} expressions Expressions to interpolate. - * @yields Processed HTML strings. - * @returns {Generator} The HTML generator. + * @param {TemplateStringsArray} literals literals + * @param {...any} expressions expressions + * @yields {string} string + * @returns {Generator} Generator */ export const htmlGenerator = function* (literals, ...expressions) { for (let i = 0; i !== expressions.length; ++i) { @@ -81,7 +76,7 @@ export const htmlGenerator = function* (literals, ...expressions) { if (typeof expression === "string") { string = expression; - } else if (expression == null) { + } else if (expression === undefined || expression === null) { string = ""; } else { if (expression[Symbol.iterator]) { @@ -100,56 +95,56 @@ export const htmlGenerator = function* (literals, ...expressions) { if (typeof expression === "string") { string = expression; } else { - if (expression == null) { + if (expression === undefined || expression === null) { continue; } if (expression[Symbol.iterator]) { for (expression of expression) { - if (typeof expression === "string") { - string = expression; - } else { - if (expression == null) { - continue; - } - - string = `${expression}`; + if (expression === undefined || expression === null) { + continue; } - if (string) { - if (!isRaw) { - string = escapeFunction(string); - } + string = String(expression); + + if (!string) { + continue; + } - yield string; + if (!isRaw) { + string = escapeFunction(string); } + + yield string; } continue; } - string = `${expression}`; + string = String(expression); } - if (string) { - if (!isRaw) { - string = escapeFunction(string); - } + if (!string) { + continue; + } - yield string; + if (!isRaw) { + string = escapeFunction(string); } + + yield string; } continue; } - string = `${expression}`; + string = String(expression); } if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); } else { - string &&= escapeFunction(string); + string = escapeFunction(string); } if (literal || string) { @@ -163,11 +158,10 @@ export const htmlGenerator = function* (literals, ...expressions) { }; /** - * This version of HTML generator should be preferred for asynchronous and streaming use cases. - * @param {TemplateStringsArray} literals Tagged template literals. - * @param {...any} expressions Expressions to interpolate. - * @yields Processed HTML strings. - * @returns {AsyncGenerator} The HTML generator. + * @param {TemplateStringsArray} literals literals + * @param {...any} expressions expressions + * @yields {string} string + * @returns {AsyncGenerator} AsyncGenerator */ export const htmlAsyncGenerator = async function* (literals, ...expressions) { for (let i = 0; i !== expressions.length; ++i) { @@ -177,7 +171,7 @@ export const htmlAsyncGenerator = async function* (literals, ...expressions) { if (typeof expression === "string") { string = expression; - } else if (expression == null) { + } else if (expression === undefined || expression === null) { string = ""; } else { if (expression[Symbol.iterator] || expression[Symbol.asyncIterator]) { @@ -196,7 +190,7 @@ export const htmlAsyncGenerator = async function* (literals, ...expressions) { if (typeof expression === "string") { string = expression; } else { - if (expression == null) { + if (expression === undefined || expression === null) { continue; } @@ -205,50 +199,50 @@ export const htmlAsyncGenerator = async function* (literals, ...expressions) { expression[Symbol.asyncIterator] ) { for await (expression of expression) { - if (typeof expression === "string") { - string = expression; - } else { - if (expression == null) { - continue; - } - - string = `${expression}`; + if (expression === undefined || expression === null) { + continue; } - if (string) { - if (!isRaw) { - string = escapeFunction(string); - } + string = String(expression); - yield string; + if (!string) { + continue; } + + if (!isRaw) { + string = escapeFunction(string); + } + + yield string; } continue; } - string = `${expression}`; + string = String(expression); } - if (string) { - if (!isRaw) { - string = escapeFunction(string); - } + if (!string) { + continue; + } - yield string; + if (!isRaw) { + string = escapeFunction(string); } + + yield string; } continue; } - string = `${expression}`; + string = String(expression); } if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); } else { - string &&= escapeFunction(string); + string = escapeFunction(string); } if (literal || string) { diff --git a/src/includeFile.js b/src/includeFile.js index 1f69cce..1a27b80 100644 --- a/src/includeFile.js +++ b/src/includeFile.js @@ -3,8 +3,8 @@ import { readFileSync } from "node:fs"; const cache = new Map(); /** - * @param {string} path The path to the file to render. - * @returns {string} The content of the file. + * @param {string} path path + * @returns {string} string */ export const includeFile = (path) => { let file = cache.get(path); diff --git a/test/index.js b/test/index.js index 7aa2c4d..8b467dd 100644 --- a/test/index.js +++ b/test/index.js @@ -22,6 +22,11 @@ const generatorExample = function* () { yield "

"; }; +const generatorExample2 = function* () { + yield ["", "

"]; + yield ""; +}; + const generatorPromiseExample = function* () { yield [ new Promise((resolve) => { @@ -33,6 +38,17 @@ const generatorPromiseExample = function* () { yield; }; +const generatorPromiseExample2 = function* () { + yield [ + new Promise((resolve) => { + resolve("

"); + }), + null, + "", + ]; + yield ""; +}; + test("renders empty input", () => { assert.strictEqual(html({ raw: [""] }), ""); }); @@ -204,6 +220,18 @@ test("htmlGenerator works with other generators (raw)", () => { assert.strictEqual(generator.next().done, true); }); +test("htmlGenerator works with other generators (raw) /2", () => { + const generator = htmlGenerator`

!${generatorExample2()}
`; + let accumulator = ""; + + for (const value of generator) { + accumulator += value; + } + + assert.strictEqual(accumulator, "

"); + assert.strictEqual(generator.next().done, true); +}); + test("htmlGenerator works with other generators (escaped)", () => { const generator = htmlGenerator`
${generatorExample()}
`; let accumulator = ""; @@ -291,6 +319,17 @@ test("htmlAsyncGenerator works with other generators (raw)", async () => { ); }); +test("htmlAsyncGenerator works with other generators (raw) /2", async () => { + const generator = htmlAsyncGenerator`
!${generatorPromiseExample2()}
`; + let accumulator = ""; + + for await (const value of generator) { + accumulator += value; + } + + assert.strictEqual(accumulator, "

"); +}); + test("htmlAsyncGenerator works with other generators (escaped)", async () => { const generator = htmlAsyncGenerator`
${generatorExample()}
`; let accumulator = "";