Skip to content
Merged
9 changes: 9 additions & 0 deletions src/compiler/compile/nodes/Text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export default class Text extends Node {
return this.within_pre();
}

/**
* @returns If true, the leading newline character should be stripped.
* @see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
*/
should_strip_leading_newline(): boolean {
const parent = this.parent;
return parent.type === 'Element' && parent.name === 'pre' && parent.children[0] === this;
}

within_pre(): boolean {
let node = this.parent;
while (node) {
Expand Down
13 changes: 11 additions & 2 deletions src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute';
import { dimensions } from '../../../../utils/patterns';
import { dimensions, start_newline } from '../../../../utils/patterns';
import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers';
Expand Down Expand Up @@ -939,7 +939,7 @@ export default class ElementWrapper extends Wrapper {
if (should_cache) {
block.chunks.update.push(b`
if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) {
${updater}
${updater}
}
`);
} else {
Expand Down Expand Up @@ -1015,6 +1015,15 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
if (!wrapper.void) {
state.quasi.value.raw += '>';

if (wrapper.node.name === 'pre') {
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) {
state.quasi.value.raw += '\n';
}
}

to_html(wrapper.fragment.nodes as Array<ElementWrapper | TextWrapper>, block, literal, state);

state.quasi.value.raw += `</${wrapper.node.name}>`;
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/compile/render_dom/wrappers/Fragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
import { link } from '../../../utils/link';
import { Identifier } from 'estree';
import { start_newline } from '../../../utils/patterns';

const wrappers = {
AwaitBlock,
Expand Down Expand Up @@ -127,8 +128,12 @@ export default class FragmentWrapper {
if (strip_whitespace) {
const first = this.nodes[0] as Text;

if (first && first.node.type === 'Text' && !first.node.keep_space()) {
first.data = trim_start(first.data);
if (first && first.node.type === 'Text') {
if (!first.node.keep_space()) {
first.data = trim_start(first.data);
} else if (first.node.should_strip_leading_newline()) {
first.data = first.data.replace(start_newline, '');
}
if (!first.data) {
first.var = null;
this.nodes.shift();
Expand Down
1 change: 1 addition & 0 deletions src/compiler/utils/patterns.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const whitespace = /[ \t\r\n]/;
export const start_whitespace = /^[ \t\r\n]*/;
export const end_whitespace = /[ \t\r\n]*$/;
export const start_newline = /^\r?\n/;

export const dimensions = /^(?:offset|client)(?:Width|Height)$/;
2 changes: 2 additions & 0 deletions test/runtime/samples/pre-tag/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[main.svelte]
trim_trailing_whitespace = unset
26 changes: 12 additions & 14 deletions test/runtime/samples/pre-tag/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,14 @@ export default {
const elementDiv = target.querySelector('#div');
// Test for <pre> tag in non <pre> tag
const elementDivWithPre = target.querySelector('#div-with-pre');

// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
// depending on how the innerHTML is set.
// (There is no difference in the display.)
// Reassign innerHTML to add the same optimizations to innerHTML.

// eslint-disable-next-line no-self-assign
elementPre.innerHTML = elementPre.innerHTML;
// eslint-disable-next-line no-self-assign
elementDiv.innerHTML = elementDiv.innerHTML;
// eslint-disable-next-line no-self-assign
elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;
// Test for <pre> tag with leading newline
const elementPreWithLeadingNewline = target.querySelector('#pre-with-leading-newline');
const elementPreWithoutLeadingNewline = target.querySelector('#pre-without-leading-newline');
const elementPreWithMultipleLeadingNewline = target.querySelector('#pre-with-multiple-leading-newlines');

assert.equal(
elementPre.innerHTML,
`
A
` A
B
<span>
C
Expand Down Expand Up @@ -53,5 +44,12 @@ export default {
F
</pre>`
);
assert.equal(elementPreWithLeadingNewline.children[0].innerHTML, 'leading newline');
assert.equal(elementPreWithLeadingNewline.children[1].innerHTML, ' leading newline and spaces');
assert.equal(elementPreWithLeadingNewline.children[2].innerHTML, '\nleading newlines');
assert.equal(elementPreWithoutLeadingNewline.children[0].innerHTML, 'without spaces');
assert.equal(elementPreWithoutLeadingNewline.children[1].innerHTML, ' with spaces ');
assert.equal(elementPreWithoutLeadingNewline.children[2].innerHTML, ' \nnewline after leading space');
assert.equal(elementPreWithMultipleLeadingNewline.innerHTML, '\n\nmultiple leading newlines');
}
};
22 changes: 22 additions & 0 deletions test/runtime/samples/pre-tag/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,25 @@
F
</pre>
</div>

<div id="pre-with-leading-newline">
<pre>
leading newline</pre>
<pre>
leading newline and spaces</pre>
<pre>

leading newlines</pre>
</div>

<div id="pre-without-leading-newline">
<pre>without spaces</pre>
<pre> with spaces </pre>
<pre>
newline after leading space</pre>
</div>

<pre id="pre-with-multiple-leading-newlines">


multiple leading newlines</pre>
15 changes: 1 addition & 14 deletions test/runtime/samples/preserve-whitespaces/_config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,9 @@ export default {
// Test for <pre> tag in non <pre> tag
const elementDivWithPre = target.querySelector('#div-with-pre');

// There is a slight difference in innerHTML because there is a difference in HTML optimization (in jsdom)
// depending on how the innerHTML is set.
// (There is no difference in the display.)
// Reassign innerHTML to add the same optimizations to innerHTML.

// eslint-disable-next-line no-self-assign
elementPre.innerHTML = elementPre.innerHTML;
// eslint-disable-next-line no-self-assign
elementDiv.innerHTML = elementDiv.innerHTML;
// eslint-disable-next-line no-self-assign
elementDivWithPre.innerHTML = elementDivWithPre.innerHTML;

assert.equal(
elementPre.innerHTML,
`
A
` A
B
<span>
C
Expand Down
2 changes: 2 additions & 0 deletions test/server-side-rendering/samples/pre-tag/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[{main.svelte,_expected.html}]
trim_trailing_whitespace = unset
13 changes: 13 additions & 0 deletions test/server-side-rendering/samples/pre-tag/_expected.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@
E
F
</pre></div>

<div id="pre-with-leading-newline"><pre>
leading newline</pre>
<pre>
leading newline and spaces</pre>
<pre>

leading newlines</pre></div>

<div id="pre-without-leading-newline"><pre>without spaces</pre>
<pre> with spaces </pre>
<pre>
newline after leading space</pre></div>
17 changes: 17 additions & 0 deletions test/server-side-rendering/samples/pre-tag/main.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,20 @@
F
</pre>
</div>

<div id="pre-with-leading-newline">
<pre>
leading newline</pre>
<pre>
leading newline and spaces</pre>
<pre>

leading newlines</pre>
</div>

<div id="pre-without-leading-newline">
<pre>without spaces</pre>
<pre> with spaces </pre>
<pre>
newline after leading space</pre>
</div>