implement conditional slot for ssr

pull/8304/head
tanhauhau 4 years ago
parent 9acdaea3ab
commit 54f65696cb

@ -297,8 +297,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}>`
})
};

@ -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';

@ -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';

@ -67,7 +67,7 @@ export default class SlotTemplateIfBlock extends AbstractBlock {
}
}
export function validate_get_slot_names(children: Array<INode>, component: Component, component_name: string) {
export function validate_get_slot_names(children: INode[], component: Component, component_name: string) {
const slot_names = new Map<string, SlotTemplate>();
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<INode>, component: Compo
}
}
return slot_names;
}
}

@ -311,7 +311,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 => {

@ -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);`

@ -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';

@ -17,7 +17,7 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
children: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper> = [];
else: Array<SlotTemplateWrapper | SlotTemplateIfBlockWrapper> = [];
parent: SlotTemplateIfBlockWrapper | InlineComponentWrapper;
scope: TemplateScope
scope: TemplateScope;
constructor(
renderer: Renderer,

@ -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<SlotTemplateIf
collect(children);
return result;
}
}

@ -10,12 +10,13 @@ import InlineComponent from './handlers/InlineComponent';
import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot';
import SlotTemplate from './handlers/SlotTemplate';
import SlotTemplateIfBlock from './handlers/SlotTemplateIfBlock';
import Tag from './handlers/Tag';
import Text from './handlers/Text';
import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/interfaces';
import { Expression, TemplateLiteral, Identifier } from 'estree';
import { Expression, TemplateLiteral, Identifier, Node } from 'estree';
import { collapse_template_literal } from '../utils/collapse_template_literal';
import { escape_template } from '../utils/stringify';
@ -39,6 +40,7 @@ const handlers: Record<string, Handler> = {
RawMustacheTag: HtmlTag,
Slot,
SlotTemplate,
SlotTemplateIfBlock,
Text,
Title,
Window: noop
@ -47,6 +49,7 @@ const handlers: Record<string, Handler> = {
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number };
head_id?: string;
slot_scopes?: Array<Node[] | Node>;
}
export default class Renderer {

@ -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) {

@ -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<any, any>;
}) {
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()};
`);
}
}

@ -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<any, any>;
}) {
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;
}

@ -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)}
}`);
}
}

@ -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;

@ -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;

@ -1,7 +1,4 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: '<p>Fallback</p>',
test({ assert, component, target }) {
component.value = 1;

@ -1,7 +1,4 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div>Slot A</div>
4A

@ -1,7 +1,4 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div id="_">
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, `
<div id="_">
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, `
<div id="_">
value: _

@ -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');

@ -1,7 +1,4 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
<div>
Top content
@ -24,5 +21,5 @@ export default {
<hr>
bottom fallback
</div>
`,
`
};

@ -1,7 +1,4 @@
export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: `
top fallback
<hr>

@ -1,4 +1,5 @@
export default {
solo:true,
html: `
<p slot='one'>one: 1 two: 2</p>
`,

@ -1,3 +1,4 @@
export default {
solo:true,
html: '<div><span>lol</span></div>'
};

Loading…
Cancel
Save