feat: support const tags in conditional slots

pull/8304/head
tanhauhau 4 years ago
parent 54f65696cb
commit 58ddbde888

@ -12,7 +12,7 @@ import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors'; import compiler_errors from '../compiler_errors';
import { Node as ESTreeNode } from 'estree'; import { Node as ESTreeNode } from 'estree';
const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock']); const allowed_parents = new Set(['EachBlock', 'CatchBlock', 'ThenBlock', 'InlineComponent', 'SlotTemplate', 'IfBlock', 'ElseBlock', 'SlotTemplateIfBlock', 'SlotTemplateElseBlock']);
export default class ConstTag extends Node { export default class ConstTag extends Node {
type: 'ConstTag'; type: 'ConstTag';

@ -12,6 +12,7 @@ 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 { validate_get_slot_names } from './SlotTemplateIfBlock'; import { validate_get_slot_names } from './SlotTemplateIfBlock';
import { BaseNode } from 'estree-walker';
export default class InlineComponent extends Node { export default class InlineComponent extends Node {
type: 'InlineComponent'; type: 'InlineComponent';
@ -147,7 +148,7 @@ export default class InlineComponent extends Node {
} else if (child.type === 'Comment' && children.length > 0) { } else if (child.type === 'Comment' && children.length > 0) {
children[children.length - 1].children.unshift(child); children[children.length - 1].children.unshift(child);
info.children.splice(i, 1); info.children.splice(i, 1);
} else if (child.type === 'IfBlock' && child.children.some(if_child => if_child.type === 'SlotTemplate')) { } else if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) {
children.push({ children.push({
...child, ...child,
type: 'SlotTemplateIfBlock' type: 'SlotTemplateIfBlock'
@ -193,3 +194,11 @@ function get_namespace(parent: Node, explicit_namespace: string) {
return parent_element.namespace; return parent_element.namespace;
} }
function if_block_contains_slot_template(node: TemplateNode) {
for (const child of node.children) {
if (child.type === 'SlotTemplate') return true;
if (child.type === 'IfBlock' && if_block_contains_slot_template(child)) return true;
}
return false;
}

@ -69,6 +69,7 @@ export default class SlotTemplate extends Node {
let parent = this.parent; let parent = this.parent;
while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') parent = parent.parent; while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') parent = parent.parent;
if (parent.type === 'IfBlock' || parent.type === 'ElseBlock') { if (parent.type === 'IfBlock' || parent.type === 'ElseBlock') {
console.log({ parent });
return this.component.error(this, compiler_errors.invalid_mix_element_and_conditional_slot); return this.component.error(this, compiler_errors.invalid_mix_element_and_conditional_slot);
} }
if (parent.type !== 'InlineComponent') { if (parent.type !== 'InlineComponent') {

@ -173,7 +173,7 @@ export default class InlineComponentWrapper extends Wrapper {
: this.children.length > 0 : this.children.length > 0
? [ ? [
p`$$slots: { p`$$slots: {
${this.children.map((slot: SlotTemplateWrapper) => p`${slot.slot_template_name}: ${slot.slot_definition}`)} ${this.children.filter((slot: SlotTemplateWrapper) => slot.slot_definition).map((slot: SlotTemplateWrapper) => p`${slot.slot_template_name}: ${slot.slot_definition}`)}
}`, }`,
p`$$scope: { ctx: #ctx }` p`$$scope: { ctx: #ctx }`
] ]

@ -13,6 +13,7 @@ import SlotTemplate from '../../nodes/SlotTemplate';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags'; import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
import TemplateScope from '../../nodes/shared/TemplateScope'; import TemplateScope from '../../nodes/shared/TemplateScope';
import SlotTemplateIfBlockWrapper from './SlotTemplateIfBlock'; import SlotTemplateIfBlockWrapper from './SlotTemplateIfBlock';
import { INode } from '../../nodes/interfaces';
export default class SlotTemplateWrapper extends Wrapper { export default class SlotTemplateWrapper extends Wrapper {
node: SlotTemplate; node: SlotTemplate;
@ -83,7 +84,7 @@ export default class SlotTemplateWrapper extends Wrapper {
this.render_get_context(); this.render_get_context();
} }
if (!this.block.has_content()) { if (this.slot_template_name === 'default' && !this.block.has_content()) {
this.renderer.remove_block(this.block); this.renderer.remove_block(this.block);
this.slot_definition = null; this.slot_definition = null;
} }
@ -100,10 +101,23 @@ export default class SlotTemplateWrapper extends Wrapper {
} }
render_get_context() { render_get_context() {
const if_const_tags = [];
let parent = this.node.parent;
while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') {
if_const_tags.push(parent.const_tags);
if (parent.type === 'SlotTemplateElseBlock') parent = parent.parent;
parent = parent.parent;
}
const const_tags = [];
for (let i = if_const_tags.length - 1; i >= 0; i--) {
const_tags.push(...if_const_tags[i]);
}
const_tags.push(...this.node.const_tags);
const get_context = this.block.renderer.component.get_unique_name('get_context'); const get_context = this.block.renderer.component.get_unique_name('get_context');
this.block.renderer.blocks.push(b` this.block.renderer.blocks.push(b`
function ${get_context}(#ctx) { function ${get_context}(#ctx) {
${add_const_tags(this.block, this.node.const_tags, '#ctx')} ${add_const_tags(this.block, const_tags, '#ctx')}
} }
`); `);
this.block.chunks.declarations.push(b`${get_context}(#ctx)`); this.block.chunks.declarations.push(b`${get_context}(#ctx)`);

@ -5,9 +5,10 @@ import { Identifier } from 'estree';
import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock'; import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock';
import SlotTemplateWrapper from './SlotTemplate'; import SlotTemplateWrapper from './SlotTemplate';
import SlotTemplate from '../../nodes/SlotTemplate'; import SlotTemplate from '../../nodes/SlotTemplate';
import { b } from 'code-red'; import { x, b } from 'code-red';
import InlineComponentWrapper from './InlineComponent'; import InlineComponentWrapper from './InlineComponent';
import TemplateScope from '../../nodes/shared/TemplateScope'; import TemplateScope from '../../nodes/shared/TemplateScope';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
export default class SlotTemplateIfBlockWrapper extends Wrapper { export default class SlotTemplateIfBlockWrapper extends Wrapper {
node: SlotTemplateIfBlock; node: SlotTemplateIfBlock;
@ -30,6 +31,8 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.scope = node.scope; this.scope = node.scope;
add_const_tags_context(renderer, this.node.const_tags);
for (const child of this.node.children) { for (const child of this.node.children) {
if (child.type === 'SlotTemplate') { if (child.type === 'SlotTemplate') {
this.children.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling)); this.children.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
@ -39,6 +42,7 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
} }
if (node.else) { if (node.else) {
add_const_tags_context(renderer, this.node.else.const_tags);
for (const child of node.else.children) { for (const child of node.else.children) {
if (child.type === 'SlotTemplate') { if (child.type === 'SlotTemplate') {
this.else.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling)); this.else.push(new SlotTemplateWrapper(renderer, block, this, child as SlotTemplate, strip_whitespace, next_sibling));
@ -55,11 +59,33 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
} }
render_slot_template_definition(block: Block) { render_slot_template_definition(block: Block) {
let if_get_context;
let else_get_context;
if (this.node.const_tags.length) {
if_get_context = block.renderer.component.get_unique_name('get_context');
block.renderer.blocks.push(b`
function ${if_get_context}(#ctx) {
${add_const_tags(block, this.node.const_tags, '#ctx')}
}
`);
}
if (this.node.else && this.node.else.const_tags.length) {
else_get_context = block.renderer.component.get_unique_name('get_context');
block.renderer.blocks.push(b`
function ${else_get_context}(#ctx) {
${add_const_tags(block, this.node.const_tags, '#ctx')}
}
`);
}
if (this.else.length > 0) { if (this.else.length > 0) {
return b` return b`
if (${this.node.expression.manipulate(block, '#ctx')}) { if (${this.node.expression.manipulate(block, '#ctx')}) {
${if_get_context ? x`${if_get_context}(#ctx)` : null}
${this.children.map(slot => slot.render_slot_template_definition(block))} ${this.children.map(slot => slot.render_slot_template_definition(block))}
} else { } else {
${else_get_context ? x`${else_get_context}(#ctx)` : null}
${this.else.map(slot => slot.render_slot_template_definition(block))} ${this.else.map(slot => slot.render_slot_template_definition(block))}
} }
`; `;
@ -67,6 +93,7 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
return b` return b`
if (${this.node.expression.manipulate(block, '#ctx')}) { if (${this.node.expression.manipulate(block, '#ctx')}) {
${if_get_context ? x`${if_get_context}(#ctx)` : null}
${this.children.map(slot => slot.render_slot_template_definition(block))} ${this.children.map(slot => slot.render_slot_template_definition(block))}
} }
`; `;

@ -1,7 +1,7 @@
import Renderer, { RenderOptions } from '../Renderer'; import Renderer, { RenderOptions } from '../Renderer';
import { b } from 'code-red'; import { b } from 'code-red';
import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock'; import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock';
import { flatten } from '../../../utils/flatten'; import { get_const_tags } from './shared/get_const_tags';
export default function (node: SlotTemplateIfBlock, renderer: Renderer, options: RenderOptions) { export default function (node: SlotTemplateIfBlock, renderer: Renderer, options: RenderOptions) {
const if_slot_scopes = []; const if_slot_scopes = [];
@ -16,14 +16,17 @@ export default function (node: SlotTemplateIfBlock, renderer: Renderer, options:
})); }));
options.slot_scopes.push(b` options.slot_scopes.push(b`
if (${node.expression.node}) { if (${node.expression.node}) {
${get_const_tags(node.const_tags)}
${if_slot_scopes} ${if_slot_scopes}
} else { } else {
${get_const_tags(node.else.const_tags)}
${else_slot_scopes} ${else_slot_scopes}
} }
`); `);
} else { } else {
options.slot_scopes.push(b`if (${node.expression.node}) { options.slot_scopes.push(b`if (${node.expression.node}) {
${flatten(if_slot_scopes)} ${get_const_tags(node.const_tags)}
${if_slot_scopes}
}`); }`);
} }
} }

@ -0,0 +1,7 @@
<script>
export let constant;
</script>
<slot name="top" {constant} />
<hr />
<slot name="bottom" {constant} />

@ -0,0 +1,55 @@
export default {
html: `
3 ~ 15
<hr>
<div>sum: 7 product: 2 all: 14</div>
`,
test({ assert, component, target }) {
component.top = { a: 5, b: 6 };
assert.htmlEqual(target.innerHTML, `
11 ~ 55
<hr>
<div>sum: 7 product: 30 all: 42</div>
`);
component.main_constant = 3;
assert.htmlEqual(target.innerHTML, `
11 ~ 165
<hr>
<div>sum: 7 product: 90 all: 102</div>
`);
component.top = false;
assert.htmlEqual(target.innerHTML, `
<hr>
<div>sum: 7 all: 12</div>
`);
component.bottom = false;
assert.htmlEqual(target.innerHTML, `
<hr>
<div>35</div>
`);
component.top = { a: 2, b: 3 };
assert.htmlEqual(target.innerHTML, `
5 ~ 75
<hr>
<div>35</div>
`);
component.main_constant = 1;
assert.htmlEqual(target.innerHTML, `
5 ~ 25
<hr>
<div>15</div>
`);
component.foo_constant = 3;
assert.htmlEqual(target.innerHTML, `
5 ~ 15
<hr>
<div>9</div>
`);
}
};

@ -0,0 +1,38 @@
<script>
import Foo from "./Foo.svelte";
export let main_constant = 1;
export let foo_constant = 5;
export let top = { a: 1, b: 2 };
export let bottom = { a: 3, b: 4 };
</script>
<Foo constant={foo_constant}>
{#if top}
{@const sum = top.a + top.b}
<svelte:fragment slot="top" let:constant>
{@const csum = sum * constant * main_constant}
{sum} ~ {csum}
</svelte:fragment>
{/if}
{#if bottom}
{@const sum = bottom.a + bottom.b}
{#if top}
{@const product = top.a * top.b * main_constant}
<svelte:fragment slot="bottom" let:constant>
{@const all = constant + product + sum}
<div>sum: {sum} product: {product} all: {all}</div>
</svelte:fragment>
{:else}
<svelte:fragment slot="bottom" let:constant>
{@const all = constant + sum}
<div>sum: {sum} all: {all}</div>
</svelte:fragment>
{/if}
{:else}
{@const product = foo_constant + foo_constant}
<svelte:fragment slot="bottom" let:constant>
{@const all = constant + product * main_constant}
<div>{all}</div>
</svelte:fragment>
{/if}
</Foo>

@ -0,0 +1,11 @@
<script>
export let value;
</script>
<div>
<slot name="child" {value}>fallback {value * 2}</slot>
{#if $$slots.alert}
<hr>
<slot name="alert" />
{/if}
</div>

@ -0,0 +1,76 @@
export default {
html: `
<div>
<span>2 ~ 3</span>
<div>
<span>3 ~ 5</span>
<div>
<span>5 ~ 8</span>
<hr>
#2 &gt; 5
</div>
</div>
</div>
`,
test({ assert, component, target }) {
component.array = [3, 5, 8];
assert.htmlEqual(target.innerHTML, `
<div>
<span>2 ~ 5</span>
<div>
<span>5 ~ 10</span>
<div>
<span>10 ~ 18</span>
<hr>
#2 &gt; 5
</div>
<hr>
#1 &gt; 5
</div>
</div>
`);
component.value = 8;
assert.htmlEqual(target.innerHTML, `
<div>
<span>8 ~ 11</span>
<div>
<span>11 ~ 16</span>
<div>
<span>16 ~ 24</span>
<hr>
#2 &gt; 5
</div>
<hr>
#1 &gt; 5
</div>
<hr>
#0 &gt; 5
</div>
`);
component.array = [1, 3];
assert.htmlEqual(target.innerHTML, `
<div>
<span>8 ~ 9</span>
<div>
<span>9 ~ 12</span>
<div>
fallback 24
</div>
<hr>
#1 &gt; 5
</div>
<hr>
#0 &gt; 5
</div>
`);
component.array = [];
assert.htmlEqual(target.innerHTML, `
<div>
fallback 16
</div>
`);
}
};

@ -0,0 +1,39 @@
<script>
import Foo from "./Foo.svelte";
export let value = 2;
export let array = [1, 2, 3];
</script>
<Foo {value}>
{#if array[0]}
{@const sum = array[0] + value}
{#if sum > 5}
<svelte:fragment slot="alert">#0 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum}</span>
<Foo value={sum}>
{#if array[1]}
{@const sum1 = array[1] + sum}
{#if sum1 > 5}
<svelte:fragment slot="alert">#1 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum1}</span>
<Foo value={sum1}>
{#if array[2]}
{@const sum2 = array[2] + sum1}
{#if sum2 > 5}
<svelte:fragment slot="alert">#2 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum2}</span>
</svelte:fragment>
{/if}
</Foo>
</svelte:fragment>
{/if}
</Foo>
</svelte:fragment>
{/if}
</Foo>

@ -0,0 +1,11 @@
<script>
export let value;
</script>
<div>
<slot name="child" {value}>fallback {value * 2}</slot>
{#if $$slots.alert}
<hr>
<slot name="alert" />
{/if}
</div>

@ -0,0 +1,76 @@
export default {
html: `
<div>
<span>2 ~ 3</span>
<div>
<span>3 ~ 5</span>
<div>
<span>5 ~ 8</span>
<hr>
#2 &gt; 5
</div>
</div>
</div>
`,
test({ assert, component, target }) {
component.array = [3, 5, 8];
assert.htmlEqual(target.innerHTML, `
<div>
<span>2 ~ 5</span>
<div>
<span>5 ~ 10</span>
<div>
<span>10 ~ 18</span>
<hr>
#2 &gt; 5
</div>
<hr>
#1 &gt; 5
</div>
</div>
`);
component.value = 8;
assert.htmlEqual(target.innerHTML, `
<div>
<span>8 ~ 11</span>
<div>
<span>11 ~ 16</span>
<div>
<span>16 ~ 24</span>
<hr>
#2 &gt; 5
</div>
<hr>
#1 &gt; 5
</div>
<hr>
#0 &gt; 5
</div>
`);
component.array = [1, 3];
assert.htmlEqual(target.innerHTML, `
<div>
<span>8 ~ 9</span>
<div>
<span>9 ~ 12</span>
<div>
fallback 24
</div>
<hr>
#1 &gt; 5
</div>
<hr>
#0 &gt; 5
</div>
`);
component.array = [];
assert.htmlEqual(target.innerHTML, `
<div>
fallback 16
</div>
`);
}
};

@ -0,0 +1,39 @@
<script>
import Foo from "./Foo.svelte";
export let value = 2;
export let array = [1, 2, 3];
</script>
<Foo {value}>
{#if array[0]}
{@const sum = array[0] + value}
{#if sum > 5}
<svelte:fragment slot="alert">#0 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum}</span>
<Foo value={sum}>
{#if array[1]}
{@const sum = array[0] + array[1] + value}
{#if sum > 5}
<svelte:fragment slot="alert">#1 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum}</span>
<Foo value={sum}>
{#if array[2]}
{@const sum = array[1] + array[2] + value}
{#if sum > 5}
<svelte:fragment slot="alert">#2 > 5</svelte:fragment>
{/if}
<svelte:fragment slot="child" let:value>
<span>{value} ~ {sum}</span>
</svelte:fragment>
{/if}
</Foo>
</svelte:fragment>
{/if}
</Foo>
</svelte:fragment>
{/if}
</Foo>

@ -1,5 +1,4 @@
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,4 +1,3 @@
export default { export default {
solo:true,
html: '<div><span>lol</span></div>' html: '<div><span>lol</span></div>'
}; };

Loading…
Cancel
Save