From 69f8a91b25ae3923b66b2ff25c40d5b8c6e99a7f Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Wed, 22 Feb 2023 21:43:34 +0800 Subject: [PATCH] implement conditional slot for ssr --- src/compiler/compile/compiler_errors.ts | 6 ++-- src/compiler/compile/nodes/InlineComponent.ts | 3 +- .../compile/nodes/SlotTemplateElseBlock.ts | 3 -- .../compile/nodes/SlotTemplateIfBlock.ts | 4 +-- .../wrappers/InlineComponent/index.ts | 3 +- .../compile/render_dom/wrappers/Slot.ts | 2 +- .../render_dom/wrappers/SlotTemplate.ts | 2 +- .../wrappers/SlotTemplateIfBlock.ts | 2 +- .../render_dom/wrappers/shared/slots.ts | 10 +++--- src/compiler/compile/render_ssr/Renderer.ts | 5 ++- .../render_ssr/handlers/InlineComponent.ts | 19 ++++-------- .../compile/render_ssr/handlers/Slot.ts | 14 ++++----- .../render_ssr/handlers/SlotTemplate.ts | 31 +++++++++---------- .../handlers/SlotTemplateIfBlock.ts | 29 +++++++++++++++++ src/runtime/internal/utils.ts | 6 ++-- test/helpers.ts | 1 - .../component-dynamic-slot-1/_config.js | 3 -- .../component-dynamic-slot-2/_config.js | 3 -- .../component-dynamic-slot-3/_config.js | 8 ++--- .../component-dynamic-slot-4/_config.js | 5 +-- .../component-dynamic-slot-5/_config.js | 5 +-- .../component-dynamic-slot-6/_config.js | 3 -- .../component-slot-nested-in-slot/_config.js | 1 + .../samples/component-slot-slot/_config.js | 1 + 24 files changed, 85 insertions(+), 84 deletions(-) create mode 100644 src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 64fb1bd7c0..0a418c95b6 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -293,8 +293,8 @@ export default { duplicate_slot_name_in_component: (slot_name: string, component_name: string) => ({ code: 'duplicate-slot-name-in-component', message: - slot_name === "default" + slot_name === 'default' ? 'Found elements without slot attribute when using slot="default"' - : `Duplicate slot name "${slot_name}" in <${component_name}>`, - }), + : `Duplicate slot name "${slot_name}" in <${component_name}>` + }) }; diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index 8651ba170a..41ffce83ec 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -11,8 +11,7 @@ import { INode } from './interfaces'; import { TemplateNode } from '../../interfaces'; import compiler_errors from '../compiler_errors'; import { regex_only_whitespaces } from '../../utils/patterns'; -import SlotTemplateIfBlock, { validate_get_slot_names } from './SlotTemplateIfBlock'; -import SlotTemplate from './SlotTemplate'; +import { validate_get_slot_names } from './SlotTemplateIfBlock'; export default class InlineComponent extends Node { type: 'InlineComponent'; diff --git a/src/compiler/compile/nodes/SlotTemplateElseBlock.ts b/src/compiler/compile/nodes/SlotTemplateElseBlock.ts index 7b1e23c650..10578da7e7 100644 --- a/src/compiler/compile/nodes/SlotTemplateElseBlock.ts +++ b/src/compiler/compile/nodes/SlotTemplateElseBlock.ts @@ -1,9 +1,6 @@ import Component from '../Component'; import Expression from './shared/Expression'; import TemplateScope from './shared/TemplateScope'; -import Node from './shared/Node'; -import Let from './Let'; -import Attribute from './Attribute'; import { INode } from './interfaces'; import compiler_errors from '../compiler_errors'; import get_const_tags from './shared/get_const_tags'; diff --git a/src/compiler/compile/nodes/SlotTemplateIfBlock.ts b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts index 40144ad5d6..0386db17ab 100644 --- a/src/compiler/compile/nodes/SlotTemplateIfBlock.ts +++ b/src/compiler/compile/nodes/SlotTemplateIfBlock.ts @@ -67,7 +67,7 @@ export default class SlotTemplateIfBlock extends AbstractBlock { } } -export function validate_get_slot_names(children: Array, component: Component, component_name: string) { +export function validate_get_slot_names(children: INode[], component: Component, component_name: string) { const slot_names = new Map(); function add_slot_name(slot_name: string, child: SlotTemplate) { if (slot_names.has(slot_name)) { @@ -87,4 +87,4 @@ export function validate_get_slot_names(children: Array, component: Compo } } return slot_names; -} \ No newline at end of file +} diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index d52f0595d6..8bb4bfde8d 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -9,7 +9,6 @@ import { sanitize } from '../../../../utils/names'; import add_to_set from '../../../utils/add_to_set'; import { b, x, p } from 'code-red'; import Attribute from '../../../nodes/Attribute'; -import is_dynamic from '../shared/is_dynamic'; import bind_this from '../shared/bind_this'; import { Node, Identifier, ObjectExpression } from 'estree'; import EventHandler from '../Element/EventHandler'; @@ -308,7 +307,7 @@ export default class InlineComponentWrapper extends Wrapper { if (${renderer.dirty(Array.from(dependencies))}) { ${name_changes}.$$slots = ${get_slots_definition}(#ctx); } - `) + `); } const munged_bindings = this.node.bindings.map(binding => { diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index dacadb593f..14721c7edb 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -234,7 +234,7 @@ export default class SlotWrapper extends Wrapper { if (!(${renderer.dirty(['#slots'])} && ${slot}.p(#ctx))) { ${slot_or_fallback_update} } - `) + `); block.chunks.destroy.push( b`if (${slot}) ${slot}.d(detaching);` diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts index f1321f1422..dd832f0f42 100644 --- a/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplate.ts @@ -3,7 +3,7 @@ import Renderer from '../Renderer'; import Block from '../Block'; import FragmentWrapper from './Fragment'; import create_debugging_comment from './shared/create_debugging_comment'; -import { get_slot_definition, SlotDefinition } from './shared/get_slot_definition'; +import { get_slot_definition } from './shared/get_slot_definition'; import { b, x } from 'code-red'; import { sanitize } from '../../../utils/names'; import { Identifier, Node } from 'estree'; diff --git a/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts b/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts index 9915aad3f4..7f0cb129ee 100644 --- a/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/SlotTemplateIfBlock.ts @@ -17,7 +17,7 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper { children: Array = []; else: Array = []; parent: SlotTemplateIfBlockWrapper | InlineComponentWrapper; - scope: TemplateScope + scope: TemplateScope; constructor( renderer: Renderer, diff --git a/src/compiler/compile/render_dom/wrappers/shared/slots.ts b/src/compiler/compile/render_dom/wrappers/shared/slots.ts index 94003e5c8b..64ee0e6af0 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/slots.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/slots.ts @@ -1,7 +1,7 @@ -import Renderer from "../../Renderer"; -import SlotTemplateWrapper from "../SlotTemplate"; -import SlotTemplateIfBlockWrapper from "../SlotTemplateIfBlock"; -import is_dynamic from "./is_dynamic"; +import Renderer from '../../Renderer'; +import SlotTemplateWrapper from '../SlotTemplate'; +import SlotTemplateIfBlockWrapper from '../SlotTemplateIfBlock'; +import is_dynamic from './is_dynamic'; export function collect_slot_fragment_dependencies( renderer: Renderer, @@ -49,4 +49,4 @@ export function collect_slot_dynamic_dependencies(children: Array = { RawMustacheTag: HtmlTag, Slot, SlotTemplate, + SlotTemplateIfBlock, Text, Title, Window: noop @@ -47,6 +49,7 @@ const handlers: Record = { export interface RenderOptions extends CompileOptions{ locate: (c: number) => { line: number; column: number }; head_id?: string; + slot_scopes?: Array; } export default class Renderer { diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 73d31940e2..e2e7793ba0 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -66,27 +66,20 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend : node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any) ); - const slot_fns = []; + const slot_scopes = []; const children = node.children; - if (children.length) { - const slot_scopes = new Map(); - renderer.render(children, Object.assign({}, options, { slot_scopes })); - - slot_scopes.forEach(({ input, output, statements }, name) => { - slot_fns.push( - p`${name}: (${input}) => { ${statements}; return ${output}; }` - ); - }); } - const slots = x`{ - ${slot_fns} - }`; + const slots = x`(() => { + const #slots_definition = {}; + ${slot_scopes} + return #slots_definition; + })()`; if (node.css_custom_properties.length > 0) { if (node.namespace === namespaces.svg) { diff --git a/src/compiler/compile/render_ssr/handlers/Slot.ts b/src/compiler/compile/render_ssr/handlers/Slot.ts index f89b619c46..18d96c6a6e 100644 --- a/src/compiler/compile/render_ssr/handlers/Slot.ts +++ b/src/compiler/compile/render_ssr/handlers/Slot.ts @@ -1,12 +1,10 @@ import Renderer, { RenderOptions } from '../Renderer'; import Slot from '../../nodes/Slot'; -import { x } from 'code-red'; +import { x, b } from 'code-red'; import get_slot_data from '../../utils/get_slot_data'; import { get_slot_scope } from './shared/get_slot_scope'; -export default function(node: Slot, renderer: Renderer, options: RenderOptions & { - slot_scopes: Map; -}) { +export default function(node: Slot, renderer: Renderer, options: RenderOptions) { const slot_data = get_slot_data(node.values); const slot = node.get_static_attribute_value('slot'); const nearest_inline_component = node.find_nearest(/InlineComponent/); @@ -32,9 +30,9 @@ export default function(node: Slot, renderer: Renderer, options: RenderOptions & nearest_inline_component.lets.forEach(l => { if (!seen.has(l.name.name)) lets.push(l); }); - options.slot_scopes.set(slot, { - input: get_slot_scope(node.lets), - output: renderer.pop() - }); + + options.slot_scopes.push(b`#slots_definition['${node.slot_template_name}'] = + (${get_slot_scope(node.lets)}) => ${renderer.pop()}; + `); } } diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts index 09f3293301..f7ad3ebdd9 100644 --- a/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplate.ts @@ -4,11 +4,11 @@ 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 { x } from 'code-red'; +import { INode } from '../../nodes/interfaces'; -export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & { - slot_scopes: Map; -}) { - const parent_inline_component = node.parent as InlineComponent; +export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions) { + const parent_inline_component = get_parent_inline_component(node.parent); const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); renderer.push(); @@ -22,18 +22,9 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO const slot_fragment_content = renderer.pop(); if (!is_empty_template_literal(slot_fragment_content)) { - if (options.slot_scopes.has(node.slot_template_name)) { - if (node.slot_template_name === 'default') { - throw new Error('Found elements without slot attribute when using slot="default"'); - } - throw new Error(`Duplicate slot name "${node.slot_template_name}" in <${parent_inline_component.name}>`); - } - - options.slot_scopes.set(node.slot_template_name, { - input: get_slot_scope(node.lets), - output: slot_fragment_content, - statements: get_const_tags(node.const_tags) - }); + options.slot_scopes.push(x`#slots_definition['${node.slot_template_name}'] = + (${get_slot_scope(node.lets)}) => { ${get_const_tags(node.const_tags)}; return ${slot_fragment_content}; } + `); } } @@ -44,3 +35,11 @@ function is_empty_template_literal(template_literal) { template_literal.quasis[0].value.raw === '' ); } + +function get_parent_inline_component(node: INode) { + let parent = node; + while (parent.type !== 'InlineComponent') { + parent = parent.parent; + } + return parent as InlineComponent; +} diff --git a/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts b/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts new file mode 100644 index 0000000000..c0f0b458aa --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/SlotTemplateIfBlock.ts @@ -0,0 +1,29 @@ +import Renderer, { RenderOptions } from '../Renderer'; +import { b } from 'code-red'; +import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock'; +import { flatten } from '../../../utils/flatten'; + +export default function (node: SlotTemplateIfBlock, renderer: Renderer, options: RenderOptions) { + const if_slot_scopes = []; + renderer.render(node.children, Object.assign({}, options, { + slot_scopes: if_slot_scopes + })); + + if (node.else) { + const else_slot_scopes = []; + renderer.render(node.else.children, Object.assign({}, options, { + slot_scopes: else_slot_scopes + })); + options.slot_scopes.push(b` + if (${node.expression.node}) { + ${if_slot_scopes} + } else { + ${else_slot_scopes} + } + `); + } else { + options.slot_scopes.push(b`if (${node.expression.node}) { + ${flatten(if_slot_scopes)} + }`); + } +} diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts index d46495bba4..b86a82b045 100644 --- a/src/runtime/internal/utils.ts +++ b/src/runtime/internal/utils.ts @@ -133,7 +133,8 @@ export function create_slot(definition_index: number, definition_name: string, $ i: (local) => transition_in(slot_block, local), o: (local) => transition_out(slot_block, local), d: (detaching) => slot_block && slot_block.d(detaching), - } + l: (nodes) => slot_block && slot_block.l(nodes) + }; init(); return slot; @@ -184,7 +185,8 @@ export function create_slot_with_fallback(definition_index: number, definition_n i: (local) => transition_in(slot_or_fallback, local), o: (local) => transition_out(slot_or_fallback, local), d: (detaching) => slot_or_fallback.d(detaching), - } + l: (nodes) => slot_or_fallback.l(nodes) + }; init(); return slot; diff --git a/test/helpers.ts b/test/helpers.ts index d263541d02..1ea0b19880 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -207,7 +207,6 @@ export function addLineNumbers(code) { } export function showOutput(cwd, options = {}, compile = svelte.compile) { - return; glob('**/*.svelte', { cwd }).forEach(file => { if (file[0] === '_') return; diff --git a/test/runtime/samples/component-dynamic-slot-1/_config.js b/test/runtime/samples/component-dynamic-slot-1/_config.js index 74dfd060bf..6162641102 100644 --- a/test/runtime/samples/component-dynamic-slot-1/_config.js +++ b/test/runtime/samples/component-dynamic-slot-1/_config.js @@ -1,7 +1,4 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, html: '

Fallback

', test({ assert, component, target }) { component.value = 1; diff --git a/test/runtime/samples/component-dynamic-slot-2/_config.js b/test/runtime/samples/component-dynamic-slot-2/_config.js index c297da11a2..096bd3077f 100644 --- a/test/runtime/samples/component-dynamic-slot-2/_config.js +++ b/test/runtime/samples/component-dynamic-slot-2/_config.js @@ -1,7 +1,4 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, html: `
Slot A
4A diff --git a/test/runtime/samples/component-dynamic-slot-3/_config.js b/test/runtime/samples/component-dynamic-slot-3/_config.js index 0ff43f5998..8773caf8e2 100644 --- a/test/runtime/samples/component-dynamic-slot-3/_config.js +++ b/test/runtime/samples/component-dynamic-slot-3/_config.js @@ -1,7 +1,4 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, html: `
value: _ @@ -26,7 +23,7 @@ export default { test({ assert, component, target }) { const lvl1 = target.querySelector('#a'); const lvl2 = target.querySelector('#b'); - component.paths = ["x", "y", "z"]; + component.paths = ['x', 'y', 'z']; assert.htmlEqual(target.innerHTML, `
value: _ @@ -57,9 +54,8 @@ export default { assert.equal(lvl1, target.querySelector('#x')); assert.equal(lvl2, target.querySelector('#y')); - const lvl3 = target.querySelector('#z'); - component.paths = ["p"]; + component.paths = ['p']; assert.htmlEqual(target.innerHTML, `
value: _ diff --git a/test/runtime/samples/component-dynamic-slot-4/_config.js b/test/runtime/samples/component-dynamic-slot-4/_config.js index 0bc73b00a0..f32f20ddb4 100644 --- a/test/runtime/samples/component-dynamic-slot-4/_config.js +++ b/test/runtime/samples/component-dynamic-slot-4/_config.js @@ -1,8 +1,5 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, - html: `a`, + html: 'a', test({ assert, component, target }) { component.a = 'foo'; assert.htmlEqual(target.innerHTML, 'foo'); diff --git a/test/runtime/samples/component-dynamic-slot-5/_config.js b/test/runtime/samples/component-dynamic-slot-5/_config.js index f20d52eca8..effe0c04a0 100644 --- a/test/runtime/samples/component-dynamic-slot-5/_config.js +++ b/test/runtime/samples/component-dynamic-slot-5/_config.js @@ -1,7 +1,4 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, html: `
Top content @@ -24,5 +21,5 @@ export default {
bottom fallback
- `, + ` }; diff --git a/test/runtime/samples/component-dynamic-slot-6/_config.js b/test/runtime/samples/component-dynamic-slot-6/_config.js index eb11146fe5..55c883f267 100644 --- a/test/runtime/samples/component-dynamic-slot-6/_config.js +++ b/test/runtime/samples/component-dynamic-slot-6/_config.js @@ -1,7 +1,4 @@ export default { - solo: true, - skip_if_ssr: true, - skip_if_hydrate: true, html: ` top fallback
diff --git a/test/runtime/samples/component-slot-nested-in-slot/_config.js b/test/runtime/samples/component-slot-nested-in-slot/_config.js index 56a9ef2668..91a1b79c42 100644 --- a/test/runtime/samples/component-slot-nested-in-slot/_config.js +++ b/test/runtime/samples/component-slot-nested-in-slot/_config.js @@ -1,4 +1,5 @@ export default { + solo:true, html: `

one: 1 two: 2

`, diff --git a/test/runtime/samples/component-slot-slot/_config.js b/test/runtime/samples/component-slot-slot/_config.js index 32af774fac..09311852cc 100644 --- a/test/runtime/samples/component-slot-slot/_config.js +++ b/test/runtime/samples/component-slot-slot/_config.js @@ -1,3 +1,4 @@ export default { + solo:true, html: '
lol
' };