fix: don't collapse whitespace within text nodes (#10691)

fixes #9892
pull/10693/head
Simon H 2 years ago committed by GitHub
parent 0a9ba9340b
commit 5d3385c56f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: don't collapse whitespace within text nodes

@ -162,24 +162,36 @@ export function clean_nodes(
/** @type {import('#compiler').SvelteNode[]} */
const trimmed = [];
/** @type {import('#compiler').Text | null} */
let last_text = null;
// Replace any whitespace between a text and non-text node with a single spaceand keep whitespace
// as-is within text nodes, or between text nodes and expression tags (because in the end they count
// as one text). This way whitespace is mostly preserved when using CSS with `white-space: pre-line`
// and default slot content going into a pre tag (which we can't see).
for (let i = 0; i < regular.length; i++) {
const prev = regular[i - 1];
const node = regular[i];
const next = regular[i + 1];
// Replace any inbetween whitespace with a single space
for (const node of regular) {
if (node.type === 'Text') {
node.data = node.data.replace(regex_whitespaces_strict, ' ');
node.raw = node.raw.replace(regex_whitespaces_strict, ' ');
if (
(last_text === null && !can_remove_entirely) ||
node.data !== ' ' ||
node.data.charCodeAt(0) === 160 // non-breaking space
) {
if (prev?.type !== 'ExpressionTag') {
const prev_is_text_ending_with_whitespace =
prev?.type === 'Text' && regex_ends_with_whitespaces.test(prev.data);
node.data = node.data.replace(
regex_starts_with_whitespaces,
prev_is_text_ending_with_whitespace ? '' : ' '
);
node.raw = node.raw.replace(
regex_starts_with_whitespaces,
prev_is_text_ending_with_whitespace ? '' : ' '
);
}
if (next?.type !== 'ExpressionTag') {
node.data = node.data.replace(regex_ends_with_whitespaces, ' ');
node.raw = node.raw.replace(regex_ends_with_whitespaces, ' ');
}
if (node.data && (node.data !== ' ' || !can_remove_entirely)) {
trimmed.push(node);
}
last_text = node;
} else {
last_text = null;
trimmed.push(node);
}
}

@ -2,9 +2,9 @@ export const regex_whitespace = /\s/;
export const regex_whitespaces = /\s+/;
export const regex_starts_with_newline = /^\r?\n/;
export const regex_starts_with_whitespace = /^\s/;
export const regex_starts_with_whitespaces = /^[ \t\r\n]*/;
export const regex_starts_with_whitespaces = /^[ \t\r\n]+/;
export const regex_ends_with_whitespace = /\s$/;
export const regex_ends_with_whitespaces = /[ \t\r\n]*$/;
export const regex_ends_with_whitespaces = /[ \t\r\n]+$/;
/** Not \S because that also removes explicit whitespace defined through things like `&nbsp;` */
export const regex_not_whitespace = /[^ \t\r\n]/;
/** Not \s+ because that also includes explicit whitespace defined through things like `&nbsp;` */

@ -20,7 +20,10 @@ function get_html(ssr) {
</span>
E
F
</pre> <div id="div">A B <span>C D</span> E F</div> <div id="div-with-pre"><pre> A
</pre> <div id="div">A
B <span>C
D</span> E
F</div> <div id="div-with-pre"><pre> A
B
<span>
C

@ -66,7 +66,7 @@ export interface RuntimeTest<Props extends Record<string, any> = Record<string,
runtime_error?: string;
warnings?: string[];
expect_unhandled_rejections?: boolean;
withoutNormalizeHtml?: boolean;
withoutNormalizeHtml?: boolean | 'only-strip-comments';
recover?: boolean;
}
@ -213,13 +213,15 @@ async function run_test_variant(
if (variant === 'ssr') {
if (config.ssrHtml) {
assert_html_equal_with_options(target.innerHTML, config.ssrHtml, {
preserveComments: config.compileOptions?.preserveComments,
withoutNormalizeHtml: config.withoutNormalizeHtml
preserveComments:
config.withoutNormalizeHtml === 'only-strip-comments' ? false : undefined,
withoutNormalizeHtml: !!config.withoutNormalizeHtml
});
} else if (config.html) {
assert_html_equal_with_options(target.innerHTML, config.html, {
preserveComments: config.compileOptions?.preserveComments,
withoutNormalizeHtml: config.withoutNormalizeHtml
preserveComments:
config.withoutNormalizeHtml === 'only-strip-comments' ? false : undefined,
withoutNormalizeHtml: !!config.withoutNormalizeHtml
});
}
@ -283,7 +285,9 @@ async function run_test_variant(
if (config.html) {
$.flushSync();
assert_html_equal_with_options(target.innerHTML, config.html, {
withoutNormalizeHtml: config.withoutNormalizeHtml
preserveComments:
config.withoutNormalizeHtml === 'only-strip-comments' ? false : undefined,
withoutNormalizeHtml: !!config.withoutNormalizeHtml
});
}

@ -4,5 +4,11 @@ export default test({
compileOptions: {
dev: true // Render in dev mode to check that the validation error is not thrown
},
html: `A\nB\nC\nD`
withoutNormalizeHtml: 'only-strip-comments',
html: `A B C D <pre>Testing
123 ;
456</pre>`,
ssrHtml: `A B C D <pre>Testing
123 ;
456</pre>`
});

@ -1,5 +1,15 @@
<script>
import Pre from "./pre.svelte";
</script>
A
{#snippet snip()}C{/snippet}
B
{@render snip()}
D
<Pre>
Testing
123 ;
456
</Pre>

@ -0,0 +1,5 @@
<script>
let { children } = $props();
</script>
<pre>{@render children()}</pre>
Loading…
Cancel
Save