ITADN

potentially improve HTML escaping performance

#15Closedgunjam 创建于 2024-06-05
G
gunjamcommented
Hi, I've been comparing HTML escaping performance for a module I'm building and between the implementations within [nunjucks](https://github.com/mozilla/nunjucks/blob/master/nunjucks/src/lib.js#L141-L143), [html-escaper](https://github.com/WebReflection/html-escaper), ghtml and [kitajs](https://github.com/kitajs/html/blob/master/packages/html/index.js#L73-L121) it would appear kita's is the fastest: ``` ┌─────────┬────────────────┬─────────┬────────────────────┬──────────┬─────────┐ │ (index) │ Task Name │ ops/sec │ Average Time (ns) │ Margin │ Samples │ ├─────────┼────────────────┼─────────┼────────────────────┼──────────┼─────────┤ │ 0 │ 'nunjucks' │ '1,986' │ 503448.95118268783 │ '±1.19%' │ 1987 │ │ 1 │ 'kitajs' │ '3,166' │ 315781.80454688927 │ '±0.42%' │ 3167 │ │ 2 │ 'ghtml' │ '1,906' │ 524471.7005768198 │ '±0.93%' │ 1907 │ │ 3 │ 'html-escaper' │ '2,077' │ 481274.3864292391 │ '±0.86%' │ 2078 │ └─────────┴────────────────┴─────────┴────────────────────┴──────────┴─────────┘ ``` Part of the reason kita's implementation is faster is because it does not escape `>`, which apparently isn't needed! I tested the following markup with the [W3 validator](https://validator.w3.org/nu/#textarea) and it passed, so I guess it's true: ```html <!DOCTYPE html> <html lang="en-GB"> <head> <title>Title</title> </head> <body> <p class="hello >"> 4 > 2 </p> </body> </html> ``` Hope this is useful! <details> <summary>Benchmark</summary> ```javascript import { escape as htmlEscaper } from "html-escaper"; import { Bench } from "tinybench"; /** nunjucks escape * @see: https://github.com/mozilla/nunjucks/blob/master/nunjucks/src/lib.js#L141-L143 */ var escapeMap = { "&": "&amp;", '"': "&quot;", "'": "&#39;", "<": "&lt;", ">": "&gt;", "\\": "&#92;", }; var escapeRegex = /[&"'<>\\]/g; function lookupEscape(ch) { return escapeMap[ch]; } function nunjucksEscape(val) { return val.replace(escapeRegex, lookupEscape); } /** kitajs escape * @see: https://github.com/kitajs/html/blob/master/packages/html/index.js#L73-L121 */ const ESCAPED_REGEX = /[<"'&]/; const kitaHtml = (value) => { if (typeof value !== "string") { value = value.toString(); } // This is a optimization to avoid the whole conversion process when the // string does not contain any uppercase characters. if (!ESCAPED_REGEX.test(value)) { return value; } const length = value.length; let escaped = "", start = 0, end = 0; // Escapes double quotes to be used inside attributes // Faster than using regex // https://jsperf.app/kakihu for (; end < length; end++) { // https://wonko.com/post/html-escaping switch (value[end]) { case "&": escaped += value.slice(start, end) + "&amp;"; start = end + 1; continue; // We don't need to escape > because it is only used to close tags. // https://stackoverflow.com/a/9189067 case "<": escaped += value.slice(start, end) + "&lt;"; start = end + 1; continue; case '"': escaped += value.slice(start, end) + "&#34;"; start = end + 1; continue; case "'": escaped += value.slice(start, end) + "&#39;"; start = end + 1; continue; } } // Appends the remaining string. escaped += value.slice(start, end); return escaped; }; /** ghtml escape * @see: https://github.com/kitajs/html/blob/master/packages/html/index.js#L73-L121 */ const escapeDictionary = { '"': "&quot;", "'": "&apos;", "&": "&amp;", "<": "&lt;", ">": "&gt;", }; const escapeRegExp = new RegExp( `[${Object.keys(escapeDictionary).join("")}]`, "gu", ); const escapeFunction = (key) => { return escapeDictionary[key]; }; function ghtmlEscape(val) { return val.replace(escapeRegExp, escapeFunction); } // Get a big chunk of HTML from GOV.UK const res = await fetch("https://gov.uk"); const html = await res.text(); const bench = new Bench({ time: 1_000 }); bench .add("nunjucks", () => nunjucksEscape(html)) .add("kitajs", () => kitaHtml(html)) .add("ghtml", () => ghtmlEscape(html)) .add("html-escaper", () => htmlEscaper(html)); await bench.warmup(); await bench.run(); console.table(bench.table()); ``` </details>
关闭于 2024-06-09 5 条评论