feat: support const tags in conditional slots

pull/8304/head
tanhauhau 4 years ago
parent 69f8a91b25
commit 004b559373

@ -12,7 +12,7 @@ import get_object from '../utils/get_object';
import compiler_errors from '../compiler_errors';
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 {
type: 'ConstTag';

@ -12,6 +12,7 @@ import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns';
import { validate_get_slot_names } from './SlotTemplateIfBlock';
import { BaseNode } from 'estree-walker';
export default class InlineComponent extends Node {
type: 'InlineComponent';
@ -147,7 +148,7 @@ export default class InlineComponent extends Node {
} else if (child.type === 'Comment' && children.length > 0) {
children[children.length - 1].children.unshift(child);
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({
...child,
type: 'SlotTemplateIfBlock'
@ -193,3 +194,11 @@ function get_namespace(parent: Node, explicit_namespace: string) {
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;
while (parent.type === 'SlotTemplateIfBlock' || parent.type === 'SlotTemplateElseBlock') parent = parent.parent;
if (parent.type === 'IfBlock' || parent.type === 'ElseBlock') {
console.log({ parent });
return this.component.error(this, compiler_errors.invalid_mix_element_and_conditional_slot);
}
if (parent.type !== 'InlineComponent') {

@ -169,7 +169,7 @@ export default class InlineComponentWrapper extends Wrapper {
: this.children.length > 0
? [
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 }`
]

@ -13,6 +13,7 @@ import SlotTemplate from '../../nodes/SlotTemplate';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
import TemplateScope from '../../nodes/shared/TemplateScope';
import SlotTemplateIfBlockWrapper from './SlotTemplateIfBlock';
import { INode } from '../../nodes/interfaces';
export default class SlotTemplateWrapper extends Wrapper {
node: SlotTemplate;
@ -83,7 +84,7 @@ export default class SlotTemplateWrapper extends Wrapper {
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.slot_definition = null;
}
@ -100,10 +101,23 @@ export default class SlotTemplateWrapper extends Wrapper {
}
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');
this.block.renderer.blocks.push(b`
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)`);

@ -5,9 +5,10 @@ import { Identifier } from 'estree';
import SlotTemplateIfBlock from '../../nodes/SlotTemplateIfBlock';
import SlotTemplateWrapper from './SlotTemplate';
import SlotTemplate from '../../nodes/SlotTemplate';
import { b } from 'code-red';
import { x, b } from 'code-red';
import InlineComponentWrapper from './InlineComponent';
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 {
node: SlotTemplateIfBlock;
@ -30,6 +31,8 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
super(renderer, block, parent, node);
this.scope = node.scope;
add_const_tags_context(renderer, this.node.const_tags);
for (const child of this.node.children) {
if (child.type === 'SlotTemplate') {
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) {
add_const_tags_context(renderer, this.node.else.const_tags);
for (const child of node.else.children) {
if (child.type === 'SlotTemplate') {
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) {
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) {
return b`
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))}
} else {
${else_get_context ? x`${else_get_context}(#ctx)` : null}
${this.else.map(slot => slot.render_slot_template_definition(block))}
}
`;
@ -67,6 +93,7 @@ export default class SlotTemplateIfBlockWrapper extends Wrapper {
return b`
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))}
}
`;

@ -1,7 +1,7 @@
import Renderer, { RenderOptions } from '../Renderer';
import { b } from 'code-red';
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) {
const if_slot_scopes = [];
@ -16,14 +16,17 @@ export default function (node: SlotTemplateIfBlock, renderer: Renderer, options:
}));
options.slot_scopes.push(b`
if (${node.expression.node}) {
${get_const_tags(node.const_tags)}
${if_slot_scopes}
} else {
${get_const_tags(node.else.const_tags)}
${else_slot_scopes}
}
`);
} else {
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 {
solo:true,
html: `
<p slot='one'>one: 1 two: 2</p>
`,

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

Loading…
Cancel
Save