From 0f7e87c804a81333a63e3ca91464d999de6b1b05 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Aug 2017 21:51:06 -0400 Subject: [PATCH 01/14] do dirty check in _set, so we can easily skip unnecessary computations later (#768) --- src/generators/dom/index.ts | 66 +++++++------------ src/generators/dom/preprocess.ts | 2 +- src/generators/dom/visitors/MustacheTag.ts | 16 +++-- src/shared/index.js | 58 ++++++++++++---- .../samples/flush-before-bindings/Nested.html | 2 +- .../helpers-invoked-if-changed/_config.js | 24 +++++++ .../helpers-invoked-if-changed/main.html | 13 ++++ 7 files changed, 116 insertions(+), 65 deletions(-) create mode 100644 test/runtime/samples/helpers-invoked-if-changed/_config.js create mode 100644 test/runtime/samples/helpers-invoked-if-changed/main.html diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 2ffae2ee2f..8ffed33173 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -74,10 +74,9 @@ export default function dom( }); const builder = new CodeBuilder(); + const computationBuilder = new CodeBuilder(); if (computations.length) { - const computationBuilder = new CodeBuilder(); - computations.forEach(({ key, deps }) => { if (generator.readonly.has(key)) { // <:Window> bindings @@ -88,48 +87,18 @@ export default function dom( generator.readonly.add(key); - const condition = `isInitial || ${deps - .map( - dep => - `( '${dep}' in newState && @differs( state.${dep}, oldState.${dep} ) )` - ) - .join(' || ')}`; - const statement = `state.${key} = newState.${key} = @template.computed.${key}( ${deps + const condition = `isInitial || ${deps.map(dep => + `( '${dep}' in changed )` + ).join(' || ')}`; + + const statement = `if ( @differs( ( state.${key} = @template.computed.${key}( ${deps .map(dep => `state.${dep}`) - .join(', ')} );`; + .join(', ')} ) ), oldState.${key} ) ) changed.${key} = true;`; computationBuilder.addConditionalLine(condition, statement); }); - - builder.addBlock(deindent` - function @recompute ( state, newState, oldState, isInitial ) { - ${computationBuilder} - } - `); } - const _set = deindent` - ${options.dev && - deindent` - if ( typeof newState !== 'object' ) { - throw new Error( 'Component .set was called without an object of data key-values to update.' ); - } - - ${Array.from(generator.readonly).map( - prop => - `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` - )} - `} - - var oldState = this._state; - this._state = @assign( {}, oldState, newState ); - ${computations.length && - `@recompute( this._state, newState, oldState, false )`} - @dispatchObservers( this, this._observers.pre, newState, oldState ); - ${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`} - @dispatchObservers( this, this._observers.post, newState, oldState ); - `; - if (hasJs) { builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`); } @@ -174,7 +143,7 @@ export default function dom( ? `@proto ` : deindent` { - ${['destroy', 'get', 'fire', 'observe', 'on', 'set', 'teardown'] + ${['destroy', 'get', 'fire', 'observe', 'on', 'set', '_set', 'teardown'] .map(n => `${n}: @${n === 'teardown' ? 'destroy' : n}`) .join(',\n')} }`; @@ -190,7 +159,7 @@ export default function dom( ? `@assign( @template.data(), options.data )` : `options.data || {}`}; ${generator.metaBindings} - ${computations.length && `@recompute( this._state, this._state, {}, true );`} + ${computations.length && `this._recompute( {}, this._state, {}, true );`} ${options.dev && Array.from(generator.expectedProperties).map( prop => @@ -259,9 +228,20 @@ export default function dom( @assign( ${prototypeBase}, ${proto}); - ${name}.prototype._set = function _set ( newState ) { - ${_set} - }; + ${options.dev && deindent` + ${name}.prototype._checkReadOnly = function _checkReadOnly ( newState ) { + ${Array.from(generator.readonly).map( + prop => + `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` + )} + }; + `} + + ${computations.length ? deindent` + ${name}.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { + ${computationBuilder} + } + ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} ${templateProperties.setup && `@template.setup( ${name} );`} `); diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 1811bc762f..af460756da 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -462,7 +462,7 @@ export default function preprocess( generator.blocks.push(block); preprocessChildren(generator, block, state, node, false, [], true, null); - block.hasUpdateMethod = block.dependencies.size > 0; + block.hasUpdateMethod = true; return { block, state }; } diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 3ef015a9fd..7be3a541d9 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -13,7 +13,7 @@ export default function visitMustacheTag( const name = node._state.name; const value = block.getUniqueName(`${name}_value`); - const { snippet } = block.contextualise(node.expression); + const { dependencies, snippet } = block.contextualise(node.expression); block.addVariable(value); block.addElement( @@ -26,9 +26,13 @@ export default function visitMustacheTag( true ); - block.builders.update.addBlock(deindent` - if ( ${value} !== ( ${value} = ${snippet} ) ) { - ${name}.data = ${value}; - } - `); + if (dependencies.length) { + const changedCheck = dependencies.map(dependency => `'${dependency}' in changed`).join(' || '); + + block.builders.update.addBlock(deindent` + if ( ( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} ) ) { + ${name}.data = ${value}; + } + `); + } } diff --git a/src/shared/index.js b/src/shared/index.js index f198a4ef95..67360fc025 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -26,25 +26,23 @@ export function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -export function dispatchObservers(component, group, newState, oldState) { +export function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -133,6 +131,34 @@ export function set(newState) { this._root._lock = false; } +export function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, newState, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, newState, oldState); +} + +export function _setDev(newState) { + if (typeof newState !== 'object') { + throw new Error( + 'Component .set was called without an object of data key-values to update.' + ); + } + + this._checkReadOnly(newState); + _set.call(this, newState); +} + export function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -144,7 +170,9 @@ export var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; export var protoDev = { @@ -154,5 +182,7 @@ export var protoDev = { observe: observeDev, on: onDev, set: set, - teardown: destroyDev + teardown: destroyDev, + _recompute: noop, + _set: _setDev }; diff --git a/test/runtime/samples/flush-before-bindings/Nested.html b/test/runtime/samples/flush-before-bindings/Nested.html index 9b5a838cb4..d4071beee9 100644 --- a/test/runtime/samples/flush-before-bindings/Nested.html +++ b/test/runtime/samples/flush-before-bindings/Nested.html @@ -1,7 +1,7 @@ {{#each things as thing}}

{{thing}} ({{visibilityMap[thing]}})

-
+ {{/each}} From 9b9aa0416f8f4681b5ac226c1ce7bcaa6336dc97 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Aug 2017 21:52:17 -0400 Subject: [PATCH 02/14] make errors easier to track down --- test/js/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/js/index.js b/test/js/index.js index d6497b6b75..737a76e700 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -52,7 +52,7 @@ describe("js", () => { } ] }).then(bundle => { - return bundle.generate({ format: "es" }) + return bundle.generate({ format: "es" }); }).then(({ code }) => { fs.writeFileSync(`${dir}/_actual-bundle.js`, code); @@ -65,6 +65,9 @@ describe("js", () => { code.trim().replace(/^\s+$/gm, ""), expectedBundle.trim().replace(/^\s+$/gm, "") ); + }).catch(err => { + console.error(err.loc); + throw err; }); }); }); From caffcd5f79db6a1f8b72f35e4200b54f6f0edb1c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Aug 2017 22:06:17 -0400 Subject: [PATCH 03/14] pass updated state to observers --- src/shared/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shared/index.js b/src/shared/index.js index 67360fc025..0ebd390afb 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -143,9 +143,9 @@ export function _set(newState) { this._state = assign({}, oldState, newState); this._recompute(changed, this._state, oldState, false); - dispatchObservers(this, this._observers.pre, changed, newState, oldState); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); this._fragment.update(changed, this._state); - dispatchObservers(this, this._observers.post, changed, newState, oldState); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); } export function _setDev(newState) { From bfc940f39673c3d4326aafcd6ef9e531ab4a6b9d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Aug 2017 22:35:42 -0400 Subject: [PATCH 04/14] handle funky edge case around outroing blocks --- src/generators/dom/Block.ts | 2 +- src/generators/dom/visitors/MustacheTag.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index f851727276..069fb7f2b8 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -195,7 +195,7 @@ export default class Block { let outroing; const hasOutros = !this.builders.outro.isEmpty(); if (hasOutros) { - outroing = this.getUniqueName('outroing'); + outroing = this.alias('outroing'); this.addVariable(outroing); } diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 7be3a541d9..355e3b99fc 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -27,7 +27,10 @@ export default function visitMustacheTag( ); if (dependencies.length) { - const changedCheck = dependencies.map(dependency => `'${dependency}' in changed`).join(' || '); + const changedCheck = ( + ( block.hasOutroMethod ? `#outroing || ` : '' ) + + dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + ); block.builders.update.addBlock(deindent` if ( ( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} ) ) { From 427a0b5925dc22446bef51be281d6e9219db775d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 13 Aug 2017 22:36:39 -0400 Subject: [PATCH 05/14] update tests --- .../expected-bundle.js | 53 ++++++++------- .../expected.js | 12 +--- .../computed-collapsed-if/expected-bundle.js | 65 +++++++++++-------- .../samples/computed-collapsed-if/expected.js | 26 +++----- .../css-media-query/expected-bundle.js | 52 +++++++++------ test/js/samples/css-media-query/expected.js | 11 +--- .../expected-bundle.js | 61 +++++++++-------- .../each-block-changed-check/expected.js | 20 ++---- .../event-handlers-custom/expected-bundle.js | 52 +++++++++------ .../samples/event-handlers-custom/expected.js | 11 +--- .../if-block-no-update/expected-bundle.js | 51 +++++++++------ .../js/samples/if-block-no-update/expected.js | 10 +-- .../if-block-simple/expected-bundle.js | 51 +++++++++------ test/js/samples/if-block-simple/expected.js | 10 +-- .../non-imported-component/expected-bundle.js | 52 +++++++++------ .../non-imported-component/expected.js | 11 +--- .../expected-bundle.js | 52 +++++++++------ .../onrender-onteardown-rewritten/expected.js | 11 +--- .../samples/setup-method/expected-bundle.js | 52 +++++++++------ test/js/samples/setup-method/expected.js | 11 +--- .../expected-bundle.js | 51 +++++++++------ .../use-elements-as-anchors/expected.js | 10 +-- 22 files changed, 386 insertions(+), 349 deletions(-) diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index e52f943862..11d4b52fe9 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -52,25 +52,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -134,6 +132,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -145,7 +160,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; var template = (function () { @@ -187,7 +204,7 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( text_value !== ( text_value = state.foo ) ) { + if ( ( 'foo' in changed ) && text_value !== ( text_value = state.foo ) ) { text.data = text_value; } }, @@ -226,12 +243,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 5a3e3d52f6..15292cd526 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; +import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; var template = (function () { return { @@ -39,7 +39,7 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( text_value !== ( text_value = state.foo ) ) { + if ( ( 'foo' in changed ) && text_value !== ( text_value = state.foo ) ) { text.data = text_value; } }, @@ -78,12 +78,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 0da5cdf32c..112d21f8d8 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -28,25 +28,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -110,6 +108,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -121,16 +136,11 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; -function recompute ( state, newState, oldState, isInitial ) { - if ( isInitial || ( 'x' in newState && differs( state.x, oldState.x ) ) ) { - state.a = newState.a = template.computed.a( state.x ); - state.b = newState.b = template.computed.b( state.x ); - } -} - var template = (function () { return { computed: { @@ -147,6 +157,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -156,7 +168,7 @@ function create_main_fragment ( state, component ) { function SvelteComponent ( options ) { options = options || {}; this._state = options.data || {}; - recompute( this._state, this._state, {}, true ); + this._recompute( {}, this._state, {}, true ); this._observers = { pre: Object.create( null ), @@ -178,12 +190,11 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - recompute( this._state, newState, oldState, false ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); +SvelteComponent.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { + if ( isInitial || ( 'x' in changed ) ) { + if ( differs( ( state.a = template.computed.a( state.x ) ), oldState.a ) ) changed.a = true; + if ( differs( ( state.b = template.computed.b( state.x ) ), oldState.b ) ) changed.b = true; + } }; export default SvelteComponent; diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index a17e730122..edfca61b86 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -1,11 +1,4 @@ -import { assign, differs, dispatchObservers, noop, proto } from "svelte/shared.js"; - -function recompute ( state, newState, oldState, isInitial ) { - if ( isInitial || ( 'x' in newState && differs( state.x, oldState.x ) ) ) { - state.a = newState.a = template.computed.a( state.x ); - state.b = newState.b = template.computed.b( state.x ); - } -} +import { assign, differs, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -23,6 +16,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -32,7 +27,7 @@ function create_main_fragment ( state, component ) { function SvelteComponent ( options ) { options = options || {}; this._state = options.data || {}; - recompute( this._state, this._state, {}, true ); + this._recompute( {}, this._state, {}, true ); this._observers = { pre: Object.create( null ), @@ -54,12 +49,11 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - recompute( this._state, newState, oldState, false ) - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; +SvelteComponent.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { + if ( isInitial || ( 'x' in changed ) ) { + if ( differs( ( state.a = template.computed.a( state.x ) ), oldState.a ) ) changed.a = true; + if ( differs( ( state.b = template.computed.b( state.x ) ), oldState.b ) ) changed.b = true; + } +} export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index 3324ebf9be..ee643bae65 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -48,25 +48,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -130,6 +128,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -141,7 +156,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; function encapsulateStyles ( node ) { @@ -172,6 +189,8 @@ function create_main_fragment ( state, component ) { insertNode( div, target, anchor ); }, + update: noop, + unmount: function () { detachNode( div ); }, @@ -206,11 +225,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index 72338bb55b..8fb4bde21a 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createElement, detachNode, dispatchObservers, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; +import { appendNode, assign, createElement, detachNode, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; function encapsulateStyles ( node ) { setAttribute( node, 'svelte-2363328337', '' ); @@ -28,6 +28,8 @@ function create_main_fragment ( state, component ) { insertNode( div, target, anchor ); }, + update: noop, + unmount: function () { detachNode( div ); }, @@ -62,11 +64,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index aac78b9adb..13c79c6933 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -61,25 +61,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -143,6 +141,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -154,7 +169,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; function create_main_fragment ( state, component ) { @@ -210,7 +227,7 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( text_1_value !== ( text_1_value = state.foo ) ) { + if ( ( 'foo' in changed ) && text_1_value !== ( text_1_value = state.foo ) ) { text_1.data = text_1_value; } }, @@ -272,15 +289,11 @@ function create_each_block ( state, each_block_value, comment, i, component ) { }, update: function ( changed, state, each_block_value, comment, i ) { - if ( text_value !== ( text_value = i ) ) { - text.data = text_value; - } - - if ( text_2_value !== ( text_2_value = comment.author ) ) { + if ( ( 'comments' in changed ) && text_2_value !== ( text_2_value = comment.author ) ) { text_2.data = text_2_value; } - if ( text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { + if ( ( 'elapsed' in changed || 'comments' in changed || 'time' in changed ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { text_4.data = text_4_value; } @@ -324,12 +337,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index e7683e0743..a7c1e31f92 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; +import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { var text, p, text_1_value, text_1; @@ -53,7 +53,7 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( text_1_value !== ( text_1_value = state.foo ) ) { + if ( ( 'foo' in changed ) && text_1_value !== ( text_1_value = state.foo ) ) { text_1.data = text_1_value; } }, @@ -115,15 +115,11 @@ function create_each_block ( state, each_block_value, comment, i, component ) { }, update: function ( changed, state, each_block_value, comment, i ) { - if ( text_value !== ( text_value = i ) ) { - text.data = text_value; - } - - if ( text_2_value !== ( text_2_value = comment.author ) ) { + if ( ( 'comments' in changed ) && text_2_value !== ( text_2_value = comment.author ) ) { text_2.data = text_2_value; } - if ( text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { + if ( ( 'elapsed' in changed || 'comments' in changed || 'time' in changed ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { text_4.data = text_4_value; } @@ -167,12 +163,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index ed200a7b4c..dafef990c2 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -48,25 +48,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -130,6 +128,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -141,7 +156,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; var template = (function () { @@ -181,6 +198,8 @@ function create_main_fragment ( state, component ) { appendNode( text, button ); }, + update: noop, + unmount: function () { detachNode( button ); }, @@ -215,11 +234,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, template.methods, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index 9d5e771bdd..54e9111aff 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createElement, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js"; +import { appendNode, assign, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -37,6 +37,8 @@ function create_main_fragment ( state, component ) { appendNode( text, button ); }, + update: noop, + unmount: function () { detachNode( button ); }, @@ -71,11 +73,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, template.methods, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index 273ce81fc1..f3f7a371be 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -52,25 +52,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -134,6 +132,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -145,7 +160,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; function create_main_fragment ( state, component ) { @@ -259,12 +276,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index 8d30b66a03..025e0a0e43 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; +import { appendNode, assign, createComment, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { var if_block_anchor; @@ -111,12 +111,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 3b57117283..1ffa205c9c 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -52,25 +52,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -134,6 +132,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -145,7 +160,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; function create_main_fragment ( state, component ) { @@ -235,12 +252,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index db04589093..2cc3be36f9 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; +import { appendNode, assign, createComment, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { var if_block_anchor; @@ -87,12 +87,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index b40f56d109..98c6b12764 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -42,25 +42,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -124,6 +122,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -135,7 +150,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; var template = (function () { @@ -170,6 +187,8 @@ function create_main_fragment ( state, component ) { nonimported._fragment.mount( target, anchor ); }, + update: noop, + unmount: function () { imported._fragment.unmount(); detachNode( text ); @@ -221,11 +240,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index 2382e04ab3..ce86538e00 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -1,6 +1,6 @@ import Imported from 'Imported.html'; -import { assign, callAll, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js"; +import { assign, callAll, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -34,6 +34,8 @@ function create_main_fragment ( state, component ) { nonimported._fragment.mount( target, anchor ); }, + update: noop, + unmount: function () { imported._fragment.unmount(); detachNode( text ); @@ -85,11 +87,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index a754118158..f06892cbfb 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -28,25 +28,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -110,6 +108,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -121,7 +136,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; var template = (function () { @@ -139,6 +156,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -182,11 +201,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index bd473fc2d7..a5125745c6 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -1,4 +1,4 @@ -import { assign, callAll, dispatchObservers, noop, proto } from "svelte/shared.js"; +import { assign, callAll, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -15,6 +15,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -58,11 +60,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 006152cea5..4421ec1f87 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -28,25 +28,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -110,6 +108,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -121,7 +136,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; var template = (function () { @@ -150,6 +167,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -180,13 +199,6 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, template.methods, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - template.setup( SvelteComponent ); export default SvelteComponent; diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index 85f0d5c393..bf9da033ad 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -1,4 +1,4 @@ -import { assign, dispatchObservers, noop, proto } from "svelte/shared.js"; +import { assign, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -26,6 +26,8 @@ function create_main_fragment ( state, component ) { mount: noop, + update: noop, + unmount: noop, destroy: noop @@ -56,13 +58,6 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, template.methods, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - template.setup( SvelteComponent ); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 502e08b22e..9b64cd8600 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -52,25 +52,23 @@ function differs(a, b) { return a !== b || ((a && typeof a === 'object') || typeof a === 'function'); } -function dispatchObservers(component, group, newState, oldState) { +function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in newState)) continue; + if (!(key in changed)) continue; var newValue = newState[key]; var oldValue = oldState[key]; - if (differs(newValue, oldValue)) { - var callbacks = group[key]; - if (!callbacks) continue; + var callbacks = group[key]; + if (!callbacks) continue; - for (var i = 0; i < callbacks.length; i += 1) { - var callback = callbacks[i]; - if (callback.__calling) continue; + for (var i = 0; i < callbacks.length; i += 1) { + var callback = callbacks[i]; + if (callback.__calling) continue; - callback.__calling = true; - callback.call(component, newValue, oldValue); - callback.__calling = false; - } + callback.__calling = true; + callback.call(component, newValue, oldValue); + callback.__calling = false; } } } @@ -134,6 +132,23 @@ function set(newState) { this._root._lock = false; } +function _set(newState) { + var oldState = this._state, + changed = {}, + dirty = false; + + for (var key in newState) { + if (differs(newState[key], oldState[key])) changed[key] = dirty = true; + } + if (!dirty) return; + + this._state = assign({}, oldState, newState); + this._recompute(changed, this._state, oldState, false); + dispatchObservers(this, this._observers.pre, changed, this._state, oldState); + this._fragment.update(changed, this._state); + dispatchObservers(this, this._observers.post, changed, this._state, oldState); +} + function callAll(fns) { while (fns && fns.length) fns.pop()(); } @@ -145,7 +160,9 @@ var proto = { observe: observe, on: on, set: set, - teardown: destroy + teardown: destroy, + _recompute: noop, + _set: _set }; function create_main_fragment ( state, component ) { @@ -419,12 +436,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 3ed3f1e7db..28f0a660d4 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -1,4 +1,4 @@ -import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; +import { appendNode, assign, createComment, createElement, createText, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { var div, text, p, text_1, text_2, text_3, text_4, p_1, text_5, text_6, text_8, if_block_4_anchor; @@ -271,12 +271,4 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); -SvelteComponent.prototype._set = function _set ( newState ) { - var oldState = this._state; - this._state = assign( {}, oldState, newState ); - dispatchObservers( this, this._observers.pre, newState, oldState ); - this._fragment.update( newState, this._state ); - dispatchObservers( this, this._observers.post, newState, oldState ); -}; - export default SvelteComponent; \ No newline at end of file From 136a2fd31c25f19404dffc0cc2f8172631973956 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 07:29:34 -0400 Subject: [PATCH 06/14] only cache values if necessary --- .../dom/visitors/Component/Component.ts | 2 +- src/generators/dom/visitors/MustacheTag.ts | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index a9fff4ee39..56681ddcf6 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -212,7 +212,7 @@ export default function visitComponent( ${updates.join('\n')} - if ( Object.keys( ${name}_changes ).length ) ${name}._set( ${name}_changes ); + ${name}._set( ${name}_changes ); `); } diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 355e3b99fc..77c84d3330 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -10,17 +10,21 @@ export default function visitMustacheTag( state: State, node: Node ) { + const { dependencies, snippet } = block.contextualise(node.expression); + + const shouldCache = node.expression.type !== 'Identifier' || block.contexts.has(node.expression.name); + const name = node._state.name; - const value = block.getUniqueName(`${name}_value`); + const value = shouldCache && block.getUniqueName(`${name}_value`); + const init = shouldCache ? `${value} = ${snippet}` : snippet; - const { dependencies, snippet } = block.contextualise(node.expression); + if (shouldCache) block.addVariable(value); - block.addVariable(value); block.addElement( name, - `@createText( ${value} = ${snippet} )`, + `@createText( ${init} )`, generator.hydratable - ? `@claimText( ${state.parentNodes}, ${value} = ${snippet} )` + ? `@claimText( ${state.parentNodes}, ${init} )` : '', state.parentNode, true @@ -32,10 +36,13 @@ export default function visitMustacheTag( dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') ); - block.builders.update.addBlock(deindent` - if ( ( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} ) ) { - ${name}.data = ${value}; - } - `); + const condition = shouldCache ? + `( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} )` : + changedCheck; + + block.builders.update.addConditionalLine( + condition, + `${name}.data = ${shouldCache ? value : snippet};` + ); } } From 5636506fc0add8b0b66b14c417f06db3b1a65b21 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 07:29:45 -0400 Subject: [PATCH 07/14] update tests --- .../expected-bundle.js | 8 ++++---- .../collapses-text-around-comments/expected.js | 8 ++++---- .../each-block-changed-check/expected-bundle.js | 12 ++++++------ test/js/samples/each-block-changed-check/expected.js | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index 11d4b52fe9..ee1601cc81 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -185,12 +185,12 @@ function add_css () { } function create_main_fragment ( state, component ) { - var p, text_value, text; + var p, text; return { create: function () { p = createElement( 'p' ); - text = createText( text_value = state.foo ); + text = createText( state.foo ); this.hydrate(); }, @@ -204,8 +204,8 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( ( 'foo' in changed ) && text_value !== ( text_value = state.foo ) ) { - text.data = text_value; + if ( 'foo' in changed ) { + text.data = state.foo; } }, diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 15292cd526..f773a9eac1 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -20,12 +20,12 @@ function add_css () { } function create_main_fragment ( state, component ) { - var p, text_value, text; + var p, text; return { create: function () { p = createElement( 'p' ); - text = createText( text_value = state.foo ); + text = createText( state.foo ); this.hydrate(); }, @@ -39,8 +39,8 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( ( 'foo' in changed ) && text_value !== ( text_value = state.foo ) ) { - text.data = text_value; + if ( 'foo' in changed ) { + text.data = state.foo; } }, diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 13c79c6933..e0cb0b9ea4 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -175,7 +175,7 @@ var proto = { }; function create_main_fragment ( state, component ) { - var text, p, text_1_value, text_1; + var text, p, text_1; var each_block_value = state.comments; @@ -193,7 +193,7 @@ function create_main_fragment ( state, component ) { text = createText( "\n\n" ); p = createElement( 'p' ); - text_1 = createText( text_1_value = state.foo ); + text_1 = createText( state.foo ); }, mount: function ( target, anchor ) { @@ -227,8 +227,8 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( ( 'foo' in changed ) && text_1_value !== ( text_1_value = state.foo ) ) { - text_1.data = text_1_value; + if ( 'foo' in changed ) { + text_1.data = state.foo; } }, @@ -248,13 +248,13 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var div, strong, text_value, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; + var div, strong, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; return { create: function () { div = createElement( 'div' ); strong = createElement( 'strong' ); - text = createText( text_value = i ); + text = createText( i ); text_1 = createText( "\n\n\t\t" ); span = createElement( 'span' ); text_2 = createText( text_2_value = comment.author ); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index a7c1e31f92..48ac7c6613 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -1,7 +1,7 @@ import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var text, p, text_1_value, text_1; + var text, p, text_1; var each_block_value = state.comments; @@ -19,7 +19,7 @@ function create_main_fragment ( state, component ) { text = createText( "\n\n" ); p = createElement( 'p' ); - text_1 = createText( text_1_value = state.foo ); + text_1 = createText( state.foo ); }, mount: function ( target, anchor ) { @@ -53,8 +53,8 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( ( 'foo' in changed ) && text_1_value !== ( text_1_value = state.foo ) ) { - text_1.data = text_1_value; + if ( 'foo' in changed ) { + text_1.data = state.foo; } }, @@ -74,13 +74,13 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var div, strong, text_value, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; + var div, strong, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; return { create: function () { div = createElement( 'div' ); strong = createElement( 'strong' ); - text = createText( text_value = i ); + text = createText( i ); text_1 = createText( "\n\n\t\t" ); span = createElement( 'span' ); text_2 = createText( text_2_value = comment.author ); From 531354fc39512722b5a926ccebaa1ef0e656a7e1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 08:13:25 -0400 Subject: [PATCH 08/14] only cache values when it makes sense --- src/generators/Generator.ts | 11 +++++++---- src/generators/dom/Block.ts | 3 +++ src/generators/dom/preprocess.ts | 12 +++++++++++- .../dom/visitors/Element/meta/Window.ts | 4 ++-- src/generators/dom/visitors/MustacheTag.ts | 16 ++++++++++++---- src/utils/CodeBuilder.ts | 4 ++-- test/runtime/samples/each-block-keyed/_config.js | 7 +++++-- test/runtime/samples/each-block-keyed/main.html | 4 ++-- 8 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 002e52bbf9..7798cf3c86 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -125,7 +125,8 @@ export default class Generator { ) { this.addSourcemapLocations(expression); - const usedContexts: string[] = []; + const usedContexts = new Set(); + const usedIndexes = new Set(); const { code, helpers } = this; const { contexts, indexes } = block; @@ -168,12 +169,13 @@ export default class Generator { ); } - if (!~usedContexts.indexOf(name)) usedContexts.push(name); + usedContexts.add(name); } else if (helpers.has(name)) { code.prependRight(node.start, `${self.alias('template')}.helpers.`); } else if (indexes.has(name)) { const context = indexes.get(name); - if (!~usedContexts.indexOf(context)) usedContexts.push(context); + usedContexts.add(context); // TODO is this right? + usedIndexes.add(name); } else { // handle shorthand properties if (parent && parent.type === 'Property' && parent.shorthand) { @@ -193,7 +195,7 @@ export default class Generator { code.prependRight(node.start, `state.`); } - if (!~usedContexts.indexOf('state')) usedContexts.push('state'); + usedContexts.add('state'); } this.skip(); @@ -221,6 +223,7 @@ export default class Generator { return { dependencies: Array.from(dependencies), contexts: usedContexts, + indexes: usedIndexes, snippet: `[✂${expression.start}-${expression.end}✂]`, }; } diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 069fb7f2b8..bf38aebc74 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -12,6 +12,7 @@ export interface BlockOptions { key?: string; contexts?: Map; indexes?: Map; + changeableIndexes?: Map; contextDependencies?: Map; params?: string[]; indexNames?: Map; @@ -32,6 +33,7 @@ export default class Block { contexts: Map; indexes: Map; + changeableIndexes: Map; contextDependencies: Map; dependencies: Set; params: string[]; @@ -77,6 +79,7 @@ export default class Block { this.contexts = options.contexts; this.indexes = options.indexes; + this.changeableIndexes = options.changeableIndexes; this.contextDependencies = options.contextDependencies; this.dependencies = new Set(); diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index af460756da..9b784bc558 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -189,7 +189,10 @@ const preprocessors = { contexts.set(node.context, context); const indexes = new Map(block.indexes); - if (node.index) indexes.set(indexName, node.context); + if (node.index) indexes.set(node.index, node.context); + + const changeableIndexes = new Map(block.changeableIndexes); + if (node.index) changeableIndexes.set(node.index, node.key); const contextDependencies = new Map(block.contextDependencies); contextDependencies.set(node.context, dependencies); @@ -203,6 +206,7 @@ const preprocessors = { contextDependencies, contexts, indexes, + changeableIndexes, listName, indexName, @@ -274,6 +278,11 @@ const preprocessors = { } } }); + } else if (attribute.type === 'EventHandler' && attribute.expression) { + attribute.expression.arguments.forEach((arg: Node) => { + const dependencies = block.findDependencies(arg); + block.addDependencies(dependencies); + }); } else if (attribute.type === 'Binding') { const dependencies = block.findDependencies(attribute.value); block.addDependencies(dependencies); @@ -444,6 +453,7 @@ export default function preprocess( contexts: new Map(), indexes: new Map(), + changeableIndexes: new Map(), contextDependencies: new Map(), params: ['state'], diff --git a/src/generators/dom/visitors/Element/meta/Window.ts b/src/generators/dom/visitors/Element/meta/Window.ts index d6c848c342..8e15ee2512 100644 --- a/src/generators/dom/visitors/Element/meta/Window.ts +++ b/src/generators/dom/visitors/Element/meta/Window.ts @@ -38,8 +38,8 @@ export default function visitWindow( let usesState = false; attribute.expression.arguments.forEach((arg: Node) => { - const { contexts } = block.contextualise(arg, null, true); - if (contexts.length) usesState = true; + const { dependencies } = block.contextualise(arg, null, true); + if (dependencies.length) usesState = true; }); const flattened = flattenReference(attribute.expression.callee); diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 77c84d3330..91575e58d0 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -10,9 +10,15 @@ export default function visitMustacheTag( state: State, node: Node ) { - const { dependencies, snippet } = block.contextualise(node.expression); + const { dependencies, indexes, snippet } = block.contextualise(node.expression); - const shouldCache = node.expression.type !== 'Identifier' || block.contexts.has(node.expression.name); + const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); + + const shouldCache = ( + node.expression.type !== 'Identifier' || + block.contexts.has(node.expression.name) || + hasChangeableIndex + ); const name = node._state.name; const value = shouldCache && block.getUniqueName(`${name}_value`); @@ -30,14 +36,16 @@ export default function visitMustacheTag( true ); - if (dependencies.length) { + if (dependencies.length || hasChangeableIndex) { const changedCheck = ( ( block.hasOutroMethod ? `#outroing || ` : '' ) + dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') ); + const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; + const condition = shouldCache ? - `( ${changedCheck} ) && ${value} !== ( ${value} = ${snippet} )` : + ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : changedCheck; block.builders.update.addConditionalLine( diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 92c88ea483..934f29b9a4 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -26,10 +26,10 @@ export default class CodeBuilder { this.result += `\n\t${line}`; } else { if (this.lastCondition) { - this.result += `\n}\n\n`; + this.result += `\n}`; } - this.result += `if ( ${condition} ) {\n\t${line}`; + this.result += `${this.last === ChunkType.Block ? '\n\n' : '\n'}if ( ${condition} ) {\n\t${line}`; this.lastCondition = condition; } diff --git a/test/runtime/samples/each-block-keyed/_config.js b/test/runtime/samples/each-block-keyed/_config.js index 35b47f5be8..29cf422fbf 100644 --- a/test/runtime/samples/each-block-keyed/_config.js +++ b/test/runtime/samples/each-block-keyed/_config.js @@ -6,7 +6,10 @@ export default { ] }, - html: '

implement keyed each blocks

implement client-side hydration

', + html: ` +

1: implement keyed each blocks

+

2: implement client-side hydration

+ `, test ( assert, component, target ) { const [ p1, p2 ] = target.querySelectorAll( 'p' ); @@ -16,7 +19,7 @@ export default { { id: 234, description: 'implement client-side hydration' } ] }); - assert.htmlEqual( target.innerHTML, '

implement client-side hydration

' ); + assert.htmlEqual( target.innerHTML, '

1: implement client-side hydration

' ); const [ p3 ] = target.querySelectorAll( 'p' ); diff --git a/test/runtime/samples/each-block-keyed/main.html b/test/runtime/samples/each-block-keyed/main.html index 7d5b90a9f8..da74eaa701 100644 --- a/test/runtime/samples/each-block-keyed/main.html +++ b/test/runtime/samples/each-block-keyed/main.html @@ -1,3 +1,3 @@ -{{#each todos as todo @id}} -

{{todo.description}}

+{{#each todos as todo, i @id}} +

{{i+1}}: {{todo.description}}

{{/each}} From 5070219218b63dafd7c7aa27cc30b952628dbdc4 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 10:48:14 -0400 Subject: [PATCH 09/14] fix select edge case --- .../dom/visitors/Element/Attribute.ts | 84 +++++++++++++++---- .../helpers-invoked-if-changed/_config.js | 32 +++++-- .../helpers-invoked-if-changed/counter.js | 4 + .../helpers-invoked-if-changed/main.html | 11 ++- 4 files changed, 103 insertions(+), 28 deletions(-) create mode 100644 test/runtime/samples/helpers-invoked-if-changed/counter.js diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index b03c379482..80b363bd01 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -47,10 +47,26 @@ export default function visitAttribute( if (isDynamic) { let value; + const allDependencies = new Set(); + let shouldCache; + let hasChangeableIndex; + if (attribute.value.length === 1) { // single {{tag}} — may be a non-string - const { snippet } = block.contextualise(attribute.value[0].expression); + const { expression } = attribute.value[0]; + const { snippet, dependencies, indexes } = block.contextualise(expression); value = snippet; + dependencies.forEach(d => { + allDependencies.add(d); + }); + + hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); + + shouldCache = ( + expression.type !== 'Identifier' || + block.contexts.has(expression.name) || + hasChangeableIndex + ); } else { // '{{foo}} {{bar}}' — treat as string concatenation value = @@ -60,22 +76,35 @@ export default function visitAttribute( if (chunk.type === 'Text') { return stringify(chunk.data); } else { - const { snippet } = block.contextualise(chunk.expression); + const { snippet, dependencies, indexes } = block.contextualise(chunk.expression); + + if (Array.from(indexes).some(index => block.changeableIndexes.get(index))) { + hasChangeableIndex = true; + } + + dependencies.forEach(d => { + allDependencies.add(d); + }); + return `( ${snippet} )`; } }) .join(' + '); + + shouldCache = true; } - const last = block.getUniqueName( + const isSelectValueAttribute = + name === 'value' && state.parentNodeName === 'select'; + + const last = (shouldCache || isSelectValueAttribute) && block.getUniqueName( `${state.parentNode}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` ); - block.addVariable(last); - const isSelectValueAttribute = - name === 'value' && state.parentNodeName === 'select'; + if (shouldCache || isSelectValueAttribute) block.addVariable(last); let updater; + const init = shouldCache ? `${last} = ${value}` : value; if (isSelectValueAttribute) { // annoying special case @@ -104,27 +133,48 @@ export default function visitAttribute( } `; - block.builders.hydrate.addLine(deindent` - ${last} = ${value} + block.builders.hydrate.addBlock(deindent` + ${last} = ${value}; ${updater} `); + + block.builders.update.addLine(`${last} = ${value};`); } else if (propertyName) { block.builders.hydrate.addLine( - `${state.parentNode}.${propertyName} = ${last} = ${value};` + `${state.parentNode}.${propertyName} = ${init};` ); - updater = `${state.parentNode}.${propertyName} = ${last};`; + updater = `${state.parentNode}.${propertyName} = ${shouldCache || isSelectValueAttribute ? last : value};`; } else { block.builders.hydrate.addLine( - `${method}( ${state.parentNode}, '${name}', ${last} = ${value} );` + `${method}( ${state.parentNode}, '${name}', ${init} );` ); - updater = `${method}( ${state.parentNode}, '${name}', ${last} );`; + updater = `${method}( ${state.parentNode}, '${name}', ${shouldCache || isSelectValueAttribute ? last : value} );`; } - block.builders.update.addBlock(deindent` - if ( ${last} !== ( ${last} = ${value} ) ) { - ${updater} - } - `); + if (allDependencies.size || hasChangeableIndex || isSelectValueAttribute) { + const dependencies = Array.from(allDependencies); + const changedCheck = ( + ( block.hasOutroMethod ? `#outroing || ` : '' ) + + dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + ); + + const updateCachedValue = `${last} !== ( ${last} = ${value} )`; + + const condition = shouldCache ? + ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : + changedCheck; + + // block.builders.update.addConditionalLine( + // condition, + // updater + // ); + + block.builders.update.addBlock(deindent` + if ( ${condition} ) { + ${updater} + } + `); + } } else { const value = attribute.value === true ? 'true' diff --git a/test/runtime/samples/helpers-invoked-if-changed/_config.js b/test/runtime/samples/helpers-invoked-if-changed/_config.js index bdc2f19fdb..fd8145ae74 100644 --- a/test/runtime/samples/helpers-invoked-if-changed/_config.js +++ b/test/runtime/samples/helpers-invoked-if-changed/_config.js @@ -1,24 +1,38 @@ +import counter from './counter.js'; + export default { data: { x: 1, - y: 2 + y: 2, + z: 3 }, html: `

1

-

2

+

3

`, test(assert, component) { - global.count = 0; + counter.y = counter.z = 0; + + component.set({ x: 4 }); + assert.equal(counter.y, 0); + assert.equal(counter.z, 0); + + component.set({ x: 5, y: 6 }); + assert.equal(counter.y, 1); + assert.equal(counter.z, 0); - component.set({ x: 3 }); - assert.equal(global.count, 0); + component.set({ x: 6, y: 6 }); + assert.equal(counter.y, 1); + assert.equal(counter.z, 0); - component.set({ x: 4, y: 5 }); - assert.equal(global.count, 1); + component.set({ z: 7 }); + assert.equal(counter.y, 1); + assert.equal(counter.z, 1); - component.set({ x: 5, y: 5 }); - assert.equal(global.count, 1); + component.set({ x: 8, z: 7 }); + assert.equal(counter.y, 1); + assert.equal(counter.z, 1); } }; diff --git a/test/runtime/samples/helpers-invoked-if-changed/counter.js b/test/runtime/samples/helpers-invoked-if-changed/counter.js new file mode 100644 index 0000000000..5367ba3c2b --- /dev/null +++ b/test/runtime/samples/helpers-invoked-if-changed/counter.js @@ -0,0 +1,4 @@ +export default { + y: 0, + z: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/helpers-invoked-if-changed/main.html b/test/runtime/samples/helpers-invoked-if-changed/main.html index bda367155f..1bcd70c4c1 100644 --- a/test/runtime/samples/helpers-invoked-if-changed/main.html +++ b/test/runtime/samples/helpers-invoked-if-changed/main.html @@ -1,11 +1,18 @@

{{x}}

-

{{myHelper(y)}}

+

{{myHelper(z)}}

diff --git a/test/runtime/samples/ignore-unchanged-raw/_config.js b/test/runtime/samples/ignore-unchanged-raw/_config.js new file mode 100644 index 0000000000..b8c58367b3 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` +

1

+

2

+ `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/ignore-unchanged-raw/counter.js b/test/runtime/samples/ignore-unchanged-raw/counter.js new file mode 100644 index 0000000000..63872cd6a2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/counter.js @@ -0,0 +1,3 @@ +export default { + count: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/ignore-unchanged-raw/main.html b/test/runtime/samples/ignore-unchanged-raw/main.html new file mode 100644 index 0000000000..375985c0d2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-raw/main.html @@ -0,0 +1,15 @@ +

{{x}}

+

{{{myHelper(y)}}}

+ + diff --git a/test/runtime/samples/ignore-unchanged-tag/_config.js b/test/runtime/samples/ignore-unchanged-tag/_config.js new file mode 100644 index 0000000000..b8c58367b3 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/_config.js @@ -0,0 +1,26 @@ +import counter from './counter.js'; + +export default { + data: { + x: 1, + y: 2 + }, + + html: ` +

1

+

2

+ `, + + test(assert, component) { + counter.count = 0; + + component.set({ x: 3 }); + assert.equal(counter.count, 0); + + component.set({ x: 4, y: 5 }); + assert.equal(counter.count, 1); + + component.set({ x: 5, y: 5 }); + assert.equal(counter.count, 1); + } +}; diff --git a/test/runtime/samples/ignore-unchanged-tag/counter.js b/test/runtime/samples/ignore-unchanged-tag/counter.js new file mode 100644 index 0000000000..63872cd6a2 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/counter.js @@ -0,0 +1,3 @@ +export default { + count: 0 +}; \ No newline at end of file diff --git a/test/runtime/samples/ignore-unchanged-tag/main.html b/test/runtime/samples/ignore-unchanged-tag/main.html new file mode 100644 index 0000000000..f50864df94 --- /dev/null +++ b/test/runtime/samples/ignore-unchanged-tag/main.html @@ -0,0 +1,15 @@ +

{{x}}

+

{{myHelper(y)}}

+ + From f31c206b73517e7d21bca031980651e293c4793b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 12:24:39 -0400 Subject: [PATCH 11/14] tidy up, dedupe a bit --- src/generators/dom/Block.ts | 3 +- src/generators/dom/visitors/EachBlock.ts | 6 +- src/generators/dom/visitors/IfBlock.ts | 3 +- src/generators/dom/visitors/MustacheTag.ts | 47 ++++------------ src/generators/dom/visitors/RawMustacheTag.ts | 56 ++++++------------- src/generators/dom/visitors/Text.ts | 7 +-- src/generators/dom/visitors/shared/Tag.ts | 49 ++++++++++++++++ .../expected-bundle.js | 8 +-- .../each-block-changed-check/expected.js | 8 +-- 9 files changed, 90 insertions(+), 97 deletions(-) create mode 100644 src/generators/dom/visitors/shared/Tag.ts diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index bf38aebc74..0cfce0924a 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -124,8 +124,7 @@ export default class Block { name: string, renderStatement: string, claimStatement: string, - parentNode: string, - needsIdentifier = false + parentNode: string ) { const isToplevel = !parentNode; diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 2ad8117c36..f6b2a07210 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -57,8 +57,7 @@ export default function visitEachBlock( anchor, `@createComment()`, `@createComment()`, - state.parentNode, - true + state.parentNode ); } else if (node.next) { node.next.usedAsAnchor = true; @@ -172,8 +171,7 @@ function keyed( node._block.first, `@createComment()`, `@createComment()`, - null, - true + null ); } diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index dfb6b3bfcd..193edd872b 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -119,8 +119,7 @@ export default function visitIfBlock( anchor, `@createComment()`, `@createComment()`, - state.parentNode, - true + state.parentNode ); } else if (node.next) { node.next.usedAsAnchor = true; diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 91575e58d0..3c03c51167 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -1,4 +1,5 @@ import deindent from '../../../utils/deindent'; +import visitTag from './shared/Tag'; import { DomGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; @@ -10,47 +11,21 @@ export default function visitMustacheTag( state: State, node: Node ) { - const { dependencies, indexes, snippet } = block.contextualise(node.expression); + const { name } = node._state; - const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); - - const shouldCache = ( - node.expression.type !== 'Identifier' || - block.contexts.has(node.expression.name) || - hasChangeableIndex + const { init } = visitTag( + generator, + block, + state, + node, + name, + value => `${name}.data = ${value};` ); - const name = node._state.name; - const value = shouldCache && block.getUniqueName(`${name}_value`); - const init = shouldCache ? `${value} = ${snippet}` : snippet; - - if (shouldCache) block.addVariable(value); - block.addElement( name, `@createText( ${init} )`, - generator.hydratable - ? `@claimText( ${state.parentNodes}, ${init} )` - : '', - state.parentNode, - true + `@claimText( ${state.parentNodes}, ${init} )`, + state.parentNode ); - - if (dependencies.length || hasChangeableIndex) { - const changedCheck = ( - ( block.hasOutroMethod ? `#outroing || ` : '' ) + - dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') - ); - - const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; - - const condition = shouldCache ? - ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : - changedCheck; - - block.builders.update.addConditionalLine( - condition, - `${name}.data = ${shouldCache ? value : snippet};` - ); - } } diff --git a/src/generators/dom/visitors/RawMustacheTag.ts b/src/generators/dom/visitors/RawMustacheTag.ts index 624e424983..02facfcd24 100644 --- a/src/generators/dom/visitors/RawMustacheTag.ts +++ b/src/generators/dom/visitors/RawMustacheTag.ts @@ -1,4 +1,6 @@ import deindent from '../../../utils/deindent'; +import addUpdateBlock from './shared/addUpdateBlock'; +import visitTag from './shared/Tag'; import { DomGenerator } from '../index'; import Block from '../Block'; import { Node } from '../../../interfaces'; @@ -14,60 +16,34 @@ export default function visitRawMustacheTag( const before = node._state.name; const after = block.getUniqueName(`${name}_after`); - const { dependencies, indexes, snippet } = block.contextualise(node.expression); - - const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); - - const shouldCache = ( - node.expression.type !== 'Identifier' || - block.contexts.has(node.expression.name) || - hasChangeableIndex + const { init } = visitTag( + generator, + block, + state, + node, + name, + value => deindent` + @detachBetween( ${before}, ${after} ); + ${before}.insertAdjacentHTML( 'afterend', ${value} ); + ` ); - const value = shouldCache && block.getUniqueName(`${name}_value`); - const init = shouldCache ? `${value} = ${snippet}` : snippet; - if (shouldCache) block.addVariable(value); - // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. block.addElement( before, `@createElement( 'noscript' )`, `@createElement( 'noscript' )`, - state.parentNode, - true + state.parentNode ); + block.addElement( after, `@createElement( 'noscript' )`, `@createElement( 'noscript' )`, - state.parentNode, - true + state.parentNode ); - const isToplevel = !state.parentNode; - block.builders.mount.addLine(`${before}.insertAdjacentHTML( 'afterend', ${init} );`); block.builders.detachRaw.addBlock(`@detachBetween( ${before}, ${after} );`); - - if (dependencies.length || hasChangeableIndex) { - const changedCheck = ( - ( block.hasOutroMethod ? `#outroing || ` : '' ) + - dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') - ); - - const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; - - const condition = shouldCache ? - ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : - changedCheck; - - block.builders.update.addConditionalLine( - condition, - deindent` - @detachBetween( ${before}, ${after} ); - ${before}.insertAdjacentHTML( 'afterend', ${shouldCache ? value : snippet} ); - ` - ); - } -} +} \ No newline at end of file diff --git a/src/generators/dom/visitors/Text.ts b/src/generators/dom/visitors/Text.ts index 49135a129e..796bf9e309 100644 --- a/src/generators/dom/visitors/Text.ts +++ b/src/generators/dom/visitors/Text.ts @@ -14,10 +14,7 @@ export default function visitText( block.addElement( node._state.name, `@createText( ${stringify(node.data)} )`, - generator.hydratable - ? `@claimText( ${state.parentNodes}, ${stringify(node.data)} )` - : '', - state.parentNode, - node.usedAsAnchor + `@claimText( ${state.parentNodes}, ${stringify(node.data)} )`, + state.parentNode ); } diff --git a/src/generators/dom/visitors/shared/Tag.ts b/src/generators/dom/visitors/shared/Tag.ts new file mode 100644 index 0000000000..63185d60c5 --- /dev/null +++ b/src/generators/dom/visitors/shared/Tag.ts @@ -0,0 +1,49 @@ +import deindent from '../../../../utils/deindent'; +import { DomGenerator } from '../../index'; +import Block from '../../Block'; +import { Node } from '../../../../interfaces'; +import { State } from '../../interfaces'; + +export default function visitTag( + generator: DomGenerator, + block: Block, + state: State, + node: Node, + name: string, + update: (value: string) => string +) { + const { dependencies, indexes, snippet } = block.contextualise(node.expression); + + const hasChangeableIndex = Array.from(indexes).some(index => block.changeableIndexes.get(index)); + + const shouldCache = ( + node.expression.type !== 'Identifier' || + block.contexts.has(node.expression.name) || + hasChangeableIndex + ); + + const value = shouldCache && block.getUniqueName(`${name}_value`); + const init = shouldCache ? value : snippet; + + if (shouldCache) block.addVariable(value, snippet); + + if (dependencies.length || hasChangeableIndex) { + const changedCheck = ( + ( block.hasOutroMethod ? `#outroing || ` : '' ) + + dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + ); + + const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; + + const condition = shouldCache ? + ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : + changedCheck; + + block.builders.update.addConditionalLine( + condition, + update(shouldCache ? value : snippet) + ); + } + + return { init }; +} diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index b9f5b5b766..15410339ef 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -248,7 +248,7 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var div, strong, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; + var div, strong, text, text_1, span, text_2_value = comment.author, text_2, text_3, text_4_value = state.elapsed(comment.time, state.time), text_4, text_5, text_6, raw_value = comment.html, raw_before, raw_after; return { create: function () { @@ -257,9 +257,9 @@ function create_each_block ( state, each_block_value, comment, i, component ) { text = createText( i ); text_1 = createText( "\n\n\t\t" ); span = createElement( 'span' ); - text_2 = createText( text_2_value = comment.author ); + text_2 = createText( text_2_value ); text_3 = createText( " wrote " ); - text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); + text_4 = createText( text_4_value ); text_5 = createText( " ago:" ); text_6 = createText( "\n\n\t\t" ); raw_before = createElement( 'noscript' ); @@ -285,7 +285,7 @@ function create_each_block ( state, each_block_value, comment, i, component ) { appendNode( text_6, div ); appendNode( raw_before, div ); appendNode( raw_after, div ); - raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); + raw_before.insertAdjacentHTML( 'afterend', raw_value ); }, update: function ( changed, state, each_block_value, comment, i ) { diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 18ba61d59a..07feca6020 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -74,7 +74,7 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var div, strong, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; + var div, strong, text, text_1, span, text_2_value = comment.author, text_2, text_3, text_4_value = state.elapsed(comment.time, state.time), text_4, text_5, text_6, raw_value = comment.html, raw_before, raw_after; return { create: function () { @@ -83,9 +83,9 @@ function create_each_block ( state, each_block_value, comment, i, component ) { text = createText( i ); text_1 = createText( "\n\n\t\t" ); span = createElement( 'span' ); - text_2 = createText( text_2_value = comment.author ); + text_2 = createText( text_2_value ); text_3 = createText( " wrote " ); - text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); + text_4 = createText( text_4_value ); text_5 = createText( " ago:" ); text_6 = createText( "\n\n\t\t" ); raw_before = createElement( 'noscript' ); @@ -111,7 +111,7 @@ function create_each_block ( state, each_block_value, comment, i, component ) { appendNode( text_6, div ); appendNode( raw_before, div ); appendNode( raw_after, div ); - raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); + raw_before.insertAdjacentHTML( 'afterend', raw_value ); }, update: function ( changed, state, each_block_value, comment, i ) { From 622219801714491776297bfa70f496e4cda3dee2 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 12:28:58 -0400 Subject: [PATCH 12/14] add note to self --- src/generators/dom/visitors/Element/Attribute.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index 80b363bd01..12a6842658 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -51,6 +51,8 @@ export default function visitAttribute( let shouldCache; let hasChangeableIndex; + // TODO some of this code is repeated in Tag.ts — would be good to + // DRY it out if that's possible without introducing crazy indirection if (attribute.value.length === 1) { // single {{tag}} — may be a non-string const { expression } = attribute.value[0]; From 9053e954607bc32b6c8a142eb1e9a333068fdf60 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 12:30:37 -0400 Subject: [PATCH 13/14] rename addConditionalLine to addConditional --- src/generators/dom/index.ts | 2 +- src/generators/dom/visitors/Element/Attribute.ts | 14 ++++---------- src/generators/dom/visitors/shared/Tag.ts | 2 +- src/utils/CodeBuilder.ts | 2 +- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 8ffed33173..21d155c12f 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -95,7 +95,7 @@ export default function dom( .map(dep => `state.${dep}`) .join(', ')} ) ), oldState.${key} ) ) changed.${key} = true;`; - computationBuilder.addConditionalLine(condition, statement); + computationBuilder.addConditional(condition, statement); }); } diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index 12a6842658..2134ad62d0 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -166,16 +166,10 @@ export default function visitAttribute( ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : changedCheck; - // block.builders.update.addConditionalLine( - // condition, - // updater - // ); - - block.builders.update.addBlock(deindent` - if ( ${condition} ) { - ${updater} - } - `); + block.builders.update.addConditional( + condition, + updater + ); } } else { const value = attribute.value === true diff --git a/src/generators/dom/visitors/shared/Tag.ts b/src/generators/dom/visitors/shared/Tag.ts index 63185d60c5..3d4f3ed211 100644 --- a/src/generators/dom/visitors/shared/Tag.ts +++ b/src/generators/dom/visitors/shared/Tag.ts @@ -39,7 +39,7 @@ export default function visitTag( ( dependencies.length ? `( ${changedCheck} ) && ${updateCachedValue}` : updateCachedValue ) : changedCheck; - block.builders.update.addConditionalLine( + block.builders.update.addConditional( condition, update(shouldCache ? value : snippet) ); diff --git a/src/utils/CodeBuilder.ts b/src/utils/CodeBuilder.ts index 2a412a8a06..bdbcc3fcb6 100644 --- a/src/utils/CodeBuilder.ts +++ b/src/utils/CodeBuilder.ts @@ -21,7 +21,7 @@ export default class CodeBuilder { this.lastCondition = null; } - addConditionalLine(condition: string, body: string) { + addConditional(condition: string, body: string) { body = body.replace(/^/gm, '\t'); if (condition === this.lastCondition) { From 3daa7aa6b37d594d320dd7a826d07ee651b19303 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 14 Aug 2017 12:35:45 -0400 Subject: [PATCH 14/14] we can now do changed.foo instead of "foo" in changed - is faster and smaller --- src/generators/dom/index.ts | 4 +--- src/generators/dom/visitors/Component/Binding.ts | 2 +- src/generators/dom/visitors/Component/Component.ts | 6 ++---- src/generators/dom/visitors/EachBlock.ts | 2 +- src/generators/dom/visitors/Element/Attribute.ts | 2 +- src/generators/dom/visitors/shared/Tag.ts | 2 +- src/shared/index.js | 2 +- .../expected-bundle.js | 4 ++-- .../collapses-text-around-comments/expected.js | 2 +- .../samples/computed-collapsed-if/expected-bundle.js | 4 ++-- test/js/samples/computed-collapsed-if/expected.js | 2 +- test/js/samples/css-media-query/expected-bundle.js | 2 +- .../each-block-changed-check/expected-bundle.js | 12 ++++++------ test/js/samples/each-block-changed-check/expected.js | 10 +++++----- .../samples/event-handlers-custom/expected-bundle.js | 2 +- .../js/samples/if-block-no-update/expected-bundle.js | 2 +- test/js/samples/if-block-simple/expected-bundle.js | 2 +- .../non-imported-component/expected-bundle.js | 2 +- .../onrender-onteardown-rewritten/expected-bundle.js | 2 +- test/js/samples/setup-method/expected-bundle.js | 2 +- .../use-elements-as-anchors/expected-bundle.js | 2 +- 21 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 21d155c12f..2513623c49 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -87,9 +87,7 @@ export default function dom( generator.readonly.add(key); - const condition = `isInitial || ${deps.map(dep => - `( '${dep}' in changed )` - ).join(' || ')}`; + const condition = `isInitial || ${deps.map(dep => `changed.${dep}`).join(' || ')}`; const statement = `if ( @differs( ( state.${key} = @template.computed.${key}( ${deps .map(dep => `state.${dep}`) diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index e87ab0bff1..75cdd637e3 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -89,7 +89,7 @@ export default function visitBinding( local.update.addBlock(deindent` if ( !${updating} && ${dependencies - .map(dependency => `'${dependency}' in changed`) + .map(dependency => `changed.${dependency}`) .join(' || ')} ) { ${updating} = true; ${local.name}._set({ ${attribute.name}: ${snippet} }); diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 56681ddcf6..0e966cdc0f 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -195,10 +195,8 @@ export default function visitComponent( if (attribute.dependencies.length) { return deindent` if ( ${attribute.dependencies - .map(dependency => `'${dependency}' in changed`) - .join( - '||' - )} ) ${name}_changes.${attribute.name} = ${attribute.value}; + .map(dependency => `changed.${dependency}`) + .join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value}; `; } diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index f6b2a07210..1c8a9c9346 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -409,7 +409,7 @@ function unkeyed( // TODO do this for keyed blocks as well const condition = Array.from(allDependencies) - .map(dependency => `'${dependency}' in changed`) + .map(dependency => `changed.${dependency}`) .join(' || '); const parentNode = state.parentNode || `${anchor}.parentNode`; diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index 2134ad62d0..5ee1afdea7 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -157,7 +157,7 @@ export default function visitAttribute( const dependencies = Array.from(allDependencies); const changedCheck = ( ( block.hasOutroMethod ? `#outroing || ` : '' ) + - dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + dependencies.map(dependency => `changed.${dependency}`).join(' || ') ); const updateCachedValue = `${last} !== ( ${last} = ${value} )`; diff --git a/src/generators/dom/visitors/shared/Tag.ts b/src/generators/dom/visitors/shared/Tag.ts index 3d4f3ed211..f15d72a5ba 100644 --- a/src/generators/dom/visitors/shared/Tag.ts +++ b/src/generators/dom/visitors/shared/Tag.ts @@ -30,7 +30,7 @@ export default function visitTag( if (dependencies.length || hasChangeableIndex) { const changedCheck = ( ( block.hasOutroMethod ? `#outroing || ` : '' ) + - dependencies.map(dependency => `'${dependency}' in changed`).join(' || ') + dependencies.map(dependency => `changed.${dependency}`).join(' || ') ); const updateCachedValue = `${value} !== ( ${value} = ${snippet} )`; diff --git a/src/shared/index.js b/src/shared/index.js index 0ebd390afb..901fbfdb3b 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -28,7 +28,7 @@ export function differs(a, b) { export function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index ee1601cc81..10a7a08930 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -54,7 +54,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; @@ -204,7 +204,7 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( 'foo' in changed ) { + if ( changed.foo ) { text.data = state.foo; } }, diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index f773a9eac1..60a7180f1d 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -39,7 +39,7 @@ function create_main_fragment ( state, component ) { }, update: function ( changed, state ) { - if ( 'foo' in changed ) { + if ( changed.foo ) { text.data = state.foo; } }, diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 112d21f8d8..20fe6ef0fa 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -30,7 +30,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; @@ -191,7 +191,7 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); SvelteComponent.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { - if ( isInitial || ( 'x' in changed ) ) { + if ( isInitial || changed.x ) { if ( differs( ( state.a = template.computed.a( state.x ) ), oldState.a ) ) changed.a = true; if ( differs( ( state.b = template.computed.b( state.x ) ), oldState.b ) ) changed.b = true; } diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index edfca61b86..6318b3b5cf 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -50,7 +50,7 @@ function SvelteComponent ( options ) { assign( SvelteComponent.prototype, proto ); SvelteComponent.prototype._recompute = function _recompute ( changed, state, oldState, isInitial ) { - if ( isInitial || ( 'x' in changed ) ) { + if ( isInitial || changed.x ) { if ( differs( ( state.a = template.computed.a( state.x ) ), oldState.a ) ) changed.a = true; if ( differs( ( state.b = template.computed.b( state.x ) ), oldState.b ) ) changed.b = true; } diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index ee643bae65..868ca03e71 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -50,7 +50,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 15410339ef..2d14d12151 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -63,7 +63,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; @@ -209,7 +209,7 @@ function create_main_fragment ( state, component ) { update: function ( changed, state ) { var each_block_value = state.comments; - if ( 'comments' in changed || 'elapsed' in changed || 'time' in changed ) { + if ( changed.comments || changed.elapsed || changed.time ) { for ( var i = 0; i < each_block_value.length; i += 1 ) { if ( each_block_iterations[i] ) { each_block_iterations[i].update( changed, state, each_block_value, each_block_value[i], i ); @@ -227,7 +227,7 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( 'foo' in changed ) { + if ( changed.foo ) { text_1.data = state.foo; } }, @@ -289,15 +289,15 @@ function create_each_block ( state, each_block_value, comment, i, component ) { }, update: function ( changed, state, each_block_value, comment, i ) { - if ( ( 'comments' in changed ) && text_2_value !== ( text_2_value = comment.author ) ) { + if ( ( changed.comments ) && text_2_value !== ( text_2_value = comment.author ) ) { text_2.data = text_2_value; } - if ( ( 'elapsed' in changed || 'comments' in changed || 'time' in changed ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { + if ( ( changed.elapsed || changed.comments || changed.time ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { text_4.data = text_4_value; } - if ( ( 'comments' in changed ) && raw_value !== ( raw_value = comment.html ) ) { + if ( ( changed.comments ) && raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); raw_before.insertAdjacentHTML( 'afterend', raw_value ); } diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index 07feca6020..9bfc875e78 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -35,7 +35,7 @@ function create_main_fragment ( state, component ) { update: function ( changed, state ) { var each_block_value = state.comments; - if ( 'comments' in changed || 'elapsed' in changed || 'time' in changed ) { + if ( changed.comments || changed.elapsed || changed.time ) { for ( var i = 0; i < each_block_value.length; i += 1 ) { if ( each_block_iterations[i] ) { each_block_iterations[i].update( changed, state, each_block_value, each_block_value[i], i ); @@ -53,7 +53,7 @@ function create_main_fragment ( state, component ) { each_block_iterations.length = each_block_value.length; } - if ( 'foo' in changed ) { + if ( changed.foo ) { text_1.data = state.foo; } }, @@ -115,15 +115,15 @@ function create_each_block ( state, each_block_value, comment, i, component ) { }, update: function ( changed, state, each_block_value, comment, i ) { - if ( ( 'comments' in changed ) && text_2_value !== ( text_2_value = comment.author ) ) { + if ( ( changed.comments ) && text_2_value !== ( text_2_value = comment.author ) ) { text_2.data = text_2_value; } - if ( ( 'elapsed' in changed || 'comments' in changed || 'time' in changed ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { + if ( ( changed.elapsed || changed.comments || changed.time ) && text_4_value !== ( text_4_value = state.elapsed(comment.time, state.time) ) ) { text_4.data = text_4_value; } - if ( ( 'comments' in changed ) && raw_value !== ( raw_value = comment.html ) ) { + if ( ( changed.comments ) && raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); raw_before.insertAdjacentHTML( 'afterend', raw_value ); } diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index dafef990c2..97090f092d 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -50,7 +50,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index f3f7a371be..a36be53f93 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -54,7 +54,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 1ffa205c9c..c16dbcaaa3 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -54,7 +54,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 98c6b12764..09576689bc 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -44,7 +44,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index f06892cbfb..227ac71e7c 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -30,7 +30,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 4421ec1f87..0ecdbafa5b 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -30,7 +30,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key]; diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 9b64cd8600..33f1b49df3 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -54,7 +54,7 @@ function differs(a, b) { function dispatchObservers(component, group, changed, newState, oldState) { for (var key in group) { - if (!(key in changed)) continue; + if (!changed[key]) continue; var newValue = newState[key]; var oldValue = oldState[key];