fix function slot props based on context (#5607)

pull/5620/head
Tan Li Hau 4 years ago committed by GitHub
parent 6fa3e91b5d
commit 5d7ffdb8a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@
## Unreleased ## Unreleased
* Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482)) * Fix `$$props` and `$$restProps` when compiling to a custom element ([#5482](https://github.com/sveltejs/svelte/issues/5482))
* Fix function calls in `<slot>` props that use contextual values ([#5565](https://github.com/sveltejs/svelte/issues/5565))
* Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586)) * Add `Element` and `Node` to known globals ([#5586](https://github.com/sveltejs/svelte/issues/5586))
## 3.29.4 ## 3.29.4

@ -4,7 +4,6 @@ import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference'; import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope'; import { create_scopes, Scope, extract_names } from '../../utils/scope';
import { sanitize } from '../../../utils/names'; import { sanitize } from '../../../utils/names';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope'; import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object'; import get_object from '../../utils/get_object';
import Block from '../../render_dom/Block'; import Block from '../../render_dom/Block';
@ -12,12 +11,12 @@ import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
import { b } from 'code-red'; import { b } from 'code-red';
import { invalidate } from '../../render_dom/invalidate'; import { invalidate } from '../../render_dom/invalidate';
import { Node, FunctionExpression, Identifier } from 'estree'; import { Node, FunctionExpression, Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces'; import { INode } from '../interfaces';
import { is_reserved_keyword } from '../../utils/reserved_keywords'; import { is_reserved_keyword } from '../../utils/reserved_keywords';
import replace_object from '../../utils/replace_object'; import replace_object from '../../utils/replace_object';
import EachBlock from '../EachBlock'; import EachBlock from '../EachBlock';
type Owner = Wrapper | TemplateNode; type Owner = INode;
export default class Expression { export default class Expression {
type: 'Expression' = 'Expression'; type: 'Expression' = 'Expression';
@ -37,7 +36,6 @@ export default class Expression {
manipulated: Node; manipulated: Node;
// todo: owner type
constructor(component: Component, owner: Owner, template_scope: TemplateScope, info, lazy?: boolean) { constructor(component: Component, owner: Owner, template_scope: TemplateScope, info, lazy?: boolean) {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
@ -276,10 +274,12 @@ export default class Expression {
else { else {
// we need a combo block/init recipe // we need a combo block/init recipe
const deps = Array.from(contextual_dependencies); const deps = Array.from(contextual_dependencies);
const function_expression = node as FunctionExpression;
(node as FunctionExpression).params = [ const has_args = function_expression.params.length > 0;
function_expression.params = [
...deps.map(name => ({ type: 'Identifier', name } as Identifier)), ...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
...(node as FunctionExpression).params ...function_expression.params
]; ];
const context_args = deps.map(name => block.renderer.reference(name)); const context_args = deps.map(name => block.renderer.reference(name));
@ -291,18 +291,49 @@ export default class Expression {
this.replace(id as any); this.replace(id as any);
if ((node as FunctionExpression).params.length > 0) { const func_declaration = has_args
declarations.push(b` ? b`function ${id}(...args) {
function ${id}(...args) { return ${callee}(${context_args}, ...args);
return ${callee}(${context_args}, ...args); }`
} : b`function ${id}() {
`); return ${callee}(${context_args});
}`;
if (owner.type === 'Attribute' && owner.parent.name === 'slot') {
const dep_scopes = new Set<INode>(deps.map(name => template_scope.get_owner(name)));
// find the nearest scopes
let node: INode = owner.parent;
while (node && !dep_scopes.has(node)) {
node = node.parent;
}
const func_expression = func_declaration[0];
if (node.type === 'InlineComponent') {
// <Comp let:data />
this.replace(func_expression);
} else {
// {#each}, {#await}
const func_id = component.get_unique_name(id.name + '_func');
block.renderer.add_to_context(func_id.name, true);
// rename #ctx -> child_ctx;
walk(func_expression, {
enter(node) {
if (node.type === 'Identifier' && node.name === '#ctx') {
node.name = 'child_ctx';
}
}
});
// add to get_xxx_context
// child_ctx[x] = function () { ... }
(template_scope.get_owner(deps[0]) as EachBlock).contexts.push({
key: func_id,
modifier: () => func_expression
});
this.replace(block.renderer.reference(func_id));
}
} else { } else {
declarations.push(b` declarations.push(func_declaration);
function ${id}() {
return ${callee}(${context_args});
}
`);
} }
} }

@ -196,11 +196,6 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.is_dom_node() : ? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node(); !parent_node || !this.parent.is_dom_node();
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
const snippet = this.node.expression.manipulate(block); const snippet = this.node.expression.manipulate(block);
block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`); block.chunks.init.push(b`let ${this.vars.each_block_value} = ${snippet};`);
@ -208,15 +203,6 @@ export default class EachBlockWrapper extends Wrapper {
block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`); block.chunks.init.push(b`@validate_each_argument(${this.vars.each_block_value});`);
} }
// TODO which is better — Object.create(array) or array.slice()?
renderer.blocks.push(b`
function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = #ctx.slice();
${this.context_props}
return child_ctx;
}
`);
const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' }; const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : '#anchor' };
const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' }; const initial_mount_node: Identifier = parent_node || { type: 'Identifier', name: '#target' };
const update_anchor_node = needs_anchor const update_anchor_node = needs_anchor
@ -360,6 +346,19 @@ export default class EachBlockWrapper extends Wrapper {
if (this.else) { if (this.else) {
this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier); this.else.fragment.render(this.else.block, null, x`#nodes` as Identifier);
} }
this.context_props = this.node.contexts.map(prop => b`child_ctx[${renderer.context_lookup.get(prop.key.name).index}] = ${prop.modifier(x`list[i]`)};`);
if (this.node.has_binding) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.vars.each_block_value.name).index}] = list;`);
if (this.node.has_binding || this.node.has_index_binding || this.node.index) this.context_props.push(b`child_ctx[${renderer.context_lookup.get(this.index_name.name).index}] = i;`);
// TODO which is better — Object.create(array) or array.slice()?
renderer.blocks.push(b`
function ${this.vars.get_each_context}(#ctx, list, i) {
const child_ctx = #ctx.slice();
${this.context_props}
return child_ctx;
}
`);
} }
render_keyed({ render_keyed({

@ -0,0 +1,14 @@
<script>
let keys = ['a', 'b'];
let items = ['c', 'd'];
export let log;
function setKey(key, value, item) {
log.push(`setKey(${key}, ${value}, ${item})`);
}
</script>
{#each items as item (item)}
{#each keys as key (key)}
<slot {key} {item} set={(value) => setKey(key, value, item)} />
{/each}
{/each}

@ -0,0 +1,36 @@
export default {
html: `
<button type="button">Set a-c</button>
<button type="button">Set b-c</button>
<button type="button">Set a-d</button>
<button type="button">Set b-d</button>
`,
async test({ assert, target, window, component }) {
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
const click = new window.MouseEvent('click');
await btn1.dispatchEvent(click);
assert.deepEqual(component.log, ['setKey(a, value-a-c, c)']);
await btn2.dispatchEvent(click);
assert.deepEqual(component.log, [
'setKey(a, value-a-c, c)',
'setKey(b, value-b-c, c)'
]);
await btn3.dispatchEvent(click);
assert.deepEqual(component.log, [
'setKey(a, value-a-c, c)',
'setKey(b, value-b-c, c)',
'setKey(a, value-a-d, d)'
]);
await btn4.dispatchEvent(click);
assert.deepEqual(component.log, [
'setKey(a, value-a-c, c)',
'setKey(b, value-b-c, c)',
'setKey(a, value-a-d, d)',
'setKey(b, value-b-d, d)'
]);
}
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
export let log = [];
</script>
<Nested {log} let:set let:key let:item>
<button type="button" on:click={() => set(`value-${key}-${item}`)}>Set {key}-{item}</button>
</Nested>

@ -0,0 +1,11 @@
<script>
let keys = ['a', 'b'];
export let log;
function setKey(key, value) {
log.push(`setKey(${key}, ${value})`);
}
</script>
{#each keys as key (key)}
<slot {key} set={(value) => setKey(key, value)} />
{/each}

@ -0,0 +1,19 @@
export default {
html: `
<button type="button">Set a</button>
<button type="button">Set b</button>
`,
async test({ assert, target, window, component }) {
const [btn1, btn2] = target.querySelectorAll('button');
const click = new window.MouseEvent('click');
await btn1.dispatchEvent(click);
assert.deepEqual(component.log, ['setKey(a, value-a)']);
await btn2.dispatchEvent(click);
assert.deepEqual(component.log, [
'setKey(a, value-a)',
'setKey(b, value-b)'
]);
}
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
export let log = [];
</script>
<Nested {log} let:set let:key>
<button type="button" on:click={() => set(`value-${key}`)}>Set {key}</button>
</Nested>

@ -0,0 +1,9 @@
<script>
export let log;
function setKey(key, value) {
log.push(`setKey(${key}, ${value})`);
}
</script>
<slot key="a" set={setKey} />
<slot key="b" set={setKey} />

@ -0,0 +1,8 @@
<script>
import Inner from './Inner.svelte';
export let log;
</script>
<Inner {log} let:key let:set>
<slot {key} set={(value) => set(key, value)} />
</Inner>

@ -0,0 +1,19 @@
export default {
html: `
<button type="button">Set a</button>
<button type="button">Set b</button>
`,
async test({ assert, target, window, component }) {
const [btn1, btn2] = target.querySelectorAll('button');
const click = new window.MouseEvent('click');
await btn1.dispatchEvent(click);
assert.deepEqual(component.log, ['setKey(a, value-a)']);
await btn2.dispatchEvent(click);
assert.deepEqual(component.log, [
'setKey(a, value-a)',
'setKey(b, value-b)'
]);
}
};

@ -0,0 +1,8 @@
<script>
import Nested from './Nested.svelte';
export let log = [];
</script>
<Nested {log} let:set let:key>
<button type="button" on:click={() => set(`value-${key}`)}>Set {key}</button>
</Nested>
Loading…
Cancel
Save