Skip to content

Commit 5b030d0

Browse files
committed
feat allow multiple elements assigned with same slot
1 parent 109639c commit 5b030d0

File tree

9 files changed

+212
-89
lines changed

9 files changed

+212
-89
lines changed

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

+8
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,14 @@ export default class ElementWrapper extends Wrapper {
205205
get_slot_definition(child_block, scope, lets)
206206
);
207207
this.renderer.blocks.push(child_block);
208+
} else {
209+
const { lets } = this.node;
210+
const seen = new Set(lets.map(l => l.name.name));
211+
212+
(owner as InlineComponentWrapper).node.lets.forEach(l => {
213+
if (!seen.has(l.name.name)) lets.push(l);
214+
});
215+
(owner as InlineComponentWrapper).slots.get(name).add_let_binding(lets);
208216
}
209217

210218
this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;

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

+3-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ import { b, x, p } from 'code-red';
99
import Attribute from '../../../nodes/Attribute';
1010
import get_object from '../../../utils/get_object';
1111
import create_debugging_comment from '../shared/create_debugging_comment';
12-
import { get_slot_definition } from '../shared/get_slot_definition';
12+
import { get_slot_definition, SlotDefinition } from '../shared/get_slot_definition';
1313
import EachBlock from '../../../nodes/EachBlock';
14-
import TemplateScope from '../../../nodes/shared/TemplateScope';
1514
import is_dynamic from '../shared/is_dynamic';
1615
import bind_this from '../shared/bind_this';
1716
import { Node, Identifier, ObjectExpression } from 'estree';
@@ -20,7 +19,7 @@ import { extract_names } from 'periscopic';
2019

2120
export default class InlineComponentWrapper extends Wrapper {
2221
var: Identifier;
23-
slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
22+
slots: Map<string, SlotDefinition> = new Map();
2423
node: InlineComponent;
2524
fragment: FragmentWrapper;
2625

@@ -130,7 +129,7 @@ export default class InlineComponentWrapper extends Wrapper {
130129
? [
131130
p`$$slots: {
132131
${Array.from(this.slots).map(([name, slot]) => {
133-
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`;
132+
return p`${name}: ${slot.render()}`;
134133
})}
135134
}`,
136135
p`$$scope: {

src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts

+105-71
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,112 @@ import Block from '../../Block';
44
import TemplateScope from '../../../nodes/shared/TemplateScope';
55
import { BinaryExpression } from 'estree';
66

7-
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
8-
if (lets.length === 0) return { block, scope };
9-
10-
const input = {
11-
type: 'ObjectPattern',
12-
properties: lets.map(l => ({
13-
type: 'Property',
14-
kind: 'init',
15-
key: l.name,
16-
value: l.value || l.name
17-
}))
18-
};
19-
20-
const names: Set<string> = new Set();
21-
lets.forEach(l => {
22-
l.names.forEach(name => {
23-
names.add(name);
24-
});
25-
});
26-
27-
const context = {
28-
type: 'ObjectExpression',
29-
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
30-
};
31-
32-
const { context_lookup } = block.renderer;
33-
34-
// i am well aware that this code is gross
35-
// TODO make it less gross
36-
const changes = {
37-
type: 'ParenthesizedExpression',
38-
get expression() {
39-
if (block.renderer.context_overflow) {
40-
const grouped = [];
41-
42-
Array.from(names).forEach(name => {
43-
const i = context_lookup.get(name).index.value as number;
44-
const g = Math.floor(i / 31);
45-
46-
if (!grouped[g]) grouped[g] = [];
47-
grouped[g].push({ name, n: i % 31 });
48-
});
49-
50-
const elements = [];
51-
52-
for (let g = 0; g < grouped.length; g += 1) {
53-
elements[g] = grouped[g]
54-
? grouped[g]
55-
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
56-
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
57-
: x`0`;
58-
}
7+
export function get_slot_definition(
8+
block: Block,
9+
scope: TemplateScope,
10+
lets: Let[]
11+
) {
12+
return new SlotDefinition(block, scope, lets);
13+
}
14+
15+
export class SlotDefinition {
16+
block: Block;
17+
scope: TemplateScope;
18+
lets: Let[];
19+
lets_set: Set<string>;
5920

60-
return {
61-
type: 'ArrayExpression',
62-
elements
63-
};
21+
constructor(block: Block, scope: TemplateScope, lets: Let[]) {
22+
this.block = block;
23+
this.scope = scope;
24+
this.lets = lets;
25+
this.lets_set = new Set(this.lets.map(l => l.name.name));
26+
}
27+
28+
add_let_binding(lets: Let[]) {
29+
for (const l of lets) {
30+
if (!this.lets_set.has(l.name.name)) {
31+
this.lets_set.add(l.name.name);
32+
this.lets.push(l);
6433
}
34+
}
35+
}
6536

66-
return Array.from(names)
67-
.map(name => {
68-
const i = context_lookup.get(name).index.value as number;
69-
return x`${name} ? ${1 << i} : 0`;
70-
})
71-
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
37+
render() {
38+
if (this.lets.length === 0) {
39+
return x`[${this.block.name}, null, null]`;
7240
}
73-
};
74-
75-
return {
76-
block,
77-
scope,
78-
get_context: x`${input} => ${context}`,
79-
get_changes: x`${input} => ${changes}`
80-
};
81-
}
41+
42+
const input = {
43+
type: 'ObjectPattern',
44+
properties: this.lets.map(l => ({
45+
type: 'Property',
46+
kind: 'init',
47+
key: l.name,
48+
value: l.value || l.name,
49+
})),
50+
};
51+
52+
const names: Set<string> = new Set();
53+
this.lets.forEach(l => {
54+
l.names.forEach(name => {
55+
names.add(name);
56+
});
57+
});
58+
59+
const context = {
60+
type: 'ObjectExpression',
61+
properties: Array.from(names).map(
62+
name =>
63+
p`${this.block.renderer.context_lookup.get(name).index}: ${name}`
64+
),
65+
};
66+
67+
const { context_lookup } = this.block.renderer;
68+
const { renderer } = this.block;
69+
70+
// i am well aware that this code is gross
71+
// TODO make it less gross
72+
const changes = {
73+
type: 'ParenthesizedExpression',
74+
get expression() {
75+
if (renderer.context_overflow) {
76+
const grouped = [];
77+
Array.from(names).forEach(name => {
78+
const i = context_lookup.get(name).index.value as number;
79+
const g = Math.floor(i / 31);
80+
81+
if (!grouped[g]) grouped[g] = [];
82+
grouped[g].push({ name, n: i % 31 });
83+
});
84+
85+
const elements = [];
86+
87+
for (let g = 0; g < grouped.length; g += 1) {
88+
elements[g] = grouped[g]
89+
? grouped[g]
90+
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
91+
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
92+
: x`0`;
93+
}
94+
95+
return {
96+
type: 'ArrayExpression',
97+
elements,
98+
};
99+
}
100+
101+
return Array.from(names)
102+
.map(name => {
103+
const i = context_lookup.get(name).index.value as number;
104+
return x`${name} ? ${1 << i} : 0`;
105+
})
106+
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
107+
},
108+
};
109+
110+
const get_context = x`${input} => ${context}`;
111+
const get_changes = x`${input} => ${changes}`;
112+
113+
return x`[${this.block.name}, ${get_context}, ${get_changes}]`;
114+
}
115+
}

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

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
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';
8+
import { SlotDefinition } from './shared/get_slot_definition';
99

1010
export default function(node: Element, renderer: Renderer, options: RenderOptions & {
11-
slot_scopes: Map<any, any>;
11+
slot_scopes: Map<string, SlotDefinition>;
1212
}) {
1313
// awkward special case
1414
let node_contents;
@@ -154,10 +154,11 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
154154
if (!seen.has(l.name.name)) lets.push(l);
155155
});
156156

157-
options.slot_scopes.set(slot, {
158-
input: get_slot_scope(node.lets),
159-
output: renderer.pop()
160-
});
157+
if (options.slot_scopes.has(slot as string)) {
158+
options.slot_scopes.get(slot as string).add(node.lets, renderer.pop());
159+
} else {
160+
options.slot_scopes.set(slot as string, new SlotDefinition(node.lets, renderer.pop()));
161+
}
161162
} else {
162163
renderer.render(node.children, options);
163164

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

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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';
54
import { p, x } from 'code-red';
5+
import { SlotDefinition } from './shared/get_slot_definition';
66

77
function get_prop_value(attribute) {
88
if (attribute.is_true) return x`true`;
@@ -68,22 +68,19 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend
6868
const slot_fns = [];
6969

7070
if (node.children.length) {
71-
const slot_scopes = new Map();
71+
const slot_scopes: Map<string, SlotDefinition> = new Map();
7272

7373
renderer.push();
7474

7575
renderer.render(node.children, Object.assign({}, options, {
7676
slot_scopes
7777
}));
7878

79-
slot_scopes.set('default', {
80-
input: get_slot_scope(node.lets),
81-
output: renderer.pop()
82-
});
79+
slot_scopes.set('default', new SlotDefinition(node.lets, renderer.pop()));
8380

84-
slot_scopes.forEach(({ input, output }, name) => {
81+
slot_scopes.forEach((slot, name) => {
8582
slot_fns.push(
86-
p`${name}: (${input}) => ${output}`
83+
p`${name}: ${slot.render()}`
8784
);
8885
});
8986
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import Let from '../../../nodes/Let';
2+
import { TemplateLiteral } from 'estree';
3+
import { x } from 'code-red';
4+
import { get_slot_scope } from './get_slot_scope';
5+
6+
export function get_slot_definition(
7+
lets: Let[],
8+
output_template: TemplateLiteral
9+
) {
10+
return new SlotDefinition(lets, output_template);
11+
}
12+
13+
export class SlotDefinition {
14+
lets: Let[];
15+
lets_set: Set<string>;
16+
output_template: TemplateLiteral;
17+
18+
constructor(lets: Let[], output_template: TemplateLiteral) {
19+
this.lets = lets;
20+
this.output_template = output_template;
21+
this.lets_set = new Set(this.lets.map(l => l.name.name));
22+
}
23+
24+
add(lets: Let[], output_template: TemplateLiteral) {
25+
for (const l of lets) {
26+
if (!this.lets_set.has(l.name.name)) {
27+
this.lets_set.add(l.name.name);
28+
this.lets.push(l);
29+
}
30+
}
31+
this.output_template = merge_template_literal(
32+
this.output_template,
33+
output_template
34+
);
35+
}
36+
37+
render() {
38+
return x`(${get_slot_scope(this.lets)}) => ${this.output_template}`;
39+
}
40+
}
41+
42+
function merge_template_literal(a: TemplateLiteral, b: TemplateLiteral): TemplateLiteral {
43+
const quasis = [...a.quasis];
44+
quasis[quasis.length - 1] = {
45+
type: 'TemplateElement',
46+
value: {
47+
raw: quasis[quasis.length - 1].value.raw + b.quasis[0].value.raw,
48+
cooked: quasis[quasis.length - 1].value.cooked + b.quasis[0].value.cooked,
49+
},
50+
tail: false,
51+
};
52+
quasis.push(...b.quasis.slice(1));
53+
54+
return {
55+
type: 'TemplateLiteral',
56+
quasis,
57+
expressions: [...a.expressions, ...b.expressions],
58+
};
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<script>
2+
let a = "A", b = "B", x = "X";
3+
</script>
4+
5+
<slot name="a" {x} {a}></slot>
6+
<slot name="b" {x} {b}></slot>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export default {
2+
html: `
3+
<p slot="a">A X</p>
4+
<p slot="a">X</p>
5+
<p slot="b">X</p>
6+
<p slot="b">B X</p>
7+
`,
8+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
</script>
4+
5+
<Nested let:x>
6+
<p slot="a" let:a>{a} {x}</p>
7+
<p slot="a">{x}</p>
8+
9+
<p slot="b">{x}</p>
10+
<p slot="b" let:b>{b} {x}</p>
11+
</Nested>

0 commit comments

Comments
 (0)