implement conditional slot for ssr

pull/8304/head
tanhauhau 4 years ago
parent 7d93872c73
commit 69f8a91b25

@ -293,8 +293,8 @@ export default {
duplicate_slot_name_in_component: (slot_name: string, component_name: string) => ({ duplicate_slot_name_in_component: (slot_name: string, component_name: string) => ({
code: 'duplicate-slot-name-in-component', code: 'duplicate-slot-name-in-component',
message: message:
slot_name === "default" slot_name === 'default'
? 'Found elements without slot attribute when using slot="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 { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns'; import { regex_only_whitespaces } from '../../utils/patterns';
import SlotTemplateIfBlock, { validate_get_slot_names } from './SlotTemplateIfBlock'; import { validate_get_slot_names } from './SlotTemplateIfBlock';
import SlotTemplate from './SlotTemplate';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';

@ -1,9 +1,6 @@
import Component from '../Component'; import Component from '../Component';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import Node from './shared/Node';
import Let from './Let';
import Attribute from './Attribute';
import { INode } from './interfaces'; import { INode } from './interfaces';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import get_const_tags from './shared/get_const_tags'; 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>(); const slot_names = new Map<string, SlotTemplate>();
function add_slot_name(slot_name: string, child: SlotTemplate) { function add_slot_name(slot_name: string, child: SlotTemplate) {
if (slot_names.has(slot_name)) { if (slot_names.has(slot_name)) {

@ -9,7 +9,6 @@ import { sanitize } from '../../../../utils/names';
import add_to_set from '../../../utils/add_to_set'; import add_to_set from '../../../utils/add_to_set';
import { b, x, p } from 'code-red'; import { b, x, p } from 'code-red';
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this'; import bind_this from '../shared/bind_this';
import { Node, Identifier, ObjectExpression } from 'estree'; import { Node, Identifier, ObjectExpression } from 'estree';
import EventHandler from '../Element/EventHandler'; import EventHandler from '../Element/EventHandler';
@ -308,7 +307,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (${renderer.dirty(Array.from(dependencies))}) { if (${renderer.dirty(Array.from(dependencies))}) {
${name_changes}.$$slots = ${get_slots_definition}(#ctx); ${name_changes}.$$slots = ${get_slots_definition}(#ctx);
} }
`) `);
} }
const munged_bindings = this.node.bindings.map(binding => { 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))) { if (!(${renderer.dirty(['#slots'])} && ${slot}.p(#ctx))) {
${slot_or_fallback_update} ${slot_or_fallback_update}
} }
`) `);
block.chunks.destroy.push( block.chunks.destroy.push(
b`if (${slot}) ${slot}.d(detaching);` b`if (${slot}) ${slot}.d(detaching);`

@ -3,7 +3,7 @@ import Renderer from '../Renderer';
import Block from '../Block'; import Block from '../Block';
import FragmentWrapper from './Fragment'; import FragmentWrapper from './Fragment';
import create_debugging_comment from './shared/create_debugging_comment'; 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 { b, x } from 'code-red';
import { sanitize } from '../../../utils/names'; import { sanitize } from '../../../utils/names';
import { Identifier, Node } from 'estree'; import { Identifier, Node } from 'estree';

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

@ -1,7 +1,7 @@
import Renderer from "../../Renderer"; import Renderer from '../../Renderer';
import SlotTemplateWrapper from "../SlotTemplate"; import SlotTemplateWrapper from '../SlotTemplate';
import SlotTemplateIfBlockWrapper from "../SlotTemplateIfBlock"; import SlotTemplateIfBlockWrapper from '../SlotTemplateIfBlock';
import is_dynamic from "./is_dynamic"; import is_dynamic from './is_dynamic';
export function collect_slot_fragment_dependencies( export function collect_slot_fragment_dependencies(
renderer: Renderer, renderer: Renderer,

@ -10,12 +10,13 @@ import InlineComponent from './handlers/InlineComponent';
import KeyBlock from './handlers/KeyBlock'; import KeyBlock from './handlers/KeyBlock';
import Slot from './handlers/Slot'; import Slot from './handlers/Slot';
import SlotTemplate from './handlers/SlotTemplate'; import SlotTemplate from './handlers/SlotTemplate';
import SlotTemplateIfBlock from './handlers/SlotTemplateIfBlock';
import Tag from './handlers/Tag'; import Tag from './handlers/Tag';
import Text from './handlers/Text'; import Text from './handlers/Text';
import Title from './handlers/Title'; import Title from './handlers/Title';
import { AppendTarget, CompileOptions } from '../../interfaces'; import { AppendTarget, CompileOptions } from '../../interfaces';
import { INode } from '../nodes/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 { collapse_template_literal } from '../utils/collapse_template_literal';
import { escape_template } from '../utils/stringify'; import { escape_template } from '../utils/stringify';
@ -39,6 +40,7 @@ const handlers: Record<string, Handler> = {
RawMustacheTag: HtmlTag, RawMustacheTag: HtmlTag,
Slot, Slot,
SlotTemplate, SlotTemplate,
SlotTemplateIfBlock,
Text, Text,
Title, Title,
Window: noop Window: noop
@ -47,6 +49,7 @@ const handlers: Record<string, Handler> = {
export interface RenderOptions extends CompileOptions{ export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number }; locate: (c: number) => { line: number; column: number };
head_id?: string; head_id?: string;
slot_scopes?: Array<Node[] | Node>;
} }
export default class Renderer { 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) : node.name.split('.').reduce(((lhs, rhs) => x`${lhs}.${rhs}`) as any)
); );
const slot_fns = []; const slot_scopes = [];
const children = node.children; const children = node.children;
if (children.length) { if (children.length) {
const slot_scopes = new Map();
renderer.render(children, Object.assign({}, options, { renderer.render(children, Object.assign({}, options, {
slot_scopes slot_scopes
})); }));
slot_scopes.forEach(({ input, output, statements }, name) => {
slot_fns.push(
p`${name}: (${input}) => { ${statements}; return ${output}; }`
);
});
} }
const slots = x`{ const slots = x`(() => {
${slot_fns} const #slots_definition = {};
}`; ${slot_scopes}
return #slots_definition;
})()`;
if (node.css_custom_properties.length > 0) { if (node.css_custom_properties.length > 0) {
if (node.namespace === namespaces.svg) { if (node.namespace === namespaces.svg) {

@ -1,12 +1,10 @@
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import Slot from '../../nodes/Slot'; 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_data from '../../utils/get_slot_data';
import { get_slot_scope } from './shared/get_slot_scope'; import { get_slot_scope } from './shared/get_slot_scope';
export default function(node: Slot, renderer: Renderer, options: RenderOptions & { export default function(node: Slot, renderer: Renderer, options: RenderOptions) {
slot_scopes: Map<any, any>;
}) {
const slot_data = get_slot_data(node.values); const slot_data = get_slot_data(node.values);
const slot = node.get_static_attribute_value('slot'); const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/); 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 => { nearest_inline_component.lets.forEach(l => {
if (!seen.has(l.name.name)) lets.push(l); if (!seen.has(l.name.name)) lets.push(l);
}); });
options.slot_scopes.set(slot, {
input: get_slot_scope(node.lets), options.slot_scopes.push(b`#slots_definition['${node.slot_template_name}'] =
output: renderer.pop() (${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 { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent'; import InlineComponent from '../../nodes/InlineComponent';
import { get_const_tags } from './shared/get_const_tags'; 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 & { export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions) {
slot_scopes: Map<any, any>; const parent_inline_component = get_parent_inline_component(node.parent);
}) {
const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next); const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next);
renderer.push(); renderer.push();
@ -22,18 +22,9 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
const slot_fragment_content = renderer.pop(); const slot_fragment_content = renderer.pop();
if (!is_empty_template_literal(slot_fragment_content)) { if (!is_empty_template_literal(slot_fragment_content)) {
if (options.slot_scopes.has(node.slot_template_name)) { options.slot_scopes.push(x`#slots_definition['${node.slot_template_name}'] =
if (node.slot_template_name === 'default') { (${get_slot_scope(node.lets)}) => { ${get_const_tags(node.const_tags)}; return ${slot_fragment_content}; }
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)
});
} }
} }
@ -44,3 +35,11 @@ function is_empty_template_literal(template_literal) {
template_literal.quasis[0].value.raw === '' 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), i: (local) => transition_in(slot_block, local),
o: (local) => transition_out(slot_block, local), o: (local) => transition_out(slot_block, local),
d: (detaching) => slot_block && slot_block.d(detaching), d: (detaching) => slot_block && slot_block.d(detaching),
} l: (nodes) => slot_block && slot_block.l(nodes)
};
init(); init();
return slot; 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), i: (local) => transition_in(slot_or_fallback, local),
o: (local) => transition_out(slot_or_fallback, local), o: (local) => transition_out(slot_or_fallback, local),
d: (detaching) => slot_or_fallback.d(detaching), d: (detaching) => slot_or_fallback.d(detaching),
} l: (nodes) => slot_or_fallback.l(nodes)
};
init(); init();
return slot; return slot;

@ -207,7 +207,6 @@ export function addLineNumbers(code) {
} }
export function showOutput(cwd, options = {}, compile = svelte.compile) { export function showOutput(cwd, options = {}, compile = svelte.compile) {
return;
glob('**/*.svelte', { cwd }).forEach(file => { glob('**/*.svelte', { cwd }).forEach(file => {
if (file[0] === '_') return; if (file[0] === '_') return;

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

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

@ -1,7 +1,4 @@
export default { export default {
solo: true,
skip_if_ssr: true,
skip_if_hydrate: true,
html: ` html: `
<div id="_"> <div id="_">
value: _ value: _
@ -26,7 +23,7 @@ export default {
test({ assert, component, target }) { test({ assert, component, target }) {
const lvl1 = target.querySelector('#a'); const lvl1 = target.querySelector('#a');
const lvl2 = target.querySelector('#b'); const lvl2 = target.querySelector('#b');
component.paths = ["x", "y", "z"]; component.paths = ['x', 'y', 'z'];
assert.htmlEqual(target.innerHTML, ` assert.htmlEqual(target.innerHTML, `
<div id="_"> <div id="_">
value: _ value: _
@ -57,9 +54,8 @@ export default {
assert.equal(lvl1, target.querySelector('#x')); assert.equal(lvl1, target.querySelector('#x'));
assert.equal(lvl2, target.querySelector('#y')); assert.equal(lvl2, target.querySelector('#y'));
const lvl3 = target.querySelector('#z');
component.paths = ["p"]; component.paths = ['p'];
assert.htmlEqual(target.innerHTML, ` assert.htmlEqual(target.innerHTML, `
<div id="_"> <div id="_">
value: _ value: _

@ -1,8 +1,5 @@
export default { export default {
solo: true, html: 'a',
skip_if_ssr: true,
skip_if_hydrate: true,
html: `a`,
test({ assert, component, target }) { test({ assert, component, target }) {
component.a = 'foo'; component.a = 'foo';
assert.htmlEqual(target.innerHTML, 'foo'); assert.htmlEqual(target.innerHTML, 'foo');

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

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

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

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

Loading…
Cancel
Save