From 1f472e5c2cb52be4fdda06ba47e05f7b95eaeb9a Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Fri, 12 Dec 2025 06:58:24 -0800 Subject: [PATCH] use non-validating HTML formatter --- package.json | 4 +- src/html-formatter.js | 141 ++++++++++++++++++++++++++++++++++++++++++ src/index.tsx | 4 +- 3 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 src/html-formatter.js diff --git a/package.json b/package.json index 58ef4f9..807f976 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "hono": "^4.10.4", - "kleur": "^4.1.5", - "prettier": "^3.7.3" + "hype": "^0.0.3", + "kleur": "^4.1.5" } } \ No newline at end of file diff --git a/src/html-formatter.js b/src/html-formatter.js new file mode 100644 index 0000000..0c4cfb8 --- /dev/null +++ b/src/html-formatter.js @@ -0,0 +1,141 @@ +// HTML Formatter +// https://github.com/uznam8x/html-formatter +// MIT License + +const single = [ + 'br', 'hr', 'img', 'input', 'meta', 'link', + 'col', 'base', 'param', 'track', 'source', 'wbr', + 'command', 'keygen', 'area', 'embed', 'menuitem' +]; + +const closing = function(el) { + el = el.replace(/<([a-zA-Z\-0-9]+)[^>]*>/g, function(match, capture) { + if (single.indexOf(capture) > -1) { + return (match.substring(0, match.length - 1) + ' />').replace(/\/\s\//g, '/'); + } + return match.replace(/[\s]?\/>/g, '>'); + }); + return el; +}; + +const entity = function(el) { + el = el.replace(/(]*>)\n\s+/g, '$1'); + el = el.replace(/\s+<\/textarea>/g, ''); + el = el.replace(/]*>((.|\n)*?)<\/textarea>/g, function(match, capture) { + return match.replace(capture, function(match) { + return match + .replace(//g, '>') + .replace(/\//g, '/') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/\n/g, ' ') + .replace(/%/g, '%') + .replace(/\{/g, '{') + .replace(/\}/g, '}') + .replace(/\s/g, ' '); + }); + }); + return el; +}; + +const minify = function(el) { + return el + .replace(/\n|\t/g, '') + .replace(/[a-z]+="\s*"/ig, '') + .replace(/>\s+<') + .replace(/\s+/g, ' ') + .replace(/\s>/g, '>') + .replace(/>\s/g, '>') + .replace(/\s)/g, function (match) { + convert.comment.push(match); + return ''; + }); + return el; +}; + +const line = function (el) { + convert.line = []; + let i = -1; + el = el.replace(/<[^>]*>/g, function (match) { + convert.line.push(match); + i++; + return '\n[#-# : ' + i + ' : ' + match + ' : #-#]\n'; + }); + el = el.replace(/\n\n/g, '\n'); + return el; +}; + +const tidy = function (el) { + let tab = ''; + convert.line.forEach(function (source, index) { + el = el.replace('[#-# : ' + index + ' : ' + source + ' : #-#]', function (match) { + const prevLine = '[#-# : ' + (index - 1) + ' : ' + convert.line[index - 1] + ' : #-#]'; + tab += '\t'; + let remove = 0; + if (index === 0) remove++; + if (match.indexOf('#-# : ' + (index) + ' : -1) remove++; + if (prevLine.indexOf(' -1) remove++; + if (prevLine.indexOf('', 'g'), function (match) { + const cnt = /%(\d+)%/g.exec(match); + const t = generateTab(cnt[1]); + return source.replace(/\n/g, '\n'+t); + }); + }); + + return el.substring(1, el.length - 1); +}; + +const render = function (el, opt) { + el = closing(el); + el = comment(el); + el = entity(el); + el = minify(el); + el = line(el); + el = tidy(el); + + return el; +}; + +// Export for ES modules +export { closing, entity, minify, render }; +export default render; diff --git a/src/index.tsx b/src/index.tsx index 789cf96..ed6280b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,5 +1,5 @@ import { join } from 'path' -import prettier from 'prettier' +import { render as formatHTML } from './html-formatter' import { type Context, Hono, type Schema, type Env } from 'hono' import { serveStatic } from 'hono/bun' import color from 'kleur' @@ -78,7 +78,7 @@ export class Hype< if (c.res.headers.get('content-type')?.includes('text/html')) { const html = await c.res.text() - const formatted = await prettier.format(html, { parser: 'html' }) + const formatted = formatHTML(html) c.res = new Response(formatted, c.res) } })