Skip to content

Commit 9b2cd4c

Browse files
committed
implement <svelte:fragment>
add validation and test replace svelte:slot -> svelte:fragment slot as a sugar syntax fix eslint
1 parent 148b610 commit 9b2cd4c

File tree

109 files changed

+1193
-205
lines changed

Some content is hidden

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

109 files changed

+1193
-205
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Component from '../Component';
2+
import TemplateScope from './shared/TemplateScope';
3+
import Node from './shared/Node';
4+
import Let from './Let';
5+
import { INode } from './interfaces';
6+
7+
export default class DefaultSlotTemplate extends Node {
8+
type: 'SlotTemplate';
9+
scope: TemplateScope;
10+
children: INode[];
11+
lets: Let[] = [];
12+
slot_template_name = 'default';
13+
14+
constructor(
15+
component: Component,
16+
parent: INode,
17+
scope: TemplateScope,
18+
info: any,
19+
lets: Let[],
20+
children: INode[]
21+
) {
22+
super(component, parent, scope, info);
23+
this.type = 'SlotTemplate';
24+
this.children = children;
25+
this.scope = scope;
26+
this.lets = lets;
27+
}
28+
}

src/compiler/compile/nodes/Element.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ export default class Element extends Node {
429429
component.slot_outlets.add(name);
430430
}
431431

432-
if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) {
432+
if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) {
433433
component.error(attribute, {
434434
code: 'invalid-slotted-content',
435435
message: 'Element with a slot=\'...\' attribute must be a child of a component or a descendant of a custom element'
@@ -863,6 +863,10 @@ export default class Element extends Node {
863863
);
864864
}
865865
}
866+
867+
get slot_template_name() {
868+
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
869+
}
866870
}
867871

868872
function should_have_attribute(

src/compiler/compile/nodes/InlineComponent.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,6 @@ export default class InlineComponent extends Node {
4545
});
4646

4747
case 'Attribute':
48-
if (node.name === 'slot') {
49-
component.error(node, {
50-
code: 'invalid-prop',
51-
message: "'slot' is reserved for future use in named slots"
52-
});
53-
}
5448
// fallthrough
5549
case 'Spread':
5650
this.attributes.push(new Attribute(component, this, scope, node));
@@ -111,6 +105,57 @@ export default class InlineComponent extends Node {
111105
});
112106
});
113107

114-
this.children = map_children(component, this, this.scope, info.children);
108+
const children = [];
109+
for (let i=info.children.length - 1; i >= 0; i--) {
110+
const child = info.children[i];
111+
if (child.type === 'SlotTemplate') {
112+
children.push(child);
113+
info.children.splice(i, 1);
114+
} else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) {
115+
const slot_template = {
116+
start: child.start,
117+
end: child.end,
118+
type: 'SlotTemplate',
119+
name: 'svelte:fragment',
120+
attributes: [],
121+
children: [child]
122+
};
123+
124+
// transfer attributes
125+
for (let i=child.attributes.length - 1; i >= 0; i--) {
126+
const attribute = child.attributes[i];
127+
if (attribute.type === 'Let') {
128+
slot_template.attributes.push(attribute);
129+
child.attributes.splice(i, 1);
130+
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
131+
slot_template.attributes.push(attribute);
132+
}
133+
}
134+
135+
children.push(slot_template);
136+
info.children.splice(i, 1);
137+
}
138+
}
139+
140+
if (info.children.some(node => not_whitespace_text(node))) {
141+
children.push({
142+
start: info.start,
143+
end: info.end,
144+
type: 'SlotTemplate',
145+
name: 'svelte:fragment',
146+
attributes: [],
147+
children: info.children
148+
});
149+
}
150+
151+
this.children = map_children(component, this, this.scope, children);
152+
}
153+
154+
get slot_template_name() {
155+
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
115156
}
116157
}
158+
159+
function not_whitespace_text(node) {
160+
return !(node.type === 'Text' && /^\s+$/.test(node.data));
161+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import map_children from './shared/map_children';
2+
import Component from '../Component';
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+
9+
export default class SlotTemplate extends Node {
10+
type: 'SlotTemplate';
11+
scope: TemplateScope;
12+
children: INode[];
13+
lets: Let[] = [];
14+
slot_attribute: Attribute;
15+
slot_template_name: string = 'default';
16+
17+
constructor(
18+
component: Component,
19+
parent: INode,
20+
scope: TemplateScope,
21+
info: any
22+
) {
23+
super(component, parent, scope, info);
24+
25+
this.validate_slot_template_placement();
26+
27+
const has_let = info.attributes.some((node) => node.type === 'Let');
28+
if (has_let) {
29+
scope = scope.child();
30+
}
31+
32+
info.attributes.forEach((node) => {
33+
switch (node.type) {
34+
case 'Let': {
35+
const l = new Let(component, this, scope, node);
36+
this.lets.push(l);
37+
const dependencies = new Set([l.name.name]);
38+
39+
l.names.forEach((name) => {
40+
scope.add(name, dependencies, this);
41+
});
42+
break;
43+
}
44+
case 'Attribute': {
45+
if (node.name === 'slot') {
46+
this.slot_attribute = new Attribute(component, this, scope, node);
47+
if (!this.slot_attribute.is_static) {
48+
component.error(node, {
49+
code: 'invalid-slot-attribute',
50+
message: 'slot attribute cannot have a dynamic value'
51+
});
52+
}
53+
const value = this.slot_attribute.get_static_value();
54+
if (typeof value === 'boolean') {
55+
component.error(node, {
56+
code: 'invalid-slot-attribute',
57+
message: 'slot attribute value is missing'
58+
});
59+
}
60+
this.slot_template_name = value as string;
61+
break;
62+
}
63+
throw new Error(`Invalid attribute '${node.name}' in <svelte:fragment>`);
64+
}
65+
default:
66+
throw new Error(`Not implemented: ${node.type}`);
67+
}
68+
});
69+
70+
this.scope = scope;
71+
this.children = map_children(component, this, this.scope, info.children);
72+
}
73+
74+
validate_slot_template_placement() {
75+
if (this.parent.type !== 'InlineComponent') {
76+
this.component.error(this, {
77+
code: 'invalid-slotted-content',
78+
message: '<svelte:fragment> must be a child of a component'
79+
});
80+
}
81+
}
82+
}

src/compiler/compile/nodes/Text.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default class Text extends Node {
2929
should_skip() {
3030
if (/\S/.test(this.data)) return false;
3131

32-
const parent_element = this.find_nearest(/(?:Element|InlineComponent|Head)/);
32+
const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/);
3333
if (!parent_element) return false;
3434

3535
if (parent_element.type === 'Head') return true;

src/compiler/compile/nodes/interfaces.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import Options from './Options';
2525
import PendingBlock from './PendingBlock';
2626
import RawMustacheTag from './RawMustacheTag';
2727
import Slot from './Slot';
28+
import SlotTemplate from './SlotTemplate';
29+
import DefaultSlotTemplate from './DefaultSlotTemplate';
2830
import Text from './Text';
2931
import ThenBlock from './ThenBlock';
3032
import Title from './Title';
@@ -58,6 +60,8 @@ export type INode = Action
5860
| PendingBlock
5961
| RawMustacheTag
6062
| Slot
63+
| SlotTemplate
64+
| DefaultSlotTemplate
6165
| Tag
6266
| Text
6367
| ThenBlock

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import ThenBlock from '../ThenBlock';
33
import CatchBlock from '../CatchBlock';
44
import InlineComponent from '../InlineComponent';
55
import Element from '../Element';
6+
import SlotTemplate from '../SlotTemplate';
67

7-
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element;
8+
type NodeWithScope = EachBlock | ThenBlock | CatchBlock | InlineComponent | Element | SlotTemplate;
89

910
export default class TemplateScope {
1011
names: Set<string>;
@@ -40,7 +41,7 @@ export default class TemplateScope {
4041

4142
is_let(name: string) {
4243
const owner = this.get_owner(name);
43-
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent');
44+
return owner && (owner.type === 'Element' || owner.type === 'InlineComponent' || owner.type === 'SlotTemplate');
4445
}
4546

4647
is_await(name: string) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Options from '../Options';
1212
import RawMustacheTag from '../RawMustacheTag';
1313
import DebugTag from '../DebugTag';
1414
import Slot from '../Slot';
15+
import SlotTemplate from '../SlotTemplate';
1516
import Text from '../Text';
1617
import Title from '../Title';
1718
import Window from '../Window';
@@ -35,6 +36,7 @@ function get_constructor(type) {
3536
case 'RawMustacheTag': return RawMustacheTag;
3637
case 'DebugTag': return DebugTag;
3738
case 'Slot': return Slot;
39+
case 'SlotTemplate': return SlotTemplate;
3840
case 'Text': return Text;
3941
case 'Title': return Title;
4042
case 'Window': return Window;

src/compiler/compile/render_dom/wrappers/Element/create_slot_block.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/compiler/compile/render_dom/wrappers/Element/index.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { extract_names } from 'periscopic';
2525
import Action from '../../../nodes/Action';
2626
import MustacheTagWrapper from '../MustacheTag';
2727
import RawMustacheTagWrapper from '../RawMustacheTag';
28-
import create_slot_block from './create_slot_block';
2928

3029
interface BindingGroup {
3130
events: string[];
@@ -141,7 +140,6 @@ export default class ElementWrapper extends Wrapper {
141140
event_handlers: EventHandler[];
142141
class_dependencies: string[];
143142

144-
slot_block: Block;
145143
select_binding_dependencies?: Set<string>;
146144

147145
var: any;
@@ -174,9 +172,6 @@ export default class ElementWrapper extends Wrapper {
174172
}
175173

176174
this.attributes = this.node.attributes.map(attribute => {
177-
if (attribute.name === 'slot') {
178-
block = create_slot_block(attribute, this, block);
179-
}
180175
if (attribute.name === 'style') {
181176
return new StyleAttributeWrapper(this, block, attribute);
182177
}
@@ -231,26 +226,13 @@ export default class ElementWrapper extends Wrapper {
231226
}
232227

233228
this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling);
234-
235-
if (this.slot_block) {
236-
block.parent.add_dependencies(block.dependencies);
237-
238-
// appalling hack
239-
const index = block.parent.wrappers.indexOf(this);
240-
block.parent.wrappers.splice(index, 1);
241-
block.wrappers.push(this);
242-
}
243229
}
244230

245231
render(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
246232
const { renderer } = this;
247233

248234
if (this.node.name === 'noscript') return;
249235

250-
if (this.slot_block) {
251-
block = this.slot_block;
252-
}
253-
254236
const node = this.var;
255237
const nodes = parent_nodes && block.get_unique_name(`${this.var.name}_nodes`); // if we're in unclaimable territory, i.e. <head>, parent_nodes is null
256238
const children = x`@children(${this.node.name === 'template' ? x`${node}.content` : node})`;

0 commit comments

Comments
 (0)