diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 55c6599cd7..bb2313252e 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -125,9 +125,6 @@ export default function dom( @dispatchObservers( this, this._observers.pre, newState, oldState ); ${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`} @dispatchObservers( this, this._observers.post, newState, oldState ); - ${generator.hasComponents && `@callAll(this._oncreate);`} - ${generator.hasComplexBindings && `@callAll(this._bindings);`} - ${generator.hasIntroTransitions && `@callAll(this._postcreate);`} `; if (hasJs) { @@ -207,9 +204,20 @@ export default function dom( ${generator.stylesheet.hasStyles && options.css !== false && `if ( !document.getElementById( '${generator.stylesheet.id}-style' ) ) @add_css();`} - ${generator.hasComponents && `this._oncreate = [];`} - ${generator.hasComplexBindings && `this._bindings = [];`} - ${generator.hasIntroTransitions && `this._postcreate = [];`} + + ${templateProperties.oncreate && `var oncreate = @template.oncreate.bind( this );`} + + ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` + if ( !options._root ) { + this._oncreate = [${templateProperties.oncreate && `oncreate`}]; + ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} + ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} + } ${templateProperties.oncreate && deindent` + else { + this._root._oncreate.push(oncreate); + } + `} + `} this._fragment = @create_main_fragment( this._state, this ); @@ -226,18 +234,16 @@ export default function dom( `} this._fragment.${block.hasIntroMethod ? 'intro' : 'mount'}( options.target, null ); } - - ${generator.hasComponents && `@callAll(this._oncreate);`} - ${generator.hasComplexBindings && `@callAll(this._bindings);`} - - ${templateProperties.oncreate && deindent` - if ( options._root ) { - options._root._oncreate.push( @template.oncreate.bind( this ) ); - } else { - @template.oncreate.call( this ); - }`} - - ${generator.hasIntroTransitions && `@callAll(this._postcreate);`} + + ${(generator.hasComponents || generator.hasComplexBindings || templateProperties.oncreate || generator.hasIntroTransitions) && deindent` + if ( !options._root ) { + ${generator.hasComponents && `this._lock = true;`} + ${(generator.hasComponents || generator.hasComplexBindings) && `@callAll(this._beforecreate);`} + ${(generator.hasComponents || templateProperties.oncreate) && `@callAll(this._oncreate);`} + ${(generator.hasComponents || generator.hasIntroTransitions) && `@callAll(this._aftercreate);`} + ${generator.hasComponents && `this._lock = false;`} + } + `} } @assign( ${prototypeBase}, ${proto}); diff --git a/src/generators/dom/visitors/Component/Binding.ts b/src/generators/dom/visitors/Component/Binding.ts index 26d853242a..e87ab0bff1 100644 --- a/src/generators/dom/visitors/Component/Binding.ts +++ b/src/generators/dom/visitors/Component/Binding.ts @@ -66,15 +66,24 @@ export default function visitBinding( const updating = block.getUniqueName(`${local.name}_updating`); block.addVariable(updating, 'false'); + const observer = block.getUniqueName('observer'); + const value = block.getUniqueName('value'); + local.create.addBlock(deindent` - #component._bindings.push( function () { - if ( ${local.name}._torndown ) return; - ${local.name}.observe( '${attribute.name}', function ( value ) { - if ( ${updating} ) return; - ${updating} = true; - ${setter} - ${updating} = false; - }, { init: @differs( ${local.name}.get( '${attribute.name}' ), ${snippet} ) }); + function ${observer} ( value ) { + if ( ${updating} ) return; + ${updating} = true; + ${setter} + ${updating} = false; + } + + ${local.name}.observe( '${attribute.name}', ${observer}, { init: false }); + + #component._root._beforecreate.push( function () { + var value = ${local.name}.get( '${attribute.name}' ); + if ( @differs( value, ${snippet} ) ) { + ${observer}.call( ${local.name}, value ); + } }); `); diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index 37482f1986..c5fb6c2f6c 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -94,7 +94,7 @@ export default function visitBinding( generator.hasComplexBindings = true; block.builders.hydrate.addBlock( - `if ( !('${name}' in state) ) #component._bindings.push( ${handler} );` + `if ( !('${name}' in state) ) #component._root._beforecreate.push( ${handler} );` ); } else if (attribute.name === 'group') { // special case @@ -120,7 +120,7 @@ export default function visitBinding( updateElement = `${state.parentNode}.checked = ${condition};`; } else if (node.name === 'audio' || node.name === 'video') { generator.hasComplexBindings = true; - block.builders.hydrate.addBlock(`#component._bindings.push( ${handler} );`); + block.builders.hydrate.addBlock(`#component._root._beforecreate.push( ${handler} );`); if (attribute.name === 'currentTime') { const frame = block.getUniqueName(`${state.parentNode}_animationframe`); diff --git a/src/generators/dom/visitors/Element/addTransitions.ts b/src/generators/dom/visitors/Element/addTransitions.ts index 84b31a4201..889cf1eb0b 100644 --- a/src/generators/dom/visitors/Element/addTransitions.ts +++ b/src/generators/dom/visitors/Element/addTransitions.ts @@ -23,7 +23,7 @@ export default function addTransitions( const fn = `@template.transitions.${intro.name}`; block.builders.intro.addBlock(deindent` - #component._postcreate.push( function () { + #component._root._aftercreate.push( function () { if ( !${name} ) ${name} = @wrapTransition( #component, ${state.name}, ${fn}, ${snippet}, true, null ); ${name}.run( true, function () { #component.fire( 'intro.end', { node: ${state.name} }); @@ -58,7 +58,7 @@ export default function addTransitions( } block.builders.intro.addBlock(deindent` - #component._postcreate.push( function () { + #component._root._aftercreate.push( function () { ${introName} = @wrapTransition( #component, ${state.name}, ${fn}, ${snippet}, true, null ); ${introName}.run( true, function () { #component.fire( 'intro.end', { node: ${state.name} }); diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts index 784e1d7302..6c25d67a72 100644 --- a/src/generators/dom/visitors/shared/binding/getSetter.ts +++ b/src/generators/dom/visitors/shared/binding/getSetter.ts @@ -27,10 +27,10 @@ export default function getSetter({ list[index]${tail} = ${value}; ${computed - ? `#component._set({ ${dependencies + ? `#component.set({ ${dependencies .map((prop: string) => `${prop}: state.${prop}`) .join(', ')} });` - : `#component._set({ ${dependencies + : `#component.set({ ${dependencies .map((prop: string) => `${prop}: #component.get( '${prop}' )`) .join(', ')} });`} `; @@ -40,13 +40,13 @@ export default function getSetter({ return deindent` var state = #component.get(); ${snippet} = ${value}; - #component._set({ ${dependencies + #component.set({ ${dependencies .map((prop: string) => `${prop}: state.${prop}`) .join(', ')} }); `; } - return `#component._set({ ${name}: ${value} });`; + return `#component.set({ ${name}: ${value} });`; } function isComputed(node: Node) { diff --git a/src/shared/index.js b/src/shared/index.js index acac5b216c..119f50452b 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -106,7 +106,12 @@ export function onDev(eventName, handler) { export function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } export function callAll(fns) { 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 ea478745cd..535273b636 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -115,7 +115,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 5cd719b372..547cb5e958 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -91,7 +91,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { 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 d98ac1db37..47d43b2aea 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -124,7 +124,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 3912710a15..c916b10078 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -109,7 +109,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { 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 f00e3816a9..39ea6622f5 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -115,7 +115,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 2e2e7e7c09..382c2fd8d3 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -115,7 +115,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 878bdf223e..91a8b8c292 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -103,7 +103,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { @@ -178,7 +183,12 @@ function SvelteComponent ( options ) { this._yield = options._yield; this._torndown = false; - this._oncreate = []; + + if ( !options._root ) { + this._oncreate = []; + this._beforecreate = []; + this._aftercreate = []; + } this._fragment = create_main_fragment( this._state, this ); @@ -187,7 +197,13 @@ function SvelteComponent ( options ) { this._fragment.mount( options.target, null ); } - callAll(this._oncreate); + if ( !options._root ) { + this._lock = true; + callAll(this._beforecreate); + callAll(this._oncreate); + callAll(this._aftercreate); + this._lock = false; + } } assign( SvelteComponent.prototype, proto ); @@ -197,7 +213,6 @@ SvelteComponent.prototype._set = function _set ( newState ) { this._state = assign( {}, oldState, newState ); dispatchObservers( this, this._observers.pre, newState, oldState ); dispatchObservers( this, this._observers.post, newState, oldState ); - callAll(this._oncreate); }; SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) { diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index c154930593..787e62c54b 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -62,7 +62,12 @@ function SvelteComponent ( options ) { this._yield = options._yield; this._torndown = false; - this._oncreate = []; + + if ( !options._root ) { + this._oncreate = []; + this._beforecreate = []; + this._aftercreate = []; + } this._fragment = create_main_fragment( this._state, this ); @@ -71,7 +76,13 @@ function SvelteComponent ( options ) { this._fragment.mount( options.target, null ); } - callAll(this._oncreate); + if ( !options._root ) { + this._lock = true; + callAll(this._beforecreate); + callAll(this._oncreate); + callAll(this._aftercreate); + this._lock = false; + } } assign( SvelteComponent.prototype, proto ); @@ -81,7 +92,6 @@ SvelteComponent.prototype._set = function _set ( newState ) { this._state = assign( {}, oldState, newState ); dispatchObservers( this, this._observers.pre, newState, oldState ); dispatchObservers( this, this._observers.post, newState, oldState ); - callAll(this._oncreate); }; SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) { diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index e74522a945..25fae9ba8c 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -91,7 +91,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { @@ -143,6 +148,14 @@ function SvelteComponent ( options ) { this._torndown = false; + var oncreate = template.oncreate.bind( this ); + + if ( !options._root ) { + this._oncreate = [oncreate]; + } else { + this._root._oncreate.push(oncreate); + } + this._fragment = create_main_fragment( this._state, this ); if ( options.target ) { @@ -150,10 +163,8 @@ function SvelteComponent ( options ) { this._fragment.mount( options.target, null ); } - if ( options._root ) { - options._root._oncreate.push( template.oncreate.bind( this ) ); - } else { - template.oncreate.call( this ); + if ( !options._root ) { + callAll(this._oncreate); } } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index 96aa37d766..1a1c10f7d6 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, dispatchObservers, noop, proto } from "svelte/shared.js"; +import { assign, callAll, dispatchObservers, noop, proto } from "svelte/shared.js"; var template = (function () { return { @@ -37,6 +37,14 @@ function SvelteComponent ( options ) { this._torndown = false; + var oncreate = template.oncreate.bind( this ); + + if ( !options._root ) { + this._oncreate = [oncreate]; + } else { + this._root._oncreate.push(oncreate); + } + this._fragment = create_main_fragment( this._state, this ); if ( options.target ) { @@ -44,10 +52,8 @@ function SvelteComponent ( options ) { this._fragment.mount( options.target, null ); } - if ( options._root ) { - options._root._oncreate.push( template.oncreate.bind( this ) ); - } else { - template.oncreate.call( this ); + if ( !options._root ) { + callAll(this._oncreate); } } 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 62a6ac86e0..32cd0203c2 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -115,7 +115,12 @@ function on(eventName, handler) { function set(newState) { this._set(assign({}, newState)); + if (this._root._lock) return; + this._root._lock = true; + callAll(this._root._beforecreate); callAll(this._root._oncreate); + callAll(this._root._aftercreate); + this._root._lock = false; } function callAll(fns) { diff --git a/test/runtime/index.js b/test/runtime/index.js index 16d5820f0b..dea60082a3 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -137,10 +137,9 @@ describe("runtime", () => { throw err; } + let usedObjectAssign = false; Object.assign = () => { - throw new Error( - "cannot use Object.assign in generated code, as it is not supported everywhere" - ); + usedObjectAssign = true; }; global.window = window; @@ -182,14 +181,18 @@ describe("runtime", () => { assert.htmlEqual(target.innerHTML, config.html); } - Object.assign = Object_assign; - if (config.test) { config.test(assert, component, target, window, raf); } else { component.destroy(); assert.equal(target.innerHTML, ""); } + + if (usedObjectAssign) { + throw new Error( + "cannot use Object.assign in generated code, as it is not supported everywhere" + ); + } }) .catch(err => { Object.assign = Object_assign; diff --git a/test/runtime/samples/component-binding-blowback-b/Nested.html b/test/runtime/samples/component-binding-blowback-b/Nested.html new file mode 100644 index 0000000000..a1420a6916 --- /dev/null +++ b/test/runtime/samples/component-binding-blowback-b/Nested.html @@ -0,0 +1,18 @@ +
  • + {{yield}} +
  • + + diff --git a/test/runtime/samples/component-binding-blowback-b/_config.js b/test/runtime/samples/component-binding-blowback-b/_config.js new file mode 100644 index 0000000000..7f2bd38d9b --- /dev/null +++ b/test/runtime/samples/component-binding-blowback-b/_config.js @@ -0,0 +1,33 @@ +export default { + 'skip-ssr': true, + + data: { + count: 3 + }, + + html: ` + +
      +
    1. id-0: value is zero
    2. +
    3. id-1: value is one
    4. +
    5. id-2: value is two
    6. +
    + `, + + test (assert, component, target, window) { + const input = target.querySelector('input'); + + input.value = 4; + input.dispatchEvent(new window.Event('input')); + + assert.htmlEqual(target.innerHTML, ` + +
      +
    1. id-0: value is zero
    2. +
    3. id-1: value is one
    4. +
    5. id-2: value is two
    6. +
    7. id-3: value is three
    8. +
    + `); + } +}; diff --git a/test/runtime/samples/component-binding-blowback-b/main.html b/test/runtime/samples/component-binding-blowback-b/main.html new file mode 100644 index 0000000000..4e3ca43813 --- /dev/null +++ b/test/runtime/samples/component-binding-blowback-b/main.html @@ -0,0 +1,33 @@ + + +
      + {{#each ids as id}} + + {{id}}: value is {{idToValue[id]}} + + {{/each}} +
    + + \ No newline at end of file