From 6787855c527ce4ea8a8bf6a69c00a9c2216903b1 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Sat, 15 Apr 2023 23:29:59 -0400 Subject: [PATCH 01/11] Implemented dynamic slot name --- src/compiler/compile/nodes/Slot.ts | 29 ++++++++++++++----- .../compile/render_dom/wrappers/Slot.ts | 5 ++-- .../compile/render_ssr/handlers/Slot.ts | 6 ++-- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/compiler/compile/nodes/Slot.ts b/src/compiler/compile/nodes/Slot.ts index c21a48f5d665..362a9276dbd8 100644 --- a/src/compiler/compile/nodes/Slot.ts +++ b/src/compiler/compile/nodes/Slot.ts @@ -3,7 +3,7 @@ import Attribute from './Attribute'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; -import { TemplateNode } from '../../interfaces'; +import { TemplateNode, Attribute as AttributeNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; export default class Slot extends Element { @@ -11,31 +11,44 @@ export default class Slot extends Element { name: string; children: INode[]; slot_name: string; + name_attribute: Attribute; values: Map = new Map(); constructor(component: Component, parent: INode, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); - info.attributes.forEach(attr => { + info.attributes.forEach((attr: AttributeNode) => { if (attr.type !== 'Attribute' && attr.type !== 'Spread') { return component.error(attr, compiler_errors.invalid_slot_directive); } - + + const new_attribute = new Attribute(component, this, scope, attr); if (attr.name === 'name') { - if (attr.value.length !== 1 || attr.value[0].type !== 'Text') { - return component.error(attr, compiler_errors.dynamic_slot_name); + if (attr.value.length === 1 && attr.value[0].type === 'Text') { + this.slot_name = attr.value[0].data; + } else { + this.slot_name = component.get_unique_name('dynamic_slot_name').name; } - this.slot_name = attr.value[0].data; if (this.slot_name === 'default') { return component.error(attr, compiler_errors.invalid_slot_name); } + + this.name_attribute = new_attribute; } - this.values.set(attr.name, new Attribute(component, this, scope, attr)); + this.values.set(attr.name, new_attribute); }); - if (!this.slot_name) this.slot_name = 'default'; + if (!this.slot_name) { + // If there is no name attribute, pretend we do have a name attribute with value 'default' + this.slot_name = 'default'; + this.name_attribute = new Attribute(component, this, scope, { + type: 'Attribute', + name: 'name', + value: [ { type: 'Text', data: 'default' } ] + } as AttributeNode); + } if (this.slot_name === 'default') { // if this is the default slot, add our dependencies to any diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index 0a589e339489..040909c73dbc 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -69,7 +69,7 @@ export default class SlotWrapper extends Wrapper { ) { const { renderer } = this; - const { slot_name } = this.node; + const { slot_name, name_attribute } = this.node; if (this.slot_block) { block = this.slot_block; @@ -128,8 +128,9 @@ export default class SlotWrapper extends Wrapper { const slot_definition = block.get_unique_name(`${sanitize(slot_name)}_slot_template`); const slot_or_fallback = has_fallback ? block.get_unique_name(`${sanitize(slot_name)}_slot_or_fallback`) : slot; + const slot_expression = name_attribute.get_value(block); block.chunks.init.push(b` - const ${slot_definition} = ${renderer.reference('#slots')}.${slot_name}; + const ${slot_definition} = ${renderer.reference('#slots')}[${slot_expression}]; const ${slot} = @create_slot(${slot_definition}, #ctx, ${renderer.reference('$$scope')}, ${get_slot_context_fn}); ${has_fallback ? b`const ${slot_or_fallback} = ${slot} || ${this.fallback.name}(#ctx);` : null} `); diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts index f89b619c466d..137d9c8e6e40 100644 --- a/src/compiler/compile/render_ssr/handlers/Slot.ts +++ b/src/compiler/compile/render_ssr/handlers/Slot.ts @@ -2,6 +2,7 @@ import Renderer, { RenderOptions } from '../Renderer'; import Slot from '../../nodes/Slot'; import { x } from 'code-red'; import get_slot_data from '../../utils/get_slot_data'; +import { get_attribute_value } from './shared/get_attribute_value'; import { get_slot_scope } from './shared/get_slot_scope'; export default function(node: Slot, renderer: Renderer, options: RenderOptions & { @@ -19,9 +20,10 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions & renderer.render(node.children, options); const result = renderer.pop(); + const slot_expression = get_attribute_value(node.name_attribute); renderer.add_expression(x` - #slots.${node.slot_name} - ? #slots.${node.slot_name}(${slot_data}) + #slots[${slot_expression}] + ? #slots[${slot_expression}](${slot_data}) : ${result} `); From 2f32f948ee3a436eb132ea902d85812c22e76be9 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Sat, 15 Apr 2023 23:41:35 -0400 Subject: [PATCH 02/11] Remove dynamic slot name error --- src/compiler/compile/compiler_errors.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index bad3911673fc..da0ad0defea8 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -132,10 +132,6 @@ export default { code: 'invalid-slot-directive', message: ' cannot have directives' }, - dynamic_slot_name: { - code: 'dynamic-slot-name', - message: ' name cannot be dynamic' - }, invalid_slot_name: { code: 'invalid-slot-name', message: 'default is a reserved word — it cannot be used as a slot name' From ae02d4720afb24af122bd18905c168a9ae60c94f Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Tue, 25 Apr 2023 13:56:35 -0400 Subject: [PATCH 03/11] Implement dynamic slot attribute --- src/compiler/compile/nodes/Element.ts | 4 --- src/compiler/compile/nodes/SlotTemplate.ts | 18 ++++++++----- .../wrappers/InlineComponent/index.ts | 27 ++++++++++++------- .../render_dom/wrappers/SlotTemplate.ts | 5 +--- .../render_ssr/handlers/InlineComponent.ts | 4 +-- .../compile/render_ssr/handlers/Slot.ts | 2 +- .../render_ssr/handlers/SlotTemplate.ts | 3 ++- 7 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 2410904d6301..9f3284cab947 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -529,10 +529,6 @@ export default class Element extends Node { } if (name === 'slot') { - if (!attribute.is_static) { - return component.error(attribute, compiler_errors.invalid_slot_attribute); - } - if (component.slot_outlets.has(name)) { return component.error(attribute, compiler_errors.duplicate_slot_attribute(name)); diff --git a/src/compiler/compile/nodes/SlotTemplate.ts b/src/compiler/compile/nodes/SlotTemplate.ts index 90299c8e3954..c9bee04e37af 100644 --- a/src/compiler/compile/nodes/SlotTemplate.ts +++ b/src/compiler/compile/nodes/SlotTemplate.ts @@ -16,6 +16,7 @@ export default class SlotTemplate extends Node { const_tags: ConstTag[]; slot_attribute: Attribute; slot_template_name: string = 'default'; + is_static: boolean = true; constructor( component: Component, @@ -44,14 +45,17 @@ export default class SlotTemplate extends Node { case 'Attribute': { if (node.name === 'slot') { this.slot_attribute = new Attribute(component, this, scope, node); - if (!this.slot_attribute.is_static) { - return component.error(node, compiler_errors.invalid_slot_attribute); + if (this.slot_attribute.is_static) { + const value = this.slot_attribute.get_static_value(); + if (typeof value === 'boolean') { + return component.error(node, compiler_errors.invalid_slot_attribute_value_missing); + } + this.slot_template_name = value as string; + this.is_static = true; + } else { + this.slot_template_name = component.get_unique_name('dynamic_slot_template').name; + this.is_static = false; } - const value = this.slot_attribute.get_static_value(); - if (typeof value === 'boolean') { - return component.error(node, compiler_errors.invalid_slot_attribute_value_missing); - } - this.slot_template_name = value as string; break; } throw new Error(`Invalid attribute '${node.name}' in `); diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index ab140a75c500..502690d2c5f2 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -29,7 +29,8 @@ const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g; export default class InlineComponentWrapper extends Wrapper { var: Identifier; - slots: Map = new Map(); + slots: Map = new Map(); + staic_slot_names: Set = new Set(); node: InlineComponent; fragment: FragmentWrapper; children: Array = []; @@ -95,14 +96,20 @@ export default class InlineComponentWrapper extends Wrapper { block.add_outro(); } - set_slot(name: string, slot_definition: SlotDefinition) { - if (this.slots.has(name)) { - if (name === 'default') { - throw new Error('Found elements without slot attribute when using slot="default"'); + set_slot(slot: SlotTemplate, slot_definition: SlotDefinition) { + if (slot.is_static) { + const name = slot.slot_template_name; + if (this.staic_slot_names.has(name)) { + if (name === 'default') { + throw new Error('Found elements without slot attribute when using slot="default"'); + } + throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`); + } else { + this.staic_slot_names.add(name); } - throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`); } - this.slots.set(name, slot_definition); + + this.slots.set(slot, slot_definition); } warn_if_reactive() { @@ -167,8 +174,10 @@ export default class InlineComponentWrapper extends Wrapper { const initial_props = this.slots.size > 0 ? [ p`$$slots: { - ${Array.from(this.slots).map(([name, slot]) => { - return p`${name}: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; + ${Array.from(this.slots).map(([slot_template, slot]) => { + const { is_static, slot_template_name, slot_attribute } = slot_template; + const slot_expression = is_static ? { type: 'Literal', value: slot_template_name } : slot_attribute.get_value(block); + return p`[${slot_expression}]: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; })} }`, p`$$scope: { diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts index a50f74fc04c8..e88582715766 100644 --- a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts @@ -52,10 +52,7 @@ export default class SlotTemplateWrapper extends Wrapper { if (!seen.has(l.name.name)) lets.push(l); }); - this.parent.set_slot( - slot_template_name, - get_slot_definition(this.block, scope, lets) - ); + this.parent.set_slot(this.node, get_slot_definition(this.block, scope, lets)); this.fragment = new FragmentWrapper( renderer, diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 73d31940e203..445db7d9a852 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -77,9 +77,9 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend slot_scopes })); - slot_scopes.forEach(({ input, output, statements }, name) => { + slot_scopes.forEach(({ input, output, statements }, slot_exp) => { slot_fns.push( - p`${name}: (${input}) => { ${statements}; return ${output}; }` + p`[${slot_exp}]: (${input}) => { ${statements}; return ${output}; }` ); }); } diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts index 137d9c8e6e40..e92847f177fc 100644 --- a/src/compiler/compile/render_ssr/handlers/Slot.ts +++ b/src/compiler/compile/render_ssr/handlers/Slot.ts @@ -9,7 +9,7 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions & slot_scopes: Map; }) { const slot_data = get_slot_data(node.values); - const slot = node.get_static_attribute_value('slot'); + const slot = node.values.get('slot')?.get_value(null); const nearest_inline_component = node.find_nearest(/InlineComponent/); if (slot && nearest_inline_component) { diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts index 09f329330157..b09ead78dd74 100644 --- a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts @@ -29,7 +29,8 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`); } - options.slot_scopes.set(node.slot_template_name, { + const slot_expression = node.is_static ? { type: 'Literal', value: node.slot_template_name } : node.slot_attribute.get_value(null); + options.slot_scopes.set(slot_expression, { input: get_slot_scope(node.lets), output: slot_fragment_content, statements: get_const_tags(node.const_tags) From 8098b6bc7e06024a747599c04bc0026d6c55b201 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Tue, 25 Apr 2023 15:34:32 -0400 Subject: [PATCH 04/11] Simplify getting value of slot attribute --- src/compiler/compile/nodes/SlotTemplate.ts | 15 +++++++++++++-- .../render_dom/wrappers/InlineComponent/index.ts | 4 ++-- .../compile/render_ssr/handlers/SlotTemplate.ts | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/compiler/compile/nodes/SlotTemplate.ts b/src/compiler/compile/nodes/SlotTemplate.ts index c9bee04e37af..12bb2194a686 100644 --- a/src/compiler/compile/nodes/SlotTemplate.ts +++ b/src/compiler/compile/nodes/SlotTemplate.ts @@ -7,6 +7,7 @@ import { INode } from './interfaces'; import compiler_errors from '../compiler_errors'; import get_const_tags from './shared/get_const_tags'; import ConstTag from './ConstTag'; +import { Attribute as AttributeNode } from '../../interfaces'; export default class SlotTemplate extends Node { type: 'SlotTemplate'; @@ -15,8 +16,8 @@ export default class SlotTemplate extends Node { lets: Let[] = []; const_tags: ConstTag[]; slot_attribute: Attribute; - slot_template_name: string = 'default'; - is_static: boolean = true; + is_static: boolean; + slot_template_name: string; constructor( component: Component, @@ -65,6 +66,16 @@ export default class SlotTemplate extends Node { } }); + if (!this.slot_template_name) { + this.slot_template_name = 'default'; + this.is_static = false; + this.slot_attribute = new Attribute(component, this, scope, { + type: 'Attribute', + name: 'slot', + value: [ { type: 'Text', data: 'default' } ] + } as AttributeNode); + } + this.scope = scope; ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); } diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 502690d2c5f2..2621c366411a 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -175,8 +175,8 @@ export default class InlineComponentWrapper extends Wrapper { ? [ p`$$slots: { ${Array.from(this.slots).map(([slot_template, slot]) => { - const { is_static, slot_template_name, slot_attribute } = slot_template; - const slot_expression = is_static ? { type: 'Literal', value: slot_template_name } : slot_attribute.get_value(block); + const { slot_attribute } = slot_template; + const slot_expression = slot_attribute.get_value(block); return p`[${slot_expression}]: [${slot.block.name}, ${slot.get_context || null}, ${slot.get_changes || null}]`; })} }`, diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts index b09ead78dd74..064b3cce2afe 100644 --- a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts @@ -4,6 +4,7 @@ import remove_whitespace_children from './utils/remove_whitespace_children'; import { get_slot_scope } from './shared/get_slot_scope'; import InlineComponent from '../../nodes/InlineComponent'; import { get_const_tags } from './shared/get_const_tags'; +import { get_attribute_value } from './shared/get_attribute_value'; export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & { slot_scopes: Map; @@ -29,7 +30,7 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`); } - const slot_expression = node.is_static ? { type: 'Literal', value: node.slot_template_name } : node.slot_attribute.get_value(null); + const slot_expression = get_attribute_value(node.slot_attribute); options.slot_scopes.set(slot_expression, { input: get_slot_scope(node.lets), output: slot_fragment_content, From 3972cecf5e46bf4e6612557e11b11ea795e5b3a6 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Tue, 25 Apr 2023 15:42:28 -0400 Subject: [PATCH 05/11] Add first test for dynamic slots --- .../samples/dynamic-slots-dev/Nested.svelte | 13 +++++++++++++ .../runtime/samples/dynamic-slots-dev/_config.js | 9 +++++++++ .../samples/dynamic-slots-dev/main.svelte | 16 ++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 test/runtime/samples/dynamic-slots-dev/Nested.svelte create mode 100644 test/runtime/samples/dynamic-slots-dev/_config.js create mode 100644 test/runtime/samples/dynamic-slots-dev/main.svelte diff --git a/test/runtime/samples/dynamic-slots-dev/Nested.svelte b/test/runtime/samples/dynamic-slots-dev/Nested.svelte new file mode 100644 index 000000000000..0aed3e2ab27e --- /dev/null +++ b/test/runtime/samples/dynamic-slots-dev/Nested.svelte @@ -0,0 +1,13 @@ + + +
+ + + +
diff --git a/test/runtime/samples/dynamic-slots-dev/_config.js b/test/runtime/samples/dynamic-slots-dev/_config.js new file mode 100644 index 000000000000..e391084e5914 --- /dev/null +++ b/test/runtime/samples/dynamic-slots-dev/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + + html: '

Hello

Hello

Hello

', + + warnings: [' received an unexpected slot "dynamic-slot-again".'] +}; diff --git a/test/runtime/samples/dynamic-slots-dev/main.svelte b/test/runtime/samples/dynamic-slots-dev/main.svelte new file mode 100644 index 000000000000..65dee9e1366a --- /dev/null +++ b/test/runtime/samples/dynamic-slots-dev/main.svelte @@ -0,0 +1,16 @@ + + + +

Hello

+

Hello

+

Hello

+

Hello

+
From d2105a3de99eea97b441f72e2c4a700b39f004f5 Mon Sep 17 00:00:00 2001 From: Nguyen Tran Date: Tue, 25 Apr 2023 15:45:47 -0400 Subject: [PATCH 06/11] Fix issue where name is undefined for some reason --- .../compile/render_ssr/handlers/shared/get_attribute_value.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts index 1d365da673ad..18a4976d0512 100644 --- a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts +++ b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts @@ -23,7 +23,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression { * For value attribute of textarea, it will render as child node of `