From 04c9b3507e6bb6f1beb8b10c5ab32318a9a6b6e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrg=C3=BCn=20Day=C4=B1o=C4=9Flu?= Date: Fri, 30 Aug 2024 12:32:21 +0200 Subject: [PATCH] perf: improve sparse escape (#27) * improve escape * escape * improve escape * add sparse escape * improve branching * start * no unnecessary assignment * use already created var --- bench/index.js | 4 +++ src/html.js | 90 ++++++++++++++++++++++++-------------------------- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/bench/index.js b/bench/index.js index c7b7c61..d944dc2 100644 --- a/bench/index.js +++ b/bench/index.js @@ -104,6 +104,10 @@ bench.add("mixed escaped and unescaped expressions", () => { `; }); +bench.add("sparse escape", () => { + result = html`

${`${"noescape".repeat(250)}<>`.repeat(5)}

`; +}); + await bench.warmup(); await bench.run(); diff --git a/src/html.js b/src/html.js index ec5b262..60584d0 100644 --- a/src/html.js +++ b/src/html.js @@ -1,41 +1,41 @@ -const escapeRegExp = /["&'<=>]/; +const escapeRegExp = /["&'<=>]/g; const escapeFunction = (string) => { + if (!string || !escapeRegExp.test(string)) { + return string; + } + let escaped = ""; let start = 0; - for (let end = 0; end !== string.length; ++end) { - switch (string.charCodeAt(end)) { + do { + const i = escapeRegExp.lastIndex - 1; + + switch (string.charCodeAt(i)) { case 34: // " - escaped += string.slice(start, end) + """; - start = end + 1; - continue; + escaped += string.slice(start, i) + """; + break; case 38: // & - escaped += string.slice(start, end) + "&"; - start = end + 1; - continue; + escaped += string.slice(start, i) + "&"; + break; case 39: // ' - escaped += string.slice(start, end) + "'"; - start = end + 1; - continue; + escaped += string.slice(start, i) + "'"; + break; case 60: // < - escaped += string.slice(start, end) + "<"; - start = end + 1; - continue; + escaped += string.slice(start, i) + "<"; + break; case 61: // = - escaped += string.slice(start, end) + "="; - start = end + 1; - continue; + escaped += string.slice(start, i) + "="; + break; case 62: // > - escaped += string.slice(start, end) + ">"; - start = end + 1; - continue; + escaped += string.slice(start, i) + ">"; + break; } - } - escaped += string.slice(start); + start = escapeRegExp.lastIndex; + } while (escapeRegExp.test(string)); - return escaped; + return escaped + string.slice(start); }; /** @@ -59,16 +59,14 @@ export const html = ({ raw: literals }, ...expressions) => { if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); - } else if (string && escapeRegExp.test(string)) { + } else { string = escapeFunction(string); } accumulator += literal + string; } - accumulator += literals[expressions.length]; - - return accumulator; + return accumulator + literals[expressions.length]; }; /** @@ -120,11 +118,11 @@ export const htmlGenerator = function* ({ raw: literals }, ...expressions) { string = `${expression}`; } - if (string) { - if (!isRaw && escapeRegExp.test(string)) { - string = escapeFunction(string); - } + if (!isRaw) { + string = escapeFunction(string); + } + if (string) { yield string; } } @@ -135,11 +133,11 @@ export const htmlGenerator = function* ({ raw: literals }, ...expressions) { string = `${expression}`; } - if (string) { - if (!isRaw && escapeRegExp.test(string)) { - string = escapeFunction(string); - } + if (!isRaw) { + string = escapeFunction(string); + } + if (string) { yield string; } } @@ -152,7 +150,7 @@ export const htmlGenerator = function* ({ raw: literals }, ...expressions) { if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); - } else if (string && escapeRegExp.test(string)) { + } else { string = escapeFunction(string); } @@ -221,11 +219,11 @@ export const htmlAsyncGenerator = async function* ( string = `${expression}`; } - if (string) { - if (!isRaw && escapeRegExp.test(string)) { - string = escapeFunction(string); - } + if (!isRaw) { + string = escapeFunction(string); + } + if (string) { yield string; } } @@ -236,11 +234,11 @@ export const htmlAsyncGenerator = async function* ( string = `${expression}`; } - if (string) { - if (!isRaw && escapeRegExp.test(string)) { - string = escapeFunction(string); - } + if (!isRaw) { + string = escapeFunction(string); + } + if (string) { yield string; } } @@ -253,7 +251,7 @@ export const htmlAsyncGenerator = async function* ( if (literal && literal.charCodeAt(literal.length - 1) === 33) { literal = literal.slice(0, -1); - } else if (string && escapeRegExp.test(string)) { + } else { string = escapeFunction(string); }