From aa15e9a6da253ec112ed16121e703ff69957e646 Mon Sep 17 00:00:00 2001
From: Rich Harris <richard.a.harris@gmail.com>
Date: Thu, 19 Nov 2020 20:30:35 -0500
Subject: [PATCH 01/10] faster SSR, modelled on ryansolid/dom-expressions#27

---
 .../handlers/shared/get_attribute_value.ts    |  4 +-
 src/runtime/internal/ssr.ts                   | 49 +++++++++++++++----
 2 files changed, 41 insertions(+), 12 deletions(-)

diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
index 5c89a7a0087f..9efb0868543f 100644
--- a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
+++ b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
@@ -9,7 +9,7 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
 	// handle special case — `class={possiblyUndefined}` with scoped CSS
 	if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
 		const value = (attribute.chunks[0] as Expression).node;
-		return x`@escape(@null_to_empty(${value})) + "${(attribute.chunks[1] as Text).data}"`;
+		return x`@escape(@null_to_empty(${value}), 1) + "${(attribute.chunks[1] as Text).data}"`;
 	}
 
 	return get_attribute_value(attribute);
@@ -22,7 +22,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
 		.map((chunk) => {
 			return chunk.type === 'Text'
 				? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression
-				: x`@escape(${chunk.node})`;
+				: x`@escape(${chunk.node}, 1)`;
 		})
 		.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
 }
diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 2d843abb2f3a..022fb67ddbb5 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -32,16 +32,45 @@ export function spread(args, classes_to_add) {
 	return str;
 }
 
-export const escaped = {
-	'"': '&quot;',
-	"'": '&#39;',
-	'&': '&amp;',
-	'<': '&lt;',
-	'>': '&gt;'
-};
+const ATTR_REGEX = /[&<"]/;
+const CONTENT_REGEX = /[&<]/;
+
+export function escape(html: string, attr: 0 | 1 = 0) {
+	if (typeof html !== 'string') return html;
+
+	const match = (attr ? ATTR_REGEX : CONTENT_REGEX).exec(html);
+	if (!match) return html;
+
+	let index = 0;
+	let lastIndex = 0;
+	let out = '';
+	let escape = '';
+
+	for (index = match.index; index < html.length; index++) {
+		switch (html.charCodeAt(index)) {
+			case 34: // "
+			if (!attr) continue;
+			escape = '&quot;';
+			break;
+			case 38: // &
+			escape = '&amp;';
+			break;
+			case 60: // <
+			escape = '&lt;';
+			break;
+			default:
+			continue;
+		}
+
+		if (lastIndex !== index) {
+			out += html.substring(lastIndex, index);
+		}
+
+		lastIndex = index + 1;
+		out += escape;
+	}
 
-export function escape(html) {
-	return String(html).replace(/["'&<>]/g, match => escaped[match]);
+	return lastIndex !== index ? out + html.substring(lastIndex, index) : out;
 }
 
 export function each(items, fn) {
@@ -129,7 +158,7 @@ export function create_ssr_component(fn) {
 
 export function add_attribute(name, value, boolean) {
 	if (value == null || (boolean && !value)) return '';
-	return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `"${value}"`}`}`;
+	return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value, 1)) : `"${value}"`}`}`;
 }
 
 export function add_classes(classes) {

From e58d65822a18a7d04b4b7a3171e51f6f9a6732d7 Mon Sep 17 00:00:00 2001
From: Rich Harris <richard.a.harris@gmail.com>
Date: Thu, 19 Nov 2020 23:04:40 -0500
Subject: [PATCH 02/10] simplify

---
 src/runtime/internal/ssr.ts | 48 +++++++++++++------------------------
 1 file changed, 17 insertions(+), 31 deletions(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 022fb67ddbb5..51e8fc6f4cd6 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -32,45 +32,31 @@ export function spread(args, classes_to_add) {
 	return str;
 }
 
-const ATTR_REGEX = /[&<"]/;
-const CONTENT_REGEX = /[&<]/;
+const ATTR_REGEX = /[&"]/g;
+const CONTENT_REGEX = /[&<]/g;
+
+const escapes = {
+	'"': '&quot;',
+	'&': '&amp;',
+	'<': '&lt'
+};
 
 export function escape(html: string, attr: 0 | 1 = 0) {
 	if (typeof html !== 'string') return html;
 
-	const match = (attr ? ATTR_REGEX : CONTENT_REGEX).exec(html);
-	if (!match) return html;
-
-	let index = 0;
-	let lastIndex = 0;
-	let out = '';
-	let escape = '';
-
-	for (index = match.index; index < html.length; index++) {
-		switch (html.charCodeAt(index)) {
-			case 34: // "
-			if (!attr) continue;
-			escape = '&quot;';
-			break;
-			case 38: // &
-			escape = '&amp;';
-			break;
-			case 60: // <
-			escape = '&lt;';
-			break;
-			default:
-			continue;
-		}
+	const pattern = (attr ? ATTR_REGEX : CONTENT_REGEX);
+	pattern.lastIndex = 0;
 
-		if (lastIndex !== index) {
-			out += html.substring(lastIndex, index);
-		}
+	let escaped = '';
+	let last = 0;
 
-		lastIndex = index + 1;
-		out += escape;
+	while (pattern.test(html)) {
+		const i = pattern.lastIndex - 1;
+		escaped += html.slice(last, i) + escapes[html[i]];
+		last = i + 1;
 	}
 
-	return lastIndex !== index ? out + html.substring(lastIndex, index) : out;
+	return escaped + html.slice(last);
 }
 
 export function each(items, fn) {

From 57bb045cd1947feefe42783ec0ea53278b5ab0f7 Mon Sep 17 00:00:00 2001
From: Rich Harris <richard.a.harris@gmail.com>
Date: Thu, 19 Nov 2020 23:26:07 -0500
Subject: [PATCH 03/10] remove redundant parens

---
 src/runtime/internal/ssr.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 51e8fc6f4cd6..92e6a923b818 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -44,7 +44,7 @@ const escapes = {
 export function escape(html: string, attr: 0 | 1 = 0) {
 	if (typeof html !== 'string') return html;
 
-	const pattern = (attr ? ATTR_REGEX : CONTENT_REGEX);
+	const pattern = attr ? ATTR_REGEX : CONTENT_REGEX;
 	pattern.lastIndex = 0;
 
 	let escaped = '';

From 29d4695fc769e995e98bc538d4325e5679ec0298 Mon Sep 17 00:00:00 2001
From: Rich Harris <richard.a.harris@gmail.com>
Date: Fri, 20 Nov 2020 08:43:05 -0500
Subject: [PATCH 04/10] boolean argument

---
 .../compile/render_ssr/handlers/shared/get_attribute_value.ts | 4 ++--
 src/runtime/internal/ssr.ts                                   | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
index 9efb0868543f..d62b2d14e9c8 100644
--- a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
+++ b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts
@@ -9,7 +9,7 @@ export function get_class_attribute_value(attribute: Attribute): ESTreeExpressio
 	// handle special case — `class={possiblyUndefined}` with scoped CSS
 	if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) {
 		const value = (attribute.chunks[0] as Expression).node;
-		return x`@escape(@null_to_empty(${value}), 1) + "${(attribute.chunks[1] as Text).data}"`;
+		return x`@escape(@null_to_empty(${value}), true) + "${(attribute.chunks[1] as Text).data}"`;
 	}
 
 	return get_attribute_value(attribute);
@@ -22,7 +22,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
 		.map((chunk) => {
 			return chunk.type === 'Text'
 				? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression
-				: x`@escape(${chunk.node}, 1)`;
+				: x`@escape(${chunk.node}, true)`;
 		})
 		.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
 }
diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 92e6a923b818..5d321abee40b 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -41,10 +41,10 @@ const escapes = {
 	'<': '&lt'
 };
 
-export function escape(html: string, attr: 0 | 1 = 0) {
+export function escape(html: string, is_attr = false) {
 	if (typeof html !== 'string') return html;
 
-	const pattern = attr ? ATTR_REGEX : CONTENT_REGEX;
+	const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
 	pattern.lastIndex = 0;
 
 	let escaped = '';

From 3a94f2aca56391bd64b37ac0ff71b3aa385dafda Mon Sep 17 00:00:00 2001
From: Rich Harris <richard.a.harris@gmail.com>
Date: Fri, 20 Nov 2020 10:21:06 -0500
Subject: [PATCH 05/10] Update src/runtime/internal/ssr.ts

Co-authored-by: Conduitry <git@chor.date>
---
 src/runtime/internal/ssr.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 5d321abee40b..bc333d000b5b 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -144,7 +144,7 @@ export function create_ssr_component(fn) {
 
 export function add_attribute(name, value, boolean) {
 	if (value == null || (boolean && !value)) return '';
-	return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value, 1)) : `"${value}"`}`}`;
+	return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value, true)) : `"${value}"`}`}`;
 }
 
 export function add_classes(classes) {

From 30df740729e695eb270701642c1f6da34cfcea8d Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 6 May 2022 09:38:44 -0700
Subject: [PATCH 06/10] fix merge

---
 src/runtime/internal/ssr.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index deb1ed836b4b..416267fa03f7 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -97,7 +97,7 @@ export function escape(html: string, is_attr = false) {
 }
 
 export function escape_attribute_value(value) {
-	return typeof value === 'string' ? escape(value) : value;
+	return typeof value === 'string' ? escape(value, true) : value;
 }
 
 export function escape_object(obj) {

From 88e2146701668bd4cd9f8052eb0e050488dcef3b Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 6 May 2022 10:59:24 -0700
Subject: [PATCH 07/10] use ternary

---
 src/runtime/internal/ssr.ts | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 416267fa03f7..ecffc82fa47d 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -72,12 +72,6 @@ export function merge_ssr_styles(style_attribute, style_directive) {
 const ATTR_REGEX = /[&"]/g;
 const CONTENT_REGEX = /[&<]/g;
 
-const escapes = {
-	'"': '&quot;',
-	'&': '&amp;',
-	'<': '&lt'
-};
-
 export function escape(html: string, is_attr = false) {
 	if (typeof html !== 'string') return html;
 
@@ -87,11 +81,12 @@ export function escape(html: string, is_attr = false) {
 	let escaped = '';
 	let last = 0;
 
-	while (pattern.test(html)) {
-		const i = pattern.lastIndex - 1;
-		escaped += html.slice(last, i) + escapes[html[i]];
-		last = i + 1;
-	}
+  while (pattern.test(html)) {
+    const i = pattern.lastIndex - 1;
+    const ch = html[i];
+    escaped += html.slice(last, i) + (ch === '&' ? '&amp;' : (ch === '"' ? '&quot;' : '&lt;'));
+    last = i + 1;
+  }
 
 	return escaped + html.slice(last);
 }

From 1b0e836e95bbc57008529ed858d90150620b5573 Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 6 May 2022 11:03:54 -0700
Subject: [PATCH 08/10] slice -> substring

---
 src/runtime/internal/ssr.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index ecffc82fa47d..f9cd27bcbcba 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -84,11 +84,11 @@ export function escape(html: string, is_attr = false) {
   while (pattern.test(html)) {
     const i = pattern.lastIndex - 1;
     const ch = html[i];
-    escaped += html.slice(last, i) + (ch === '&' ? '&amp;' : (ch === '"' ? '&quot;' : '&lt;'));
+    escaped += html.substring(last, i) + (ch === '&' ? '&amp;' : (ch === '"' ? '&quot;' : '&lt;'));
     last = i + 1;
   }
 
-	return escaped + html.slice(last);
+	return escaped + html.substring(last);
 }
 
 export function escape_attribute_value(value) {

From 61e180b2b9d9dcb4ca930375081786e47caa1918 Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 6 May 2022 11:07:10 -0700
Subject: [PATCH 09/10] add comment

---
 src/runtime/internal/ssr.ts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index f9cd27bcbcba..7fee80a0ecbd 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -72,6 +72,10 @@ export function merge_ssr_styles(style_attribute, style_directive) {
 const ATTR_REGEX = /[&"]/g;
 const CONTENT_REGEX = /[&<]/g;
 
+/**
+ * Note: this method is performance sensitive and has been optimized
+ * https://github.com/sveltejs/svelte/pull/5701
+ */
 export function escape(html: string, is_attr = false) {
 	if (typeof html !== 'string') return html;
 

From b32049bfdf40089e341c2fa2c8a200545488a59c Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Fri, 6 May 2022 16:12:24 -0700
Subject: [PATCH 10/10] restore String conversion

---
 src/runtime/internal/ssr.ts | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index 7fee80a0ecbd..2c7d24a2cd61 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -76,8 +76,8 @@ const CONTENT_REGEX = /[&<]/g;
  * Note: this method is performance sensitive and has been optimized
  * https://github.com/sveltejs/svelte/pull/5701
  */
-export function escape(html: string, is_attr = false) {
-	if (typeof html !== 'string') return html;
+export function escape(value: unknown, is_attr = false) {
+	const str = String(value);
 
 	const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX;
 	pattern.lastIndex = 0;
@@ -85,14 +85,14 @@ export function escape(html: string, is_attr = false) {
 	let escaped = '';
 	let last = 0;
 
-  while (pattern.test(html)) {
+  while (pattern.test(str)) {
     const i = pattern.lastIndex - 1;
-    const ch = html[i];
-    escaped += html.substring(last, i) + (ch === '&' ? '&amp;' : (ch === '"' ? '&quot;' : '&lt;'));
+    const ch = str[i];
+    escaped += str.substring(last, i) + (ch === '&' ? '&amp;' : (ch === '"' ? '&quot;' : '&lt;'));
     last = i + 1;
   }
 
-	return escaped + html.substring(last);
+	return escaped + str.substring(last);
 }
 
 export function escape_attribute_value(value) {