Skip to content

Commit 1497cf4

Browse files
committed
feat: conditional slots csr
1 parent 5a3a1e4 commit 1497cf4

File tree

72 files changed

+1408
-115
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+1408
-115
lines changed

src/compiler/compile/compiler_errors.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -285,5 +285,16 @@ export default {
285285
invalid_style_directive_modifier: (valid: string) => ({
286286
code: 'invalid-style-directive-modifier',
287287
message: `Valid modifiers for style directives are: ${valid}`
288-
})
288+
}),
289+
invalid_mix_element_and_conditional_slot: {
290+
code: 'invalid-mix-element-and-conditional-slot',
291+
message: 'Do not mix <svelte:fragment> and other elements under the same {#if}{:else} group. Default slot content should be wrapped with <svelte:fragment slot="default">'
292+
},
293+
duplicate_slot_name_in_component: (slot_name: string, component_name: string) => ({
294+
code: 'duplicate-slot-name-in-component',
295+
message:
296+
slot_name === "default"
297+
? 'Found elements without slot attribute when using slot="default"'
298+
: `Duplicate slot name "${slot_name}" in <${component_name}>`,
299+
}),
289300
};

src/compiler/compile/nodes/InlineComponent.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { INode } from './interfaces';
1111
import { TemplateNode } from '../../interfaces';
1212
import compiler_errors from '../compiler_errors';
1313
import { regex_only_whitespaces } from '../../utils/patterns';
14+
import SlotTemplateIfBlock, { validate_get_slot_names } from './SlotTemplateIfBlock';
15+
import SlotTemplate from './SlotTemplate';
1416

1517
export default class InlineComponent extends Node {
1618
type: 'InlineComponent';
@@ -52,7 +54,7 @@ export default class InlineComponent extends Node {
5254
this.css_custom_properties.push(new Attribute(component, this, scope, node));
5355
break;
5456
}
55-
// fallthrough
57+
// fallthrough
5658
case 'Spread':
5759
this.attributes.push(new Attribute(component, this, scope, node));
5860
break;
@@ -145,9 +147,15 @@ export default class InlineComponent extends Node {
145147
info.children.splice(i, 1);
146148
} else if (child.type === 'Comment' && children.length > 0) {
147149
children[children.length - 1].children.unshift(child);
150+
info.children.splice(i, 1);
151+
} else if (child.type === 'IfBlock' && child.children.some(if_child => if_child.type === 'SlotTemplate')) {
152+
children.push({
153+
...child,
154+
type: 'SlotTemplateIfBlock'
155+
});
156+
info.children.splice(i, 1);
148157
}
149158
}
150-
151159
if (info.children.some(node => not_whitespace_text(node))) {
152160
children.push({
153161
start: info.start,
@@ -159,12 +167,18 @@ export default class InlineComponent extends Node {
159167
});
160168
}
161169

162-
this.children = map_children(component, this, this.scope, children);
170+
this.children = map_children(component, this, this.scope, children.reverse());
171+
172+
this.validate_duplicate_slot_name();
163173
}
164174

165175
get slot_template_name() {
166176
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
167177
}
178+
179+
validate_duplicate_slot_name() {
180+
validate_get_slot_names(this.children, this.component, this.name);
181+
}
168182
}
169183

170184
function not_whitespace_text(node) {

src/compiler/compile/nodes/SlotTemplate.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@ export default class SlotTemplate extends Node {
6666
}
6767

6868
validate_slot_template_placement() {
69-
if (this.parent.type !== 'InlineComponent') {
69+
let parent = this.parent;
70+
while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') parent = parent.parent;
71+
if (parent.type === 'IfBlock' || parent.type === 'ElseBlock') {
72+
return this.component.error(this, compiler_errors.invalid_mix_element_and_conditional_slot);
73+
}
74+
if (parent.type !== 'InlineComponent') {
7075
return this.component.error(this, compiler_errors.invalid_slotted_content_fragment);
7176
}
7277
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Component from '../Component';
2+
import Expression from './shared/Expression';
3+
import TemplateScope from './shared/TemplateScope';
4+
import Node from './shared/Node';
5+
import Let from './Let';
6+
import Attribute from './Attribute';
7+
import { INode } from './interfaces';
8+
import compiler_errors from '../compiler_errors';
9+
import get_const_tags from './shared/get_const_tags';
10+
import ConstTag from './ConstTag';
11+
import AbstractBlock from './shared/AbstractBlock';
12+
import { regex_only_whitespaces } from '../../utils/patterns';
13+
14+
15+
export default class SlotTemplateElseBlock extends AbstractBlock {
16+
type: 'SlotTemplateElseBlock';
17+
expression: Expression;
18+
scope: TemplateScope;
19+
const_tags: ConstTag[];
20+
21+
constructor(
22+
component: Component,
23+
parent: INode,
24+
scope: TemplateScope,
25+
info: any
26+
) {
27+
super(component, parent, scope, info);
28+
this.scope = scope.child();
29+
30+
const children = [];
31+
for (const child of info.children) {
32+
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
33+
children.push(child);
34+
} else if (child.type === 'Comment') {
35+
// ignore
36+
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
37+
// ignore
38+
} else if (child.type === 'IfBlock') {
39+
children.push({
40+
...child,
41+
type: 'SlotTemplateIfBlock'
42+
});
43+
} else {
44+
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
45+
}
46+
}
47+
48+
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import SlotTemplateElseBlock from './SlotTemplateElseBlock';
2+
import Component from '../Component';
3+
import AbstractBlock from './shared/AbstractBlock';
4+
import Expression from './shared/Expression';
5+
import TemplateScope from './shared/TemplateScope';
6+
import Node from './shared/Node';
7+
import compiler_errors from '../compiler_errors';
8+
import get_const_tags from './shared/get_const_tags';
9+
import { TemplateNode } from '../../interfaces';
10+
import ConstTag from './ConstTag';
11+
import { regex_only_whitespaces } from '../../utils/patterns';
12+
import SlotTemplate from './SlotTemplate';
13+
import { INode } from './interfaces';
14+
15+
16+
export default class SlotTemplateIfBlock extends AbstractBlock {
17+
type: 'SlotTemplateIfBlock';
18+
expression: Expression;
19+
else: SlotTemplateElseBlock;
20+
scope: TemplateScope;
21+
const_tags: ConstTag[];
22+
slot_names = new Set<string>();
23+
24+
constructor(
25+
component: Component,
26+
parent: Node,
27+
scope: TemplateScope,
28+
info: TemplateNode
29+
) {
30+
super(component, parent, scope, info);
31+
this.scope = scope.child();
32+
33+
const children = [];
34+
for (const child of info.children) {
35+
if (child.type === 'SlotTemplate' || child.type === 'ConstTag') {
36+
children.push(child);
37+
} else if (child.type === 'Comment') {
38+
// ignore
39+
} else if (child.type === 'Text' && regex_only_whitespaces.test(child.data)) {
40+
// ignore
41+
} else if (child.type === 'IfBlock') {
42+
children.push({
43+
...child,
44+
type: 'SlotTemplateIfBlock'
45+
});
46+
} else {
47+
this.component.error(child, compiler_errors.invalid_mix_element_and_conditional_slot);
48+
}
49+
}
50+
51+
this.expression = new Expression(component, this, this.scope, info.expression);
52+
([this.const_tags, this.children] = get_const_tags(children, component, this, this));
53+
54+
this.else = info.else
55+
? new SlotTemplateElseBlock(component, this, scope, { ...info.else, type: 'SlotTemplateElseBlock' })
56+
: null;
57+
}
58+
59+
validate_duplicate_slot_name(component_name: string): Map<string, SlotTemplate> {
60+
const if_slot_names = validate_get_slot_names(this.children, this.component, component_name);
61+
if (!this.else) {
62+
return if_slot_names;
63+
}
64+
65+
const else_slot_names = validate_get_slot_names(this.else.children, this.component, component_name);
66+
return new Map([...if_slot_names, ...else_slot_names]);
67+
}
68+
}
69+
70+
export function validate_get_slot_names(children: Array<INode>, component: Component, component_name: string) {
71+
const slot_names = new Map<string, SlotTemplate>();
72+
function add_slot_name(slot_name: string, child: SlotTemplate) {
73+
if (slot_names.has(slot_name)) {
74+
component.error(child, compiler_errors.duplicate_slot_name_in_component(slot_name, component_name));
75+
}
76+
slot_names.set(slot_name, child);
77+
}
78+
79+
for (const child of children) {
80+
if (child.type === 'SlotTemplateIfBlock') {
81+
const child_slot_names = child.validate_duplicate_slot_name(component_name);
82+
for (const [slot_name, child] of child_slot_names) {
83+
add_slot_name(slot_name, child);
84+
}
85+
} else if (child.type === 'SlotTemplate') {
86+
add_slot_name(child.slot_template_name, child);
87+
}
88+
}
89+
return slot_names;
90+
}

src/compiler/compile/nodes/interfaces.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import ThenBlock from './ThenBlock';
3333
import Title from './Title';
3434
import Transition from './Transition';
3535
import Window from './Window';
36+
import SlotTemplateIfBlock from './SlotTemplateIfBlock';
37+
import SlottemplateElseBlock from './SlotTemplateElseBlock';
3638

3739
// note: to write less types each of types in union below should have type defined as literal
3840
// https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
@@ -63,6 +65,8 @@ export type INode = Action
6365
| RawMustacheTag
6466
| Slot
6567
| SlotTemplate
68+
| SlotTemplateIfBlock
69+
| SlottemplateElseBlock
6670
| StyleDirective
6771
| Tag
6872
| Text
@@ -78,4 +82,6 @@ export type INodeAllowConstTag =
7882
| CatchBlock
7983
| ThenBlock
8084
| InlineComponent
81-
| SlotTemplate;
85+
| SlotTemplate
86+
| SlotTemplateIfBlock
87+
| SlottemplateElseBlock;

src/compiler/compile/nodes/shared/map_children.ts

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import Title from '../Title';
1919
import Window from '../Window';
2020
import { TemplateNode } from '../../../interfaces';
2121
import { push_array } from '../../../utils/push_array';
22+
import SlotTemplateIfBlock from '../SlotTemplateIfBlock';
2223

2324
export type Children = ReturnType<typeof map_children>;
2425

@@ -40,6 +41,7 @@ function get_constructor(type) {
4041
case 'DebugTag': return DebugTag;
4142
case 'Slot': return Slot;
4243
case 'SlotTemplate': return SlotTemplate;
44+
case 'SlotTemplateIfBlock': return SlotTemplateIfBlock;
4345
case 'Text': return Text;
4446
case 'Title': return Title;
4547
case 'Window': return Window;

src/compiler/compile/render_dom/index.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default function dom(
8686
let compute_slots: Node[] | undefined;
8787
if (uses_slots) {
8888
compute_slots = b`
89-
const $$slots = @compute_slots(#slots);
89+
let $$slots = @compute_slots(#slots);
9090
`;
9191
}
9292

@@ -114,7 +114,13 @@ export default function dom(
114114
b`if ('${prop.export_name}' in ${$$props}) ${renderer.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};`
115115
)}
116116
${component.slots.size > 0 &&
117-
b`if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`}
117+
b`
118+
if ('$$scope' in ${$$props}) ${renderer.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};
119+
if ('$$slots' in ${$$props}) {
120+
${renderer.invalidate('#slots', x`#slots = ${$$props}.$$slots`)};
121+
${uses_slots ? renderer.invalidate('$$slots', x`$$slots = @compute_slots(#slots)`) : null}
122+
}
123+
`}
118124
}
119125
`
120126
: null;

0 commit comments

Comments
 (0)