diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 83bc8be94e66..b9bbbc0f0654 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -665,37 +665,51 @@ export default class ElementWrapper extends Wrapper { const initial_props = []; const updates = []; + const all_dependencies = new Set(); + this.attributes.forEach(attr => { + add_to_set(all_dependencies, attr.node.get_dependencies()); + }); + this.attributes .forEach(attr => { - const condition = attr.node.dependencies.size > 0 - ? block.renderer.dirty(Array.from(attr.node.dependencies)) + const dependencies = attr.node.get_dependencies(); + + const condition = dependencies.length > 0 && (dependencies.length !== all_dependencies.size) + ? block.renderer.dirty(dependencies) : null; + const unchanged = dependencies.length === 0; + let snippet; if (attr.node.is_spread) { - const snippet = attr.node.expression.manipulate(block); + snippet = attr.node.expression.manipulate(block); initial_props.push(snippet); - updates.push(condition ? x`${condition} && ${snippet}` : snippet); + if (attr.node.expression.node.type !== 'ObjectExpression') { + snippet = x`@get_spread_object(${snippet})`; + } } else { const metadata = attr.get_metadata(); const name = attr.is_indirectly_bound_value() ? '__value' : (metadata && metadata.property_name) || fix_attribute_casing(attr.node.name); - const snippet = x`{ ${name}: ${attr.get_value(block)} }`; + snippet = x`{ ${name}: ${attr.get_value(block)} }`; initial_props.push(snippet); - - updates.push(condition ? x`${condition} && ${snippet}` : snippet); } + + updates.push( + unchanged + ? x`false` + : condition + ? x`${condition} && ${snippet}` + : snippet + ); }); block.chunks.init.push(b` let ${levels} = [${initial_props}]; - let ${data} = {}; - for (let #i = 0; #i < ${levels}.length; #i += 1) { - ${data} = @assign(${data}, ${levels}[#i]); - } + let ${data} = @get_attributes_for_spread(${levels}); `); const fn = this.node.namespace === namespaces.svg ? x`@set_svg_attributes` : x`@set_attributes`; @@ -704,27 +718,29 @@ export default class ElementWrapper extends Wrapper { b`${fn}(${this.var}, ${data});` ); - block.chunks.update.push(b` - ${fn}(${this.var}, ${data} = @get_spread_update(${levels}, [ - ${updates} - ])); - `); + const need_update_data = this.node.name === 'select'; - // handle edge cases for elements - if (this.node.name === 'select') { - const dependencies = new Set(); - for (const attr of this.attributes) { - for (const dep of attr.node.dependencies) { - dependencies.add(dep); + if (all_dependencies.size > 0) { + block.chunks.update.push(b` + if (${block.renderer.dirty(Array.from(all_dependencies))}) { + ${fn}(${this.var}, @get_spread_update(${levels}, [ + ${updates} + ])); + ${need_update_data && b`${data} = @get_attributes_for_spread(${levels});`} } - } + `); + } + // handle edge cases for elements + if (this.node.name === 'select') { block.chunks.mount.push(b` if (${data}.multiple) @select_options(${this.var}, ${data}.value); `); - block.chunks.update.push(b` - if (${block.renderer.dirty(Array.from(dependencies))} && ${data}.multiple) @select_options(${this.var}, ${data}.value); - `); + if (all_dependencies.size > 0) { + block.chunks.update.push(b` + if (${block.renderer.dirty(Array.from(all_dependencies))} && ${data}.multiple) @select_options(${this.var}, ${data}.value); + `); + } } } diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 00f803bbbd48..3e86a7e362d0 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -223,16 +223,17 @@ export default class InlineComponentWrapper extends Wrapper { const all_dependencies: Set = new Set(); this.node.attributes.forEach(attr => { - add_to_set(all_dependencies, attr.dependencies); + add_to_set(all_dependencies, attr.get_dependencies()); }); - this.node.attributes.forEach((attr, i) => { - const { name, dependencies } = attr; + this.node.attributes.forEach((attr) => { + const { name } = attr; + const dependencies = attr.get_dependencies(); - const condition = dependencies.size > 0 && (dependencies.size !== all_dependencies.size) + const condition = dependencies.length > 0 && (dependencies.length !== all_dependencies.size) ? renderer.dirty(Array.from(dependencies)) : null; - const unchanged = dependencies.size === 0; + const unchanged = dependencies.length === 0; let change_object; if (attr.is_spread) { @@ -252,7 +253,7 @@ export default class InlineComponentWrapper extends Wrapper { changes.push( unchanged - ? x`${levels}[${i}]` + ? x`false` : condition ? x`${condition} && ${change_object}` : change_object @@ -266,9 +267,7 @@ export default class InlineComponentWrapper extends Wrapper { `); statements.push(b` - for (let #i = 0; #i < ${levels}.length; #i += 1) { - ${props} = @assign(${props}, ${levels}[#i]); - } + ${props} = @assign(${props}, @get_attributes_for_spread(${levels})) `); if (all_dependencies.size) { diff --git a/src/runtime/internal/spread.ts b/src/runtime/internal/spread.ts index 203b0b11e940..3cb6e8dac3dd 100644 --- a/src/runtime/internal/spread.ts +++ b/src/runtime/internal/spread.ts @@ -1,3 +1,5 @@ +import { assign } from "./utils"; + export function get_spread_update(levels, updates) { const update = {}; @@ -11,7 +13,7 @@ export function get_spread_update(levels, updates) { if (n) { for (const key in o) { - if (!(key in n)) to_null_out[key] = 1; + if (!(key in n) && !accounted_for[key]) to_null_out[key] = 1; } for (const key in n) { @@ -24,6 +26,11 @@ export function get_spread_update(levels, updates) { levels[i] = n; } else { for (const key in o) { + // if spreads decides to null out the key + // should reset it back for static attribute + if (to_null_out[key]) { + update[key] = o[key]; + } accounted_for[key] = 1; } } @@ -36,6 +43,14 @@ export function get_spread_update(levels, updates) { return update; } +export function get_attributes_for_spread(levels) { + const attrs = {}; + for (let i = 0; i < levels.length; i++) { + assign(attrs, levels[i++]); + } + return attrs; +} + export function get_spread_object(spread_props) { return typeof spread_props === 'object' && spread_props !== null ? spread_props : {}; } \ No newline at end of file diff --git a/test/runtime/samples/spread-element-dynamic-non-object/_config.js b/test/runtime/samples/spread-element-dynamic-non-object/_config.js new file mode 100644 index 000000000000..b5c466473722 --- /dev/null +++ b/test/runtime/samples/spread-element-dynamic-non-object/_config.js @@ -0,0 +1,47 @@ +export default { + props: { + props: { + foo: 'lol', + baz: 40 + 2, + } + }, + + html: ` +
+ `, + + test({ assert, component, target }) { + const html = ` +
+ `; + + // test undefined + component.props = undefined; + assert.htmlEqual(target.innerHTML, html); + + // set object props + component.props = this.props.props; + assert.htmlEqual(target.innerHTML, this.html); + + // test null + component.props = null; + assert.htmlEqual(target.innerHTML, html); + + // set object props + component.props = this.props.props; + assert.htmlEqual(target.innerHTML, this.html); + + // test boolean + component.props = true; + assert.htmlEqual(target.innerHTML, html); + + // set object props + component.props = this.props.props; + assert.htmlEqual(target.innerHTML, this.html); + + // test number + component.props = 123; + assert.htmlEqual(target.innerHTML, html); + + } +}; diff --git a/test/runtime/samples/spread-element-dynamic-non-object/main.svelte b/test/runtime/samples/spread-element-dynamic-non-object/main.svelte new file mode 100644 index 000000000000..9dbf2ea596a7 --- /dev/null +++ b/test/runtime/samples/spread-element-dynamic-non-object/main.svelte @@ -0,0 +1,5 @@ + + +