diff --git a/.changeset/few-papayas-swim.md b/.changeset/few-papayas-swim.md new file mode 100644 index 000000000..c8e7dacd0 --- /dev/null +++ b/.changeset/few-papayas-swim.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Add the `escapeHTML` utility to `astro/internal` diff --git a/packages/astro/src/runtime/server/escape.ts b/packages/astro/src/runtime/server/escape.ts new file mode 100644 index 000000000..3b07b2290 --- /dev/null +++ b/packages/astro/src/runtime/server/escape.ts @@ -0,0 +1,3 @@ +const entities = { '"': 'quot', '&': 'amp', '\'': 'apos', '<': 'lt', '>': 'gt' } as const + +export const escapeHTML = (string: any) => string.replace(/["'&<>]/g, (char: keyof typeof entities) => '&' + entities[char] + ';') diff --git a/packages/astro/src/runtime/server/index.ts b/packages/astro/src/runtime/server/index.ts index fb8753cd6..9cdad6877 100644 --- a/packages/astro/src/runtime/server/index.ts +++ b/packages/astro/src/runtime/server/index.ts @@ -5,6 +5,7 @@ import shorthash from 'shorthash'; import { extractDirectives, generateHydrateScript } from './hydration.js'; import { serializeListValue } from './util.js'; export { createMetadata } from './metadata.js'; +export { escapeHTML } from './escape.js'; export type { Metadata } from './metadata'; const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; @@ -308,12 +309,26 @@ export function createAstro(filePathname: string, site: string, projectRootStr: const toAttributeString = (value: any) => String(value).replace(/&/g, '&').replace(/"/g, '"'); +const STATIC_DIRECTIVES = new Set([ + 'set:html', + 'set:text' +]) + // A helper used to turn expressions into attribute key/value export function addAttribute(value: any, key: string) { if (value == null || value === false) { return ''; } + // compiler directives cannot be applied dynamically, log a warning and ignore. + if (STATIC_DIRECTIVES.has(key)) { + // eslint-disable-next-line no-console + console.warn(`[astro] The "${key}" directive cannot be applied dynamically at runtime. It will not be rendered as an attribute. + +Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the dynamic spread syntax (\`{...{ "${key}": value }}\`).`); + return ''; + } + // support "class" from an expression passed into an element (#782) if (key === 'class:list') { return ` ${key.slice(0, -5)}="${toAttributeString(serializeListValue(value))}"`;