Skip to content

Commit 2d06e72

Browse files
committed
slot as a sugar syntax
1 parent 61af670 commit 2d06e72

File tree

32 files changed

+232
-138
lines changed

32 files changed

+232
-138
lines changed
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

+1-2
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ export default class Element extends Node {
409409
component.slot_outlets.add(name);
410410
}
411411

412-
if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) {
412+
if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) {
413413
component.error(attribute, {
414414
code: `invalid-slotted-content`,
415415
message: `Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`,
@@ -809,7 +809,6 @@ export default class Element extends Node {
809809
}
810810

811811
get slot_template_name() {
812-
// attribute.get_static_value
813812
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
814813
}
815814
}

src/compiler/compile/nodes/InlineComponent.ts

+52-7
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.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+
}

src/compiler/compile/nodes/interfaces.ts

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import PendingBlock from './PendingBlock';
2525
import RawMustacheTag from './RawMustacheTag';
2626
import Slot from './Slot';
2727
import SlotTemplate from './SlotTemplate';
28+
import DefaultSlotTemplate from './DefaultSlotTemplate';
2829
import Text from './Text';
2930
import ThenBlock from './ThenBlock';
3031
import Title from './Title';
@@ -58,6 +59,7 @@ export type INode = Action
5859
| RawMustacheTag
5960
| Slot
6061
| SlotTemplate
62+
| DefaultSlotTemplate
6163
| Tag
6264
| Text
6365
| ThenBlock

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

-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ export default class ElementWrapper extends Wrapper {
140140
event_handlers: EventHandler[];
141141
class_dependencies: string[];
142142

143-
slot_block: Block;
144143
select_binding_dependencies?: Set<string>;
145144

146145
var: any;

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

+6-44
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import { sanitize } from '../../../../utils/names';
99
import add_to_set from '../../../utils/add_to_set';
1010
import { b, x, p } from 'code-red';
1111
import Attribute from '../../../nodes/Attribute';
12-
import create_debugging_comment from '../shared/create_debugging_comment';
13-
import { get_slot_definition } from '../shared/get_slot_definition';
1412
import TemplateScope from '../../../nodes/shared/TemplateScope';
1513
import is_dynamic from '../shared/is_dynamic';
1614
import bind_this from '../shared/bind_this';
1715
import { Node, Identifier, ObjectExpression } from 'estree';
1816
import EventHandler from '../Element/EventHandler';
1917
import { extract_names } from 'periscopic';
2018
import mark_each_block_bindings from '../shared/mark_each_block_bindings';
19+
import SlotTemplate from '../../../nodes/SlotTemplate';
2120

2221
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
2322

@@ -27,7 +26,6 @@ export default class InlineComponentWrapper extends Wrapper {
2726
node: InlineComponent;
2827
fragment: FragmentWrapper;
2928
children: Array<Wrapper | FragmentWrapper> = [];
30-
default_slot_block: Block;
3129

3230
constructor(
3331
renderer: Renderer,
@@ -80,53 +78,17 @@ export default class InlineComponentWrapper extends Wrapper {
8078
});
8179
});
8280

83-
const children = this.node.children.slice();
84-
for (let i=children.length - 1; i>=0;) {
85-
const child = children[i];
86-
if (child.type === 'SlotTemplate' || (child.type === 'Element' && child.attributes.find(attribute => attribute.name === 'slot'))) {
87-
const slot_template = new SlotTemplateWrapper(renderer, block, this, child, strip_whitespace, next_sibling);
88-
this.children.push(slot_template);
89-
children.splice(i, 1);
90-
continue;
91-
}
92-
93-
i--;
94-
}
95-
96-
if (this.slots.has('default') && children.filter(node => !(node.type === 'Text' && node.data.trim() === ''))) {
97-
throw new Error('Found elements without slot attribute when using slot="default"');
98-
}
99-
100-
const default_slot = block.child({
101-
comment: create_debugging_comment(node, renderer.component),
102-
name: renderer.component.get_unique_name(`create_default_slot`),
103-
type: 'slot'
104-
});
105-
106-
this.renderer.blocks.push(default_slot);
107-
this.default_slot_block = default_slot;
108-
109-
this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
110-
const fragment = new FragmentWrapper(renderer, default_slot, children, this, strip_whitespace, next_sibling);
111-
this.children.push(fragment);
112-
113-
const dependencies: Set<string> = new Set();
114-
115-
// TODO is this filtering necessary? (I *think* so)
116-
default_slot.dependencies.forEach(name => {
117-
if (!this.node.scope.is_let(name)) {
118-
dependencies.add(name);
119-
}
120-
});
121-
122-
block.add_dependencies(dependencies);
81+
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
12382
}
12483

12584
block.add_outro();
12685
}
12786

12887
set_slot(name: string, slot_definition: SlotDefinition) {
12988
if (this.slots.has(name)) {
89+
if (name === 'default') {
90+
throw new Error('Found elements without slot attribute when using slot="default"');
91+
}
13092
throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`);
13193
}
13294
this.slots.set(name, slot_definition);
@@ -167,7 +129,7 @@ export default class InlineComponentWrapper extends Wrapper {
167129

168130
this.children.forEach((child) => {
169131
this.renderer.add_to_context('$$scope', true);
170-
child.render(this.default_slot_block, null, x`#nodes` as Identifier);
132+
child.render(block, null, x`#nodes` as Identifier);
171133
});
172134

173135
let props;

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

+1-29
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import { is_void } from '../../../utils/names';
22
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
3-
import { get_slot_scope } from './shared/get_slot_scope';
43
import { boolean_attributes } from './shared/boolean_attributes';
54
import Renderer, { RenderOptions } from '../Renderer';
65
import Element from '../../nodes/Element';
76
import { x } from 'code-red';
87
import Expression from '../../nodes/shared/Expression';
98
import remove_whitespace_children from './utils/remove_whitespace_children';
109

11-
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
12-
slot_scopes: Map<any, any>;
13-
}) {
10+
export default function(node: Element, renderer: Renderer, options: RenderOptions) {
1411

1512
const children = remove_whitespace_children(node.children, node.next);
1613

@@ -23,13 +20,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
2320
node.attributes.some((attribute) => attribute.name === 'contenteditable')
2421
);
2522

26-
const slot = node.get_static_attribute_value('slot');
27-
const nearest_inline_component = node.find_nearest(/InlineComponent/);
28-
29-
if (slot && nearest_inline_component) {
30-
renderer.push();
31-
}
32-
3323
renderer.add_string(`<${node.name}`);
3424

3525
const class_expression_list = node.classes.map(class_directive => {
@@ -148,24 +138,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
148138
if (!is_void(node.name)) {
149139
renderer.add_string(`</${node.name}>`);
150140
}
151-
} else if (slot && nearest_inline_component) {
152-
renderer.render(children, options);
153-
154-
if (!is_void(node.name)) {
155-
renderer.add_string(`</${node.name}>`);
156-
}
157-
158-
const lets = node.lets;
159-
const seen = new Set(lets.map(l => l.name.name));
160-
161-
nearest_inline_component.lets.forEach(l => {
162-
if (!seen.has(l.name.name)) lets.push(l);
163-
});
164-
165-
options.slot_scopes.set(slot, {
166-
input: get_slot_scope(node.lets),
167-
output: renderer.pop()
168-
});
169141
} else {
170142
renderer.render(children, options);
171143

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

+4-23
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { string_literal } from '../../utils/stringify';
22
import Renderer, { RenderOptions } from '../Renderer';
3-
import { get_slot_scope } from './shared/get_slot_scope';
43
import InlineComponent from '../../nodes/InlineComponent';
5-
import remove_whitespace_children from './utils/remove_whitespace_children';
64
import { p, x } from 'code-red';
75

86
function get_prop_value(attribute) {
@@ -68,28 +66,19 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
6866

6967
const slot_fns = [];
7068

71-
const children = remove_whitespace_children(node.children, node.next);
69+
const children = node.children;
7270

7371
if (children.length) {
7472
const slot_scopes = new Map();
7573

76-
renderer.push();
77-
7874
renderer.render(children, Object.assign({}, options, {
7975
slot_scopes
8076
}));
8177

82-
slot_scopes.set('default', {
83-
input: get_slot_scope(node.lets),
84-
output: renderer.pop()
85-
});
86-
8778
slot_scopes.forEach(({ input, output }, name) => {
88-
if (!is_empty_template_literal(output)) {
89-
slot_fns.push(
90-
p`${name}: (${input}) => ${output}`
91-
);
92-
}
79+
slot_fns.push(
80+
p`${name}: (${input}) => ${output}`
81+
);
9382
});
9483
}
9584

@@ -99,11 +88,3 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
9988

10089
renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
10190
}
102-
103-
function is_empty_template_literal(template_literal) {
104-
return (
105-
template_literal.expressions.length === 0 &&
106-
template_literal.quasis.length === 1 &&
107-
template_literal.quasis[0].value.raw === ""
108-
);
109-
}

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

+25-6
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import SlotTemplate from '../../nodes/SlotTemplate';
33
import remove_whitespace_children from './utils/remove_whitespace_children';
44
import { get_slot_scope } from './shared/get_slot_scope';
55
import InlineComponent from '../../nodes/InlineComponent';
6+
import Element from '../../nodes/Element';
67

7-
export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & {
8+
export default function(node: SlotTemplate | Element | InlineComponent, renderer: Renderer, options: RenderOptions & {
89
slot_scopes: Map<any, any>;
910
}) {
1011
const parent_inline_component = node.parent as InlineComponent;
11-
const children = remove_whitespace_children(node.children, node.next);
12+
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next);
1213

1314
renderer.push();
1415
renderer.render(children, options);
@@ -19,8 +20,26 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
1920
if (!seen.has(l.name.name)) lets.push(l);
2021
});
2122

22-
options.slot_scopes.set(node.slot_template_name, {
23-
input: get_slot_scope(node.lets),
24-
output: renderer.pop()
25-
});
23+
const slot_fragment_content = renderer.pop();
24+
if (!is_empty_template_literal(slot_fragment_content)) {
25+
if (options.slot_scopes.has(node.slot_template_name)) {
26+
if (node.slot_template_name === 'default') {
27+
throw new Error('Found elements without slot attribute when using slot="default"');
28+
}
29+
throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`);
30+
}
31+
32+
options.slot_scopes.set(node.slot_template_name, {
33+
input: get_slot_scope(node.lets),
34+
output: slot_fragment_content
35+
});
36+
}
2637
}
38+
39+
function is_empty_template_literal(template_literal) {
40+
return (
41+
template_literal.expressions.length === 0 &&
42+
template_literal.quasis.length === 1 &&
43+
template_literal.quasis[0].value.raw === ""
44+
);
45+
}

0 commit comments

Comments
 (0)