From 5b030d07539dff01cf94e0749d1c11e493618aad Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Sun, 22 Dec 2019 00:08:23 +0800 Subject: [PATCH] feat allow multiple elements assigned with same slot --- .../render_dom/wrappers/Element/index.ts | 8 + .../wrappers/InlineComponent/index.ts | 7 +- .../wrappers/shared/get_slot_definition.ts | 176 +++++++++++------- .../compile/render_ssr/handlers/Element.ts | 13 +- .../render_ssr/handlers/InlineComponent.ts | 13 +- .../handlers/shared/get_slot_definition.ts | 59 ++++++ .../Nested.svelte | 6 + .../_config.js | 8 + .../main.svelte | 11 ++ 9 files changed, 212 insertions(+), 89 deletions(-) create mode 100644 src/compiler/compile/render_ssr/handlers/shared/get_slot_definition.ts create mode 100644 test/runtime/samples/component-slot-let-multiple-slot/Nested.svelte create mode 100644 test/runtime/samples/component-slot-let-multiple-slot/_config.js create mode 100644 test/runtime/samples/component-slot-let-multiple-slot/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index cd660f202c08..a73244bd8398 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -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; diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 631c172576d9..557a6d5d7e9a 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -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'; @@ -20,7 +19,7 @@ import { extract_names } from 'periscopic'; export default class InlineComponentWrapper extends Wrapper { var: Identifier; - slots: Map = new Map(); + slots: Map = new Map(); node: InlineComponent; fragment: FragmentWrapper; @@ -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: { diff --git a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts index 2adbd3b1d063..913ca67ec96c 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts @@ -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 = 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; - 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}` - }; -} \ No newline at end of file + + const input = { + type: 'ObjectPattern', + properties: this.lets.map(l => ({ + type: 'Property', + kind: 'init', + key: l.name, + value: l.value || l.name, + })), + }; + + const names: Set = 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}]`; + } +} diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 81b8801686e1..644026bb2a7a 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -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; + slot_scopes: Map; }) { // awkward special case let node_contents; @@ -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); diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 5c4d9c73b8a8..a521fba30395 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -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`; @@ -68,7 +68,7 @@ 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 = new Map(); renderer.push(); @@ -76,14 +76,11 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend 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()}` ); }); } diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_slot_definition.ts b/src/compiler/compile/render_ssr/handlers/shared/get_slot_definition.ts new file mode 100644 index 000000000000..dfdaaf4d8eae --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/shared/get_slot_definition.ts @@ -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; + 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], + }; +} diff --git a/test/runtime/samples/component-slot-let-multiple-slot/Nested.svelte b/test/runtime/samples/component-slot-let-multiple-slot/Nested.svelte new file mode 100644 index 000000000000..4bcc12073502 --- /dev/null +++ b/test/runtime/samples/component-slot-let-multiple-slot/Nested.svelte @@ -0,0 +1,6 @@ + + + + diff --git a/test/runtime/samples/component-slot-let-multiple-slot/_config.js b/test/runtime/samples/component-slot-let-multiple-slot/_config.js new file mode 100644 index 000000000000..29f1249333d4 --- /dev/null +++ b/test/runtime/samples/component-slot-let-multiple-slot/_config.js @@ -0,0 +1,8 @@ +export default { + html: ` +

A X

+

X

+

X

+

B X

+ `, +}; diff --git a/test/runtime/samples/component-slot-let-multiple-slot/main.svelte b/test/runtime/samples/component-slot-let-multiple-slot/main.svelte new file mode 100644 index 000000000000..1bb69a28b560 --- /dev/null +++ b/test/runtime/samples/component-slot-let-multiple-slot/main.svelte @@ -0,0 +1,11 @@ + + + +

{a} {x}

+

{x}

+ +

{x}

+

{b} {x}

+