potentially improve HTML escaping performance
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 = {
"&": "&",
'"': """,
"'": "'",
"<": "<",
">": ">",
"\\": "\",
};
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) + "&";
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) + "<";
start = end + 1;
continue;
case '"':
escaped += value.slice(start, end) + """;
start = end + 1;
continue;
case "'":
escaped += value.slice(start, end) + "'";
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 = {
'"': """,
"'": "'",
"&": "&",
"<": "<",
">": ">",
};
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 条评论