From e7a235055073dc70edb13938dc7859b37c19fc74 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 12 May 2022 22:10:40 -0500 Subject: [PATCH] Faster SSR (#5701) Co-authored-by: Conduitry Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> --- .../handlers/shared/get_attribute_value.ts | 4 +-- src/runtime/internal/ssr.ts | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 13 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 8af58bcc24..20d4eddbc5 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}), 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, '"')) as ESTreeExpression - : x`@escape(${chunk.node})`; + : 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 bb1b1b8f73..2c7d24a2cd 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -69,20 +69,34 @@ export function merge_ssr_styles(style_attribute, style_directive) { return style_object; } -export const escaped = { - '"': '"', - "'": ''', - '&': '&', - '<': '<', - '>': '>' -}; - -export function escape(html) { - return String(html).replace(/["'&<>]/g, match => escaped[match]); +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(value: unknown, is_attr = false) { + const str = String(value); + + const pattern = is_attr ? ATTR_REGEX : CONTENT_REGEX; + pattern.lastIndex = 0; + + let escaped = ''; + let last = 0; + + while (pattern.test(str)) { + const i = pattern.lastIndex - 1; + const ch = str[i]; + escaped += str.substring(last, i) + (ch === '&' ? '&' : (ch === '"' ? '"' : '<')); + last = i + 1; + } + + return escaped + str.substring(last); } 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) {