Skip to content

Commit

Permalink
Merge branch 'master' into gh-2586
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris authored Jun 27, 2019
2 parents b360782 + 6af23ba commit a947d1b
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 113 deletions.
10 changes: 10 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
**/_actual.js
**/expected.js
test/*/samples/*/output.js

# output files
animate/*.js
esing/*.js
internal/*.js
motion/*.js
store/*.js
transition/*.js
index.js
compiler.js
4 changes: 2 additions & 2 deletions site/content/tutorial/04-logic/05-keyed-each-blocks/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Keyed each blocks

By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want.

It's easier to show why than to explain. Click the 'Remove first item' button a few times, and notice that it's removing `<Thing>` components from the end and updating the `value` for those that remain. Instead, we'd like to remove the first `<Thing>` component and leave the rest unaffected.
It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice that it's removing `<Thing>` components from the end and updating the `value` for those that remain. Instead, we'd like to remove the first `<Thing>` component and leave the rest unaffected.

To do that, we specify a unique identifier for the `each` block:

Expand All @@ -16,4 +16,4 @@ To do that, we specify a unique identifier for the `each` block:

The `(thing.id)` tells Svelte how to figure out what changed.

> You can use any object as the key, as Svelte uses a `Map` internally — in other words you could do `(thing)` instead of `(thing.id)`. Using a string or number is generally safer, however, since it means identity persists without referential equality, for example when updating with fresh data from an API server.
> You can use any object as the key, as Svelte uses a `Map` internally — in other words you could do `(thing)` instead of `(thing.id)`. Using a string or number is generally safer, however, since it means identity persists without referential equality, for example when updating with fresh data from an API server.
4 changes: 2 additions & 2 deletions site/content/tutorial/06-bindings/12-bind-this/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The readonly `this` binding applies to every element (and component) and allows
```html
<canvas
bind:this={canvas}
width={256}
height={256}
width={32}
height={32}
></canvas>
```

Expand Down
104 changes: 63 additions & 41 deletions src/compiler/compile/render_dom/wrappers/EachBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export default class EachBlockWrapper extends Wrapper {
fragment: FragmentWrapper;
else?: ElseBlockWrapper;
vars: {
anchor: string;
create_each_block: string;
each_block_value: string;
get_each_context: string;
Expand Down Expand Up @@ -119,10 +118,7 @@ export default class EachBlockWrapper extends Wrapper {
// optimisation for array literal
fixed_length,
data_length: fixed_length === null ? `${each_block_value}.[✂${c}-${c+4}✂]` : fixed_length,
view_length: fixed_length === null ? `${iterations}.[✂${c}-${c+4}✂]` : fixed_length,

// filled out later
anchor: null
view_length: fixed_length === null ? `${iterations}.[✂${c}-${c+4}✂]` : fixed_length
};

node.contexts.forEach(prop => {
Expand Down Expand Up @@ -175,10 +171,6 @@ export default class EachBlockWrapper extends Wrapper {
? !this.next.is_dom_node() :
!parent_node || !this.parent.is_dom_node();

this.vars.anchor = needs_anchor
? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null';

this.context_props = this.node.contexts.map(prop => `child_ctx.${prop.key.name} = ${attach_head('list[i]', prop.tail)};`);

if (this.node.has_binding) this.context_props.push(`child_ctx.${this.vars.each_block_value} = list;`);
Expand All @@ -196,10 +188,28 @@ export default class EachBlockWrapper extends Wrapper {
}
`);

const initial_anchor_node = parent_node ? 'null' : 'anchor';
const initial_mount_node = parent_node || '#target';
const update_anchor_node = needs_anchor
? block.get_unique_name(`${this.var}_anchor`)
: (this.next && this.next.var) || 'null';
const update_mount_node = this.get_update_mount_node(update_anchor_node);

const args = {
block,
parent_node,
parent_nodes,
snippet,
initial_anchor_node,
initial_mount_node,
update_anchor_node,
update_mount_node
};

if (this.node.key) {
this.render_keyed(block, parent_node, parent_nodes, snippet);
this.render_keyed(args);
} else {
this.render_unkeyed(block, parent_node, parent_nodes, snippet);
this.render_unkeyed(args);
}

if (this.block.has_intro_method || this.block.has_outro_method) {
Expand All @@ -210,7 +220,7 @@ export default class EachBlockWrapper extends Wrapper {

if (needs_anchor) {
block.add_element(
this.vars.anchor,
update_anchor_node,
`@empty()`,
parent_nodes && `@empty()`,
parent_node
Expand All @@ -232,20 +242,18 @@ export default class EachBlockWrapper extends Wrapper {

block.builders.mount.add_block(deindent`
if (${each_block_else}) {
${each_block_else}.m(${parent_node || '#target'}, null);
${each_block_else}.m(${initial_mount_node}, ${initial_anchor_node});
}
`);

const initial_mount_node = parent_node || `${this.vars.anchor}.parentNode`;

if (this.else.block.has_update_method) {
block.builders.update.add_block(deindent`
if (!${this.vars.data_length} && ${each_block_else}) {
${each_block_else}.p(changed, ctx);
} else if (!${this.vars.data_length}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else}.c();
${each_block_else}.m(${initial_mount_node}, ${this.vars.anchor});
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});
} else if (${each_block_else}) {
${each_block_else}.d(1);
${each_block_else} = null;
Expand All @@ -261,7 +269,7 @@ export default class EachBlockWrapper extends Wrapper {
} else if (!${each_block_else}) {
${each_block_else} = ${this.else.block.name}(ctx);
${each_block_else}.c();
${each_block_else}.m(${initial_mount_node}, ${this.vars.anchor});
${each_block_else}.m(${update_mount_node}, ${update_anchor_node});
}
`);
}
Expand All @@ -278,16 +286,28 @@ export default class EachBlockWrapper extends Wrapper {
}
}

render_keyed(
render_keyed({
block,
parent_node,
parent_nodes,
snippet,
initial_anchor_node,
initial_mount_node,
update_anchor_node,
update_mount_node
}: {
block: Block,
parent_node: string,
parent_nodes: string,
snippet: string
) {
snippet: string,
initial_anchor_node: string,
initial_mount_node: string,
update_anchor_node: string,
update_mount_node: string
}) {
const {
create_each_block,
length,
anchor,
iterations,
view_length
} = this.vars;
Expand Down Expand Up @@ -322,10 +342,6 @@ export default class EachBlockWrapper extends Wrapper {
}
`);

const initial_mount_node = parent_node || '#target';
const update_mount_node = this.get_update_mount_node(anchor);
const anchor_node = parent_node ? 'null' : 'anchor';

block.builders.create.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].c();
`);
Expand All @@ -337,7 +353,7 @@ export default class EachBlockWrapper extends Wrapper {
}

block.builders.mount.add_block(deindent`
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initial_mount_node}, ${anchor_node});
for (#i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
`);

const dynamic = this.block.has_update_method;
Expand All @@ -355,7 +371,7 @@ export default class EachBlockWrapper extends Wrapper {
${this.block.has_outros && `@group_outros();`}
${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`}
${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${anchor}, ${this.vars.get_each_context});
${iterations} = @update_keyed_each(${iterations}, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context});
${this.node.has_animation && `for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && `@check_outros();`}
`);
Expand All @@ -371,20 +387,30 @@ export default class EachBlockWrapper extends Wrapper {
`);
}

render_unkeyed(
render_unkeyed({
block,
parent_nodes,
snippet,
initial_anchor_node,
initial_mount_node,
update_anchor_node,
update_mount_node
}: {
block: Block,
parent_node: string,
parent_nodes: string,
snippet: string
) {
snippet: string,
initial_anchor_node: string,
initial_mount_node: string,
update_anchor_node: string,
update_mount_node: string
}) {
const {
create_each_block,
length,
iterations,
fixed_length,
data_length,
view_length,
anchor
view_length
} = this.vars;

block.builders.init.add_block(deindent`
Expand All @@ -395,10 +421,6 @@ export default class EachBlockWrapper extends Wrapper {
}
`);

const initial_mount_node = parent_node || '#target';
const update_mount_node = this.get_update_mount_node(anchor);
const anchor_node = parent_node ? 'null' : 'anchor';

block.builders.create.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].c();
Expand All @@ -415,7 +437,7 @@ export default class EachBlockWrapper extends Wrapper {

block.builders.mount.add_block(deindent`
for (var #i = 0; #i < ${view_length}; #i += 1) {
${iterations}[#i].m(${initial_mount_node}, ${anchor_node});
${iterations}[#i].m(${initial_mount_node}, ${initial_anchor_node});
}
`);

Expand All @@ -441,7 +463,7 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
${has_transitions && `@transition_in(${this.vars.iterations}[#i], 1);`}
${iterations}[#i].m(${update_mount_node}, ${anchor});
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`
: has_transitions
Expand All @@ -452,14 +474,14 @@ export default class EachBlockWrapper extends Wrapper {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
@transition_in(${this.vars.iterations}[#i], 1);
${iterations}[#i].m(${update_mount_node}, ${anchor});
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`
: deindent`
if (!${iterations}[#i]) {
${iterations}[#i] = ${create_each_block}(child_ctx);
${iterations}[#i].c();
${iterations}[#i].m(${update_mount_node}, ${anchor});
${iterations}[#i].m(${update_mount_node}, ${update_anchor_node});
}
`;

Expand Down
34 changes: 3 additions & 31 deletions src/compiler/compile/render_dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import add_event_handlers from '../shared/add_event_handlers';
import add_actions from '../shared/add_actions';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger';
import bind_this from '../shared/bind_this';

const events = [
{
Expand Down Expand Up @@ -540,38 +541,9 @@ export default class ElementWrapper extends Wrapper {

const this_binding = this.bindings.find(b => b.node.name === 'this');
if (this_binding) {
const name = renderer.component.get_unique_name(`${this.var}_binding`);
const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var);

renderer.component.add_var({
name,
internal: true,
referenced: true
});

const { handler, object } = this_binding;

const args = [];
for (const arg of handler.contextual_dependencies) {
args.push(arg);
block.add_variable(arg, `ctx.${arg}`);
}

renderer.component.partly_hoisted.push(deindent`
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
${renderer.component.invalidate(object)};
}
`);

block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.add_line(deindent`
if (changed.items) {
ctx.${name}(${['null', this.var].concat(args).join(', ')});
${args.map(a => `${a} = ctx.${a}`).join(', ')};
ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
}`
);
block.builders.mount.add_line(binding_callback);
}
}

Expand Down
38 changes: 2 additions & 36 deletions src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import add_to_set from '../../../utils/add_to_set';
import deindent from '../../../utils/deindent';
import Attribute from '../../../nodes/Attribute';
import get_object from '../../../utils/get_object';
import flatten_reference from '../../../utils/flatten_reference';
import create_debugging_comment from '../shared/create_debugging_comment';
import { get_context_merger } from '../shared/get_context_merger';
import EachBlock from '../../../nodes/EachBlock';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import is_dynamic from '../shared/is_dynamic';
import bind_this from '../shared/bind_this';

export default class InlineComponentWrapper extends Wrapper {
var: string;
Expand Down Expand Up @@ -249,41 +249,7 @@ export default class InlineComponentWrapper extends Wrapper {
component.has_reactive_assignments = true;

if (binding.name === 'this') {
const fn = component.get_unique_name(`${this.var}_binding`);

component.add_var({
name: fn,
internal: true,
referenced: true
});

let lhs;
let object;

if (binding.is_contextual && binding.expression.node.type === 'Identifier') {
// bind:x={y} — we can't just do `y = x`, we need to
// to `array[index] = x;
const { name } = binding.expression.node;
const { snippet } = block.bindings.get(name);
lhs = snippet;

// TODO we need to invalidate... something
} else {
object = flatten_reference(binding.expression.node).name;
lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim();
}

const contextual_dependencies = [...binding.expression.contextual_dependencies];

component.partly_hoisted.push(deindent`
function ${fn}(${['$$component', ...contextual_dependencies].join(', ')}) {
${lhs} = $$component;
${object && component.invalidate(object)}
}
`);

block.builders.destroy.add_line(`ctx.${fn}(null);`);
return `@add_binding_callback(() => ctx.${fn}(${[this.var, ...contextual_dependencies.map(name => `ctx.${name}`)].join(', ')}));`;
return bind_this(component, block, binding, this.var);
}

const name = component.get_unique_name(`${this.var}_${binding.name}_binding`);
Expand Down
Loading

0 comments on commit a947d1b

Please sign in to comment.