Skip to content

Commit

Permalink
slot as a sugar syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Jul 25, 2020
1 parent 61af670 commit 2d06e72
Show file tree
Hide file tree
Showing 32 changed files with 232 additions and 138 deletions.
28 changes: 28 additions & 0 deletions src/compiler/compile/nodes/DefaultSlotTemplate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Component from "../Component";
import TemplateScope from "./shared/TemplateScope";
import Node from "./shared/Node";
import Let from "./Let";
import { INode } from "./interfaces";

export default class DefaultSlotTemplate extends Node {
type: "SlotTemplate";
scope: TemplateScope;
children: INode[];
lets: Let[] = [];
slot_template_name = "default";

constructor(
component: Component,
parent: INode,
scope: TemplateScope,
info: any,
lets: Let[],
children: INode[],
) {
super(component, parent, scope, info);
this.type = 'SlotTemplate';
this.children = children;
this.scope = scope;
this.lets = lets;
}
}
3 changes: 1 addition & 2 deletions src/compiler/compile/nodes/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ export default class Element extends Node {
component.slot_outlets.add(name);
}

if (!(parent.type === 'InlineComponent' || within_custom_element(parent))) {
if (!(parent.type === 'SlotTemplate' || within_custom_element(parent))) {
component.error(attribute, {
code: `invalid-slotted-content`,
message: `Element with a slot='...' attribute must be a child of a component or a descendant of a custom element`,
Expand Down Expand Up @@ -809,7 +809,6 @@ export default class Element extends Node {
}

get slot_template_name() {
// attribute.get_static_value
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
}
}
Expand Down
59 changes: 52 additions & 7 deletions src/compiler/compile/nodes/InlineComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ export default class InlineComponent extends Node {
});

case 'Attribute':
if (node.name === 'slot') {
component.error(node, {
code: `invalid-prop`,
message: `'slot' is reserved for future use in named slots`
});
}
// fallthrough
case 'Spread':
this.attributes.push(new Attribute(component, this, scope, node));
Expand Down Expand Up @@ -111,6 +105,57 @@ export default class InlineComponent extends Node {
});
});

this.children = map_children(component, this, this.scope, info.children);
const children = [];
for (let i=info.children.length - 1; i >= 0; i--) {
const child = info.children[i];
if (child.type === 'SlotTemplate') {
children.push(child);
info.children.splice(i, 1);
} else if ((child.type === 'Element' || child.type === 'InlineComponent') && child.attributes.find(attribute => attribute.name === 'slot')) {
const slot_template = {
start: child.start,
end: child.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: [child],
};

// transfer attributes
for (let i=child.attributes.length - 1; i >= 0; i--) {
const attribute = child.attributes[i];
if (attribute.type === 'Let') {
slot_template.attributes.push(attribute);
child.attributes.splice(i, 1);
} else if (attribute.type === 'Attribute' && attribute.name === 'slot') {
slot_template.attributes.push(attribute);
}
}

children.push(slot_template);
info.children.splice(i, 1);
}
}

if (info.children.some(node => not_whitespace_text(node))) {
children.push({
start: info.start,
end: info.end,
type: 'SlotTemplate',
name: 'svelte:fragment',
attributes: [],
children: info.children
});
}

this.children = map_children(component, this, this.scope, children);
}

get slot_template_name() {
return this.attributes.find(attribute => attribute.name === 'slot').get_static_value() as string;
}
}

function not_whitespace_text(node) {
return !(node.type === 'Text' && /^\s+$/.test(node.data));
}
2 changes: 2 additions & 0 deletions src/compiler/compile/nodes/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import PendingBlock from './PendingBlock';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import SlotTemplate from './SlotTemplate';
import DefaultSlotTemplate from './DefaultSlotTemplate';
import Text from './Text';
import ThenBlock from './ThenBlock';
import Title from './Title';
Expand Down Expand Up @@ -58,6 +59,7 @@ export type INode = Action
| RawMustacheTag
| Slot
| SlotTemplate
| DefaultSlotTemplate
| Tag
| Text
| ThenBlock
Expand Down
1 change: 0 additions & 1 deletion src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export default class ElementWrapper extends Wrapper {
event_handlers: EventHandler[];
class_dependencies: string[];

slot_block: Block;
select_binding_dependencies?: Set<string>;

var: any;
Expand Down
50 changes: 6 additions & 44 deletions src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ 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 create_debugging_comment from '../shared/create_debugging_comment';
import { get_slot_definition } from '../shared/get_slot_definition';
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';
import EventHandler from '../Element/EventHandler';
import { extract_names } from 'periscopic';
import mark_each_block_bindings from '../shared/mark_each_block_bindings';
import SlotTemplate from '../../../nodes/SlotTemplate';

type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };

Expand All @@ -27,7 +26,6 @@ export default class InlineComponentWrapper extends Wrapper {
node: InlineComponent;
fragment: FragmentWrapper;
children: Array<Wrapper | FragmentWrapper> = [];
default_slot_block: Block;

constructor(
renderer: Renderer,
Expand Down Expand Up @@ -80,53 +78,17 @@ export default class InlineComponentWrapper extends Wrapper {
});
});

const children = this.node.children.slice();
for (let i=children.length - 1; i>=0;) {
const child = children[i];
if (child.type === 'SlotTemplate' || (child.type === 'Element' && child.attributes.find(attribute => attribute.name === 'slot'))) {
const slot_template = new SlotTemplateWrapper(renderer, block, this, child, strip_whitespace, next_sibling);
this.children.push(slot_template);
children.splice(i, 1);
continue;
}

i--;
}

if (this.slots.has('default') && children.filter(node => !(node.type === 'Text' && node.data.trim() === ''))) {
throw new Error('Found elements without slot attribute when using slot="default"');
}

const default_slot = block.child({
comment: create_debugging_comment(node, renderer.component),
name: renderer.component.get_unique_name(`create_default_slot`),
type: 'slot'
});

this.renderer.blocks.push(default_slot);
this.default_slot_block = default_slot;

this.slots.set('default', get_slot_definition(default_slot, this.node.scope, this.node.lets));
const fragment = new FragmentWrapper(renderer, default_slot, children, this, strip_whitespace, next_sibling);
this.children.push(fragment);

const dependencies: Set<string> = new Set();

// TODO is this filtering necessary? (I *think* so)
default_slot.dependencies.forEach(name => {
if (!this.node.scope.is_let(name)) {
dependencies.add(name);
}
});

block.add_dependencies(dependencies);
this.children = this.node.children.map(child => new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
}

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"');
}
throw new Error(`Duplicate slot name "${name}" in <${this.node.name}>`);
}
this.slots.set(name, slot_definition);
Expand Down Expand Up @@ -167,7 +129,7 @@ export default class InlineComponentWrapper extends Wrapper {

this.children.forEach((child) => {
this.renderer.add_to_context('$$scope', true);
child.render(this.default_slot_block, null, x`#nodes` as Identifier);
child.render(block, null, x`#nodes` as Identifier);
});

let props;
Expand Down
30 changes: 1 addition & 29 deletions src/compiler/compile/render_ssr/handlers/Element.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
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 remove_whitespace_children from './utils/remove_whitespace_children';

export default function(node: Element, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
export default function(node: Element, renderer: Renderer, options: RenderOptions) {

const children = remove_whitespace_children(node.children, node.next);

Expand All @@ -23,13 +20,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);

const slot = node.get_static_attribute_value('slot');
const nearest_inline_component = node.find_nearest(/InlineComponent/);

if (slot && nearest_inline_component) {
renderer.push();
}

renderer.add_string(`<${node.name}`);

const class_expression_list = node.classes.map(class_directive => {
Expand Down Expand Up @@ -148,24 +138,6 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
if (!is_void(node.name)) {
renderer.add_string(`</${node.name}>`);
}
} else if (slot && nearest_inline_component) {
renderer.render(children, options);

if (!is_void(node.name)) {
renderer.add_string(`</${node.name}>`);
}

const lets = node.lets;
const seen = new Set(lets.map(l => l.name.name));

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()
});
} else {
renderer.render(children, options);

Expand Down
27 changes: 4 additions & 23 deletions src/compiler/compile/render_ssr/handlers/InlineComponent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
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 remove_whitespace_children from './utils/remove_whitespace_children';
import { p, x } from 'code-red';

function get_prop_value(attribute) {
Expand Down Expand Up @@ -68,28 +66,19 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend

const slot_fns = [];

const children = remove_whitespace_children(node.children, node.next);
const children = node.children;

if (children.length) {
const slot_scopes = new Map();

renderer.push();

renderer.render(children, Object.assign({}, options, {
slot_scopes
}));

slot_scopes.set('default', {
input: get_slot_scope(node.lets),
output: renderer.pop()
});

slot_scopes.forEach(({ input, output }, name) => {
if (!is_empty_template_literal(output)) {
slot_fns.push(
p`${name}: (${input}) => ${output}`
);
}
slot_fns.push(
p`${name}: (${input}) => ${output}`
);
});
}

Expand All @@ -99,11 +88,3 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend

renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`);
}

function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === ""
);
}
31 changes: 25 additions & 6 deletions src/compiler/compile/render_ssr/handlers/SlotTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import SlotTemplate from '../../nodes/SlotTemplate';
import remove_whitespace_children from './utils/remove_whitespace_children';
import { get_slot_scope } from './shared/get_slot_scope';
import InlineComponent from '../../nodes/InlineComponent';
import Element from '../../nodes/Element';

export default function(node: SlotTemplate, renderer: Renderer, options: RenderOptions & {
export default function(node: SlotTemplate | Element | InlineComponent, renderer: Renderer, options: RenderOptions & {
slot_scopes: Map<any, any>;
}) {
const parent_inline_component = node.parent as InlineComponent;
const children = remove_whitespace_children(node.children, node.next);
const children = remove_whitespace_children(node instanceof SlotTemplate ? node.children : [node], node.next);

renderer.push();
renderer.render(children, options);
Expand All @@ -19,8 +20,26 @@ export default function(node: SlotTemplate, renderer: Renderer, options: RenderO
if (!seen.has(l.name.name)) lets.push(l);
});

options.slot_scopes.set(node.slot_template_name, {
input: get_slot_scope(node.lets),
output: renderer.pop()
});
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
});
}
}

function is_empty_template_literal(template_literal) {
return (
template_literal.expressions.length === 0 &&
template_literal.quasis.length === 1 &&
template_literal.quasis[0].value.raw === ""
);
}
Loading

0 comments on commit 2d06e72

Please sign in to comment.