Skip to content

feat allow multiple elements assigned with same slot #4140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ export default class ElementWrapper extends Wrapper {
get_slot_definition(child_block, scope, lets)
);
this.renderer.blocks.push(child_block);
} else {
const { lets } = this.node;
const seen = new Set(lets.map(l => l.name.name));

(owner as InlineComponentWrapper).node.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l);
});
(owner as InlineComponentWrapper).slots.get(name).add_let_binding(lets);
}

this.slot_block = (owner as unknown as InlineComponentWrapper).slots.get(name).block;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute';
import get_object from '../../../utils/get_object';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_slot_definition } from '../shared/get_slot_definition';
import { get_slot_definition, SlotDefinition } from '../shared/get_slot_definition';
import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this';
import { Node, Identifier, ObjectExpression } from 'estree';
Expand All @@ -20,7 +19,7 @@ import { extract_names } from 'periscopic';

export default class InlineComponentWrapper extends Wrapper {
var: Identifier;
slots: Map<string, { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node }> = new Map();
slots: Map<string, SlotDefinition> = new Map();
node: InlineComponent;
fragment: FragmentWrapper;

Expand Down Expand Up @@ -130,7 +129,7 @@ export default class InlineComponentWrapper extends Wrapper {
? [
p`$$slots: {
${Array.from(this.slots).map(([name, slot]) => {
return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`;
return p`${name}: ${slot.render()}`;
})}
}`,
p`$$scope: {
Expand Down
176 changes: 105 additions & 71 deletions src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,112 @@ import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression } from 'estree';

export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope };

const input = {
type: 'ObjectPattern',
properties: lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name
}))
};

const names: Set<string> = new Set();
lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});

const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
};

const { context_lookup } = block.renderer;

// i am well aware that this code is gross
// TODO make it less gross
const changes = {
type: 'ParenthesizedExpression',
get expression() {
if (block.renderer.context_overflow) {
const grouped = [];

Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31);

if (!grouped[g]) grouped[g] = [];
grouped[g].push({ name, n: i % 31 });
});

const elements = [];

for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
: x`0`;
}
export function get_slot_definition(
block: Block,
scope: TemplateScope,
lets: Let[]
) {
return new SlotDefinition(block, scope, lets);
}

export class SlotDefinition {
block: Block;
scope: TemplateScope;
lets: Let[];
lets_set: Set<string>;

return {
type: 'ArrayExpression',
elements
};
constructor(block: Block, scope: TemplateScope, lets: Let[]) {
this.block = block;
this.scope = scope;
this.lets = lets;
this.lets_set = new Set(this.lets.map(l => l.name.name));
}

add_let_binding(lets: Let[]) {
for (const l of lets) {
if (!this.lets_set.has(l.name.name)) {
this.lets_set.add(l.name.name);
this.lets.push(l);
}
}
}

return Array.from(names)
.map(name => {
const i = context_lookup.get(name).index.value as number;
return x`${name} ? ${1 << i} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
render() {
if (this.lets.length === 0) {
return x`[${this.block.name}, null, null]`;
}
};

return {
block,
scope,
get_context: x`${input} => ${context}`,
get_changes: x`${input} => ${changes}`
};
}

const input = {
type: 'ObjectPattern',
properties: this.lets.map(l => ({
type: 'Property',
kind: 'init',
key: l.name,
value: l.value || l.name,
})),
};

const names: Set<string> = new Set();
this.lets.forEach(l => {
l.names.forEach(name => {
names.add(name);
});
});

const context = {
type: 'ObjectExpression',
properties: Array.from(names).map(
name =>
p`${this.block.renderer.context_lookup.get(name).index}: ${name}`
),
};

const { context_lookup } = this.block.renderer;
const { renderer } = this.block;

// i am well aware that this code is gross
// TODO make it less gross
const changes = {
type: 'ParenthesizedExpression',
get expression() {
if (renderer.context_overflow) {
const grouped = [];
Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number;
const g = Math.floor(i / 31);

if (!grouped[g]) grouped[g] = [];
grouped[g].push({ name, n: i % 31 });
});

const elements = [];

for (let g = 0; g < grouped.length; g += 1) {
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
: x`0`;
}

return {
type: 'ArrayExpression',
elements,
};
}

return Array.from(names)
.map(name => {
const i = context_lookup.get(name).index.value as number;
return x`${name} ? ${1 << i} : 0`;
})
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
},
};

const get_context = x`${input} => ${context}`;
const get_changes = x`${input} => ${changes}`;

return x`[${this.block.name}, ${get_context}, ${get_changes}]`;
}
}
13 changes: 7 additions & 6 deletions src/compiler/compile/render_ssr/handlers/Element.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { is_void } from '../../../utils/names';
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { get_slot_scope } from './shared/get_slot_scope';
import { boolean_attributes } from './shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import { x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import { SlotDefinition } from './shared/get_slot_definition';

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

options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
if (options.slot_scopes.has(slot as string)) {
options.slot_scopes.get(slot as string).add(node.lets, renderer.pop());
} else {
options.slot_scopes.set(slot as string, new SlotDefinition(node.lets, renderer.pop()));
}
} else {
renderer.render(node.children, options);

Expand Down
13 changes: 5 additions & 8 deletions src/compiler/compile/render_ssr/handlers/InlineComponent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { string_literal } from '../../utils/stringify';
import Renderer, { RenderOptions } from '../Renderer';
import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import { p, x } from 'code-red';
import { SlotDefinition } from './shared/get_slot_definition';

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

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

renderer.push();

renderer.render(node.children, Object.assign({}, options, {
slot_scopes
}));

slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
slot_scopes.set('default', new SlotDefinition(node.lets, renderer.pop()));

slot_scopes.forEach(({ input, output }, name) => {
slot_scopes.forEach((slot, name) => {
slot_fns.push(
p`${name}: (${input}) => ${output}`
p`${name}: ${slot.render()}`
);
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Let from '../../../nodes/Let';
import { TemplateLiteral } from 'estree';
import { x } from 'code-red';
import { get_slot_scope } from './get_slot_scope';

export function get_slot_definition(
lets: Let[],
output_template: TemplateLiteral
) {
return new SlotDefinition(lets, output_template);
}

export class SlotDefinition {
lets: Let[];
lets_set: Set<string>;
output_template: TemplateLiteral;

constructor(lets: Let[], output_template: TemplateLiteral) {
this.lets = lets;
this.output_template = output_template;
this.lets_set = new Set(this.lets.map(l => l.name.name));
}

add(lets: Let[], output_template: TemplateLiteral) {
for (const l of lets) {
if (!this.lets_set.has(l.name.name)) {
this.lets_set.add(l.name.name);
this.lets.push(l);
}
}
this.output_template = merge_template_literal(
this.output_template,
output_template
);
}

render() {
return x`(${get_slot_scope(this.lets)}) => ${this.output_template}`;
}
}

function merge_template_literal(a: TemplateLiteral, b: TemplateLiteral): TemplateLiteral {
const quasis = [...a.quasis];
quasis[quasis.length - 1] = {
type: 'TemplateElement',
value: {
raw: quasis[quasis.length - 1].value.raw + b.quasis[0].value.raw,
cooked: quasis[quasis.length - 1].value.cooked + b.quasis[0].value.cooked,
},
tail: false,
};
quasis.push(...b.quasis.slice(1));

return {
type: 'TemplateLiteral',
quasis,
expressions: [...a.expressions, ...b.expressions],
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script>
let a = "A", b = "B", x = "X";
</script>

<slot name="a" {x} {a}></slot>
<slot name="b" {x} {b}></slot>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
html: `
<p slot="a">A X</p>
<p slot="a">X</p>
<p slot="b">X</p>
<p slot="b">B X</p>
`,
};
11 changes: 11 additions & 0 deletions test/runtime/samples/component-slot-let-multiple-slot/main.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
import Nested from './Nested.svelte';
</script>

<Nested let:x>
<p slot="a" let:a>{a} {x}</p>
<p slot="a">{x}</p>

<p slot="b">{x}</p>
<p slot="b" let:b>{b} {x}</p>
</Nested>