Skip to content

Commit 0292504

Browse files
committed
remove slot that does not render anything for ssr
1 parent 1b4a93b commit 0292504

File tree

9 files changed

+154
-42
lines changed

9 files changed

+154
-42
lines changed

src/compiler/compile/nodes/Text.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,18 @@ import Component from '../Component';
33
import TemplateScope from './shared/TemplateScope';
44
import { INode } from './interfaces';
55

6+
// Whitespace inside one of these elements will not result in
7+
// a whitespace node being created in any circumstances. (This
8+
// list is almost certainly very incomplete)
9+
const elements_without_text = new Set([
10+
'audio',
11+
'datalist',
12+
'dl',
13+
'optgroup',
14+
'select',
15+
'video',
16+
]);
17+
618
export default class Text extends Node {
719
type: 'Text';
820
data: string;
@@ -13,4 +25,21 @@ export default class Text extends Node {
1325
this.data = info.data;
1426
this.synthetic = info.synthetic || false;
1527
}
28+
29+
should_skip() {
30+
if (/\S/.test(this.data)) return false;
31+
32+
const parent_element = this.find_nearest(/(?:Element|InlineComponent|Head)/);
33+
if (!parent_element) return false;
34+
35+
if (parent_element.type === 'Head') return true;
36+
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0];
37+
38+
// svg namespace exclusions
39+
if (/svg$/.test(parent_element.namespace)) {
40+
if (this.prev && this.prev.type === "Element" && this.prev.name === "tspan") return false;
41+
}
42+
43+
return parent_element.namespace || elements_without_text.has(parent_element.name);
44+
}
1645
}

src/compiler/compile/render_dom/wrappers/Fragment.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { INode } from '../../nodes/interfaces';
1717
import Renderer from '../Renderer';
1818
import Block from '../Block';
1919
import { trim_start, trim_end } from '../../../utils/trim';
20+
import { link } from '../../../utils/link';
2021
import { Identifier } from 'estree';
2122

2223
const wrappers = {
@@ -38,11 +39,6 @@ const wrappers = {
3839
Window
3940
};
4041

41-
function link(next: Wrapper, prev: Wrapper) {
42-
prev.next = next;
43-
if (next) next.prev = prev;
44-
}
45-
4642
function trimmable_at(child: INode, next_sibling: Wrapper): boolean {
4743
// Whitespace is trimmable if one of the following is true:
4844
// The child and its sibling share a common nearest each block (not at an each block boundary)

src/compiler/compile/render_dom/wrappers/Text.ts

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,6 @@ import Wrapper from './shared/Wrapper';
55
import { x } from 'code-red';
66
import { Identifier } from 'estree';
77

8-
// Whitespace inside one of these elements will not result in
9-
// a whitespace node being created in any circumstances. (This
10-
// list is almost certainly very incomplete)
11-
const elements_without_text = new Set([
12-
'audio',
13-
'datalist',
14-
'dl',
15-
'optgroup',
16-
'select',
17-
'video',
18-
]);
19-
20-
// TODO this should probably be in Fragment
21-
function should_skip(node: Text) {
22-
if (/\S/.test(node.data)) return false;
23-
24-
const parent_element = node.find_nearest(/(?:Element|InlineComponent|Head)/);
25-
if (!parent_element) return false;
26-
27-
if (parent_element.type === 'Head') return true;
28-
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && node === parent_element.children[0];
29-
30-
// svg namespace exclusions
31-
if (/svg$/.test(parent_element.namespace)) {
32-
if (node.prev && node.prev.type === "Element" && node.prev.name === "tspan") return false;
33-
}
34-
35-
return parent_element.namespace || elements_without_text.has(parent_element.name);
36-
}
37-
388
export default class TextWrapper extends Wrapper {
399
node: Text;
4010
data: string;
@@ -50,7 +20,7 @@ export default class TextWrapper extends Wrapper {
5020
) {
5121
super(renderer, block, parent, node);
5222

53-
this.skip = should_skip(this.node);
23+
this.skip = this.node.should_skip();
5424
this.data = data;
5525
this.var = (this.skip ? null : x`t`) as unknown as Identifier;
5626
}

src/compiler/compile/render_ssr/handlers/Element.ts

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import { get_slot_scope } from './shared/get_slot_scope';
44
import { boolean_attributes } from './shared/boolean_attributes';
55
import Renderer, { RenderOptions } from '../Renderer';
66
import Element from '../../nodes/Element';
7+
import { INode } from '../../nodes/interfaces';
78
import { x } from 'code-red';
89
import Expression from '../../nodes/shared/Expression';
10+
import { trim_end, trim_start } from '../../../utils/trim';
11+
import { link } from '../../../utils/link';
912

1013
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
1114
slot_scopes: Map<any, any>;
1215
}) {
16+
17+
const children = remove_whitespace_children(node.children, node.next);
18+
1319
// awkward special case
1420
let node_contents;
1521

@@ -133,7 +139,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
133139
if (node_contents !== undefined) {
134140
if (contenteditable) {
135141
renderer.push();
136-
renderer.render(node.children, options);
142+
renderer.render(children, options);
137143
const result = renderer.pop();
138144

139145
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`);
@@ -145,7 +151,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
145151
renderer.add_string(`</${node.name}>`);
146152
}
147153
} else if (slot && nearest_inline_component) {
148-
renderer.render(node.children, options);
154+
renderer.render(children, options);
149155

150156
if (!is_void(node.name)) {
151157
renderer.add_string(`</${node.name}>`);
@@ -163,10 +169,80 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
163169
output: renderer.pop()
164170
});
165171
} else {
166-
renderer.render(node.children, options);
172+
renderer.render(children, options);
167173

168174
if (!is_void(node.name)) {
169175
renderer.add_string(`</${node.name}>`);
170176
}
171177
}
172178
}
179+
180+
// similar logic from `compile/render_dom/wrappers/Fragment`
181+
// We want to remove trailing whitespace inside an element/component/block,
182+
// *unless* there is no whitespace between this node and its next sibling
183+
function remove_whitespace_children(children: INode[], next?: INode): INode[] {
184+
const nodes: INode[] = [];
185+
let last_child: INode;
186+
let i = children.length;
187+
while (i--) {
188+
const child = children[i];
189+
190+
if (child.type === 'Text') {
191+
if (child.should_skip()) {
192+
continue;
193+
}
194+
195+
let { data } = child;
196+
197+
if (nodes.length === 0) {
198+
const should_trim = next
199+
? next.type === 'Text' &&
200+
/^\s/.test(next.data) &&
201+
trimmable_at(child, next)
202+
: !child.has_ancestor('EachBlock');
203+
204+
if (should_trim) {
205+
data = trim_end(data);
206+
if (!data) continue;
207+
}
208+
}
209+
210+
// glue text nodes (which could e.g. be separated by comments) together
211+
if (last_child && last_child.type === 'Text') {
212+
last_child.data = data + last_child.data;
213+
continue;
214+
}
215+
216+
nodes.unshift(child);
217+
link(last_child, last_child = child);
218+
} else {
219+
nodes.unshift(child);
220+
link(last_child, last_child = child);
221+
}
222+
}
223+
224+
const first = nodes[0];
225+
if (first && first.type === 'Text') {
226+
first.data = trim_start(first.data);
227+
if (!first.data) {
228+
first.var = null;
229+
nodes.shift();
230+
231+
if (nodes[0]) {
232+
nodes[0].prev = null;
233+
}
234+
}
235+
}
236+
237+
return nodes;
238+
}
239+
240+
function trimmable_at(child: INode, next_sibling: INode): boolean {
241+
// Whitespace is trimmable if one of the following is true:
242+
// The child and its sibling share a common nearest each block (not at an each block boundary)
243+
// The next sibling's previous node is an each block
244+
return (
245+
next_sibling.find_nearest(/EachBlock/) ===
246+
child.find_nearest(/EachBlock/) || next_sibling.prev.type === 'EachBlock'
247+
);
248+
}

src/compiler/compile/render_ssr/handlers/InlineComponent.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
8282
});
8383

8484
slot_scopes.forEach(({ input, output }, name) => {
85-
slot_fns.push(
86-
p`${name}: (${input}) => ${output}`
87-
);
85+
if (!is_empty_template_literal(output)) {
86+
slot_fns.push(
87+
p`${name}: (${input}) => ${output}`
88+
);
89+
}
8890
});
8991
}
9092

@@ -94,3 +96,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
9496

9597
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
9698
}
99+
100+
function is_empty_template_literal(template_literal) {
101+
return (
102+
template_literal.expressions.length === 0 &&
103+
template_literal.quasis.length === 1 &&
104+
template_literal.quasis[0].value.raw.replace(/[\n\t]*/, '') === ""
105+
);
106+
}

src/compiler/utils/link.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export function link<T extends { next?: T; prev?: T }>(next: T, prev: T) {
2+
prev.next = next;
3+
if (next) next.prev = prev;
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<div>
2+
<slot><p class='default'>default fallback content</p></slot>
3+
<slot name='bar'>bar fallback</slot>
4+
</div>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default {
2+
html: `
3+
<div>
4+
<p class="default">default fallback content</p>
5+
<input slot="bar">
6+
</div>
7+
8+
<div>
9+
<p class="default">default fallback content</p>
10+
bar fallback
11+
</div>
12+
`
13+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import Nested from "./Nested.svelte";
3+
</script>
4+
5+
<Nested>
6+
<input slot="bar">
7+
</Nested>
8+
9+
<Nested>
10+
</Nested>

0 commit comments

Comments
 (0)