From 162281ef4f2d280d25c849ac21f4d09059e167fd Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Wed, 19 Apr 2017 12:39:01 -0400
Subject: [PATCH 001/233] populate state in preprocess step, including naming
DOM nodes
---
src/generators/dom/index.js | 4 +-
src/generators/dom/preprocess.js | 105 ++++++--
.../dom/visitors/Component/Component.js | 4 +-
src/generators/dom/visitors/EachBlock.js | 13 +-
.../dom/visitors/Element/Element.js | 11 +-
src/generators/dom/visitors/IfBlock.js | 10 +-
src/generators/dom/visitors/MustacheTag.js | 2 +-
src/generators/dom/visitors/RawMustacheTag.js | 4 +-
src/generators/dom/visitors/Text.js | 23 +-
.../use-elements-as-anchors/expected.js | 244 ++++++++++++++++++
.../use-elements-as-anchors/input.html | 27 ++
11 files changed, 374 insertions(+), 73 deletions(-)
create mode 100644 test/js/samples/use-elements-as-anchors/expected.js
create mode 100644 test/js/samples/use-elements-as-anchors/input.html
diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js
index 1309c4ddb2..173b987aa1 100644
--- a/src/generators/dom/index.js
+++ b/src/generators/dom/index.js
@@ -43,14 +43,14 @@ export default function dom ( parsed, source, options ) {
const { computations, hasJs, templateProperties, namespace } = generator.parseJs();
- const block = preprocess( generator, parsed.html );
-
const state = {
namespace,
parentNode: null,
isTopLevel: true
};
+ const block = preprocess( generator, state, parsed.html );
+
parsed.html.children.forEach( node => {
visit( generator, block, state, node );
});
diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js
index f8ab06ddc3..320724c332 100644
--- a/src/generators/dom/preprocess.js
+++ b/src/generators/dom/preprocess.js
@@ -1,17 +1,62 @@
import Block from './Block.js';
import { trimStart, trimEnd } from '../../utils/trim.js';
+import { assign } from '../../shared/index.js';
function isElseIf ( node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
+function getChildState ( parent, child ) {
+ return assign( {}, parent, { name: null, parentNode: null }, child || {} );
+}
+
+// Whitespace inside one of these elements will not result in
+// a whitespace node being created in any circumstances. (This
+// list is almost certainly very incomplete)
+const elementsWithoutText = new Set([
+ 'audio',
+ 'datalist',
+ 'dl',
+ 'ol',
+ 'optgroup',
+ 'select',
+ 'ul',
+ 'video'
+]);
+
const preprocessors = {
- MustacheTag: ( generator, block, node ) => {
+ MustacheTag: ( generator, block, state, node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
+
+ node._state = getChildState( state, {
+ name: block.getUniqueName( 'text' )
+ });
+ },
+
+ RawMustacheTag: ( generator, block, state, node ) => {
+ const dependencies = block.findDependencies( node.expression );
+ block.addDependencies( dependencies );
+
+ const basename = block.getUniqueName( 'raw' );
+ const name = block.getUniqueName( `${basename}_before` );
+
+ node._state = getChildState( state, { basename, name });
+ },
+
+ Text: ( generator, block, state, node ) => {
+ node._state = getChildState( state );
+
+ if ( !/\S/.test( node.data ) ) {
+ if ( state.namespace ) return;
+ if ( elementsWithoutText.has( state.parentNodeName ) ) return;
+ }
+
+ node._state.shouldCreate = true;
+ node._state.name = block.getUniqueName( `text` );
},
- IfBlock: ( generator, block, node ) => {
+ IfBlock: ( generator, block, state, node ) => {
const blocks = [];
let dynamic = false;
@@ -23,8 +68,10 @@ const preprocessors = {
name: generator.getUniqueName( `create_if_block` )
});
+ node._state = getChildState( state );
+
blocks.push( node._block );
- preprocessChildren( generator, node._block, node );
+ preprocessChildren( generator, node._block, node._state, node );
if ( node._block.dependencies.size > 0 ) {
dynamic = true;
@@ -38,8 +85,10 @@ const preprocessors = {
name: generator.getUniqueName( `create_if_block` )
});
+ node.else._state = getChildState( state );
+
blocks.push( node.else._block );
- preprocessChildren( generator, node.else._block, node.else );
+ preprocessChildren( generator, node.else._block, node.else._state, node.else );
if ( node.else._block.dependencies.size > 0 ) {
dynamic = true;
@@ -57,7 +106,7 @@ const preprocessors = {
generator.blocks.push( ...blocks );
},
- EachBlock: ( generator, block, node ) => {
+ EachBlock: ( generator, block, state, node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@@ -97,8 +146,12 @@ const preprocessors = {
params: block.params.concat( listName, context, indexName )
});
+ node._state = getChildState( state, {
+ inEachBlock: true
+ });
+
generator.blocks.push( node._block );
- preprocessChildren( generator, node._block, node );
+ preprocessChildren( generator, node._block, node._state, node );
block.addDependencies( node._block.dependencies );
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
@@ -107,13 +160,32 @@ const preprocessors = {
name: generator.getUniqueName( `${node._block.name}_else` )
});
+ node.else._state = getChildState( state );
+
generator.blocks.push( node.else._block );
- preprocessChildren( generator, node.else._block, node.else );
+ preprocessChildren( generator, node.else._block, node.else._state, node.else );
node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0;
}
},
- Element: ( generator, block, node ) => {
+ Element: ( generator, block, state, node ) => {
+ const isComponent = generator.components.has( node.name ) || node.name === ':Self';
+
+ if ( isComponent ) {
+ node._state = getChildState( state );
+ } else {
+ const name = block.getUniqueName( node.name );
+
+ node._state = getChildState( state, {
+ isTopLevel: false,
+ name,
+ parentNode: name,
+ parentNodeName: node.name,
+ namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace,
+ allUsedContexts: []
+ });
+ }
+
node.attributes.forEach( attribute => {
if ( attribute.type === 'Attribute' && attribute.value !== true ) {
attribute.value.forEach( chunk => {
@@ -130,8 +202,6 @@ const preprocessors = {
}
});
- const isComponent = generator.components.has( node.name ) || node.name === ':Self';
-
if ( node.children.length ) {
if ( isComponent ) {
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );
@@ -141,21 +211,19 @@ const preprocessors = {
});
generator.blocks.push( node._block );
- preprocessChildren( generator, node._block, node );
+ preprocessChildren( generator, node._block, node._state, node );
block.addDependencies( node._block.dependencies );
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
}
else {
- preprocessChildren( generator, block, node );
+ preprocessChildren( generator, block, node._state, node );
}
}
}
};
-preprocessors.RawMustacheTag = preprocessors.MustacheTag;
-
-function preprocessChildren ( generator, block, node ) {
+function preprocessChildren ( generator, block, state, node ) {
// glue text nodes together
const cleaned = [];
let lastChild;
@@ -170,6 +238,7 @@ function preprocessChildren ( generator, block, node ) {
cleaned.push( child );
}
+ if ( lastChild ) lastChild.next = child;
lastChild = child;
});
@@ -177,11 +246,11 @@ function preprocessChildren ( generator, block, node ) {
cleaned.forEach( child => {
const preprocess = preprocessors[ child.type ];
- if ( preprocess ) preprocess( generator, block, child );
+ if ( preprocess ) preprocess( generator, block, state, child );
});
}
-export default function preprocess ( generator, node ) {
+export default function preprocess ( generator, state, node ) {
const block = new Block({
generator,
name: generator.alias( 'create_main_fragment' ),
@@ -199,7 +268,7 @@ export default function preprocess ( generator, node ) {
});
generator.blocks.push( block );
- preprocessChildren( generator, block, node );
+ preprocessChildren( generator, block, state, node );
block.hasUpdateMethod = block.dependencies.size > 0;
// trim leading and trailing whitespace from the top level
diff --git a/src/generators/dom/visitors/Component/Component.js b/src/generators/dom/visitors/Component/Component.js
index c54945338d..6430c4f07c 100644
--- a/src/generators/dom/visitors/Component/Component.js
+++ b/src/generators/dom/visitors/Component/Component.js
@@ -36,9 +36,7 @@ export default function visitComponent ( generator, block, state, node ) {
const hasChildren = node.children.length > 0;
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );
- const childState = Object.assign( {}, state, {
- parentNode: null
- });
+ const childState = node._state;
const local = {
name,
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index dbb1d2c085..7919cf1f3d 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -87,22 +87,13 @@ export default function visitEachBlock ( generator, block, state, node ) {
` );
}
- const childState = Object.assign( {}, state, {
- parentNode: null,
- inEachBlock: true
- });
-
node.children.forEach( child => {
- visit( generator, node._block, childState, child );
+ visit( generator, node._block, node._state, child );
});
if ( node.else ) {
- const childState = Object.assign( {}, state, {
- parentNode: null
- });
-
node.else.children.forEach( child => {
- visit( generator, node.else._block, childState, child );
+ visit( generator, node.else._block, node.else._state, child );
});
}
}
diff --git a/src/generators/dom/visitors/Element/Element.js b/src/generators/dom/visitors/Element/Element.js
index 7f55d84ec9..001c8a52e2 100644
--- a/src/generators/dom/visitors/Element/Element.js
+++ b/src/generators/dom/visitors/Element/Element.js
@@ -34,15 +34,8 @@ export default function visitElement ( generator, block, state, node ) {
return visitComponent( generator, block, state, node );
}
- const name = block.getUniqueName( node.name );
-
- const childState = Object.assign( {}, state, {
- isTopLevel: false,
- parentNode: name,
- parentNodeName: node.name,
- namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace,
- allUsedContexts: []
- });
+ const childState = node._state;
+ const name = childState.parentNode;
block.builders.create.addLine( `var ${name} = ${getRenderStatement( generator, childState.namespace, node.name )};` );
block.mount( name, state.parentNode );
diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js
index 1f1800b536..593a7ca986 100644
--- a/src/generators/dom/visitors/IfBlock.js
+++ b/src/generators/dom/visitors/IfBlock.js
@@ -34,12 +34,8 @@ function getBranches ( generator, block, state, node ) {
}
function visitChildren ( generator, block, state, node ) {
- const childState = Object.assign( {}, state, {
- parentNode: null
- });
-
node.children.forEach( child => {
- visit( generator, node._block, childState, child );
+ visit( generator, node._block, node._state, child );
});
}
@@ -76,7 +72,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
if ( isToplevel ) {
block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` );
} else {
- block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, ${anchor} );` );
+ block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` );
}
if ( dynamic ) {
@@ -128,7 +124,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
if ( isToplevel ) {
block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` );
} else {
- block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, ${anchor} );` );
+ block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` );
}
if ( dynamic ) {
diff --git a/src/generators/dom/visitors/MustacheTag.js b/src/generators/dom/visitors/MustacheTag.js
index 301345e6cc..568f745da5 100644
--- a/src/generators/dom/visitors/MustacheTag.js
+++ b/src/generators/dom/visitors/MustacheTag.js
@@ -1,7 +1,7 @@
import deindent from '../../../utils/deindent.js';
export default function visitMustacheTag ( generator, block, state, node ) {
- const name = block.getUniqueName( 'text' );
+ const name = node._state.name;
const value = block.getUniqueName( `${name}_value` );
const { snippet } = block.contextualise( node.expression );
diff --git a/src/generators/dom/visitors/RawMustacheTag.js b/src/generators/dom/visitors/RawMustacheTag.js
index 2eed5bfdb6..7a39643150 100644
--- a/src/generators/dom/visitors/RawMustacheTag.js
+++ b/src/generators/dom/visitors/RawMustacheTag.js
@@ -1,9 +1,9 @@
import deindent from '../../../utils/deindent.js';
export default function visitRawMustacheTag ( generator, block, state, node ) {
- const name = block.getUniqueName( 'raw' );
+ const name = node._state.basename;
+ const before = node._state.name;
const value = block.getUniqueName( `${name}_value` );
- const before = block.getUniqueName( `${name}_before` );
const after = block.getUniqueName( `${name}_after` );
const { snippet } = block.contextualise( node.expression );
diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js
index 923175e5ab..052c4e58a7 100644
--- a/src/generators/dom/visitors/Text.js
+++ b/src/generators/dom/visitors/Text.js
@@ -1,23 +1,6 @@
-// Whitespace inside one of these elements will not result in
-// a whitespace node being created in any circumstances. (This
-// list is almost certainly very incomplete)
-const elementsWithoutText = new Set([
- 'audio',
- 'datalist',
- 'dl',
- 'ol',
- 'optgroup',
- 'select',
- 'ul',
- 'video'
-]);
-export default function visitText ( generator, block, state, node ) {
- if ( !/\S/.test( node.data ) ) {
- if ( state.namespace ) return;
- if ( elementsWithoutText.has( state.parentNodeName) ) return;
- }
- const name = block.getUniqueName( `text` );
- block.addElement( name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false );
+export default function visitText ( generator, block, state, node ) {
+ if ( !node._state.shouldCreate ) return;
+ block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false );
}
\ No newline at end of file
diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js
new file mode 100644
index 0000000000..e72585e606
--- /dev/null
+++ b/test/js/samples/use-elements-as-anchors/expected.js
@@ -0,0 +1,244 @@
+import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
+
+function create_main_fragment ( state, component ) {
+ var div = createElement( 'div' );
+ var if_block_anchor = createComment();
+ appendNode( if_block_anchor, div );
+
+ var if_block = state.a && create_if_block( state, component );
+
+ if ( if_block ) if_block.mount( div, null );
+ appendNode( createText( "\n\n\t" ), div );
+ var p = createElement( 'p' );
+ appendNode( p, div );
+ appendNode( createText( "this can be used as an anchor" ), p );
+ appendNode( createText( "\n\n\t" ), div );
+
+ var if_block_1 = state.b && create_if_block_1( state, component );
+
+ if ( if_block_1 ) if_block_1.mount( div, null );
+ var if_block_1_anchor = createComment();
+ appendNode( if_block_1_anchor, div );
+
+ appendNode( createText( "\n\n\t" ), div );
+
+ var if_block_2 = state.c && create_if_block_2( state, component );
+
+ if ( if_block_2 ) if_block_2.mount( div, null );
+ appendNode( createText( "\n\n\t" ), div );
+ var p_1 = createElement( 'p' );
+ appendNode( p_1, div );
+ appendNode( createText( "so can this" ), p_1 );
+ appendNode( createText( "\n\n\t" ), div );
+
+ var if_block_3 = state.d && create_if_block_3( state, component );
+
+ if ( if_block_3 ) if_block_3.mount( div, null );
+ appendNode( createText( "\n\n\t" ), div );
+ var text_8 = createText( "\n\n" );
+ var if_block_4_anchor = createComment();
+
+ var if_block_4 = state.e && create_if_block_4( state, component );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( div, target, anchor );
+ insertNode( text_8, target, anchor );
+ insertNode( if_block_4_anchor, target, anchor );
+ if ( if_block_4 ) if_block_4.mount( target, if_block_4_anchor );
+ },
+
+ update: function ( changed, state ) {
+ if ( state.a ) {
+ if ( !if_block ) {
+ if_block = create_if_block( state, component );
+ if_block.mount( if_block_anchor.parentNode, p );
+ }
+ } else if ( if_block ) {
+ if_block.destroy( true );
+ if_block = null;
+ }
+
+ if ( state.b ) {
+ if ( !if_block_1 ) {
+ if_block_1 = create_if_block_1( state, component );
+ if_block_1.mount( if_block_1_anchor.parentNode, if_block_1_anchor );
+ }
+ } else if ( if_block_1 ) {
+ if_block_1.destroy( true );
+ if_block_1 = null;
+ }
+
+ if ( state.c ) {
+ if ( !if_block_2 ) {
+ if_block_2 = create_if_block_2( state, component );
+ if_block_2.mount( if_block_2_anchor.parentNode, p_2 );
+ }
+ } else if ( if_block_2 ) {
+ if_block_2.destroy( true );
+ if_block_2 = null;
+ }
+
+ if ( state.d ) {
+ if ( !if_block_3 ) {
+ if_block_3 = create_if_block_3( state, component );
+ if_block_3.mount( if_block_3_anchor.parentNode, null );
+ }
+ } else if ( if_block_3 ) {
+ if_block_3.destroy( true );
+ if_block_3 = null;
+ }
+
+ if ( state.e ) {
+ if ( !if_block_4 ) {
+ if_block_4 = create_if_block_4( state, component );
+ if_block_4.mount( if_block_4_anchor.parentNode, if_block_4_anchor );
+ }
+ } else if ( if_block_4 ) {
+ if_block_4.destroy( true );
+ if_block_4 = null;
+ }
+ },
+
+ destroy: function ( detach ) {
+ if ( if_block ) if_block.destroy( false );
+ if ( if_block_1 ) if_block_1.destroy( false );
+ if ( if_block_2 ) if_block_2.destroy( false );
+ if ( if_block_3 ) if_block_3.destroy( false );
+ if ( if_block_4 ) if_block_4.destroy( detach );
+
+ if ( detach ) {
+ detachNode( div );
+ detachNode( text_8 );
+ detachNode( if_block_4_anchor );
+ }
+ }
+ };
+}
+
+function create_if_block ( state, component ) {
+ var p = createElement( 'p' );
+ appendNode( createText( "a" ), p );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( p, target, anchor );
+ },
+
+ destroy: function ( detach ) {
+ if ( detach ) {
+ detachNode( p );
+ }
+ }
+ };
+}
+
+function create_if_block_1 ( state, component ) {
+ var p = createElement( 'p' );
+ appendNode( createText( "b" ), p );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( p, target, anchor );
+ },
+
+ destroy: function ( detach ) {
+ if ( detach ) {
+ detachNode( p );
+ }
+ }
+ };
+}
+
+function create_if_block_2 ( state, component ) {
+ var p = createElement( 'p' );
+ appendNode( createText( "c" ), p );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( p, target, anchor );
+ },
+
+ destroy: function ( detach ) {
+ if ( detach ) {
+ detachNode( p );
+ }
+ }
+ };
+}
+
+function create_if_block_3 ( state, component ) {
+ var p = createElement( 'p' );
+ appendNode( createText( "d" ), p );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( p, target, anchor );
+ },
+
+ destroy: function ( detach ) {
+ if ( detach ) {
+ detachNode( p );
+ }
+ }
+ };
+}
+
+function create_if_block_4 ( state, component ) {
+ var p = createElement( 'p' );
+ appendNode( createText( "e" ), p );
+
+ return {
+ mount: function ( target, anchor ) {
+ insertNode( p, target, anchor );
+ },
+
+ destroy: function ( detach ) {
+ if ( detach ) {
+ detachNode( p );
+ }
+ }
+ };
+}
+
+function SvelteComponent ( options ) {
+ options = options || {};
+ this._state = options.data || {};
+
+ this._observers = {
+ pre: Object.create( null ),
+ post: Object.create( null )
+ };
+
+ this._handlers = Object.create( null );
+
+ this._root = options._root;
+ this._yield = options._yield;
+
+ this._torndown = false;
+
+ this._fragment = create_main_fragment( this._state, this );
+ if ( options.target ) this._fragment.mount( options.target, null );
+}
+
+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 );
+ if ( this._fragment ) this._fragment.update( newState, this._state );
+ dispatchObservers( this, this._observers.post, newState, oldState );
+};
+
+SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = function destroy ( detach ) {
+ this.fire( 'destroy' );
+
+ this._fragment.destroy( detach !== false );
+ this._fragment = null;
+
+ this._state = {};
+ this._torndown = true;
+};
+
+export default SvelteComponent;
\ No newline at end of file
diff --git a/test/js/samples/use-elements-as-anchors/input.html b/test/js/samples/use-elements-as-anchors/input.html
new file mode 100644
index 0000000000..c55e7bd4de
--- /dev/null
+++ b/test/js/samples/use-elements-as-anchors/input.html
@@ -0,0 +1,27 @@
+
+ {{#if a}}
+
a
+ {{/if}}
+
+
this can be used as an anchor
+
+ {{#if b}}
+
b
+ {{/if}}
+
+ {{#if c}}
+
c
+ {{/if}}
+
+
so can this
+
+ {{#if d}}
+
d
+ {{/if}}
+
+
+
+
+{{#if e}}
+ e
+{{/if}}
\ No newline at end of file
From d274d08734b2cdbe1eda51cf06d60d7df710a569 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Wed, 19 Apr 2017 13:52:36 -0400
Subject: [PATCH 002/233] only create anchors for if blocks when necessary
---
src/generators/dom/preprocess.js | 48 ++++++++++++-------
src/generators/dom/visitors/IfBlock.js | 24 ++++++----
src/generators/dom/visitors/Text.js | 2 +-
.../js/samples/if-block-no-update/expected.js | 2 +-
test/js/samples/if-block-simple/expected.js | 2 +-
.../use-elements-as-anchors/expected.js | 27 +++++------
.../samples/if-block-widget/_config.js | 21 ++++++--
7 files changed, 81 insertions(+), 45 deletions(-)
diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js
index 320724c332..6009f4dec8 100644
--- a/src/generators/dom/preprocess.js
+++ b/src/generators/dom/preprocess.js
@@ -223,7 +223,7 @@ const preprocessors = {
}
};
-function preprocessChildren ( generator, block, state, node ) {
+function preprocessChildren ( generator, block, state, node, isTopLevel ) {
// glue text nodes together
const cleaned = [];
let lastChild;
@@ -238,16 +238,43 @@ function preprocessChildren ( generator, block, state, node ) {
cleaned.push( child );
}
- if ( lastChild ) lastChild.next = child;
lastChild = child;
});
- node.children = cleaned;
+ if ( isTopLevel ) {
+ // trim leading and trailing whitespace from the top level
+ const firstChild = cleaned[0];
+ if ( firstChild && firstChild.type === 'Text' ) {
+ firstChild.data = trimStart( firstChild.data );
+ if ( !firstChild.data ) cleaned.shift();
+ }
+
+ const lastChild = cleaned[ cleaned.length - 1 ];
+ if ( lastChild && lastChild.type === 'Text' ) {
+ lastChild.data = trimEnd( lastChild.data );
+ if ( !lastChild.data ) cleaned.pop();
+ }
+ }
+
+ lastChild = null;
cleaned.forEach( child => {
const preprocess = preprocessors[ child.type ];
if ( preprocess ) preprocess( generator, block, state, child );
+
+ if ( lastChild ) {
+ lastChild.next = child;
+ lastChild.needsAnchor = !child._state.name;
+ }
+
+ lastChild = child;
});
+
+ if ( lastChild ) {
+ lastChild.needsAnchor = !state.parentNode;
+ }
+
+ node.children = cleaned;
}
export default function preprocess ( generator, state, node ) {
@@ -268,21 +295,8 @@ export default function preprocess ( generator, state, node ) {
});
generator.blocks.push( block );
- preprocessChildren( generator, block, state, node );
+ preprocessChildren( generator, block, state, node, true );
block.hasUpdateMethod = block.dependencies.size > 0;
- // trim leading and trailing whitespace from the top level
- const firstChild = node.children[0];
- if ( firstChild && firstChild.type === 'Text' ) {
- firstChild.data = trimStart( firstChild.data );
- if ( !firstChild.data ) node.children.shift();
- }
-
- const lastChild = node.children[ node.children.length - 1 ];
- if ( lastChild && lastChild.type === 'Text' ) {
- lastChild.data = trimEnd( lastChild.data );
- if ( !lastChild.data ) node.children.pop();
- }
-
return block;
}
\ No newline at end of file
diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js
index 593a7ca986..d9fd1b81c7 100644
--- a/src/generators/dom/visitors/IfBlock.js
+++ b/src/generators/dom/visitors/IfBlock.js
@@ -41,12 +41,16 @@ function visitChildren ( generator, block, state, node ) {
export default function visitIfBlock ( generator, block, state, node ) {
const name = generator.getUniqueName( `if_block` );
- const anchor = generator.getUniqueName( `${name}_anchor` );
+ const anchor = node.needsAnchor ? block.getUniqueName( `${name}_anchor` ) : ( node.next && node.next._state.name ) || 'null';
const params = block.params.join( ', ' );
const vars = { name, anchor, params };
- block.createAnchor( anchor, state.parentNode );
+ if ( node.needsAnchor ) {
+ block.createAnchor( anchor, state.parentNode );
+ } else if ( node.next ) {
+ node.next.usedAsAnchor = true;
+ }
const branches = getBranches( generator, block, state, node, generator.getUniqueName( `create_if_block` ) );
const dynamic = branches.some( branch => branch.dynamic );
@@ -70,11 +74,13 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
const isToplevel = !state.parentNode;
if ( isToplevel ) {
- block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` );
+ block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, null );` );
} else {
block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` );
}
+ const parentNode = state.parentNode || `${anchor}.parentNode`;
+
if ( dynamic ) {
block.builders.update.addBlock( deindent`
if ( ${branch.condition} ) {
@@ -82,7 +88,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
${name}.update( changed, ${params} );
} else {
${name} = ${branch.block}( ${params}, ${block.component} );
- ${name}.mount( ${anchor}.parentNode, ${anchor} );
+ ${name}.mount( ${parentNode}, ${anchor} );
}
} else if ( ${name} ) {
${name}.destroy( true );
@@ -94,7 +100,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
if ( ${branch.condition} ) {
if ( !${name} ) {
${name} = ${branch.block}( ${params}, ${block.component} );
- ${name}.mount( ${anchor}.parentNode, ${anchor} );
+ ${name}.mount( ${parentNode}, ${anchor} );
}
} else if ( ${name} ) {
${name}.destroy( true );
@@ -122,11 +128,13 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
const isToplevel = !state.parentNode;
if ( isToplevel ) {
- block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, ${anchor} );` );
+ block.builders.mount.addLine( `if ( ${name} ) ${name}.mount( ${block.target}, null );` );
} else {
block.builders.create.addLine( `if ( ${name} ) ${name}.mount( ${state.parentNode}, null );` );
}
+ const parentNode = state.parentNode || `${anchor}.parentNode`;
+
if ( dynamic ) {
block.builders.update.addBlock( deindent`
if ( ${current_block} === ( ${current_block} = ${getBlock}( ${params} ) ) && ${name} ) {
@@ -134,7 +142,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
} else {
if ( ${name} ) ${name}.destroy( true );
${name} = ${current_block} && ${current_block}( ${params}, ${block.component} );
- if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
+ if ( ${name} ) ${name}.mount( ${parentNode}, ${anchor} );
}
` );
} else {
@@ -142,7 +150,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
if ( ${current_block} !== ( ${current_block} = ${getBlock}( ${params} ) ) ) {
if ( ${name} ) ${name}.destroy( true );
${name} = ${current_block} && ${current_block}( ${params}, ${block.component} );
- if ( ${name} ) ${name}.mount( ${anchor}.parentNode, ${anchor} );
+ if ( ${name} ) ${name}.mount( ${parentNode}, ${anchor} );
}
` );
}
diff --git a/src/generators/dom/visitors/Text.js b/src/generators/dom/visitors/Text.js
index 052c4e58a7..9abb0fa19e 100644
--- a/src/generators/dom/visitors/Text.js
+++ b/src/generators/dom/visitors/Text.js
@@ -2,5 +2,5 @@
export default function visitText ( generator, block, state, node ) {
if ( !node._state.shouldCreate ) return;
- block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, false );
+ block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor );
}
\ No newline at end of file
diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js
index 1ed5eb2e7f..ef158af47d 100644
--- a/test/js/samples/if-block-no-update/expected.js
+++ b/test/js/samples/if-block-no-update/expected.js
@@ -14,7 +14,7 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
insertNode( if_block_anchor, target, anchor );
- if ( if_block ) if_block.mount( target, if_block_anchor );
+ if ( if_block ) if_block.mount( target, null );
},
update: function ( changed, state ) {
diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js
index fcdd72f55d..c31e4ae914 100644
--- a/test/js/samples/if-block-simple/expected.js
+++ b/test/js/samples/if-block-simple/expected.js
@@ -8,7 +8,7 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
insertNode( if_block_anchor, target, anchor );
- if ( if_block ) if_block.mount( target, if_block_anchor );
+ if ( if_block ) if_block.mount( target, null );
},
update: function ( changed, state ) {
diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js
index e72585e606..792dac264e 100644
--- a/test/js/samples/use-elements-as-anchors/expected.js
+++ b/test/js/samples/use-elements-as-anchors/expected.js
@@ -2,13 +2,12 @@ import { appendNode, assign, createComment, createElement, createText, detachNod
function create_main_fragment ( state, component ) {
var div = createElement( 'div' );
- var if_block_anchor = createComment();
- appendNode( if_block_anchor, div );
var if_block = state.a && create_if_block( state, component );
if ( if_block ) if_block.mount( div, null );
- appendNode( createText( "\n\n\t" ), div );
+ var text = createText( "\n\n\t" );
+ appendNode( text, div );
var p = createElement( 'p' );
appendNode( p, div );
appendNode( createText( "this can be used as an anchor" ), p );
@@ -17,15 +16,14 @@ function create_main_fragment ( state, component ) {
var if_block_1 = state.b && create_if_block_1( state, component );
if ( if_block_1 ) if_block_1.mount( div, null );
- var if_block_1_anchor = createComment();
- appendNode( if_block_1_anchor, div );
-
- appendNode( createText( "\n\n\t" ), div );
+ var text_3 = createText( "\n\n\t" );
+ appendNode( text_3, div );
var if_block_2 = state.c && create_if_block_2( state, component );
if ( if_block_2 ) if_block_2.mount( div, null );
- appendNode( createText( "\n\n\t" ), div );
+ var text_4 = createText( "\n\n\t" );
+ appendNode( text_4, div );
var p_1 = createElement( 'p' );
appendNode( p_1, div );
appendNode( createText( "so can this" ), p_1 );
@@ -34,7 +32,8 @@ function create_main_fragment ( state, component ) {
var if_block_3 = state.d && create_if_block_3( state, component );
if ( if_block_3 ) if_block_3.mount( div, null );
- appendNode( createText( "\n\n\t" ), div );
+ var text_7 = createText( "\n\n\t" );
+ appendNode( text_7, div );
var text_8 = createText( "\n\n" );
var if_block_4_anchor = createComment();
@@ -45,14 +44,14 @@ function create_main_fragment ( state, component ) {
insertNode( div, target, anchor );
insertNode( text_8, target, anchor );
insertNode( if_block_4_anchor, target, anchor );
- if ( if_block_4 ) if_block_4.mount( target, if_block_4_anchor );
+ if ( if_block_4 ) if_block_4.mount( target, null );
},
update: function ( changed, state ) {
if ( state.a ) {
if ( !if_block ) {
if_block = create_if_block( state, component );
- if_block.mount( if_block_anchor.parentNode, p );
+ if_block.mount( div, text );
}
} else if ( if_block ) {
if_block.destroy( true );
@@ -62,7 +61,7 @@ function create_main_fragment ( state, component ) {
if ( state.b ) {
if ( !if_block_1 ) {
if_block_1 = create_if_block_1( state, component );
- if_block_1.mount( if_block_1_anchor.parentNode, if_block_1_anchor );
+ if_block_1.mount( div, text_3 );
}
} else if ( if_block_1 ) {
if_block_1.destroy( true );
@@ -72,7 +71,7 @@ function create_main_fragment ( state, component ) {
if ( state.c ) {
if ( !if_block_2 ) {
if_block_2 = create_if_block_2( state, component );
- if_block_2.mount( if_block_2_anchor.parentNode, p_2 );
+ if_block_2.mount( div, text_4 );
}
} else if ( if_block_2 ) {
if_block_2.destroy( true );
@@ -82,7 +81,7 @@ function create_main_fragment ( state, component ) {
if ( state.d ) {
if ( !if_block_3 ) {
if_block_3 = create_if_block_3( state, component );
- if_block_3.mount( if_block_3_anchor.parentNode, null );
+ if_block_3.mount( div, text_7 );
}
} else if ( if_block_3 ) {
if_block_3.destroy( true );
diff --git a/test/runtime/samples/if-block-widget/_config.js b/test/runtime/samples/if-block-widget/_config.js
index 40d9d050e9..b82b4dfeb9 100644
--- a/test/runtime/samples/if-block-widget/_config.js
+++ b/test/runtime/samples/if-block-widget/_config.js
@@ -2,11 +2,26 @@ export default {
data: {
visible: true
},
- html: 'before\nWidget
\nafter',
+
+ html: `
+ before
+ Widget
+ after
+ `,
+
test ( assert, component, target ) {
component.set({ visible: false });
- assert.equal( target.innerHTML, 'before\n\nafter' );
+ assert.htmlEqual( target.innerHTML, `
+ before
+
+ after
+ ` );
+
component.set({ visible: true });
- assert.equal( target.innerHTML, 'before\nWidget
\nafter' );
+ assert.htmlEqual( target.innerHTML, `
+ before
+ Widget
+ after
+ ` );
}
};
From 9480f349ec49c2411e74cedfb743068d50d860a8 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Wed, 19 Apr 2017 14:05:34 -0400
Subject: [PATCH 003/233] anchor-less each blocks
---
src/generators/dom/visitors/EachBlock.js | 35 ++++++++++++-------
.../each-block-changed-check/expected.js | 10 ++----
2 files changed, 26 insertions(+), 19 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 7919cf1f3d..bf7e63b4d0 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -9,13 +9,12 @@ export default function visitEachBlock ( generator, block, state, node ) {
const iterations = block.getUniqueName( `${each_block}_iterations` );
const i = block.alias( `i` );
const params = block.params.join( ', ' );
- const anchor = block.getUniqueName( `${each_block}_anchor` );
+ const anchor = node.needsAnchor ? block.getUniqueName( `${each_block}_anchor` ) : ( node.next && node.next._state.name ) || 'null';
const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor };
const { snippet } = block.contextualise( node.expression );
- block.createAnchor( anchor, state.parentNode );
block.builders.create.addLine( `var ${each_block_value} = ${snippet};` );
block.builders.create.addLine( `var ${iterations} = [];` );
@@ -30,11 +29,17 @@ export default function visitEachBlock ( generator, block, state, node ) {
if ( isToplevel ) {
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${iterations}[${i}].mount( ${block.target}, ${anchor} );
+ ${iterations}[${i}].mount( ${block.target}, null );
}
` );
}
+ if ( node.needsAnchor ) {
+ block.createAnchor( anchor, state.parentNode );
+ } else if ( node.next ) {
+ node.next.usedAsAnchor = true;
+ }
+
block.builders.destroy.addBlock(
`${generator.helper( 'destroyEach' )}( ${iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` );
@@ -47,23 +52,25 @@ export default function visitEachBlock ( generator, block, state, node ) {
block.builders.create.addBlock( deindent`
if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, ${anchor} );` : ''}
+ ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, null );` : ''}
}
` );
block.builders.mount.addBlock( deindent`
if ( ${each_block_else} ) {
- ${each_block_else}.mount( ${state.parentNode || block.target}, ${anchor} );
+ ${each_block_else}.mount( ${state.parentNode || block.target}, null );
}
` );
+ const parentNode = state.parentNode || `${anchor}.parentNode`;
+
if ( node.else._block.hasUpdateMethod ) {
block.builders.update.addBlock( deindent`
if ( !${each_block_value}.length && ${each_block_else} ) {
${each_block_else}.update( changed, ${params} );
} else if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${each_block_else}.mount( ${anchor}.parentNode, ${anchor} );
+ ${each_block_else}.mount( ${parentNode}, ${anchor} );
} else if ( ${each_block_else} ) {
${each_block_else}.destroy( true );
}
@@ -74,7 +81,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
if ( ${each_block_else} ) ${each_block_else}.destroy( true );
} else if ( !${each_block_else} ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${each_block_else}.mount( ${anchor}.parentNode, ${anchor} );
+ ${each_block_else}.mount( ${parentNode}, ${anchor} );
}
` );
}
@@ -118,7 +125,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( state.parentNode ) {
create.addLine(
- `${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );`
+ `${iterations}[${i}].mount( ${state.parentNode}, null );`
);
}
@@ -135,6 +142,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
` :
`${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];`;
+ const parentNode = state.parentNode || `${anchor}.parentNode`;
+
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
var ${_iterations} = [];
@@ -164,7 +173,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
}
- ${anchor}.parentNode.insertBefore( ${fragment}, ${anchor} );
+ ${parentNode}.insertBefore( ${fragment}, ${anchor} );
${iterations} = ${_iterations};
${lookup} = ${_lookup};
@@ -180,7 +189,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
if ( state.parentNode ) {
create.addLine(
- `${iterations}[${i}].mount( ${state.parentNode}, ${anchor} );`
+ `${iterations}[${i}].mount( ${state.parentNode}, null );`
);
}
@@ -200,6 +209,8 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
.map( dependency => `'${dependency}' in changed` )
.join( ' || ' );
+ const parentNode = state.parentNode || `${anchor}.parentNode`;
+
if ( condition !== '' ) {
const forLoopBody = node._block.hasUpdateMethod ?
deindent`
@@ -207,12 +218,12 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
- ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
+ ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
}
` :
deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
- ${iterations}[${i}].mount( ${anchor}.parentNode, ${anchor} );
+ ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
`;
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
index 7b0f01698f..40d2487c11 100644
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -1,7 +1,6 @@
-import { appendNode, assign, createComment, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
+import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, proto } from "svelte/shared.js";
function create_main_fragment ( state, component ) {
- var each_block_anchor = createComment();
var each_block_value = state.comments;
var each_block_iterations = [];
@@ -17,10 +16,8 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
- insertNode( each_block_anchor, target, anchor );
-
for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
- each_block_iterations[i].mount( target, each_block_anchor );
+ each_block_iterations[i].mount( target, null );
}
insertNode( text, target, anchor );
@@ -36,7 +33,7 @@ function create_main_fragment ( state, component ) {
each_block_iterations[i].update( changed, state, each_block_value, each_block_value[i], i );
} else {
each_block_iterations[i] = create_each_block( state, each_block_value, each_block_value[i], i, component );
- each_block_iterations[i].mount( each_block_anchor.parentNode, each_block_anchor );
+ each_block_iterations[i].mount( text.parentNode, text );
}
}
@@ -54,7 +51,6 @@ function create_main_fragment ( state, component ) {
destroyEach( each_block_iterations, detach, 0 );
if ( detach ) {
- detachNode( each_block_anchor );
detachNode( text );
detachNode( p );
}
From 6587cbdbac8a63fc123d5b4768e1e77ccc135da0 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Wed, 19 Apr 2017 14:07:55 -0400
Subject: [PATCH 004/233] yield blocks never need an anchor
---
src/generators/dom/visitors/YieldTag.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/generators/dom/visitors/YieldTag.js b/src/generators/dom/visitors/YieldTag.js
index 2ee3105669..aa95cd284e 100644
--- a/src/generators/dom/visitors/YieldTag.js
+++ b/src/generators/dom/visitors/YieldTag.js
@@ -1,9 +1,6 @@
export default function visitYieldTag ( generator, block, state ) {
- const anchor = `yield_anchor`;
- block.createAnchor( anchor, state.parentNode );
-
block.builders.mount.addLine(
- `${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || block.target}, ${anchor} );`
+ `${block.component}._yield && ${block.component}._yield.mount( ${state.parentNode || block.target}, null );`
);
block.builders.destroy.addLine(
From 4cb1578147c83d8a5266dd1ba9609418733b466f Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Wed, 19 Apr 2017 14:10:00 -0400
Subject: [PATCH 005/233] reduce indirection
---
src/generators/dom/Block.js | 5 -----
src/generators/dom/visitors/EachBlock.js | 2 +-
src/generators/dom/visitors/IfBlock.js | 2 +-
3 files changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js
index e14d8591f4..aa023dfaaf 100644
--- a/src/generators/dom/Block.js
+++ b/src/generators/dom/Block.js
@@ -78,11 +78,6 @@ export default class Block {
return this.generator.contextualise( this, expression, context, isEventHandler );
}
- createAnchor ( name, parentNode ) {
- const renderStatement = `${this.generator.helper( 'createComment' )}()`;
- this.addElement( name, renderStatement, parentNode, true );
- }
-
findDependencies ( expression ) {
return this.generator.findDependencies( this.contextDependencies, this.indexes, expression );
}
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index bf7e63b4d0..049769132f 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -35,7 +35,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
}
if ( node.needsAnchor ) {
- block.createAnchor( anchor, state.parentNode );
+ block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true );
} else if ( node.next ) {
node.next.usedAsAnchor = true;
}
diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js
index d9fd1b81c7..e6c3d9952a 100644
--- a/src/generators/dom/visitors/IfBlock.js
+++ b/src/generators/dom/visitors/IfBlock.js
@@ -47,7 +47,7 @@ export default function visitIfBlock ( generator, block, state, node ) {
const vars = { name, anchor, params };
if ( node.needsAnchor ) {
- block.createAnchor( anchor, state.parentNode );
+ block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true );
} else if ( node.next ) {
node.next.usedAsAnchor = true;
}
From 33fb0df51abf9a3e20da447d845fb01c7b4857b2 Mon Sep 17 00:00:00 2001
From: Tobias Davis
Date: Fri, 21 Apr 2017 12:15:47 -0500
Subject: [PATCH 006/233] failing test for bound event handler on
diff --git a/test/runtime/samples/binding-select-initial-value/main.html b/test/runtime/samples/binding-select-initial-value/main.html
index d7d02194c8..ea15402240 100644
--- a/test/runtime/samples/binding-select-initial-value/main.html
+++ b/test/runtime/samples/binding-select-initial-value/main.html
@@ -1,9 +1,9 @@
selected: {{selected}}
-
-
-
+
+
+
selected: {{selected}}
\ No newline at end of file
diff --git a/test/runtime/samples/binding-select-multiple/_config.js b/test/runtime/samples/binding-select-multiple/_config.js
index 89b0da9658..ec4f3dfe4e 100644
--- a/test/runtime/samples/binding-select-multiple/_config.js
+++ b/test/runtime/samples/binding-select-multiple/_config.js
@@ -1,12 +1,12 @@
export default {
- skip: true, // selectedOptions doesn't work in JSDOM???
+ skip: true, // JSDOM
data: {
selected: [ 'two', 'three' ]
},
html: `
-
+
@@ -26,7 +26,7 @@ export default {
assert.deepEqual( component.get( 'selected' ), [ 'three' ] );
assert.htmlEqual( target.innerHTML, `
-
+
@@ -40,7 +40,7 @@ export default {
assert.deepEqual( component.get( 'selected' ), [ 'one', 'three' ] );
assert.htmlEqual( target.innerHTML, `
-
+
@@ -56,7 +56,7 @@ export default {
assert.ok( !options[2].selected );
assert.htmlEqual( target.innerHTML, `
-
+
diff --git a/test/runtime/samples/binding-select/_config.js b/test/runtime/samples/binding-select/_config.js
index 85cf6e9e1a..7fce00f327 100644
--- a/test/runtime/samples/binding-select/_config.js
+++ b/test/runtime/samples/binding-select/_config.js
@@ -1,6 +1,4 @@
export default {
- skip: true, // selectedOptions doesn't work in JSDOM???
-
html: `
selected: one
@@ -13,6 +11,10 @@ export default {
selected: one
`,
+ data: {
+ selected: 'one'
+ },
+
test ( assert, component, target, window ) {
const select = target.querySelector( 'select' );
const options = [ ...target.querySelectorAll( 'option' ) ];
diff --git a/test/runtime/samples/select-change-handler/_config.js b/test/runtime/samples/select-change-handler/_config.js
index 560061a09c..015c8182b4 100644
--- a/test/runtime/samples/select-change-handler/_config.js
+++ b/test/runtime/samples/select-change-handler/_config.js
@@ -1,6 +1,4 @@
export default {
- skip: true, // JSDOM
-
data: {
options: [ { id: 'a' }, { id: 'b' }, { id: 'c' } ],
selected: 'b'
diff --git a/test/runtime/samples/select-one-way-bind-object/_config.js b/test/runtime/samples/select-one-way-bind-object/_config.js
index 946eaabceb..0d7ddaccad 100644
--- a/test/runtime/samples/select-one-way-bind-object/_config.js
+++ b/test/runtime/samples/select-one-way-bind-object/_config.js
@@ -1,8 +1,6 @@
const items = [ {}, {} ];
export default {
- skip: true, // JSDOM quirks
-
'skip-ssr': true,
data: {
From 65064cb70c441956aa0065a08a0be94c8524db3b Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 14:34:18 -0400
Subject: [PATCH 040/233] =?UTF-8?q?improve=20deindent=20slightly=20?=
=?UTF-8?q?=E2=80=94=20allow=20inline=20false=20expressions=20(which=20get?=
=?UTF-8?q?=20removed),=20and=20trim=20trailing=20tabs?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/utils/deindent.js | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/utils/deindent.js b/src/utils/deindent.js
index 63730254b3..f618dd5277 100644
--- a/src/utils/deindent.js
+++ b/src/utils/deindent.js
@@ -9,13 +9,24 @@ export default function deindent ( strings, ...values ) {
let trailingIndentation = getTrailingIndentation( result );
for ( let i = 1; i < strings.length; i += 1 ) {
- const value = String( values[ i - 1 ] ).replace( /\n/g, `\n${trailingIndentation}` );
- result += value + strings[i].replace( pattern, '' );
+ const expression = values[ i - 1 ];
+ const string = strings[i].replace( pattern, '' );
+
+ if ( expression || expression === '' ) {
+ const value = String( expression ).replace( /\n/g, `\n${trailingIndentation}` );
+ result += value + string;
+ }
+
+ else {
+ let c = result.length;
+ while ( /\s/.test( result[ c - 1 ] ) ) c -= 1;
+ result = result.slice( 0, c ) + string;
+ }
trailingIndentation = getTrailingIndentation( result );
}
- return result.trim();
+ return result.trim().replace( /\t+$/gm, '' );
}
function getTrailingIndentation ( str ) {
From a2cd983e99136a78f71143063b23ce7d0fe8c9a3 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 14:41:11 -0400
Subject: [PATCH 041/233] intro transitions in each-blocks
---
src/generators/dom/Block.js | 38 +++++++++----------
src/generators/dom/preprocess.js | 8 ++--
src/generators/dom/visitors/EachBlock.js | 27 ++++++-------
.../transition-js-each-block-intro/_config.js | 32 ++++++++++++++++
.../transition-js-each-block-intro/main.html | 18 +++++++++
.../samples/comment/_actual.html | 2 +-
6 files changed, 88 insertions(+), 37 deletions(-)
create mode 100644 test/runtime/samples/transition-js-each-block-intro/_config.js
create mode 100644 test/runtime/samples/transition-js-each-block-intro/main.html
diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js
index 922306d8f3..f905754815 100644
--- a/src/generators/dom/Block.js
+++ b/src/generators/dom/Block.js
@@ -33,8 +33,6 @@ export default class Block {
this.hasIntroMethod = false; // a block could have an intro method but not intro transitions, e.g. if a sibling block has intros
this.hasOutroMethod = false;
- this.hasIntroTransitions = false;
- this.hasOutroTransitions = false;
this.outros = 0;
this.aliases = new Map();
@@ -109,13 +107,15 @@ export default class Block {
render () {
let introing;
- if ( this.hasIntroTransitions ) {
+ const hasIntros = !this.builders.intro.isEmpty();
+ if ( hasIntros ) {
introing = this.getUniqueName( 'introing' );
this.addVariable( introing );
}
let outroing;
- if ( this.hasOutroTransitions ) {
+ const hasOutros = !this.builders.outro.isEmpty();
+ if ( hasOutros ) {
outroing = this.getUniqueName( 'outroing' );
this.addVariable( outroing );
}
@@ -178,21 +178,21 @@ export default class Block {
}
if ( this.hasIntroMethod ) {
- if ( this.builders.intro.isEmpty() ) {
+ if ( hasIntros ) {
properties.addBlock( deindent`
intro: function ( ${this.target}, anchor ) {
+ if ( ${introing} ) return;
+ ${introing} = true;
+ ${hasOutros && `${outroing} = false;`}
+
+ ${this.builders.intro}
+
this.mount( ${this.target}, anchor );
},
` );
} else {
properties.addBlock( deindent`
intro: function ( ${this.target}, anchor ) {
- if ( ${introing} ) return;
- ${introing} = true;
- ${this.hasOutroTransitions && `${outroing} = false;`}
-
- ${this.builders.intro}
-
this.mount( ${this.target}, anchor );
},
` );
@@ -200,24 +200,24 @@ export default class Block {
}
if ( this.hasOutroMethod ) {
- if ( this.builders.outro.isEmpty() ) {
- properties.addBlock( deindent`
- outro: function ( outrocallback ) {
- outrocallback();
- },
- ` );
- } else {
+ if ( hasOutros ) {
properties.addBlock( deindent`
outro: function ( ${this.alias( 'outrocallback' )} ) {
if ( ${outroing} ) return;
${outroing} = true;
- ${this.hasIntroTransitions && `${introing} = false;`}
+ ${hasIntros && `${introing} = false;`}
var ${this.alias( 'outros' )} = ${this.outros};
${this.builders.outro}
},
` );
+ } else {
+ properties.addBlock( deindent`
+ outro: function ( outrocallback ) {
+ outrocallback();
+ },
+ ` );
}
}
diff --git a/src/generators/dom/preprocess.js b/src/generators/dom/preprocess.js
index 6b43f93460..522a682074 100644
--- a/src/generators/dom/preprocess.js
+++ b/src/generators/dom/preprocess.js
@@ -80,8 +80,8 @@ const preprocessors = {
block.addDependencies( node._block.dependencies );
}
- if ( node._block.hasIntroTransitions ) hasIntros = true;
- if ( node._block.hasOutroTransitions ) hasOutros = true;
+ if ( node._block.hasIntroMethod ) hasIntros = true;
+ if ( node._block.hasOutroMethod ) hasOutros = true;
if ( isElseIf( node.else ) ) {
attachBlocks( node.else.children[0] );
@@ -209,9 +209,9 @@ const preprocessors = {
}
else if ( attribute.type === 'Transition' ) {
- if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroTransitions = true;
+ if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroMethod = true;
if ( attribute.outro ) {
- generator.hasOutroTransitions = block.hasOutroTransitions = true;
+ generator.hasOutroTransitions = block.hasOutroMethod = true;
block.outros += 1;
}
}
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index e4df8c575d..641ad9b8b5 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -11,7 +11,8 @@ export default function visitEachBlock ( generator, block, state, node ) {
const params = block.params.join( ', ' );
const anchor = node.needsAnchor ? block.getUniqueName( `${each_block}_anchor` ) : ( node.next && node.next._state.name ) || 'null';
- const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor };
+ const mountOrIntro = node._block.hasIntroMethod ? 'intro' : 'mount';
+ const vars = { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro };
const { snippet } = block.contextualise( node.expression );
@@ -29,7 +30,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
if ( isToplevel ) {
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${iterations}[${i}].mount( ${block.target}, null );
+ ${iterations}[${i}].${mountOrIntro}( ${block.target}, null );
}
` );
}
@@ -52,13 +53,13 @@ export default function visitEachBlock ( generator, block, state, node ) {
block.builders.create.addBlock( deindent`
if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${!isToplevel ? `${each_block_else}.mount( ${state.parentNode}, null );` : ''}
+ ${!isToplevel ? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );` : ''}
}
` );
block.builders.mount.addBlock( deindent`
if ( ${each_block_else} ) {
- ${each_block_else}.mount( ${state.parentNode || block.target}, null );
+ ${each_block_else}.${mountOrIntro}( ${state.parentNode || block.target}, null );
}
` );
@@ -70,7 +71,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
${each_block_else}.update( changed, ${params} );
} else if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${each_block_else}.mount( ${parentNode}, ${anchor} );
+ ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
} else if ( ${each_block_else} ) {
${each_block_else}.destroy( true );
${each_block_else} = null;
@@ -85,7 +86,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
}
} else if ( !${each_block_else} ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
- ${each_block_else}.mount( ${parentNode}, ${anchor} );
+ ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
}
` );
}
@@ -109,7 +110,7 @@ export default function visitEachBlock ( generator, block, state, node ) {
}
}
-function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor } ) {
+function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
const fragment = block.getUniqueName( 'fragment' );
const value = block.getUniqueName( 'value' );
const key = block.getUniqueName( 'key' );
@@ -129,7 +130,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( state.parentNode ) {
create.addLine(
- `${iterations}[${i}].mount( ${state.parentNode}, null );`
+ `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`
);
}
@@ -166,7 +167,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
}
- ${_iterations}[${i}].mount( ${fragment}, null );
+ ${_iterations}[${i}].${mountOrIntro}( ${fragment}, null );
}
// remove old iterations
@@ -184,7 +185,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
` );
}
-function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor } ) {
+function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
const create = new CodeBuilder();
create.addLine(
@@ -193,7 +194,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
if ( state.parentNode ) {
create.addLine(
- `${iterations}[${i}].mount( ${state.parentNode}, null );`
+ `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`
);
}
@@ -222,12 +223,12 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
- ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
+ ${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
}
` :
deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
- ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
+ ${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
`;
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
diff --git a/test/runtime/samples/transition-js-each-block-intro/_config.js b/test/runtime/samples/transition-js-each-block-intro/_config.js
new file mode 100644
index 0000000000..7deb8c97a9
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-intro/_config.js
@@ -0,0 +1,32 @@
+export default {
+ data: {
+ things: [ 'a', 'b', 'c' ]
+ },
+
+ test ( assert, component, target, window, raf ) {
+ let divs = target.querySelectorAll( 'div' );
+ assert.equal( divs[0].foo, 0 );
+ assert.equal( divs[1].foo, 0 );
+ assert.equal( divs[2].foo, 0 );
+
+ raf.tick( 50 );
+ assert.equal( divs[0].foo, 0.5 );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, 0.5 );
+
+ component.set({ things: [ 'a', 'b', 'c', 'd' ] });
+ divs = target.querySelectorAll( 'div' );
+ assert.equal( divs[0].foo, 0.5 );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, 0.5 );
+ assert.equal( divs[3].foo, 0 );
+
+ raf.tick( 75 );
+ assert.equal( divs[0].foo, 0.75 );
+ assert.equal( divs[1].foo, 0.75 );
+ assert.equal( divs[2].foo, 0.75 );
+ assert.equal( divs[3].foo, 0.25 );
+
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-each-block-intro/main.html b/test/runtime/samples/transition-js-each-block-intro/main.html
new file mode 100644
index 0000000000..7f12ef1d8f
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-intro/main.html
@@ -0,0 +1,18 @@
+{{#each things as thing}}
+ {{thing}}
+{{/each}}
+
+
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/comment/_actual.html b/test/server-side-rendering/samples/comment/_actual.html
index e2adc073dc..e5c61a4b0e 100644
--- a/test/server-side-rendering/samples/comment/_actual.html
+++ b/test/server-side-rendering/samples/comment/_actual.html
@@ -1,3 +1,3 @@
before
-
+
after
\ No newline at end of file
From 2d533f99deef7dbd3d8134688b19b3f6bc89cf13 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 14:50:12 -0400
Subject: [PATCH 042/233] remove redundant ternary
---
src/generators/dom/visitors/EachBlock.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 641ad9b8b5..1657a6b9ac 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -125,7 +125,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
create.addBlock( deindent`
var ${key} = ${each_block_value}[${i}].${node.key};
- ${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
+ ${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
` );
if ( state.parentNode ) {
@@ -164,7 +164,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( ${lookup}[ ${key} ] ) {
${consequent}
} else {
- ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}${node.key ? `, ${key}` : `` } );
+ ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
}
${_iterations}[${i}].${mountOrIntro}( ${fragment}, null );
From 42af2bb32b59887d9a5cdc7c29c4ee52fbd7a58b Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 15:01:19 -0400
Subject: [PATCH 043/233] fix mount order of keyed each-block with intros
---
src/generators/dom/visitors/EachBlock.js | 6 ++-
.../_config.js | 49 +++++++++++++++++++
.../main.html | 18 +++++++
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro/_config.js
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 1657a6b9ac..3d637bc5d9 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -149,6 +149,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const parentNode = state.parentNode || `${anchor}.parentNode`;
+ const hasIntros = node._block.hasIntroMethod;
+
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
var ${_iterations} = [];
@@ -163,11 +165,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( ${lookup}[ ${key} ] ) {
${consequent}
+ ${hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`}
} else {
${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${hasIntros && `${_iterations}[${i}].intro( ${fragment}, null );`}
}
- ${_iterations}[${i}].${mountOrIntro}( ${fragment}, null );
+ ${!hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`}
}
// remove old iterations
diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js
new file mode 100644
index 0000000000..dc61ac5ea4
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-intro/_config.js
@@ -0,0 +1,49 @@
+export default {
+ data: {
+ things: [
+ { name: 'a' },
+ { name: 'b' },
+ { name: 'c' }
+ ]
+ },
+
+ test ( assert, component, target, window, raf ) {
+ let divs = target.querySelectorAll( 'div' );
+ assert.equal( divs[0].foo, 0 );
+ assert.equal( divs[1].foo, 0 );
+ assert.equal( divs[2].foo, 0 );
+
+ raf.tick( 50 );
+ assert.equal( divs[0].foo, 0.5 );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, 0.5 );
+
+ component.set({
+ things: [
+ { name: 'a' },
+ { name: 'woo!' },
+ { name: 'b' },
+ { name: 'c' }
+ ]
+ });
+ assert.htmlEqual( target.innerHTML, `
+ a
+ woo!
+ b
+ c
+ ` );
+ divs = target.querySelectorAll( 'div' );
+ assert.equal( divs[0].foo, 0.5 );
+ assert.equal( divs[1].foo, 0 );
+ assert.equal( divs[2].foo, 0.5 );
+ assert.equal( divs[3].foo, 0.5 );
+
+ raf.tick( 75 );
+ assert.equal( divs[0].foo, 0.75 );
+ assert.equal( divs[1].foo, 0.25 );
+ assert.equal( divs[2].foo, 0.75 );
+ assert.equal( divs[3].foo, 0.75 );
+
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro/main.html b/test/runtime/samples/transition-js-each-block-keyed-intro/main.html
new file mode 100644
index 0000000000..d9442d0d6f
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-intro/main.html
@@ -0,0 +1,18 @@
+{{#each things as thing @name}}
+ {{thing.name}}
+{{/each}}
+
+
\ No newline at end of file
From f06eced938f2d2cc76fe0c688fcf0cfa6e4a0801 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 15:33:00 -0400
Subject: [PATCH 044/233] unkeyed each-blocks with outros
---
src/generators/dom/visitors/EachBlock.js | 22 ++++++++++++++++---
.../transition-js-each-block-outro/_config.js | 16 ++++++++++++++
.../transition-js-each-block-outro/main.html | 18 +++++++++++++++
3 files changed, 53 insertions(+), 3 deletions(-)
create mode 100644 test/runtime/samples/transition-js-each-block-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-each-block-outro/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 3d637bc5d9..da3353f577 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -237,6 +237,24 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
+ const destroy = node._block.hasOutroMethod ?
+ deindent`
+ function outro ( i ) {
+ if ( ${iterations}[i] ) {
+ ${iterations}[i].outro( function () {
+ ${iterations}[i].destroy( true );
+ ${iterations}[i] = null;
+ });
+ }
+ }
+
+ for ( ; ${i} < ${iterations}.length; ${i} += 1 ) outro( ${i} );
+ ` :
+ deindent`
+ ${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length );
+ ${iterations}.length = ${each_block_value}.length;
+ `;
+
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
@@ -245,9 +263,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
${forLoopBody}
}
- ${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length );
-
- ${iterations}.length = ${each_block_value}.length;
+ ${destroy}
}
` );
}
diff --git a/test/runtime/samples/transition-js-each-block-outro/_config.js b/test/runtime/samples/transition-js-each-block-outro/_config.js
new file mode 100644
index 0000000000..5e8b99d88d
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-outro/_config.js
@@ -0,0 +1,16 @@
+export default {
+ data: {
+ things: [ 'a', 'b', 'c' ]
+ },
+
+ test ( assert, component, target, window, raf ) {
+ const divs = target.querySelectorAll( 'div' );
+
+ component.set({ things: [ 'a' ] });
+
+ raf.tick( 50 );
+ assert.equal( divs[0].foo, undefined );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, 0.5 );
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-each-block-outro/main.html b/test/runtime/samples/transition-js-each-block-outro/main.html
new file mode 100644
index 0000000000..494132c9be
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-outro/main.html
@@ -0,0 +1,18 @@
+{{#each things as thing}}
+ {{thing}}
+{{/each}}
+
+
\ No newline at end of file
From 22ac50abb647d709219c96b9466b754572b3426f Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 15:52:41 -0400
Subject: [PATCH 045/233] outros on keyed each-blocks
---
src/generators/dom/visitors/EachBlock.js | 42 +++++++++++++------
.../each-block-changed-check/expected.js | 1 -
.../_config.js | 25 +++++++++++
.../main.html | 18 ++++++++
4 files changed, 72 insertions(+), 14 deletions(-)
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-outro/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index da3353f577..5a427c94c6 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -115,7 +115,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const value = block.getUniqueName( 'value' );
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
- const _lookup = block.getUniqueName( `_${each_block}_lookup` );
+ const keys = block.getUniqueName( `${each_block}_keys` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
const _iterations = block.getUniqueName( `_${each_block}_iterations` );
@@ -142,19 +142,40 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const consequent = node._block.hasUpdateMethod ?
deindent`
- ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];
- ${_lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
+ ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ];
+ ${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
` :
- `${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${lookup}[ ${key} ];`;
+ `${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ];`;
const parentNode = state.parentNode || `${anchor}.parentNode`;
const hasIntros = node._block.hasIntroMethod;
+ const destroy = node._block.hasOutroMethod ?
+ deindent`
+ function outro ( key ) {
+ ${lookup}[ key ].outro( function () {
+ ${lookup}[ key ].destroy( true );
+ ${lookup}[ key ] = null;
+ });
+ }
+
+ for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
+ ${key} = ${iterations}[${i}].key;
+ if ( !${keys}[ ${key} ] ) outro( ${key} );
+ }
+ ` :
+ deindent`
+ for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
+ var ${iteration} = ${iterations}[${i}];
+ if ( !${keys}[ ${iteration}.key ] ) ${iteration}.destroy( true );
+ }
+ `;
+
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
var ${_iterations} = [];
- var ${_lookup} = Object.create( null );
+ var ${keys} = Object.create( null );
var ${fragment} = document.createDocumentFragment();
@@ -162,12 +183,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${value} = ${each_block_value}[${i}];
var ${key} = ${value}.${node.key};
+ ${keys}[ ${key} ] = true;
if ( ${lookup}[ ${key} ] ) {
${consequent}
${hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`}
} else {
- ${_iterations}[${i}] = ${_lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${hasIntros && `${_iterations}[${i}].intro( ${fragment}, null );`}
}
@@ -175,17 +197,11 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
// remove old iterations
- for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- var ${iteration} = ${iterations}[${i}];
- if ( !${_lookup}[ ${iteration}.key ] ) {
- ${iteration}.destroy( true );
- }
- }
+ ${destroy}
${parentNode}.insertBefore( ${fragment}, ${anchor} );
${iterations} = ${_iterations};
- ${lookup} = ${_lookup};
` );
}
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
index 17b22c9a50..c0a8092727 100644
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -39,7 +39,6 @@ function create_main_fragment ( state, component ) {
}
destroyEach( each_block_iterations, true, each_block_value.length );
-
each_block_iterations.length = each_block_value.length;
}
diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
new file mode 100644
index 0000000000..d40e9a3a06
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
@@ -0,0 +1,25 @@
+export default {
+ data: {
+ things: [
+ { name: 'a' },
+ { name: 'b' },
+ { name: 'c' }
+ ]
+ },
+
+ test ( assert, component, target, window, raf ) {
+ const divs = target.querySelectorAll( 'div' );
+
+ component.set({
+ things: [
+ { name: 'a' },
+ { name: 'c' }
+ ]
+ });
+
+ raf.tick( 50 );
+ assert.equal( divs[0].foo, undefined );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, undefined );
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/main.html b/test/runtime/samples/transition-js-each-block-keyed-outro/main.html
new file mode 100644
index 0000000000..2e73512529
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-outro/main.html
@@ -0,0 +1,18 @@
+{{#each things as thing @name}}
+ {{thing.name}}
+{{/each}}
+
+
\ No newline at end of file
From b8affd42e90d150cdf42924d122324f82dba65f5 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Mon, 1 May 2017 16:29:23 -0400
Subject: [PATCH 046/233] simplify/unify transitions
---
src/shared/.eslintrc.json | 38 +++++++++++++
src/shared/transitions.js | 109 +++++++++++++++-----------------------
2 files changed, 82 insertions(+), 65 deletions(-)
create mode 100644 src/shared/.eslintrc.json
diff --git a/src/shared/.eslintrc.json b/src/shared/.eslintrc.json
new file mode 100644
index 0000000000..46f1957077
--- /dev/null
+++ b/src/shared/.eslintrc.json
@@ -0,0 +1,38 @@
+{
+ "root": true,
+ "rules": {
+ "indent": [ 2, "tab", { "SwitchCase": 1 } ],
+ "semi": [ 2, "always" ],
+ "keyword-spacing": [ 2, { "before": true, "after": true } ],
+ "space-before-blocks": [ 2, "always" ],
+ "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ],
+ "no-cond-assign": 0,
+ "no-unused-vars": 2,
+ "no-const-assign": 2,
+ "no-class-assign": 2,
+ "no-this-before-super": 2,
+ "no-unreachable": 2,
+ "valid-typeof": 2,
+ "quote-props": [ 2, "as-needed" ],
+ "arrow-spacing": 2,
+ "no-inner-declarations": 0
+ },
+ "env": {
+ "es6": true,
+ "browser": true,
+ "node": true,
+ "mocha": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:import/errors",
+ "plugin:import/warnings"
+ ],
+ "parserOptions": {
+ "ecmaVersion": 6,
+ "sourceType": "module"
+ },
+ "settings": {
+ "import/core-modules": [ "svelte" ]
+ }
+}
diff --git a/src/shared/transitions.js b/src/shared/transitions.js
index a74e09711d..349c1b375c 100644
--- a/src/shared/transitions.js
+++ b/src/shared/transitions.js
@@ -4,13 +4,42 @@ export function linear ( t ) {
return t;
}
+function generateKeyframes ( a, b, delta, duration, ease, fn, node, style ) {
+ var id = '__svelte' + ~~( Math.random() * 1e9 ); // TODO make this more robust
+ var keyframes = '@keyframes ' + id + '{\n';
+
+ for ( var p = 0; p <= 1; p += 16.666 / duration ) {
+ var t = a + delta * ease( p );
+ keyframes += ( p * 100 ) + '%{' + fn( t ) + '}\n';
+ }
+
+ keyframes += '100% {' + fn( b ) + '}\n}';
+ style.textContent += keyframes;
+
+ document.head.appendChild( style );
+
+ node.style.animation = node.style.animation.split( ',' )
+ .filter( function ( anim ) {
+ // when introing, discard old animations if there are any
+ return anim && ( delta < 0 || !/__svelte/.test( anim ) );
+ })
+ .concat( id + ' ' + duration + 'ms linear 1 forwards' )
+ .join( ', ' );
+}
+
export function wrapTransition ( node, fn, params, intro, outgroup ) {
var obj = fn( node, params, intro );
-
var duration = obj.duration || 300;
var ease = obj.easing || linear;
- var transition = {
+ // TODO share
\ No newline at end of file
diff --git a/test/css/samples/media-query/expected.css b/test/css/samples/media-query/expected.css
new file mode 100644
index 0000000000..2dd35dbb7c
--- /dev/null
+++ b/test/css/samples/media-query/expected.css
@@ -0,0 +1,6 @@
+
+ @media (min-width: 400px) {
+ [svelte-2352010302].large-screen, [svelte-2352010302] .large-screen {
+ display: block;
+ }
+ }
diff --git a/test/css/samples/media-query/input.html b/test/css/samples/media-query/input.html
new file mode 100644
index 0000000000..d5465ab4f9
--- /dev/null
+++ b/test/css/samples/media-query/input.html
@@ -0,0 +1,9 @@
+animated
+
+
\ No newline at end of file
From 37749bd3e452ff83fdee082e6a19677ab19d6ba1 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 12:25:20 -0400
Subject: [PATCH 076/233] validate <:Window>
---
package.json | 3 +-
.../dom/visitors/Element/meta/Window.js | 5 --
src/validate/html/validateElement.js | 38 +-------------
src/validate/html/validateEventHandler.js | 35 +++++++++++++
src/validate/html/validateWindow.js | 49 ++++++++++++++++++-
src/validate/js/index.js | 10 ++--
src/validate/js/propValidators/namespace.js | 9 ++--
src/validate/{js => }/utils/FuzzySet.js | 0
src/validate/utils/fuzzymatch.js | 10 ++++
src/validate/utils/list.js | 4 ++
.../errors.json | 8 +++
.../input.html | 1 +
.../window-binding-invalid-value/errors.json | 8 +++
.../window-binding-invalid-value/input.html | 1 +
.../window-binding-invalid-width/errors.json | 8 +++
.../window-binding-invalid-width/input.html | 1 +
.../window-binding-invalid/errors.json | 8 +++
.../samples/window-binding-invalid/input.html | 1 +
.../samples/window-event-invalid/errors.json | 8 +++
.../samples/window-event-invalid/input.html | 1 +
20 files changed, 153 insertions(+), 55 deletions(-)
create mode 100644 src/validate/html/validateEventHandler.js
rename src/validate/{js => }/utils/FuzzySet.js (100%)
create mode 100644 src/validate/utils/fuzzymatch.js
create mode 100644 src/validate/utils/list.js
create mode 100644 test/validator/samples/window-binding-invalid-innerwidth/errors.json
create mode 100644 test/validator/samples/window-binding-invalid-innerwidth/input.html
create mode 100644 test/validator/samples/window-binding-invalid-value/errors.json
create mode 100644 test/validator/samples/window-binding-invalid-value/input.html
create mode 100644 test/validator/samples/window-binding-invalid-width/errors.json
create mode 100644 test/validator/samples/window-binding-invalid-width/input.html
create mode 100644 test/validator/samples/window-binding-invalid/errors.json
create mode 100644 test/validator/samples/window-binding-invalid/input.html
create mode 100644 test/validator/samples/window-event-invalid/errors.json
create mode 100644 test/validator/samples/window-event-invalid/input.html
diff --git a/package.json b/package.json
index 106036084c..ea6060fcdf 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,8 @@
},
"nyc": {
"include": [
- "src/**/*.js"
+ "src/**/*.js",
+ "shared.js"
],
"exclude": [
"src/**/__test__.js",
diff --git a/src/generators/dom/visitors/Element/meta/Window.js b/src/generators/dom/visitors/Element/meta/Window.js
index 4af5984811..2478d17139 100644
--- a/src/generators/dom/visitors/Element/meta/Window.js
+++ b/src/generators/dom/visitors/Element/meta/Window.js
@@ -60,11 +60,6 @@ export default function visitWindow ( generator, block, node ) {
}
if ( attribute.type === 'Binding' ) {
- if ( attribute.value.type !== 'Identifier' ) {
- const { parts, keypath } = flattenReference( attribute.value );
- throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
- }
-
// in dev mode, throw if read-only values are written to
if ( readonly.has( attribute.name ) ) {
generator.readonly.add( attribute.value.name );
diff --git a/src/validate/html/validateElement.js b/src/validate/html/validateElement.js
index cda56ec730..42e4c75f30 100644
--- a/src/validate/html/validateElement.js
+++ b/src/validate/html/validateElement.js
@@ -1,10 +1,4 @@
-import flattenReference from '../../utils/flattenReference.js';
-
-const validBuiltins = new Set([
- 'set',
- 'fire',
- 'destroy'
-]);
+import validateEventHandler from './validateEventHandler.js';
export default function validateElement ( validator, node ) {
const isComponent = node.name === ':Self' || validator.components.has( node.name );
@@ -53,30 +47,7 @@ export default function validateElement ( validator, node ) {
}
if ( attribute.type === 'EventHandler' ) {
- const { callee, start, type } = attribute.expression;
-
- if ( type !== 'CallExpression' ) {
- validator.error( `Expected a call expression`, start );
- }
-
- const { name } = flattenReference( callee );
-
- if ( name === 'this' || name === 'event' ) return;
- if ( callee.type === 'Identifier' && validBuiltins.has( callee.name ) || validator.methods.has( callee.name ) ) return;
-
- const validCallees = [ 'this.*', 'event.*' ]
- .concat(
- Array.from( validBuiltins ),
- Array.from( validator.methods.keys() )
- );
-
- let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${list( validCallees )})`;
-
- if ( callee.type === 'Identifier' && validator.helpers.has( callee.name ) ) {
- message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
- }
-
- validator.error( message, start );
+ validateEventHandler( validator, attribute );
}
});
}
@@ -95,8 +66,3 @@ function getType ( validator, node ) {
return attribute.value[0].data;
}
-
-function list ( items, conjunction = 'or' ) {
- if ( items.length === 1 ) return items[0];
- return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`;
-}
diff --git a/src/validate/html/validateEventHandler.js b/src/validate/html/validateEventHandler.js
new file mode 100644
index 0000000000..3c0f1f288d
--- /dev/null
+++ b/src/validate/html/validateEventHandler.js
@@ -0,0 +1,35 @@
+import flattenReference from '../../utils/flattenReference.js';
+import list from '../utils/list.js';
+
+const validBuiltins = new Set([
+ 'set',
+ 'fire',
+ 'destroy'
+]);
+
+export default function validateEventHandlerCallee ( validator, attribute ) {
+ const { callee, start, type } = attribute.expression;
+
+ if ( type !== 'CallExpression' ) {
+ validator.error( `Expected a call expression`, start );
+ }
+
+ const { name } = flattenReference( callee );
+
+ if ( name === 'this' || name === 'event' ) return;
+ if ( callee.type === 'Identifier' && validBuiltins.has( callee.name ) || validator.methods.has( callee.name ) ) return;
+
+ const validCallees = [ 'this.*', 'event.*' ]
+ .concat(
+ Array.from( validBuiltins ),
+ Array.from( validator.methods.keys() )
+ );
+
+ let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${list( validCallees )})`;
+
+ if ( callee.type === 'Identifier' && validator.helpers.has( callee.name ) ) {
+ message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
+ }
+
+ validator.error( message, start );
+}
\ No newline at end of file
diff --git a/src/validate/html/validateWindow.js b/src/validate/html/validateWindow.js
index 233eefa922..e719691622 100644
--- a/src/validate/html/validateWindow.js
+++ b/src/validate/html/validateWindow.js
@@ -1,3 +1,48 @@
-export default function validateWindow () {
- // TODO
+import flattenReference from '../../utils/flattenReference.js';
+import fuzzymatch from '../utils/fuzzymatch.js';
+import list from '../utils/list.js';
+import validateEventHandler from './validateEventHandler.js';
+
+const validBindings = [
+ 'innerWidth',
+ 'innerHeight',
+ 'outerWidth',
+ 'outerHeight',
+ 'scrollX',
+ 'scrollY'
+];
+
+export default function validateWindow ( validator, node ) {
+ node.attributes.forEach( attribute => {
+ if ( attribute.type === 'Binding' ) {
+ if ( attribute.value.type !== 'Identifier' ) {
+ const { parts } = flattenReference( attribute.value );
+
+ validator.error(
+ `Bindings on <:Window/> must be to top-level properties, e.g. '${parts[ parts.length - 1 ]}' rather than '${parts.join( '.' )}'`,
+ attribute.value.start
+ );
+ }
+
+ if ( !~validBindings.indexOf( attribute.name ) ) {
+ const match = (
+ attribute.name === 'width' ? 'innerWidth' :
+ attribute.name === 'height' ? 'innerHeight' :
+ fuzzymatch( attribute.name, validBindings )
+ );
+
+ const message = `'${attribute.name}' is not a valid binding on <:Window>`;
+
+ if ( match ) {
+ validator.error( `${message} (did you mean '${match}'?)`, attribute.start );
+ } else {
+ validator.error( `${message} — valid bindings are ${list( validBindings )}`, attribute.start );
+ }
+ }
+ }
+
+ else if ( attribute.type === 'EventHandler' ) {
+ validateEventHandler( validator, attribute );
+ }
+ });
}
\ No newline at end of file
diff --git a/src/validate/js/index.js b/src/validate/js/index.js
index 6817382496..4d237e9f01 100644
--- a/src/validate/js/index.js
+++ b/src/validate/js/index.js
@@ -1,13 +1,11 @@
import propValidators from './propValidators/index.js';
-import FuzzySet from './utils/FuzzySet.js';
+import fuzzymatch from '../utils/fuzzymatch.js';
import checkForDupes from './utils/checkForDupes.js';
import checkForComputedKeys from './utils/checkForComputedKeys.js';
import namespaces from '../../utils/namespaces.js';
const validPropList = Object.keys( propValidators );
-const fuzzySet = new FuzzySet( validPropList );
-
export default function validateJs ( validator, js ) {
js.content.body.forEach( node => {
// check there are no named exports
@@ -45,9 +43,9 @@ export default function validateJs ( validator, js ) {
if ( propValidator ) {
propValidator( validator, prop );
} else {
- const matches = fuzzySet.get( prop.key.name );
- if ( matches && matches[0] && matches[0][0] > 0.7 ) {
- validator.error( `Unexpected property '${prop.key.name}' (did you mean '${matches[0][1]}'?)`, prop.start );
+ const match = fuzzymatch( prop.key.name, validPropList );
+ if ( match ) {
+ validator.error( `Unexpected property '${prop.key.name}' (did you mean '${match}'?)`, prop.start );
} else if ( /FunctionExpression/.test( prop.value.type ) ) {
validator.error( `Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`, prop.start );
} else {
diff --git a/src/validate/js/propValidators/namespace.js b/src/validate/js/propValidators/namespace.js
index ec8bad0a04..be011c45d7 100644
--- a/src/validate/js/propValidators/namespace.js
+++ b/src/validate/js/propValidators/namespace.js
@@ -1,7 +1,6 @@
import * as namespaces from '../../../utils/namespaces.js';
-import FuzzySet from '../utils/FuzzySet.js';
+import fuzzymatch from '../../utils/fuzzymatch.js';
-const fuzzySet = new FuzzySet( namespaces.validNamespaces );
const valid = new Set( namespaces.validNamespaces );
export default function namespace ( validator, prop ) {
@@ -12,9 +11,9 @@ export default function namespace ( validator, prop ) {
}
if ( !valid.has( ns ) ) {
- const matches = fuzzySet.get( ns );
- if ( matches && matches[0] && matches[0][0] > 0.7 ) {
- validator.error( `Invalid namespace '${ns}' (did you mean '${matches[0][1]}'?)`, prop.start );
+ const match = fuzzymatch( ns, namespaces.validNamespaces );
+ if ( match ) {
+ validator.error( `Invalid namespace '${ns}' (did you mean '${match}'?)`, prop.start );
} else {
validator.error( `Invalid namespace '${ns}'`, prop.start );
}
diff --git a/src/validate/js/utils/FuzzySet.js b/src/validate/utils/FuzzySet.js
similarity index 100%
rename from src/validate/js/utils/FuzzySet.js
rename to src/validate/utils/FuzzySet.js
diff --git a/src/validate/utils/fuzzymatch.js b/src/validate/utils/fuzzymatch.js
new file mode 100644
index 0000000000..7eabd79090
--- /dev/null
+++ b/src/validate/utils/fuzzymatch.js
@@ -0,0 +1,10 @@
+import FuzzySet from './FuzzySet.js';
+
+export default function fuzzymatch ( name, names ) {
+ const set = new FuzzySet( names );
+ const matches = set.get( name );
+
+ return matches && matches[0] && matches[0][0] > 0.7 ?
+ matches[0][1] :
+ null;
+}
\ No newline at end of file
diff --git a/src/validate/utils/list.js b/src/validate/utils/list.js
new file mode 100644
index 0000000000..bada8167ec
--- /dev/null
+++ b/src/validate/utils/list.js
@@ -0,0 +1,4 @@
+export default function list ( items, conjunction = 'or' ) {
+ if ( items.length === 1 ) return items[0];
+ return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`;
+}
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-innerwidth/errors.json b/test/validator/samples/window-binding-invalid-innerwidth/errors.json
new file mode 100644
index 0000000000..d4c5e99d18
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-innerwidth/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "'innerwidth' is not a valid binding on <:Window> (did you mean 'innerWidth'?)",
+ "loc": {
+ "line": 1,
+ "column": 9
+ },
+ "pos": 9
+}]
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-innerwidth/input.html b/test/validator/samples/window-binding-invalid-innerwidth/input.html
new file mode 100644
index 0000000000..867e2c92dd
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-innerwidth/input.html
@@ -0,0 +1 @@
+<:Window bind:innerwidth='w'/>
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-value/errors.json b/test/validator/samples/window-binding-invalid-value/errors.json
new file mode 100644
index 0000000000..7f0c3f8b25
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-value/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "Bindings on <:Window/> must be to top-level properties, e.g. 'baz' rather than 'foo.bar.baz'",
+ "loc": {
+ "line": 1,
+ "column": 26
+ },
+ "pos": 26
+}]
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-value/input.html b/test/validator/samples/window-binding-invalid-value/input.html
new file mode 100644
index 0000000000..aa0bb48fae
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-value/input.html
@@ -0,0 +1 @@
+<:Window bind:innerWidth='foo.bar.baz'/>
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-width/errors.json b/test/validator/samples/window-binding-invalid-width/errors.json
new file mode 100644
index 0000000000..b24b359611
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-width/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "'width' is not a valid binding on <:Window> (did you mean 'innerWidth'?)",
+ "loc": {
+ "line": 1,
+ "column": 9
+ },
+ "pos": 9
+}]
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid-width/input.html b/test/validator/samples/window-binding-invalid-width/input.html
new file mode 100644
index 0000000000..5b8348a818
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid-width/input.html
@@ -0,0 +1 @@
+<:Window bind:width='w'/>
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid/errors.json b/test/validator/samples/window-binding-invalid/errors.json
new file mode 100644
index 0000000000..f2a36540ec
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "'potato' is not a valid binding on <:Window> — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX or scrollY",
+ "loc": {
+ "line": 1,
+ "column": 9
+ },
+ "pos": 9
+}]
\ No newline at end of file
diff --git a/test/validator/samples/window-binding-invalid/input.html b/test/validator/samples/window-binding-invalid/input.html
new file mode 100644
index 0000000000..3897b664b5
--- /dev/null
+++ b/test/validator/samples/window-binding-invalid/input.html
@@ -0,0 +1 @@
+<:Window bind:potato='foo'/>
\ No newline at end of file
diff --git a/test/validator/samples/window-event-invalid/errors.json b/test/validator/samples/window-event-invalid/errors.json
new file mode 100644
index 0000000000..425c657cb1
--- /dev/null
+++ b/test/validator/samples/window-event-invalid/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "'resize' is an invalid callee (should be one of this.*, event.*, set, fire or destroy)",
+ "loc": {
+ "line": 1,
+ "column": 20
+ },
+ "pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/window-event-invalid/input.html b/test/validator/samples/window-event-invalid/input.html
new file mode 100644
index 0000000000..c9c32eecac
--- /dev/null
+++ b/test/validator/samples/window-event-invalid/input.html
@@ -0,0 +1 @@
+<:Window on:resize='resize()'/>
\ No newline at end of file
From b342f2e8b7edb42f245daa62d330f054ea99cdb9 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 13:16:53 -0400
Subject: [PATCH 077/233] fix outros on compound if blocks, get IfBlock.js
coverage to 100%
---
src/generators/dom/visitors/IfBlock.js | 53 +++++++++----------
.../_config.js | 22 ++++++++
.../main.html | 20 +++++++
.../_config.js | 18 +++++++
.../main.html | 20 +++++++
.../_config.js | 23 ++++++++
.../main.html | 20 +++++++
7 files changed, 149 insertions(+), 27 deletions(-)
create mode 100644 test/runtime/samples/transition-js-if-else-block-dynamic-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-if-else-block-dynamic-outro/main.html
create mode 100644 test/runtime/samples/transition-js-if-else-block-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-if-else-block-outro/main.html
create mode 100644 test/runtime/samples/transition-js-if-elseif-block-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-if-elseif-block-outro/main.html
diff --git a/src/generators/dom/visitors/IfBlock.js b/src/generators/dom/visitors/IfBlock.js
index f5e8ad8418..c8da4b7d62 100644
--- a/src/generators/dom/visitors/IfBlock.js
+++ b/src/generators/dom/visitors/IfBlock.js
@@ -215,6 +215,7 @@ function compoundWithOutros ( generator, block, state, node, branches, dynamic,
const if_current_block_index = hasElse ? '' : `if ( ~${current_block_index} ) `;
block.addVariable( current_block_index );
+ block.addVariable( name );
block.builders.create.addBlock( deindent`
var ${if_block_creators} = [
@@ -233,12 +234,12 @@ function compoundWithOutros ( generator, block, state, node, branches, dynamic,
if ( hasElse ) {
block.builders.create.addBlock( deindent`
${current_block_index} = ${get_block}( ${params} );
- ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
+ ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
` );
} else {
block.builders.create.addBlock( deindent`
if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) {
- ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
+ ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
}
` );
}
@@ -253,37 +254,35 @@ function compoundWithOutros ( generator, block, state, node, branches, dynamic,
const parentNode = state.parentNode || `${anchor}.parentNode`;
- const changeBlock = deindent`
- var ${name} = ${if_blocks}[ ${previous_block_index} ];
- if ( ${name} ) {
- ${name}.outro( function () {
- ${if_blocks}[ ${previous_block_index} ].destroy( true );
- ${if_blocks}[ ${previous_block_index} ] = null;
- });
- }
+ const destroyOldBlock = deindent`
+ ${name}.outro( function () {
+ ${if_blocks}[ ${previous_block_index} ].destroy( true );
+ ${if_blocks}[ ${previous_block_index} ] = null;
+ });
`;
- if ( hasElse ) {
- block.builders.create.addBlock( deindent`
- ${name} = ${if_blocks}[ ${current_block_index} ];
- if ( !${name} ) {
- ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
+ const createNewBlock = deindent`
+ ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
+ ${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
+ `;
+
+ const changeBlock = hasElse ?
+ deindent`
+ ${destroyOldBlock}
+
+ ${createNewBlock}
+ ` :
+ deindent`
+ if ( ${name} ) {
+ ${destroyOldBlock}
}
- ${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
- ` );
- } else {
- block.builders.create.addBlock( deindent`
if ( ~${current_block_index} ) {
- ${name} = ${if_blocks}[ ${current_block_index} ];
- if ( !${name} ) {
- ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
- }
-
- ${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
+ ${createNewBlock}
+ } else {
+ ${name} = null;
}
- ` );
- }
+ `;
if ( dynamic ) {
block.builders.update.addBlock( deindent`
diff --git a/test/runtime/samples/transition-js-if-else-block-dynamic-outro/_config.js b/test/runtime/samples/transition-js-if-else-block-dynamic-outro/_config.js
new file mode 100644
index 0000000000..3ea7079830
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-else-block-dynamic-outro/_config.js
@@ -0,0 +1,22 @@
+export default {
+ data: {
+ z: 'z'
+ },
+
+ test ( assert, component, target, window, raf ) {
+ assert.equal( target.querySelector( 'div' ), component.refs.no );
+
+ component.set({ x: true });
+
+ raf.tick( 25 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.75 );
+
+ raf.tick( 75 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.25 );
+
+ raf.tick( 100 );
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-if-else-block-dynamic-outro/main.html b/test/runtime/samples/transition-js-if-else-block-dynamic-outro/main.html
new file mode 100644
index 0000000000..aa1ca15fef
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-else-block-dynamic-outro/main.html
@@ -0,0 +1,20 @@
+{{#if x}}
+ {{z}}
+{{else}}
+ {{z}}
+{{/if}}
+
+
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-if-else-block-outro/_config.js b/test/runtime/samples/transition-js-if-else-block-outro/_config.js
new file mode 100644
index 0000000000..98053c817b
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-else-block-outro/_config.js
@@ -0,0 +1,18 @@
+export default {
+ test ( assert, component, target, window, raf ) {
+ assert.equal( target.querySelector( 'div' ), component.refs.no );
+
+ component.set({ x: true });
+
+ raf.tick( 25 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.75 );
+
+ raf.tick( 75 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.25 );
+
+ raf.tick( 100 );
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-if-else-block-outro/main.html b/test/runtime/samples/transition-js-if-else-block-outro/main.html
new file mode 100644
index 0000000000..47cc670d1f
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-else-block-outro/main.html
@@ -0,0 +1,20 @@
+{{#if x}}
+ yes
+{{else}}
+ no
+{{/if}}
+
+
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-if-elseif-block-outro/_config.js b/test/runtime/samples/transition-js-if-elseif-block-outro/_config.js
new file mode 100644
index 0000000000..fad2d41e3b
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-elseif-block-outro/_config.js
@@ -0,0 +1,23 @@
+export default {
+ data: {
+ x: false,
+ y: true
+ },
+
+ test ( assert, component, target, window, raf ) {
+ assert.equal( target.querySelector( 'div' ), component.refs.no );
+
+ component.set({ x: true, y: false });
+
+ raf.tick( 25 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.75 );
+
+ raf.tick( 75 );
+ assert.equal( component.refs.yes.foo, undefined );
+ assert.equal( component.refs.no.foo, 0.25 );
+
+ raf.tick( 100 );
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-if-elseif-block-outro/main.html b/test/runtime/samples/transition-js-if-elseif-block-outro/main.html
new file mode 100644
index 0000000000..7b45898ab5
--- /dev/null
+++ b/test/runtime/samples/transition-js-if-elseif-block-outro/main.html
@@ -0,0 +1,20 @@
+{{#if x}}
+ yes
+{{elseif y}}
+ no
+{{/if}}
+
+
\ No newline at end of file
From 642b414c9cd9fecaf6623ec2ef8e49be5d833004 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 14:06:50 -0400
Subject: [PATCH 078/233] validate transition directives - closes #564
---
src/validate/html/validateElement.js | 28 ++++++++++++++++++-
src/validate/index.js | 3 +-
src/validate/js/index.js | 2 +-
.../errors.json | 8 ++++++
.../input.html | 10 +++++++
.../transition-duplicate-in/errors.json | 8 ++++++
.../transition-duplicate-in/input.html | 10 +++++++
.../errors.json | 8 ++++++
.../input.html | 10 +++++++
.../transition-duplicate-out/errors.json | 8 ++++++
.../transition-duplicate-out/input.html | 10 +++++++
.../errors.json | 8 ++++++
.../input.html | 10 +++++++
.../errors.json | 8 ++++++
.../input.html | 10 +++++++
.../errors.json | 8 ++++++
.../input.html | 10 +++++++
.../samples/transition-missing/errors.json | 8 ++++++
.../samples/transition-missing/input.html | 1 +
19 files changed, 165 insertions(+), 3 deletions(-)
create mode 100644 test/validator/samples/transition-duplicate-in-transition/errors.json
create mode 100644 test/validator/samples/transition-duplicate-in-transition/input.html
create mode 100644 test/validator/samples/transition-duplicate-in/errors.json
create mode 100644 test/validator/samples/transition-duplicate-in/input.html
create mode 100644 test/validator/samples/transition-duplicate-out-transition/errors.json
create mode 100644 test/validator/samples/transition-duplicate-out-transition/input.html
create mode 100644 test/validator/samples/transition-duplicate-out/errors.json
create mode 100644 test/validator/samples/transition-duplicate-out/input.html
create mode 100644 test/validator/samples/transition-duplicate-transition-in/errors.json
create mode 100644 test/validator/samples/transition-duplicate-transition-in/input.html
create mode 100644 test/validator/samples/transition-duplicate-transition-out/errors.json
create mode 100644 test/validator/samples/transition-duplicate-transition-out/input.html
create mode 100644 test/validator/samples/transition-duplicate-transition/errors.json
create mode 100644 test/validator/samples/transition-duplicate-transition/input.html
create mode 100644 test/validator/samples/transition-missing/errors.json
create mode 100644 test/validator/samples/transition-missing/input.html
diff --git a/src/validate/html/validateElement.js b/src/validate/html/validateElement.js
index 42e4c75f30..74034ceb08 100644
--- a/src/validate/html/validateElement.js
+++ b/src/validate/html/validateElement.js
@@ -3,6 +3,10 @@ import validateEventHandler from './validateEventHandler.js';
export default function validateElement ( validator, node ) {
const isComponent = node.name === ':Self' || validator.components.has( node.name );
+ let hasIntro;
+ let hasOutro;
+ let hasTransition;
+
node.attributes.forEach( attribute => {
if ( !isComponent && attribute.type === 'Binding' ) {
const { name } = attribute;
@@ -46,9 +50,31 @@ export default function validateElement ( validator, node ) {
}
}
- if ( attribute.type === 'EventHandler' ) {
+ else if ( attribute.type === 'EventHandler' ) {
validateEventHandler( validator, attribute );
}
+
+ else if ( attribute.type === 'Transition' ) {
+ const bidi = attribute.intro && attribute.outro;
+
+ if ( hasTransition ) {
+ if ( bidi ) validator.error( `An element can only have one 'transition' directive`, attribute.start );
+ validator.error( `An element cannot have both a 'transition' directive and an '${attribute.intro ? 'in' : 'out'}' directive`, attribute.start );
+ }
+
+ if ( ( hasIntro && attribute.intro ) || ( hasOutro && attribute.outro ) ) {
+ if ( bidi ) validator.error( `An element cannot have both an '${hasIntro ? 'in' : 'out'}' directive and a 'transition' directive`, attribute.start );
+ validator.error( `An element can only have one '${hasIntro ? 'in' : 'out'}' directive`, attribute.start );
+ }
+
+ if ( attribute.intro ) hasIntro = true;
+ if ( attribute.outro ) hasOutro = true;
+ if ( bidi ) hasTransition = true;
+
+ if ( !validator.transitions.has( attribute.name ) ) {
+ validator.error( `Missing transition '${attribute.name}'`, attribute.start );
+ }
+ }
});
}
diff --git a/src/validate/index.js b/src/validate/index.js
index 8040549e56..8991803ba5 100644
--- a/src/validate/index.js
+++ b/src/validate/index.js
@@ -43,7 +43,8 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
properties: {},
components: new Map(),
methods: new Map(),
- helpers: new Map()
+ helpers: new Map(),
+ transitions: new Map()
};
try {
diff --git a/src/validate/js/index.js b/src/validate/js/index.js
index 4d237e9f01..5b101502ef 100644
--- a/src/validate/js/index.js
+++ b/src/validate/js/index.js
@@ -63,7 +63,7 @@ export default function validateJs ( validator, js ) {
}
});
- [ 'components', 'methods', 'helpers' ].forEach( key => {
+ [ 'components', 'methods', 'helpers', 'transitions' ].forEach( key => {
if ( validator.properties[ key ] ) {
validator.properties[ key ].value.properties.forEach( prop => {
validator[ key ].set( prop.key.name, prop.value );
diff --git a/test/validator/samples/transition-duplicate-in-transition/errors.json b/test/validator/samples/transition-duplicate-in-transition/errors.json
new file mode 100644
index 0000000000..c48f56ede9
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element cannot have both an 'in' directive and a 'transition' directive",
+ "loc": {
+ "line": 1,
+ "column": 12
+ },
+ "pos": 12
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in-transition/input.html b/test/validator/samples/transition-duplicate-in-transition/input.html
new file mode 100644
index 0000000000..6f47e754f4
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in-transition/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in/errors.json b/test/validator/samples/transition-duplicate-in/errors.json
new file mode 100644
index 0000000000..a3cc8b0ec5
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element can only have one 'in' directive",
+ "loc": {
+ "line": 1,
+ "column": 12
+ },
+ "pos": 12
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-in/input.html b/test/validator/samples/transition-duplicate-in/input.html
new file mode 100644
index 0000000000..b9a9218f8e
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-in/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out-transition/errors.json b/test/validator/samples/transition-duplicate-out-transition/errors.json
new file mode 100644
index 0000000000..f4bfa61ef0
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element cannot have both an 'out' directive and a 'transition' directive",
+ "loc": {
+ "line": 1,
+ "column": 13
+ },
+ "pos": 13
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out-transition/input.html b/test/validator/samples/transition-duplicate-out-transition/input.html
new file mode 100644
index 0000000000..ae2582b942
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out-transition/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out/errors.json b/test/validator/samples/transition-duplicate-out/errors.json
new file mode 100644
index 0000000000..988dc02bbe
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element can only have one 'out' directive",
+ "loc": {
+ "line": 1,
+ "column": 13
+ },
+ "pos": 13
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-out/input.html b/test/validator/samples/transition-duplicate-out/input.html
new file mode 100644
index 0000000000..949dae8638
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-out/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-in/errors.json b/test/validator/samples/transition-duplicate-transition-in/errors.json
new file mode 100644
index 0000000000..678ad4dd38
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-in/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element cannot have both a 'transition' directive and an 'in' directive",
+ "loc": {
+ "line": 1,
+ "column": 20
+ },
+ "pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-in/input.html b/test/validator/samples/transition-duplicate-transition-in/input.html
new file mode 100644
index 0000000000..bc105c2079
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-in/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-out/errors.json b/test/validator/samples/transition-duplicate-transition-out/errors.json
new file mode 100644
index 0000000000..31dc180b5a
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-out/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element cannot have both a 'transition' directive and an 'out' directive",
+ "loc": {
+ "line": 1,
+ "column": 20
+ },
+ "pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition-out/input.html b/test/validator/samples/transition-duplicate-transition-out/input.html
new file mode 100644
index 0000000000..ac82cb064e
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition-out/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition/errors.json b/test/validator/samples/transition-duplicate-transition/errors.json
new file mode 100644
index 0000000000..585ff37451
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "An element can only have one 'transition' directive",
+ "loc": {
+ "line": 1,
+ "column": 20
+ },
+ "pos": 20
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-duplicate-transition/input.html b/test/validator/samples/transition-duplicate-transition/input.html
new file mode 100644
index 0000000000..29af279564
--- /dev/null
+++ b/test/validator/samples/transition-duplicate-transition/input.html
@@ -0,0 +1,10 @@
+...
+
+
\ No newline at end of file
diff --git a/test/validator/samples/transition-missing/errors.json b/test/validator/samples/transition-missing/errors.json
new file mode 100644
index 0000000000..4f2b88c2f6
--- /dev/null
+++ b/test/validator/samples/transition-missing/errors.json
@@ -0,0 +1,8 @@
+[{
+ "message": "Missing transition 'foo'",
+ "loc": {
+ "line": 1,
+ "column": 5
+ },
+ "pos": 5
+}]
\ No newline at end of file
diff --git a/test/validator/samples/transition-missing/input.html b/test/validator/samples/transition-missing/input.html
new file mode 100644
index 0000000000..5d0e1b7067
--- /dev/null
+++ b/test/validator/samples/transition-missing/input.html
@@ -0,0 +1 @@
+...
\ No newline at end of file
From 1a92398101cbdfdda476748828e5ff53369aa01a Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 17:47:22 -0400
Subject: [PATCH 079/233] apply delays to bidirectional transitions - fixes
#562
---
.../dom/visitors/Element/addTransitions.js | 8 +-
src/shared/transitions.js | 82 +++++++++++--------
.../transition-js-delay-in-out/_config.js | 24 ++++++
.../transition-js-delay-in-out/main.html | 29 +++++++
.../samples/transition-js-delay/_config.js | 29 +++++++
.../samples/transition-js-delay/main.html | 19 +++++
.../_config.js | 10 +--
.../main.html | 2 +-
8 files changed, 160 insertions(+), 43 deletions(-)
create mode 100644 test/runtime/samples/transition-js-delay-in-out/_config.js
create mode 100644 test/runtime/samples/transition-js-delay-in-out/main.html
create mode 100644 test/runtime/samples/transition-js-delay/_config.js
create mode 100644 test/runtime/samples/transition-js-delay/main.html
diff --git a/src/generators/dom/visitors/Element/addTransitions.js b/src/generators/dom/visitors/Element/addTransitions.js
index e7fda98331..6a5953d02c 100644
--- a/src/generators/dom/visitors/Element/addTransitions.js
+++ b/src/generators/dom/visitors/Element/addTransitions.js
@@ -14,14 +14,14 @@ export default function addTransitions ( generator, block, state, node, intro, o
block.builders.intro.addBlock( deindent`
${block.component}._renderHooks.push( function () {
if ( !${name} ) ${name} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null );
- ${name}.run( ${name}.t, 1, function () {
+ ${name}.run( true, function () {
${block.component}.fire( 'intro.end', { node: ${state.name} });
});
});
` );
block.builders.outro.addBlock( deindent`
- ${name}.run( ${name}.t, 0, function () {
+ ${name}.run( false, function () {
${block.component}.fire( 'outro.end', { node: ${state.name} });
if ( --${block.alias( 'outros' )} === 0 ) ${block.alias( 'outrocallback' )}();
${name} = null;
@@ -49,7 +49,7 @@ export default function addTransitions ( generator, block, state, node, intro, o
block.builders.intro.addBlock( deindent`
${block.component}._renderHooks.push( function () {
${introName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null );
- ${introName}.run( 0, 1, function () {
+ ${introName}.run( true, function () {
${block.component}.fire( 'intro.end', { node: ${state.name} });
});
});
@@ -66,7 +66,7 @@ export default function addTransitions ( generator, block, state, node, intro, o
// group) prior to their removal from the DOM
block.builders.outro.addBlock( deindent`
${outroName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, false, null );
- ${outroName}.run( 1, 0, function () {
+ ${outroName}.run( false, function () {
${block.component}.fire( 'outro.end', { node: ${state.name} });
if ( --${block.alias( 'outros' )} === 0 ) ${block.alias( 'outrocallback' )}();
});
diff --git a/src/shared/transitions.js b/src/shared/transitions.js
index 289ded6182..f13f3a357c 100644
--- a/src/shared/transitions.js
+++ b/src/shared/transitions.js
@@ -28,7 +28,7 @@ export function generateKeyframes ( a, b, delta, duration, ease, fn, node, style
}
export function wrapTransition ( node, fn, params, intro, outgroup ) {
- var obj = fn( node, params, intro );
+ var obj = fn( node, params );
var duration = obj.duration || 300;
var ease = obj.easing || linear;
@@ -40,26 +40,20 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
if ( intro && obj.tick ) obj.tick( 0 );
return {
- start: null,
- end: null,
- a: null,
- b: null,
- d: null,
running: false,
t: intro ? 0 : 1,
- callback: null,
- run: function ( a, b, callback ) {
- this.a = a;
- this.b = b;
- this.delta = b - a;
- this.start = window.performance.now() + ( obj.delay || 0 );
- this.duration = duration * Math.abs( b - a );
- this.end = this.start + this.duration;
-
- this.callback = callback;
-
- if ( obj.css ) {
- generateKeyframes( this.a, this.b, this.delta, this.duration, ease, obj.css, node, style );
+ pending: null,
+ run: function ( intro, callback ) {
+ var program = {
+ intro: intro,
+ start: window.performance.now() + ( obj.delay || 0 ),
+ callback: callback
+ };
+
+ if ( obj.delay ) {
+ this.pending = program;
+ } else {
+ this.start( program );
}
if ( !this.running ) {
@@ -67,21 +61,41 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
transitionManager.add( this );
}
},
+ start: function ( program ) {
+ program.a = this.t;
+ program.b = program.intro ? 1 : 0;
+ program.delta = program.b - program.a;
+ program.duration = duration * Math.abs( program.b - program.a );
+ program.end = program.start + program.duration;
+
+ if ( obj.css ) {
+ generateKeyframes( program.a, program.b, program.delta, program.duration, ease, obj.css, node, style );
+ }
+
+ this.program = program;
+ this.pending = null;
+ },
update: function ( now ) {
- var p = now - this.start;
- this.t = this.a + this.delta * ease( p / this.duration );
+ var program = this.program;
+ if ( !program ) return;
+
+ var p = now - program.start;
+ this.t = program.a + program.delta * ease( p / program.duration );
if ( obj.tick ) obj.tick( this.t );
},
done: function () {
- if ( obj.tick ) obj.tick( intro ? 1 : 0 );
+ this.t = this.program.b;
+ if ( obj.tick ) obj.tick( this.t );
if ( obj.css ) document.head.removeChild( style );
- this.callback();
- this.running = false;
+ this.program.callback();
+ this.program = null;
+ this.running = !!this.pending;
},
abort: function () {
if ( obj.tick ) obj.tick( 1 );
if ( obj.css ) document.head.removeChild( style );
- this.running = false;
+ this.program = null;
+ this.running = !!this.pending;
}
};
}
@@ -108,16 +122,18 @@ export var transitionManager = {
while ( i-- ) {
var transition = transitionManager.transitions[i];
- if ( transition.running ) {
- if ( now >= transition.end ) {
- transition.running = false;
- transition.done();
- } else if ( now > transition.start ) {
- transition.update( now );
- }
+ if ( transition.program && now >= transition.program.end ) {
+ transition.done();
+ }
+ if ( transition.pending && now >= transition.pending.start ) {
+ transition.start( transition.pending );
+ }
+
+ if ( transition.running ) {
+ transition.update( now );
transitionManager.running = true;
- } else {
+ } else if ( !transition.pending ) {
transitionManager.transitions.splice( i, 1 );
}
}
diff --git a/test/runtime/samples/transition-js-delay-in-out/_config.js b/test/runtime/samples/transition-js-delay-in-out/_config.js
new file mode 100644
index 0000000000..6e97f19c3b
--- /dev/null
+++ b/test/runtime/samples/transition-js-delay-in-out/_config.js
@@ -0,0 +1,24 @@
+export default {
+ test ( assert, component, target, window, raf ) {
+ component.set({ visible: true });
+ const div = target.querySelector( 'div' );
+ assert.equal( div.foo, 0 );
+
+ raf.tick( 50 );
+ assert.equal( div.foo, 0 );
+
+ raf.tick( 150 );
+ assert.equal( div.foo, 1 );
+
+ component.set({ visible: false });
+ assert.equal( div.bar, undefined );
+
+ raf.tick( 200 );
+ assert.equal( div.bar, 1 );
+
+ raf.tick( 300 );
+ assert.equal( div.bar, 0 );
+
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-delay-in-out/main.html b/test/runtime/samples/transition-js-delay-in-out/main.html
new file mode 100644
index 0000000000..6a0a3f88a7
--- /dev/null
+++ b/test/runtime/samples/transition-js-delay-in-out/main.html
@@ -0,0 +1,29 @@
+{{#if visible}}
+ delayed
+{{/if}}
+
+
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-delay/_config.js b/test/runtime/samples/transition-js-delay/_config.js
new file mode 100644
index 0000000000..eab832ca3f
--- /dev/null
+++ b/test/runtime/samples/transition-js-delay/_config.js
@@ -0,0 +1,29 @@
+export default {
+ test ( assert, component, target, window, raf ) {
+ component.set({ visible: true });
+ const div = target.querySelector( 'div' );
+ assert.equal( div.foo, 0 );
+
+ raf.tick( 50 );
+ assert.equal( div.foo, 0 );
+
+ raf.tick( 100 );
+ assert.equal( div.foo, 0.5 );
+
+ component.set({ visible: false });
+
+ raf.tick( 125 );
+ assert.equal( div.foo, 0.75 );
+
+ raf.tick( 150 );
+ assert.equal( div.foo, 1 );
+
+ raf.tick( 175 );
+ assert.equal( div.foo, 0.75 );
+
+ raf.tick( 250 );
+ assert.equal( div.foo, 0 );
+
+ component.destroy();
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-delay/main.html b/test/runtime/samples/transition-js-delay/main.html
new file mode 100644
index 0000000000..a4277f34ce
--- /dev/null
+++ b/test/runtime/samples/transition-js-delay/main.html
@@ -0,0 +1,19 @@
+{{#if visible}}
+ delayed
+{{/if}}
+
+
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js b/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js
index fcf391ac60..65dece1a13 100644
--- a/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js
+++ b/test/runtime/samples/transition-js-dynamic-if-block-bidi/_config.js
@@ -11,7 +11,7 @@ export default {
const div = target.querySelector( 'div' );
assert.equal( div.foo, 0 );
- raf.tick( 300 );
+ raf.tick( 75 );
component.set({ name: 'everybody' });
assert.equal( div.foo, 0.75 );
assert.htmlEqual( div.innerHTML, 'hello everybody!' );
@@ -19,18 +19,18 @@ export default {
component.set({ visible: false, name: 'again' });
assert.htmlEqual( div.innerHTML, 'hello everybody!' );
- raf.tick( 500 );
+ raf.tick( 125 );
assert.equal( div.foo, 0.25 );
component.set({ visible: true });
- raf.tick( 700 );
+ raf.tick( 175 );
assert.equal( div.foo, 0.75 );
assert.htmlEqual( div.innerHTML, 'hello again!' );
- raf.tick( 800 );
+ raf.tick( 200 );
assert.equal( div.foo, 1 );
- raf.tick( 900 );
+ raf.tick( 225 );
component.destroy();
}
diff --git a/test/runtime/samples/transition-js-dynamic-if-block-bidi/main.html b/test/runtime/samples/transition-js-dynamic-if-block-bidi/main.html
index bc0dace68b..576b4efc2e 100644
--- a/test/runtime/samples/transition-js-dynamic-if-block-bidi/main.html
+++ b/test/runtime/samples/transition-js-dynamic-if-block-bidi/main.html
@@ -8,7 +8,7 @@
foo: function ( node, params ) {
global.count += 1;
return {
- duration: 400,
+ duration: 100,
tick: t => {
node.foo = t;
}
From cfd5d3e3c78fc3ea5929d24edf65c748047e2803 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 17:57:04 -0400
Subject: [PATCH 080/233] minor tidy up
---
src/shared/transitions.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/shared/transitions.js b/src/shared/transitions.js
index f13f3a357c..916a69b3dd 100644
--- a/src/shared/transitions.js
+++ b/src/shared/transitions.js
@@ -40,13 +40,14 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
if ( intro && obj.tick ) obj.tick( 0 );
return {
- running: false,
t: intro ? 0 : 1,
+ running: false,
+ program: null,
pending: null,
run: function ( intro, callback ) {
var program = {
- intro: intro,
start: window.performance.now() + ( obj.delay || 0 ),
+ intro: intro,
callback: callback
};
@@ -94,8 +95,8 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
abort: function () {
if ( obj.tick ) obj.tick( 1 );
if ( obj.css ) document.head.removeChild( style );
- this.program = null;
- this.running = !!this.pending;
+ this.program = this.pending = null;
+ this.running = false;
}
};
}
From b91ae93cae0eca256a37f8e1eaff40c4e2b06668 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 22:06:40 -0400
Subject: [PATCH 081/233] update keyed each-block outro test to check div order
---
.../samples/transition-js-each-block-keyed-outro/_config.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
index d40e9a3a06..567a671d2c 100644
--- a/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
+++ b/test/runtime/samples/transition-js-each-block-keyed-outro/_config.js
@@ -17,6 +17,11 @@ export default {
]
});
+ const divs2 = target.querySelectorAll( 'div' );
+ assert.strictEqual( divs[0], divs2[0] );
+ assert.strictEqual( divs[1], divs2[1] );
+ assert.strictEqual( divs[2], divs2[2] );
+
raf.tick( 50 );
assert.equal( divs[0].foo, undefined );
assert.equal( divs[1].foo, 0.5 );
From 08f7321d691383d29647ed77c45b57cc909a2960 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Thu, 4 May 2017 23:39:13 -0400
Subject: [PATCH 082/233] create start anchors for each-blocks that need them
---
src/generators/dom/Block.js | 9 ++++++++-
src/generators/dom/visitors/EachBlock.js | 7 +++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/generators/dom/Block.js b/src/generators/dom/Block.js
index f905754815..83841b738d 100644
--- a/src/generators/dom/Block.js
+++ b/src/generators/dom/Block.js
@@ -5,10 +5,13 @@ export default class Block {
constructor ( options ) {
this.generator = options.generator;
this.name = options.name;
- this.key = options.key;
this.expression = options.expression;
this.context = options.context;
+ // for keyed each blocks
+ this.key = options.key;
+ this.first = null;
+
this.contexts = options.contexts;
this.indexes = options.indexes;
this.contextDependencies = options.contextDependencies;
@@ -155,6 +158,10 @@ export default class Block {
properties.addBlock( `key: ${localKey},` );
}
+ if ( this.first ) {
+ properties.addBlock( `first: ${this.first},` );
+ }
+
if ( this.builders.mount.isEmpty() ) {
properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` );
} else {
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 14c4a4297e..c21ea5b2d0 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -118,6 +118,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const iteration = block.getUniqueName( `${each_block}_iteration` );
const _iterations = block.getUniqueName( `_${each_block}_iterations` );
+ if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw
+ node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing
+ } else {
+ node._block.first = node._block.getUniqueName( 'first' );
+ node._block.addElement( node._block.first, `${generator.helper( 'createComment' )}()`, null, true );
+ }
+
block.builders.create.addBlock( deindent`
var ${lookup} = Object.create( null );
From 24c4a7c9f033ce6f94397b826c0afe3b6d30e2b7 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Fri, 5 May 2017 00:25:33 -0400
Subject: [PATCH 083/233] mostly working list diffing algorithm
---
src/generators/dom/visitors/EachBlock.js | 84 +++++++++++++-----------
1 file changed, 44 insertions(+), 40 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index c21ea5b2d0..9ad173a01a 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -110,8 +110,6 @@ export default function visitEachBlock ( generator, block, state, node ) {
}
function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
- const fragment = block.getUniqueName( 'fragment' );
- const value = block.getUniqueName( 'value' );
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
const keys = block.getUniqueName( `${each_block}_keys` );
@@ -135,66 +133,72 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
` );
- const consequent = node._block.hasUpdateMethod ?
- deindent`
- ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ];
- ${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
- ` :
- `${_iterations}[${i}] = ${lookup}[ ${key} ] = ${lookup}[ ${key} ];`;
-
+ const dynamic = node._block.hasUpdateMethod;
const parentNode = state.parentNode || `${anchor}.parentNode`;
- const hasIntros = node._block.hasIntroMethod;
-
- const destroy = node._block.hasOutroMethod ?
- deindent`
- function outro ( key ) {
+ let destroy;
+ if ( node._block.hasOutroMethod ) {
+ const outro = block.getUniqueName( `${each_block}_outro` );
+ block.builders.create.addBlock( deindent`
+ function ${outro} ( key ) {
${lookup}[ key ].outro( function () {
${lookup}[ key ].destroy( true );
${lookup}[ key ] = null;
});
}
+ ` );
- for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${key} = ${iterations}[${i}].key;
- if ( !${keys}[ ${key} ] ) outro( ${key} );
- }
- ` :
- deindent`
- for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- var ${iteration} = ${iterations}[${i}];
- if ( !${keys}[ ${iteration}.key ] ) ${iteration}.destroy( true );
- }
- `;
+ destroy = `${outro}( ${key} );`;
+ } else {
+ destroy = `${iteration}.destroy( true );`;
+ }
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
- var ${_iterations} = [];
+ var ${_iterations} = Array( ${each_block_value}.length );
var ${keys} = Object.create( null );
- var ${fragment} = document.createDocumentFragment();
+ var index_by_key = Object.create( null );
+ var key_by_index = Array( ${each_block_value}.length );
- // create new iterations as necessary
- for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
- var ${value} = ${each_block_value}[${i}];
- var ${key} = ${value}.${node.key};
- ${keys}[ ${key} ] = true;
+ var new_iterations = [];
+
+ for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
+ var ${key} = ${each_block_value}[${i}].${node.key};
+ index_by_key[${key}] = ${i};
+ key_by_index[${i}] = ${key};
if ( ${lookup}[ ${key} ] ) {
- ${consequent}
- ${hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`}
+ // TODO this is an empty branch for non-dynamic blocks
+ ${dynamic && `${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
} else {
- ${_iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${hasIntros && `${_iterations}[${i}].intro( ${fragment}, null );`}
+ ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ new_iterations.push( ${lookup}[ ${key} ] );
}
- ${!hasIntros && `${_iterations}[${i}].mount( ${fragment}, null );`}
+ ${_iterations}[${i}] = ${lookup}[ ${key} ];
+ }
+
+ // TODO group consecutive runs into fragments?
+ ${i} = new_iterations.length;
+ while ( ${i}-- ) {
+ ${iteration} = new_iterations[${i}];
+ var index = index_by_key[${iteration}.key];
+ var next_sibling_key = key_by_index[index + 1];
+ ${iteration}.${mountOrIntro}( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first );
}
- // remove old iterations
- ${destroy}
+ for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
+ var ${iteration} = ${iterations}[${i}];
+ var index = index_by_key[${iteration}.key];
- ${parentNode}.insertBefore( ${fragment}, ${anchor} );
+ if ( index === undefined ) {
+ ${destroy}
+ } else {
+ var next_sibling_key = key_by_index[index + 1];
+ ${iteration}.mount( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first );
+ }
+ }
${iterations} = ${_iterations};
` );
From 776b68ff71d00b5520577bd003b6f485c6c7e2e0 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Fri, 5 May 2017 12:10:37 -0400
Subject: [PATCH 084/233] fix bug in assert.htmlEqual
---
test/helpers.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/test/helpers.js b/test/helpers.js
index 4318d6d364..64b4814e80 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -86,6 +86,7 @@ function cleanChildren ( node ) {
previous.data = previous.data.replace( /\s{2,}/, '\n' );
node.removeChild( child );
+ child = previous;
}
}
From c9dba817fbcc0c42f619ac73bc6b6f070fc842e8 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Fri, 5 May 2017 16:50:10 -0400
Subject: [PATCH 085/233] another crack at the algorithm. outros not currently
applied
---
src/generators/dom/visitors/EachBlock.js | 90 +++++++++++++------
.../_config.js | 48 ++++++++++
.../each-block-keyed-random-permute/main.html | 3 +
3 files changed, 114 insertions(+), 27 deletions(-)
create mode 100644 test/runtime/samples/each-block-keyed-random-permute/_config.js
create mode 100644 test/runtime/samples/each-block-keyed-random-permute/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 9ad173a01a..94d5886441 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -126,10 +126,16 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.create.addBlock( deindent`
var ${lookup} = Object.create( null );
+ var last;
+
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
+
+ if ( last ) last.next = ${lookup}[ ${key} ];
+ ${lookup}[ ${key} ].last = last;
+ last = ${lookup}[${key}];
}
` );
@@ -155,48 +161,78 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
+
+ if ( ${each_block_value}.length === 0 ) {
+ // special case
+ for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
+ ${iterations}[${i}].destroy( true );
+ }
+ ${iterations} = [];
+ return;
+ }
+
var ${_iterations} = Array( ${each_block_value}.length );
- var ${keys} = Object.create( null );
- var index_by_key = Object.create( null );
- var key_by_index = Array( ${each_block_value}.length );
+ var expected = ${iterations}[0];
+ var last;
- var new_iterations = [];
+ var discard_pile = [];
for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
- index_by_key[${key}] = ${i};
- key_by_index[${i}] = ${key};
- if ( ${lookup}[ ${key} ] ) {
- // TODO this is an empty branch for non-dynamic blocks
- ${dynamic && `${lookup}[ ${key} ].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
+ if ( expected ) {
+ if ( ${key} === expected.key ) {
+ expected = expected.next;
+ } else {
+ if ( ${key} in ${lookup} ) {
+ // probably a deletion
+ do {
+ expected.discard = true;
+ discard_pile.push( expected );
+ expected = expected.next;
+ } while ( expected && expected.key !== ${key} );
+
+ expected = expected && expected.next;
+ ${lookup}[${key}].discard = false;
+ ${lookup}[${key}].next = expected;
+
+ ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null );
+ } else {
+ // key is being inserted
+ ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first );
+ }
+ }
} else {
- ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- new_iterations.push( ${lookup}[ ${key} ] );
+ // we're appending from this point forward
+ if ( ${lookup}[${key}] ) {
+ ${lookup}[${key}].discard = false;
+ ${lookup}[${key}].next = null;
+ ${lookup}[${key}].mount( ${parentNode}, null );
+ } else {
+ ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${lookup}[${key}].${mountOrIntro}( ${parentNode}, null );
+ }
}
+ if ( last ) last.next = ${lookup}[${key}];
+ ${lookup}[${key}].last = last;
+ last = ${lookup}[${key}];
+
${_iterations}[${i}] = ${lookup}[ ${key} ];
}
- // TODO group consecutive runs into fragments?
- ${i} = new_iterations.length;
- while ( ${i}-- ) {
- ${iteration} = new_iterations[${i}];
- var index = index_by_key[${iteration}.key];
- var next_sibling_key = key_by_index[index + 1];
- ${iteration}.${mountOrIntro}( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first );
- }
+ last.next = null;
- for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- var ${iteration} = ${iterations}[${i}];
- var index = index_by_key[${iteration}.key];
+ while ( expected ) {
+ expected.destroy( true );
+ expected = expected.next;
+ }
- if ( index === undefined ) {
- ${destroy}
- } else {
- var next_sibling_key = key_by_index[index + 1];
- ${iteration}.mount( ${parentNode}, next_sibling_key === undefined ? ${anchor} : ${lookup}[next_sibling_key].first );
+ for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
+ if ( discard_pile[${i}].discard ) {
+ discard_pile[${i}].destroy( true );
}
}
diff --git a/test/runtime/samples/each-block-keyed-random-permute/_config.js b/test/runtime/samples/each-block-keyed-random-permute/_config.js
new file mode 100644
index 0000000000..c14c4cd0f7
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-random-permute/_config.js
@@ -0,0 +1,48 @@
+const VALUES = Array.from( 'abcdefghijklmnopqrstuvwxyz' );
+
+function toObjects ( array ) {
+ return array.split( '' ).map( x => ({ id: x }) );
+}
+
+function permute () {
+ const values = VALUES.slice();
+ const number = Math.floor(Math.random() * VALUES.length);
+ const permuted = [];
+ for (let i = 0; i < number; i++) {
+ permuted.push( ...values.splice( Math.floor( Math.random() * ( number - i ) ), 1 ) );
+ }
+
+ return permuted.join( '' );
+}
+
+export default {
+ data: {
+ values: toObjects( 'abc' )
+ },
+
+ html: `(a)(b)(c)`,
+
+ test ( assert, component, target ) {
+ function test ( sequence ) {
+ component.set({ values: toObjects( sequence ) });
+ assert.htmlEqual( target.innerHTML, sequence.split( '' ).map( x => `(${x})` ).join( '' ) );
+ }
+
+ // first, some fixed tests so that we can debug them
+ test( 'abc' );
+ test( 'abcd' );
+ test( 'abecd' );
+ test( 'fabecd' );
+ test( 'fabed' );
+ test( 'beadf' );
+ test( 'ghbeadf' );
+ test( 'gf' );
+ test( 'gc' );
+ test( 'g' );
+ test( '' );
+ test( 'abc' );
+
+ // then, we party
+ for ( let i = 0; i < 100; i += 1 ) test( permute() );
+ }
+};
diff --git a/test/runtime/samples/each-block-keyed-random-permute/main.html b/test/runtime/samples/each-block-keyed-random-permute/main.html
new file mode 100644
index 0000000000..a6aa4b621d
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-random-permute/main.html
@@ -0,0 +1,3 @@
+{{#each values as value @id}}
+ ({{value.id}})
+{{/each}}
From 5937aef3a652fa15536f4f85ee3eca129d1816c8 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 00:28:30 -0400
Subject: [PATCH 086/233] ok, i think it actually works now
---
src/generators/dom/visitors/EachBlock.js | 82 +++++++++++++++---------
1 file changed, 52 insertions(+), 30 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 94d5886441..3a4f2808ac 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -112,7 +112,6 @@ export default function visitEachBlock ( generator, block, state, node ) {
function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
- const keys = block.getUniqueName( `${each_block}_keys` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
const _iterations = block.getUniqueName( `_${each_block}_iterations` );
@@ -139,38 +138,66 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
` );
- const dynamic = node._block.hasUpdateMethod;
+ // TODO!!!
+ // const dynamic = node._block.hasUpdateMethod;
const parentNode = state.parentNode || `${anchor}.parentNode`;
let destroy;
if ( node._block.hasOutroMethod ) {
- const outro = block.getUniqueName( `${each_block}_outro` );
+ const fn = block.getUniqueName( `${each_block}_outro` );
block.builders.create.addBlock( deindent`
- function ${outro} ( key ) {
- ${lookup}[ key ].outro( function () {
- ${lookup}[ key ].destroy( true );
- ${lookup}[ key ] = null;
+ function ${fn} ( iteration ) {
+ iteration.outro( function () {
+ iteration.destroy( true );
+ if ( iteration.next ) iteration.next.last = iteration.last;
+ if ( iteration.last ) iteration.last.next = iteration.next;
+ ${lookup}[iteration.key] = null;
});
}
` );
- destroy = `${outro}( ${key} );`;
+ destroy = deindent`
+ while ( expected ) {
+ ${fn}( expected );
+ expected = expected.next;
+ }
+
+ for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
+ if ( discard_pile[${i}].discard ) {
+ ${fn}( discard_pile[${i}] );
+ }
+ }
+ `;
} else {
- destroy = `${iteration}.destroy( true );`;
+ const fn = block.getUniqueName( `${each_block}_destroy` );
+ block.builders.create.addBlock( deindent`
+ function ${fn} ( iteration ) {
+ iteration.destroy( true );
+ if ( iteration.next && iteration.next.last === iteration ) iteration.next.last = iteration.last;
+ if ( iteration.last && iteration.last.next === iteration ) iteration.last.next = iteration.next;
+ ${lookup}[iteration.key] = null;
+ }
+ ` );
+
+ destroy = deindent`
+ while ( expected ) {
+ ${fn}( expected );
+ expected = expected.next;
+ }
+
+ for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
+ var ${iteration} = discard_pile[${i}];
+ if ( ${iteration}.discard ) {
+ // console.log( 'discarding ' + [ ${iteration}.last && ${iteration}.last.key, ${iteration}.key, ${iteration}.next && ${iteration}.next.key ].join( '-' ) );
+ ${fn}( ${iteration} );
+ }
+ }
+ `;
}
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
- if ( ${each_block_value}.length === 0 ) {
- // special case
- for ( ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${iterations}[${i}].destroy( true );
- }
- ${iterations} = [];
- return;
- }
-
var ${_iterations} = Array( ${each_block_value}.length );
var expected = ${iterations}[0];
@@ -185,7 +212,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( ${key} === expected.key ) {
expected = expected.next;
} else {
- if ( ${key} in ${lookup} ) {
+ if ( ${lookup}[${key}] ) {
// probably a deletion
do {
expected.discard = true;
@@ -195,6 +222,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
expected = expected && expected.next;
${lookup}[${key}].discard = false;
+ ${lookup}[${key}].last = last;
${lookup}[${key}].next = expected;
${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null );
@@ -202,6 +230,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
// key is being inserted
${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first );
+
+ if ( expected ) expected.last = ${lookup}[${key}];
+ ${lookup}[${key}].next = expected;
}
}
} else {
@@ -223,18 +254,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
${_iterations}[${i}] = ${lookup}[ ${key} ];
}
- last.next = null;
-
- while ( expected ) {
- expected.destroy( true );
- expected = expected.next;
- }
+ if ( last ) last.next = null;
- for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
- if ( discard_pile[${i}].discard ) {
- discard_pile[${i}].destroy( true );
- }
- }
+ ${destroy}
${iterations} = ${_iterations};
` );
From e9def64d146b0b89228427f9a2954841cead21dc Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 01:39:26 -0400
Subject: [PATCH 087/233] use anchor when updating
---
src/generators/dom/visitors/EachBlock.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 3a4f2808ac..da3e30f9e1 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -225,7 +225,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
${lookup}[${key}].last = last;
${lookup}[${key}].next = expected;
- ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : null );
+ ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : ${anchor} );
} else {
// key is being inserted
${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
@@ -240,10 +240,10 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( ${lookup}[${key}] ) {
${lookup}[${key}].discard = false;
${lookup}[${key}].next = null;
- ${lookup}[${key}].mount( ${parentNode}, null );
+ ${lookup}[${key}].mount( ${parentNode}, ${anchor} );
} else {
${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${lookup}[${key}].${mountOrIntro}( ${parentNode}, null );
+ ${lookup}[${key}].${mountOrIntro}( ${parentNode}, ${anchor} );
}
}
From d829eb94ef4f1178992f9d800b71d6e58b30d856 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 02:06:07 -0400
Subject: [PATCH 088/233] handle bidirectional transitions
---
src/generators/dom/visitors/EachBlock.js | 2 +-
.../_config.js | 65 +++++++++++++++++++
.../main.html | 18 +++++
3 files changed, 84 insertions(+), 1 deletion(-)
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js
create mode 100644 test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index da3e30f9e1..4d5f9622ea 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -188,7 +188,6 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
var ${iteration} = discard_pile[${i}];
if ( ${iteration}.discard ) {
- // console.log( 'discarding ' + [ ${iteration}.last && ${iteration}.last.key, ${iteration}.key, ${iteration}.next && ${iteration}.next.key ].join( '-' ) );
${fn}( ${iteration} );
}
}
@@ -249,6 +248,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
if ( last ) last.next = ${lookup}[${key}];
${lookup}[${key}].last = last;
+ ${node._block.hasIntroMethod && `${lookup}[${key}].intro( ${parentNode}, ${anchor} );`}
last = ${lookup}[${key}];
${_iterations}[${i}] = ${lookup}[ ${key} ];
diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js
new file mode 100644
index 0000000000..0b5c3a1d1a
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/_config.js
@@ -0,0 +1,65 @@
+export default {
+ data: {
+ things: [
+ { name: 'a' },
+ { name: 'b' },
+ { name: 'c' }
+ ]
+ },
+
+ test ( assert, component, target, window, raf ) {
+ const divs = target.querySelectorAll( 'div' );
+ divs[0].i = 0; // for debugging
+ divs[1].i = 1;
+ divs[2].i = 2;
+
+ assert.equal( divs[0].foo, 0 );
+ assert.equal( divs[1].foo, 0 );
+ assert.equal( divs[2].foo, 0 );
+
+ raf.tick( 100 );
+ assert.equal( divs[0].foo, 1 );
+ assert.equal( divs[1].foo, 1 );
+ assert.equal( divs[2].foo, 1 );
+
+ component.set({
+ things: [
+ { name: 'a' },
+ { name: 'c' }
+ ]
+ });
+
+ const divs2 = target.querySelectorAll( 'div' );
+ assert.strictEqual( divs[0], divs2[0] );
+ assert.strictEqual( divs[1], divs2[1] );
+ assert.strictEqual( divs[2], divs2[2] );
+
+ raf.tick( 150 );
+ assert.equal( divs[0].foo, 1 );
+ assert.equal( divs[1].foo, 0.5 );
+ assert.equal( divs[2].foo, 1 );
+
+ component.set({
+ things: [
+ { name: 'a' },
+ { name: 'b' },
+ { name: 'c' }
+ ]
+ });
+
+ raf.tick( 175 );
+ assert.equal( divs[0].foo, 1 );
+ assert.equal( divs[1].foo, 0.75 );
+ assert.equal( divs[2].foo, 1 );
+
+ raf.tick( 225 );
+ const divs3 = target.querySelectorAll( 'div' );
+ assert.strictEqual( divs[0], divs3[0] );
+ assert.strictEqual( divs[1], divs3[1] );
+ assert.strictEqual( divs[2], divs3[2] );
+
+ assert.equal( divs[0].foo, 1 );
+ assert.equal( divs[1].foo, 1 );
+ assert.equal( divs[2].foo, 1 );
+ }
+};
\ No newline at end of file
diff --git a/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html
new file mode 100644
index 0000000000..c755bb12e6
--- /dev/null
+++ b/test/runtime/samples/transition-js-each-block-keyed-intro-outro/main.html
@@ -0,0 +1,18 @@
+{{#each things as thing @name}}
+ {{thing.name}}
+{{/each}}
+
+
\ No newline at end of file
From 1f161f7fa82786711afd7412706303f93eb2cd8b Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 02:20:13 -0400
Subject: [PATCH 089/233] update dynamic keyed each blocks
---
src/generators/dom/visitors/EachBlock.js | 44 ++++++++++---------
.../each-block-keyed-dynamic/_config.js | 35 +++++++++++++++
.../each-block-keyed-dynamic/main.html | 3 ++
3 files changed, 61 insertions(+), 21 deletions(-)
create mode 100644 test/runtime/samples/each-block-keyed-dynamic/_config.js
create mode 100644 test/runtime/samples/each-block-keyed-dynamic/main.html
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 4d5f9622ea..ae533002d9 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -138,8 +138,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
` );
- // TODO!!!
- // const dynamic = node._block.hasUpdateMethod;
+ const dynamic = node._block.hasUpdateMethod;
const parentNode = state.parentNode || `${anchor}.parentNode`;
let destroy;
@@ -206,12 +205,15 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
+ var ${iteration} = ${lookup}[${key}];
+
+ ${dynamic && `if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
if ( expected ) {
if ( ${key} === expected.key ) {
expected = expected.next;
} else {
- if ( ${lookup}[${key}] ) {
+ if ( ${iteration} ) {
// probably a deletion
do {
expected.discard = true;
@@ -220,36 +222,36 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
} while ( expected && expected.key !== ${key} );
expected = expected && expected.next;
- ${lookup}[${key}].discard = false;
- ${lookup}[${key}].last = last;
- ${lookup}[${key}].next = expected;
+ ${iteration}.discard = false;
+ ${iteration}.last = last;
+ ${iteration}.next = expected;
- ${lookup}[${key}].mount( ${parentNode}, expected ? expected.first : ${anchor} );
+ ${iteration}.mount( ${parentNode}, expected ? expected.first : ${anchor} );
} else {
// key is being inserted
- ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${lookup}[${key}].${mountOrIntro}( ${parentNode}, expected.first );
+ ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${iteration}.${mountOrIntro}( ${parentNode}, expected.first );
- if ( expected ) expected.last = ${lookup}[${key}];
- ${lookup}[${key}].next = expected;
+ if ( expected ) expected.last = ${iteration};
+ ${iteration}.next = expected;
}
}
} else {
// we're appending from this point forward
- if ( ${lookup}[${key}] ) {
- ${lookup}[${key}].discard = false;
- ${lookup}[${key}].next = null;
- ${lookup}[${key}].mount( ${parentNode}, ${anchor} );
+ if ( ${iteration} ) {
+ ${iteration}.discard = false;
+ ${iteration}.next = null;
+ ${iteration}.mount( ${parentNode}, ${anchor} );
} else {
- ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${lookup}[${key}].${mountOrIntro}( ${parentNode}, ${anchor} );
+ ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${iteration}.${mountOrIntro}( ${parentNode}, ${anchor} );
}
}
- if ( last ) last.next = ${lookup}[${key}];
- ${lookup}[${key}].last = last;
- ${node._block.hasIntroMethod && `${lookup}[${key}].intro( ${parentNode}, ${anchor} );`}
- last = ${lookup}[${key}];
+ if ( last ) last.next = ${iteration};
+ ${iteration}.last = last;
+ ${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`}
+ last = ${iteration};
${_iterations}[${i}] = ${lookup}[ ${key} ];
}
diff --git a/test/runtime/samples/each-block-keyed-dynamic/_config.js b/test/runtime/samples/each-block-keyed-dynamic/_config.js
new file mode 100644
index 0000000000..93832a7658
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-dynamic/_config.js
@@ -0,0 +1,35 @@
+export default {
+ data: {
+ todos: [
+ { id: 123, description: 'buy milk' },
+ { id: 234, description: 'drink milk' }
+ ]
+ },
+
+ html: `
+ buy milk
+ drink milk
+ `,
+
+ test ( assert, component, target ) {
+ const [ p1, p2 ] = target.querySelectorAll( 'p' );
+
+ component.set({
+ todos: [
+ { id: 123, description: 'buy beer' },
+ { id: 234, description: 'drink beer' }
+ ]
+ });
+ assert.htmlEqual( target.innerHTML, `
+ buy beer
+ drink beer
+ ` );
+
+ const [ p3, p4 ] = target.querySelectorAll( 'p' );
+
+ assert.equal( p1, p3 );
+ assert.equal( p2, p4 );
+
+ component.destroy();
+ }
+};
diff --git a/test/runtime/samples/each-block-keyed-dynamic/main.html b/test/runtime/samples/each-block-keyed-dynamic/main.html
new file mode 100644
index 0000000000..7d5b90a9f8
--- /dev/null
+++ b/test/runtime/samples/each-block-keyed-dynamic/main.html
@@ -0,0 +1,3 @@
+{{#each todos as todo @id}}
+ {{todo.description}}
+{{/each}}
From c0b7156318e5fbcefb7cd3f604ca62f101e93b02 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Sat, 6 May 2017 09:35:47 -0400
Subject: [PATCH 090/233] failing test for #569
---
.../component-if-placement/Component.html | 3 +++
.../samples/component-if-placement/_config.js | 20 +++++++++++++++++++
.../samples/component-if-placement/main.html | 14 +++++++++++++
3 files changed, 37 insertions(+)
create mode 100644 test/runtime/samples/component-if-placement/Component.html
create mode 100644 test/runtime/samples/component-if-placement/_config.js
create mode 100644 test/runtime/samples/component-if-placement/main.html
diff --git a/test/runtime/samples/component-if-placement/Component.html b/test/runtime/samples/component-if-placement/Component.html
new file mode 100644
index 0000000000..e229cff5cc
--- /dev/null
+++ b/test/runtime/samples/component-if-placement/Component.html
@@ -0,0 +1,3 @@
+{{#if true}}
+ Component
+{{/if}}
diff --git a/test/runtime/samples/component-if-placement/_config.js b/test/runtime/samples/component-if-placement/_config.js
new file mode 100644
index 0000000000..9b0a74df19
--- /dev/null
+++ b/test/runtime/samples/component-if-placement/_config.js
@@ -0,0 +1,20 @@
+export default {
+ data: {
+ flag: true
+ },
+
+ html: `
+ Before
+ Component
+ After
+ `,
+
+ test ( assert, component, target ) {
+ component.set( { flag: false } );
+ assert.htmlEqual( target.innerHTML, `
+ Before
+ Component
+ After
+ `);
+ }
+};
diff --git a/test/runtime/samples/component-if-placement/main.html b/test/runtime/samples/component-if-placement/main.html
new file mode 100644
index 0000000000..abb45738ba
--- /dev/null
+++ b/test/runtime/samples/component-if-placement/main.html
@@ -0,0 +1,14 @@
+Before
+{{#if flag}}
+
+{{else}}
+
+{{/if}}
+After
+
+
From 23331e605aeac9e100b7666bf7102c492c184b81 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 11:33:08 -0400
Subject: [PATCH 091/233] dont store keyed block iterations in an array
---
src/generators/dom/visitors/EachBlock.js | 69 ++++++++++++-------
.../each-block-changed-check/expected.js | 1 +
2 files changed, 45 insertions(+), 25 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index ae533002d9..0f110f8b4d 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -16,7 +16,6 @@ export default function visitEachBlock ( generator, block, state, node ) {
const { snippet } = block.contextualise( node.expression );
block.builders.create.addLine( `var ${each_block_value} = ${snippet};` );
- block.builders.create.addLine( `var ${iterations} = [];` );
if ( node.key ) {
keyed( generator, block, state, node, snippet, vars );
@@ -26,23 +25,12 @@ export default function visitEachBlock ( generator, block, state, node ) {
const isToplevel = !state.parentNode;
- if ( isToplevel ) {
- block.builders.mount.addBlock( deindent`
- for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${iterations}[${i}].${mountOrIntro}( ${block.target}, null );
- }
- ` );
- }
-
if ( node.needsAnchor ) {
block.addElement( anchor, `${generator.helper( 'createComment' )}()`, state.parentNode, true );
} else if ( node.next ) {
node.next.usedAsAnchor = true;
}
- block.builders.destroy.addBlock(
- `${generator.helper( 'destroyEach' )}( ${iterations}, ${isToplevel ? 'detach' : 'false'}, 0 );` );
-
if ( node.else ) {
const each_block_else = generator.getUniqueName( `${each_block}_else` );
@@ -109,11 +97,10 @@ export default function visitEachBlock ( generator, block, state, node ) {
}
}
-function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
+function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, i, params, anchor, mountOrIntro } ) {
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
- const _iterations = block.getUniqueName( `_${each_block}_iterations` );
if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw
node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing
@@ -125,19 +112,32 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.create.addBlock( deindent`
var ${lookup} = Object.create( null );
+ var head;
var last;
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
- ${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
+ var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
+ ${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`}
+
+ if ( last ) last.next = ${iteration};
+ ${iteration}.last = last;
+ last = ${iteration};
- if ( last ) last.next = ${lookup}[ ${key} ];
- ${lookup}[ ${key} ].last = last;
- last = ${lookup}[${key}];
+ if ( ${i} === 0 ) head = ${iteration};
}
` );
+ if ( !state.parentNode ) {
+ block.builders.mount.addBlock( deindent`
+ var ${iteration} = head;
+ while ( ${iteration} ) {
+ ${iteration}.${mountOrIntro}( ${block.target}, null );
+ ${iteration} = ${iteration}.next;
+ }
+ ` );
+ }
+
const dynamic = node._block.hasUpdateMethod;
const parentNode = state.parentNode || `${anchor}.parentNode`;
@@ -196,9 +196,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
- var ${_iterations} = Array( ${each_block_value}.length );
-
- var expected = ${iterations}[0];
+ var expected = head;
var last;
var discard_pile = [];
@@ -252,32 +250,49 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
${iteration}.last = last;
${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`}
last = ${iteration};
-
- ${_iterations}[${i}] = ${lookup}[ ${key} ];
}
if ( last ) last.next = null;
${destroy}
- ${iterations} = ${_iterations};
+ head = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}];
+ ` );
+
+ block.builders.destroy.addBlock( deindent`
+ var ${iteration} = head;
+ while ( ${iteration} ) {
+ ${iteration}.destroy( ${state.parentNode ? 'false' : 'detach'} );
+ ${iteration} = ${iteration}.next;
+ }
` );
}
function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
block.builders.create.addBlock( deindent`
+ var ${iterations} = [];
+
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
}
` );
+ if ( !state.parentNode ) {
+ block.builders.mount.addBlock( deindent`
+ for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
+ ${iterations}[${i}].${mountOrIntro}( ${block.target}, null );
+ }
+ ` );
+ }
+
const dependencies = block.findDependencies( node.expression );
const allDependencies = new Set( node._block.dependencies );
dependencies.forEach( dependency => {
allDependencies.add( dependency );
});
+ // TODO do this for keyed blocks as well
const condition = Array.from( allDependencies )
.map( dependency => `'${dependency}' in changed` )
.join( ' || ' );
@@ -331,4 +346,8 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
}
` );
}
+
+ block.builders.destroy.addBlock(
+ `${generator.helper( 'destroyEach' )}( ${iterations}, ${state.parentNode ? 'false' : 'detach'}, 0 );`
+ );
}
\ No newline at end of file
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
index b31be6e9fa..128d332196 100644
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -4,6 +4,7 @@ function create_main_fragment ( state, component ) {
var text_1_value;
var each_block_value = state.comments;
+
var each_block_iterations = [];
for ( var i = 0; i < each_block_value.length; i += 1 ) {
From f8e73c1f36645348548863cb4a023290b775c482 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 11:48:52 -0400
Subject: [PATCH 092/233] get rid of hardcoded variable names
---
src/generators/dom/visitors/EachBlock.js | 78 +++++++++++++-----------
1 file changed, 41 insertions(+), 37 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index 0f110f8b4d..dcf556ccfd 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -101,6 +101,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
+ const head = block.getUniqueName( `${each_block}_head` );
+ const last = block.getUniqueName( `${each_block}_last` );
+ const expected = block.getUniqueName( `${each_block}_expected` );
if ( node.children[0] && node.children[0].type === 'Element' ) { // TODO or text/tag/raw
node._block.first = node.children[0]._state.parentNode; // TODO this is highly confusing
@@ -112,25 +115,25 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.create.addBlock( deindent`
var ${lookup} = Object.create( null );
- var head;
- var last;
+ var ${head};
+ var ${last};
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${state.parentNode && `${iteration}.${mountOrIntro}( ${state.parentNode}, null );`}
- if ( last ) last.next = ${iteration};
- ${iteration}.last = last;
- last = ${iteration};
+ if ( ${last} ) ${last}.next = ${iteration};
+ ${iteration}.last = ${last};
+ ${last} = ${iteration};
- if ( ${i} === 0 ) head = ${iteration};
+ if ( ${i} === 0 ) ${head} = ${iteration};
}
` );
if ( !state.parentNode ) {
block.builders.mount.addBlock( deindent`
- var ${iteration} = head;
+ var ${iteration} = ${head};
while ( ${iteration} ) {
${iteration}.${mountOrIntro}( ${block.target}, null );
${iteration} = ${iteration}.next;
@@ -156,9 +159,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
` );
destroy = deindent`
- while ( expected ) {
- ${fn}( expected );
- expected = expected.next;
+ while ( ${expected} ) {
+ ${fn}( ${expected} );
+ ${expected} = ${expected}.next;
}
for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
@@ -179,9 +182,9 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
` );
destroy = deindent`
- while ( expected ) {
- ${fn}( expected );
- expected = expected.next;
+ while ( ${expected} ) {
+ ${fn}( ${expected} );
+ ${expected} = ${expected}.next;
}
for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
@@ -196,8 +199,8 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.update.addBlock( deindent`
var ${each_block_value} = ${snippet};
- var expected = head;
- var last;
+ var ${expected} = ${head};
+ var ${last};
var discard_pile = [];
@@ -207,31 +210,31 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
${dynamic && `if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
- if ( expected ) {
- if ( ${key} === expected.key ) {
- expected = expected.next;
+ if ( ${expected} ) {
+ if ( ${key} === ${expected}.key ) {
+ ${expected} = ${expected}.next;
} else {
if ( ${iteration} ) {
// probably a deletion
do {
- expected.discard = true;
- discard_pile.push( expected );
- expected = expected.next;
- } while ( expected && expected.key !== ${key} );
+ ${expected}.discard = true;
+ discard_pile.push( ${expected} );
+ ${expected} = ${expected}.next;
+ } while ( ${expected} && ${expected}.key !== ${key} );
- expected = expected && expected.next;
+ ${expected} = ${expected} && ${expected}.next;
${iteration}.discard = false;
- ${iteration}.last = last;
- ${iteration}.next = expected;
+ ${iteration}.last = ${last};
+ ${iteration}.next = ${expected};
- ${iteration}.mount( ${parentNode}, expected ? expected.first : ${anchor} );
+ ${iteration}.mount( ${parentNode}, ${expected} ? ${expected}.first : ${anchor} );
} else {
// key is being inserted
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
- ${iteration}.${mountOrIntro}( ${parentNode}, expected.first );
+ ${iteration}.${mountOrIntro}( ${parentNode}, ${expected}.first );
- if ( expected ) expected.last = ${iteration};
- ${iteration}.next = expected;
+ if ( ${expected} ) ${expected}.last = ${iteration};
+ ${iteration}.next = ${expected};
}
}
} else {
@@ -246,21 +249,21 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
}
- if ( last ) last.next = ${iteration};
- ${iteration}.last = last;
+ if ( ${last} ) ${last}.next = ${iteration};
+ ${iteration}.last = ${last};
${node._block.hasIntroMethod && `${iteration}.intro( ${parentNode}, ${anchor} );`}
- last = ${iteration};
+ ${last} = ${iteration};
}
- if ( last ) last.next = null;
+ if ( ${last} ) ${last}.next = null;
${destroy}
- head = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}];
+ ${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${node.key}];
` );
block.builders.destroy.addBlock( deindent`
- var ${iteration} = head;
+ var ${iteration} = ${head};
while ( ${iteration} ) {
${iteration}.destroy( ${state.parentNode ? 'false' : 'detach'} );
${iteration} = ${iteration}.next;
@@ -316,9 +319,10 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
+ const outro = block.getUniqueName( 'outro' );
const destroy = node._block.hasOutroMethod ?
deindent`
- function outro ( i ) {
+ function ${outro} ( i ) {
if ( ${iterations}[i] ) {
${iterations}[i].outro( function () {
${iterations}[i].destroy( true );
@@ -327,7 +331,7 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
}
}
- for ( ; ${i} < ${iterations}.length; ${i} += 1 ) outro( ${i} );
+ for ( ; ${i} < ${iterations}.length; ${i} += 1 ) ${outro}( ${i} );
` :
deindent`
${generator.helper( 'destroyEach' )}( ${iterations}, true, ${each_block_value}.length );
From b0a31dda150db4e03d21f0a557bd12e8b6a86336 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 13:02:12 -0400
Subject: [PATCH 093/233] reintro unkeyed each block iterations as necessary
---
src/generators/dom/visitors/EachBlock.js | 25 ++++++++++++++++--------
src/shared/dom.js | 2 +-
2 files changed, 18 insertions(+), 9 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.js b/src/generators/dom/visitors/EachBlock.js
index dcf556ccfd..d30406598c 100644
--- a/src/generators/dom/visitors/EachBlock.js
+++ b/src/generators/dom/visitors/EachBlock.js
@@ -304,14 +304,23 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
if ( condition !== '' ) {
const forLoopBody = node._block.hasUpdateMethod ?
- deindent`
- if ( ${iterations}[${i}] ) {
- ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
- } else {
- ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
- ${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
- }
- ` :
+ node._block.hasIntroMethod ?
+ deindent`
+ if ( ${iterations}[${i}] ) {
+ ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
+ } else {
+ ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
+ }
+ ${iterations}[${i}].intro( ${parentNode}, ${anchor} );
+ ` :
+ deindent`
+ if ( ${iterations}[${i}] ) {
+ ${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
+ } else {
+ ${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
+ ${iterations}[${i}].mount( ${parentNode}, ${anchor} );
+ }
+ ` :
deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
diff --git a/src/shared/dom.js b/src/shared/dom.js
index 6280783cb6..3c8bb78bc0 100644
--- a/src/shared/dom.js
+++ b/src/shared/dom.js
@@ -18,7 +18,7 @@ export function detachBetween ( before, after ) {
export function destroyEach ( iterations, detach, start ) {
for ( var i = start; i < iterations.length; i += 1 ) {
- iterations[i].destroy( detach );
+ if ( iterations[i] ) iterations[i].destroy( detach );
}
}
From 90a67d26c0bc01a0c8d0bf2c2e2b09120f8ad9d3 Mon Sep 17 00:00:00 2001
From: Rich-Harris
Date: Sat, 6 May 2017 15:35:46 -0400
Subject: [PATCH 094/233] -> v1.20.0
---
CHANGELOG.md | 15 +++++++++++++++
package.json | 2 +-
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc349a3401..789c00d13a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,20 @@
# Svelte changelog
+## 1.20.0
+
+* Faster, better updates of keyed each blocks ([#373](https://github.com/sveltejs/svelte/issues/373), [#543](https://github.com/sveltejs/svelte/issues/543))
+* Use element IDs to robustly track dynamically injected `
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-global/_config.js b/test/css/samples/cascade-false-global/_config.js
new file mode 100644
index 0000000000..b37866f9b6
--- /dev/null
+++ b/test/css/samples/cascade-false-global/_config.js
@@ -0,0 +1,3 @@
+export default {
+ cascade: false
+};
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-global/expected.css b/test/css/samples/cascade-false-global/expected.css
new file mode 100644
index 0000000000..96071a3d6f
--- /dev/null
+++ b/test/css/samples/cascade-false-global/expected.css
@@ -0,0 +1,12 @@
+
+ div {
+ color: red;
+ }
+
+ div.foo {
+ color: blue;
+ }
+
+ .foo {
+ font-weight: bold;
+ }
diff --git a/test/css/samples/cascade-false-global/input.html b/test/css/samples/cascade-false-global/input.html
new file mode 100644
index 0000000000..947edb6ab5
--- /dev/null
+++ b/test/css/samples/cascade-false-global/input.html
@@ -0,0 +1,16 @@
+red
+bold/blue
+
+
\ No newline at end of file
diff --git a/test/css/samples/cascade-false-keyframes/expected.css b/test/css/samples/cascade-false-keyframes/expected.css
new file mode 100644
index 0000000000..cf1d171417
--- /dev/null
+++ b/test/css/samples/cascade-false-keyframes/expected.css
@@ -0,0 +1,13 @@
+
+ @keyframes svelte-776829126-why {
+ 0% { color: red; }
+ 100% { color: blue; }
+ }
+
+ [svelte-776829126].animated, [svelte-776829126] .animated {
+ animation: svelte-776829126-why 2s;
+ }
+
+ [svelte-776829126].also-animated, [svelte-776829126] .also-animated {
+ animation: not-defined-here 2s;
+ }
diff --git a/test/css/samples/cascade-false-keyframes/input.html b/test/css/samples/cascade-false-keyframes/input.html
new file mode 100644
index 0000000000..ba220a5e22
--- /dev/null
+++ b/test/css/samples/cascade-false-keyframes/input.html
@@ -0,0 +1,17 @@
+animated
+also animated
+
+
\ No newline at end of file
diff --git a/test/css/samples/cascade-false/_config.js b/test/css/samples/cascade-false/_config.js
new file mode 100644
index 0000000000..b37866f9b6
--- /dev/null
+++ b/test/css/samples/cascade-false/_config.js
@@ -0,0 +1,3 @@
+export default {
+ cascade: false
+};
\ No newline at end of file
diff --git a/test/css/samples/cascade-false/expected.css b/test/css/samples/cascade-false/expected.css
new file mode 100644
index 0000000000..0a9cb4502e
--- /dev/null
+++ b/test/css/samples/cascade-false/expected.css
@@ -0,0 +1,12 @@
+
+ div[svelte-4161687011] {
+ color: red;
+ }
+
+ div.foo[svelte-4161687011] {
+ color: blue;
+ }
+
+ .foo[svelte-4161687011] {
+ font-weight: bold;
+ }
diff --git a/test/css/samples/cascade-false/input.html b/test/css/samples/cascade-false/input.html
new file mode 100644
index 0000000000..f56b586ef8
--- /dev/null
+++ b/test/css/samples/cascade-false/input.html
@@ -0,0 +1,16 @@
+red
+bold/blue
+
+
\ No newline at end of file
From 869ae4b3cf9836d67d5f8ea878f1d35c0531864a Mon Sep 17 00:00:00 2001
From: Luke Edwards
Date: Wed, 31 May 2017 17:30:11 -0700
Subject: [PATCH 140/233] improve assign() util performance
---
src/shared/utils.js | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/shared/utils.js b/src/shared/utils.js
index edd1da3a14..9eb3ed2cee 100644
--- a/src/shared/utils.js
+++ b/src/shared/utils.js
@@ -1,10 +1,11 @@
export function noop () {}
export function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
-}
\ No newline at end of file
+}
From a538f7a07107056eb831bece80263de4b6bbe24d Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 31 May 2017 20:47:17 -0400
Subject: [PATCH 141/233] update tests to use new assign helper
---
.../collapses-text-around-comments/_actual-bundle.js | 7 ++++---
.../collapses-text-around-comments/expected-bundle.js | 7 ++++---
test/js/samples/computed-collapsed-if/_actual-bundle.js | 7 ++++---
test/js/samples/computed-collapsed-if/expected-bundle.js | 7 ++++---
test/js/samples/each-block-changed-check/_actual-bundle.js | 7 ++++---
.../js/samples/each-block-changed-check/expected-bundle.js | 7 ++++---
test/js/samples/event-handlers-custom/_actual-bundle.js | 7 ++++---
test/js/samples/event-handlers-custom/expected-bundle.js | 7 ++++---
test/js/samples/if-block-no-update/_actual-bundle.js | 7 ++++---
test/js/samples/if-block-no-update/expected-bundle.js | 7 ++++---
test/js/samples/if-block-simple/_actual-bundle.js | 7 ++++---
test/js/samples/if-block-simple/expected-bundle.js | 7 ++++---
test/js/samples/non-imported-component/_actual-bundle.js | 7 ++++---
test/js/samples/non-imported-component/expected-bundle.js | 7 ++++---
.../onrender-onteardown-rewritten/_actual-bundle.js | 7 ++++---
.../onrender-onteardown-rewritten/expected-bundle.js | 7 ++++---
test/js/samples/use-elements-as-anchors/_actual-bundle.js | 7 ++++---
test/js/samples/use-elements-as-anchors/expected-bundle.js | 7 ++++---
18 files changed, 72 insertions(+), 54 deletions(-)
diff --git a/test/js/samples/collapses-text-around-comments/_actual-bundle.js b/test/js/samples/collapses-text-around-comments/_actual-bundle.js
index 6372e1ef4f..669f13d791 100644
--- a/test/js/samples/collapses-text-around-comments/_actual-bundle.js
+++ b/test/js/samples/collapses-text-around-comments/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
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 6372e1ef4f..669f13d791 100644
--- a/test/js/samples/collapses-text-around-comments/expected-bundle.js
+++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/computed-collapsed-if/_actual-bundle.js b/test/js/samples/computed-collapsed-if/_actual-bundle.js
index 65a703f19b..f3719a18af 100644
--- a/test/js/samples/computed-collapsed-if/_actual-bundle.js
+++ b/test/js/samples/computed-collapsed-if/_actual-bundle.js
@@ -1,9 +1,10 @@
function noop () {}
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js
index 65a703f19b..f3719a18af 100644
--- a/test/js/samples/computed-collapsed-if/expected-bundle.js
+++ b/test/js/samples/computed-collapsed-if/expected-bundle.js
@@ -1,9 +1,10 @@
function noop () {}
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/each-block-changed-check/_actual-bundle.js b/test/js/samples/each-block-changed-check/_actual-bundle.js
index 95a876bad5..2c7ba6db88 100644
--- a/test/js/samples/each-block-changed-check/_actual-bundle.js
+++ b/test/js/samples/each-block-changed-check/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
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 95a876bad5..2c7ba6db88 100644
--- a/test/js/samples/each-block-changed-check/expected-bundle.js
+++ b/test/js/samples/each-block-changed-check/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/event-handlers-custom/_actual-bundle.js b/test/js/samples/event-handlers-custom/_actual-bundle.js
index 7fae3fe412..3680d44d25 100644
--- a/test/js/samples/event-handlers-custom/_actual-bundle.js
+++ b/test/js/samples/event-handlers-custom/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js
index 7fae3fe412..3680d44d25 100644
--- a/test/js/samples/event-handlers-custom/expected-bundle.js
+++ b/test/js/samples/event-handlers-custom/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/if-block-no-update/_actual-bundle.js b/test/js/samples/if-block-no-update/_actual-bundle.js
index 29b6104b9b..781adc2850 100644
--- a/test/js/samples/if-block-no-update/_actual-bundle.js
+++ b/test/js/samples/if-block-no-update/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
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 29b6104b9b..781adc2850 100644
--- a/test/js/samples/if-block-no-update/expected-bundle.js
+++ b/test/js/samples/if-block-no-update/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/if-block-simple/_actual-bundle.js b/test/js/samples/if-block-simple/_actual-bundle.js
index 0998c38be3..67abd439bb 100644
--- a/test/js/samples/if-block-simple/_actual-bundle.js
+++ b/test/js/samples/if-block-simple/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js
index 0998c38be3..67abd439bb 100644
--- a/test/js/samples/if-block-simple/expected-bundle.js
+++ b/test/js/samples/if-block-simple/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/non-imported-component/_actual-bundle.js b/test/js/samples/non-imported-component/_actual-bundle.js
index 3f8554332a..b0995e4498 100644
--- a/test/js/samples/non-imported-component/_actual-bundle.js
+++ b/test/js/samples/non-imported-component/_actual-bundle.js
@@ -1,9 +1,10 @@
import Imported from 'Imported.html';
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js
index 3f8554332a..b0995e4498 100644
--- a/test/js/samples/non-imported-component/expected-bundle.js
+++ b/test/js/samples/non-imported-component/expected-bundle.js
@@ -1,9 +1,10 @@
import Imported from 'Imported.html';
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/onrender-onteardown-rewritten/_actual-bundle.js b/test/js/samples/onrender-onteardown-rewritten/_actual-bundle.js
index 424d82b761..4c035dc8fa 100644
--- a/test/js/samples/onrender-onteardown-rewritten/_actual-bundle.js
+++ b/test/js/samples/onrender-onteardown-rewritten/_actual-bundle.js
@@ -1,9 +1,10 @@
function noop () {}
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
index 424d82b761..4c035dc8fa 100644
--- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
+++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js
@@ -1,9 +1,10 @@
function noop () {}
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
diff --git a/test/js/samples/use-elements-as-anchors/_actual-bundle.js b/test/js/samples/use-elements-as-anchors/_actual-bundle.js
index 4cc501c982..5277d03f53 100644
--- a/test/js/samples/use-elements-as-anchors/_actual-bundle.js
+++ b/test/js/samples/use-elements-as-anchors/_actual-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
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 4cc501c982..5277d03f53 100644
--- a/test/js/samples/use-elements-as-anchors/expected-bundle.js
+++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js
@@ -1,7 +1,8 @@
function assign ( target ) {
- for ( var i = 1; i < arguments.length; i += 1 ) {
- var source = arguments[i];
- for ( var k in source ) target[k] = source[k];
+ var k, source, i = 1, len = arguments.length;
+ for ( ; i < len; i++ ) {
+ source = arguments[i];
+ for ( k in source ) target[k] = source[k];
}
return target;
From 41c214a9860fc815c5239a69e97c5a8c82c292be Mon Sep 17 00:00:00 2001
From: Hunter Perrin
Date: Wed, 31 May 2017 18:58:30 -0700
Subject: [PATCH 142/233] Fixed each block producing elements in wrong order
after change. Fixes #610.
---
src/generators/dom/visitors/EachBlock.ts | 4 ++--
test/js/samples/each-block-changed-check/_actual-bundle.js | 2 +-
test/js/samples/each-block-changed-check/expected-bundle.js | 2 +-
test/js/samples/each-block-changed-check/expected.js | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts
index 212f1af8b3..39657aff4d 100644
--- a/src/generators/dom/visitors/EachBlock.ts
+++ b/src/generators/dom/visitors/EachBlock.ts
@@ -139,7 +139,7 @@ function keyed ( generator: DomGenerator, block: Block, state: State, node: Node
block.builders.mount.addBlock( deindent`
var ${iteration} = ${head};
while ( ${iteration} ) {
- ${iteration}.${mountOrIntro}( ${block.target}, null );
+ ${iteration}.${mountOrIntro}( ${block.target}, anchor );
${iteration} = ${iteration}.next;
}
` );
@@ -284,7 +284,7 @@ function unkeyed ( generator: DomGenerator, block: Block, state: State, node: No
if ( !state.parentNode ) {
block.builders.mount.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
- ${iterations}[${i}].${mountOrIntro}( ${block.target}, null );
+ ${iterations}[${i}].${mountOrIntro}( ${block.target}, anchor );
}
` );
}
diff --git a/test/js/samples/each-block-changed-check/_actual-bundle.js b/test/js/samples/each-block-changed-check/_actual-bundle.js
index 95a876bad5..759b1ef75b 100644
--- a/test/js/samples/each-block-changed-check/_actual-bundle.js
+++ b/test/js/samples/each-block-changed-check/_actual-bundle.js
@@ -153,7 +153,7 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
- each_block_iterations[i].mount( target, null );
+ each_block_iterations[i].mount( target, anchor );
}
insertNode( text, target, anchor );
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 95a876bad5..759b1ef75b 100644
--- a/test/js/samples/each-block-changed-check/expected-bundle.js
+++ b/test/js/samples/each-block-changed-check/expected-bundle.js
@@ -153,7 +153,7 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
- each_block_iterations[i].mount( target, null );
+ each_block_iterations[i].mount( target, anchor );
}
insertNode( text, target, anchor );
diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js
index 128d332196..96624d88db 100644
--- a/test/js/samples/each-block-changed-check/expected.js
+++ b/test/js/samples/each-block-changed-check/expected.js
@@ -19,7 +19,7 @@ function create_main_fragment ( state, component ) {
return {
mount: function ( target, anchor ) {
for ( var i = 0; i < each_block_iterations.length; i += 1 ) {
- each_block_iterations[i].mount( target, null );
+ each_block_iterations[i].mount( target, anchor );
}
insertNode( text, target, anchor );
@@ -160,4 +160,4 @@ SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = functio
this._torndown = true;
};
-export default SvelteComponent;
\ No newline at end of file
+export default SvelteComponent;
From e1001a7f34fd62a3ed80ad81037f6673eb738487 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 31 May 2017 22:31:35 -0400
Subject: [PATCH 143/233] add test for #610
---
.../Nested.html | 5 +++
.../_config.js | 36 +++++++++++++++++++
.../main.html | 13 +++++++
3 files changed, 54 insertions(+)
create mode 100644 test/runtime/samples/each-block-containing-component-in-if/Nested.html
create mode 100644 test/runtime/samples/each-block-containing-component-in-if/_config.js
create mode 100644 test/runtime/samples/each-block-containing-component-in-if/main.html
diff --git a/test/runtime/samples/each-block-containing-component-in-if/Nested.html b/test/runtime/samples/each-block-containing-component-in-if/Nested.html
new file mode 100644
index 0000000000..b9d76a696d
--- /dev/null
+++ b/test/runtime/samples/each-block-containing-component-in-if/Nested.html
@@ -0,0 +1,5 @@
+{{#if show}}
+ {{#each fields as field}}
+ {{field}}
+ {{/each}}
+{{/if}}
\ No newline at end of file
diff --git a/test/runtime/samples/each-block-containing-component-in-if/_config.js b/test/runtime/samples/each-block-containing-component-in-if/_config.js
new file mode 100644
index 0000000000..bfee6beb20
--- /dev/null
+++ b/test/runtime/samples/each-block-containing-component-in-if/_config.js
@@ -0,0 +1,36 @@
+export default {
+ data: {
+ show: false,
+ fields: [1, 2]
+ },
+
+ html: ``,
+
+ test ( assert, component, target ) {
+ component.set({
+ show: true,
+ fields: [1, 2, 3]
+ });
+
+ assert.htmlEqual( target.innerHTML, `
+
+ 1
+ 2
+ 3
+
+ ` );
+
+ component.set({
+ fields: [1, 2, 3, 4]
+ });
+
+ assert.htmlEqual( target.innerHTML, `
+
+ 1
+ 2
+ 3
+ 4
+
+ ` );
+ }
+};
diff --git a/test/runtime/samples/each-block-containing-component-in-if/main.html b/test/runtime/samples/each-block-containing-component-in-if/main.html
new file mode 100644
index 0000000000..9a6bbfd10f
--- /dev/null
+++ b/test/runtime/samples/each-block-containing-component-in-if/main.html
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
From 0491d7a6cc48a8a6af0c66a2e3111f9406125fee Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Wed, 31 May 2017 22:45:52 -0400
Subject: [PATCH 144/233] -> v1.22.0
---
CHANGELOG.md | 8 ++++++++
package.json | 2 +-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee6b79e43a..cd44ac7808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Svelte changelog
+## 1.22.0
+
+* Symmetry between `mount` and `unmount`. This is potentially a breaking change if your components import other components that were precompiled with an earlier version of Svelte ([#592](https://github.com/sveltejs/svelte/issues/592))
+* Add `cascade` option, which prevents styles affecting child components if `false`, unless selectors are wrapped in `:global(...)` and keyframe declaration IDs are prefixed with `-global-`. This will become the default behaviour in v2 ([#583](https://github.com/sveltejs/svelte/issues/583))
+* Support binding to computed member expressions ([#602](https://github.com/sveltejs/svelte/issues/602))
+* Coerce empty string in `number`/`range` inputs to `undefined`, not `0` ([#584](https://github.com/sveltejs/svelte/issues/584))
+* Fix insert location of DOM elements in each/if/nested component edge cases ([#610](https://github.com/sveltejs/svelte/issues/610))
+
## 1.21.0
* Always use `helpers` if referenced, not just for call expressions ([#575](https://github.com/sveltejs/svelte/issues/575))
diff --git a/package.json b/package.json
index 958621042a..00a27c6e18 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "1.21.0",
+ "version": "1.22.0",
"description": "The magical disappearing UI framework",
"main": "compiler/svelte.js",
"files": [
From 9112671263399212eb96b07996dd38c78c3d46c9 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Thu, 1 Jun 2017 07:28:27 -0400
Subject: [PATCH 145/233] sanitize event name in handler function name (#612)
---
.../dom/visitors/Element/EventHandler.ts | 8 +++----
.../samples/event-handler-sanitize/_config.js | 21 +++++++++++++++++++
.../samples/event-handler-sanitize/main.html | 5 +++++
3 files changed, 29 insertions(+), 5 deletions(-)
create mode 100644 test/runtime/samples/event-handler-sanitize/_config.js
create mode 100644 test/runtime/samples/event-handler-sanitize/main.html
diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts
index 1bf9dda947..b244bdcae4 100644
--- a/src/generators/dom/visitors/Element/EventHandler.ts
+++ b/src/generators/dom/visitors/Element/EventHandler.ts
@@ -47,9 +47,7 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
// get a name for the event handler that is globally unique
// if hoisted, locally unique otherwise
- const handlerName = shouldHoist ?
- generator.getUniqueName( `${name}_handler` ) :
- block.getUniqueName( `${name}_handler` );
+ const handlerName = ( shouldHoist ? generator : block ).getUniqueName( `${name.replace( /[^a-zA-Z0-9_$]/g, '_' )}_handler` );
// create the handler body
const handlerBody = deindent`
@@ -71,7 +69,7 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
`;
if ( shouldHoist ) {
- generator.blocks.push({
+ generator.blocks.push({
render: () => handler
});
} else {
@@ -91,4 +89,4 @@ export default function visitEventHandler ( generator: DomGenerator, block: Bloc
${generator.helper( 'removeEventListener' )}( ${state.parentNode}, '${name}', ${handlerName} );
` );
}
-}
\ No newline at end of file
+}
diff --git a/test/runtime/samples/event-handler-sanitize/_config.js b/test/runtime/samples/event-handler-sanitize/_config.js
new file mode 100644
index 0000000000..2037f292f2
--- /dev/null
+++ b/test/runtime/samples/event-handler-sanitize/_config.js
@@ -0,0 +1,21 @@
+export default {
+ html: `
+ toggle
+ `,
+
+ test ( assert, component, target, window ) {
+ const div = target.querySelector( 'div' );
+ const event = new window.MouseEvent( 'some-event' );
+
+ div.dispatchEvent( event );
+ assert.htmlEqual( target.innerHTML, `
+ toggle
+ hello!
+ ` );
+
+ div.dispatchEvent( event );
+ assert.htmlEqual( target.innerHTML, `
+ toggle
+ ` );
+ }
+};
diff --git a/test/runtime/samples/event-handler-sanitize/main.html b/test/runtime/samples/event-handler-sanitize/main.html
new file mode 100644
index 0000000000..eb8e10f825
--- /dev/null
+++ b/test/runtime/samples/event-handler-sanitize/main.html
@@ -0,0 +1,5 @@
+toggle
+
+{{#if visible}}
+ hello!
+{{/if}}
From d0996e071e9099f2c268f8b6b68cd842a8fa16d2 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Thu, 1 Jun 2017 07:36:18 -0400
Subject: [PATCH 146/233] document `cascade` option in readme
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index f116e2eeb1..526867a53d 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,7 @@ The Svelte compiler optionally takes a second argument, an object of configurati
| `name` | `string` | The name of the constructor in the compiled component. | `'SvelteComponent'` |
| `filename` | `string` | The filename to use in sourcemaps and compiler error and warning messages. | `'SvelteComponent.html'` |
| `amd`.`id` | `string` | The AMD module ID to use for the `'amd'` and `'umd'` output formats. | `undefined` |
+| `cascade` | `true`, `false` | Whether to cascade all of the component's styles to child components. If `false`, only selectors wrapped in `:global(...)` and keyframe IDs beginning with `-global-` are cascaded. | `true` |
| `shared` | `true`, `false`, `string` | Whether to import various helpers from a shared external library. When you have a project with multiple components, this reduces the overall size of your JavaScript bundle, at the expense of having immediately-usable component. You can pass a string of the module path to use, or `true` will import from `'svelte/shared.js'`. | `false` |
| `dev` | `true`, `false` | Whether to enable run-time checks in the compiled component. These are helpful during development, but slow your component down. | `false` |
| `css` | `true`, `false` | Whether to include code to inject your component's styles into the DOM. | `true` |
From 6bfddc21413174e30b570f361f6e14c6a51c7626 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Thu, 1 Jun 2017 08:34:43 -0400
Subject: [PATCH 147/233] -> v1.22.1
---
CHANGELOG.md | 4 ++++
package.json | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd44ac7808..595e1de605 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Svelte changelog
+## 1.22.1
+
+* Sanitise event handler names ([#612](https://github.com/sveltejs/svelte/issues/612))
+
## 1.22.0
* Symmetry between `mount` and `unmount`. This is potentially a breaking change if your components import other components that were precompiled with an earlier version of Svelte ([#592](https://github.com/sveltejs/svelte/issues/592))
diff --git a/package.json b/package.json
index 00a27c6e18..d049875125 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "1.22.0",
+ "version": "1.22.1",
"description": "The magical disappearing UI framework",
"main": "compiler/svelte.js",
"files": [
From 03616943dd6186a6592ed81edf8d2957727d8080 Mon Sep 17 00:00:00 2001
From: Conduitry
Date: Fri, 2 Jun 2017 20:58:28 -0400
Subject: [PATCH 148/233] update for magic-string .overwrite API change
---
src/generators/Generator.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts
index f09de86550..6ee4171cf1 100644
--- a/src/generators/Generator.ts
+++ b/src/generators/Generator.ts
@@ -114,7 +114,7 @@ export default class Generator {
}
if ( node.type === 'ThisExpression' ) {
- if ( lexicalDepth === 0 && context ) code.overwrite( node.start, node.end, context, true );
+ if ( lexicalDepth === 0 && context ) code.overwrite( node.start, node.end, context, { storeName: true, contentOnly: false } );
}
else if ( isReference( node, parent ) ) {
@@ -129,7 +129,7 @@ export default class Generator {
const contextName = contexts.get( name );
if ( contextName !== name ) {
// this is true for 'reserved' names like `state` and `component`
- code.overwrite( node.start, node.start + name.length, contextName, true );
+ code.overwrite( node.start, node.start + name.length, contextName, { storeName: true, contentOnly: false } );
}
if ( !~usedContexts.indexOf( name ) ) usedContexts.push( name );
@@ -426,13 +426,13 @@ export default class Generator {
// Remove these after version 2
if ( templateProperties.onrender ) {
const { key } = templateProperties.onrender;
- this.code.overwrite( key.start, key.end, 'oncreate', true );
+ this.code.overwrite( key.start, key.end, 'oncreate', { storeName: true, contentOnly: false } );
templateProperties.oncreate = templateProperties.onrender;
}
if ( templateProperties.onteardown ) {
const { key } = templateProperties.onteardown;
- this.code.overwrite( key.start, key.end, 'ondestroy', true );
+ this.code.overwrite( key.start, key.end, 'ondestroy', { storeName: true, contentOnly: false } );
templateProperties.ondestroy = templateProperties.onteardown;
}
From 2f54282ac005506d774899da5e82d9749f526db1 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 2 Jun 2017 21:47:58 -0400
Subject: [PATCH 149/233] remove temporary file
---
rename.js | 9 ---------
1 file changed, 9 deletions(-)
delete mode 100644 rename.js
diff --git a/rename.js b/rename.js
deleted file mode 100644
index ad3804e39d..0000000000
--- a/rename.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const glob = require( 'glob' );
-const fs = require( 'fs' );
-
-glob.sync( 'src/**/*.js' ).forEach( file => {
- console.log( file );
- const js = fs.readFileSync( file, 'utf-8' );
- fs.writeFileSync( file.replace( /\.js$/, '.ts' ), js );
- fs.unlinkSync( file );
-});
\ No newline at end of file
From 399ee4595aa4a0dd4d10c40560153d4cb4d785f9 Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 2 Jun 2017 21:48:08 -0400
Subject: [PATCH 150/233] add prettier
---
package.json | 4 +++-
yarn.lock | 18 ++++++++++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index d049875125..87310dc409 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,8 @@
"dev": "node src/shared/_build.js && rollup -c rollup/rollup.config.main.js -w",
"dev:shared": "rollup -c rollup/rollup.config.shared.js -w",
"pretest": "npm run build",
- "prepublish": "npm run build && npm run lint"
+ "prepublish": "npm run build && npm run lint",
+ "prettier": "prettier --use-tabs --single-quote --write \"src/**/*.ts\" \"src/**/*.js\""
},
"repository": {
"type": "git",
@@ -70,6 +71,7 @@
"mocha": "^3.2.0",
"node-resolve": "^1.3.3",
"nyc": "^10.0.0",
+ "prettier": "^1.4.1",
"reify": "^0.4.4",
"rollup": "^0.39.0",
"rollup-plugin-buble": "^0.15.0",
diff --git a/yarn.lock b/yarn.lock
index 0304d30ae6..abc1ddc757 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,14 @@
# yarn lockfile v1
+"@types/mocha@^2.2.41":
+ version "2.2.41"
+ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.41.tgz#e27cf0817153eb9f2713b2d3f6c68f1e1c3ca608"
+
+"@types/node@^7.0.22":
+ version "7.0.27"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.27.tgz#ba5e1a87aca2b4f5817289615ffe56472927687e"
+
abab@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
@@ -1915,6 +1923,12 @@ magic-string@^0.19.0, magic-string@~0.19.0:
dependencies:
vlq "^0.2.1"
+magic-string@^0.21.1:
+ version "0.21.3"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.21.3.tgz#87e201009ebfde6f46dc5757305a70af71e31624"
+ dependencies:
+ vlq "^0.2.1"
+
md5-hex@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4"
@@ -2221,6 +2235,10 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
+prettier@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.1.tgz#3526cc46aea102e980db5b70cabe2020910ef142"
+
private@^0.1.6:
version "0.1.7"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1"
From 84595fb38192ef00dcad44676e64155ba85f26af Mon Sep 17 00:00:00 2001
From: Rich Harris
Date: Fri, 2 Jun 2017 21:57:25 -0400
Subject: [PATCH 151/233] run prettier on src, update tests
---
src/generators/Generator.ts | 490 +++++++++++-------
src/generators/dom/Block.ts | 208 ++++----
src/generators/dom/index.ts | 298 +++++++----
src/generators/dom/interfaces.ts | 2 +-
src/generators/dom/preprocess.ts | 314 ++++++-----
src/generators/dom/visit.ts | 13 +-
.../dom/visitors/Component/Attribute.ts | 68 +--
.../dom/visitors/Component/Binding.ts | 70 ++-
.../dom/visitors/Component/Component.ts | 164 +++---
.../dom/visitors/Component/EventHandler.ts | 45 +-
src/generators/dom/visitors/Component/Ref.ts | 15 +-
src/generators/dom/visitors/EachBlock.ts | 281 ++++++----
.../dom/visitors/Element/Attribute.ts | 154 +++---
.../dom/visitors/Element/Binding.ts | 231 ++++++---
.../dom/visitors/Element/Element.ts | 133 +++--
.../dom/visitors/Element/EventHandler.ts | 96 ++--
src/generators/dom/visitors/Element/Ref.ts | 14 +-
.../dom/visitors/Element/addTransitions.ts | 79 +--
.../Element/getStaticAttributeValue.ts | 14 +-
src/generators/dom/visitors/Element/lookup.ts | 313 +++++++----
.../dom/visitors/Element/meta/Window.ts | 131 +++--
src/generators/dom/visitors/IfBlock.ts | 308 ++++++-----
src/generators/dom/visitors/MustacheTag.ts | 26 +-
src/generators/dom/visitors/RawMustacheTag.ts | 47 +-
src/generators/dom/visitors/Text.ts | 18 +-
src/generators/dom/visitors/YieldTag.ts | 10 +-
.../dom/visitors/shared/binding/getSetter.ts | 40 +-
src/generators/server-side-rendering/Block.ts | 34 +-
src/generators/server-side-rendering/index.ts | 75 ++-
src/generators/server-side-rendering/visit.ts | 12 +-
.../server-side-rendering/visitors/Comment.ts | 2 +-
.../visitors/Component.ts | 73 +--
.../visitors/EachBlock.ts | 36 +-
.../server-side-rendering/visitors/Element.ts | 62 ++-
.../server-side-rendering/visitors/IfBlock.ts | 28 +-
.../visitors/MustacheTag.ts | 12 +-
.../visitors/RawMustacheTag.ts | 12 +-
.../server-side-rendering/visitors/Text.ts | 10 +-
.../visitors/YieldTag.ts | 6 +-
.../visitors/meta/Window.ts | 4 +-
src/generators/shared/processCss.ts | 113 ++--
src/generators/shared/utils/getGlobals.ts | 48 +-
src/generators/shared/utils/getIntro.ts | 101 ++--
src/generators/shared/utils/getOutro.ts | 29 +-
src/generators/shared/utils/walkHtml.ts | 20 +-
src/index.ts | 67 +--
src/interfaces.ts | 14 +-
src/parse/index.ts | 124 +++--
src/parse/read/directives.ts | 109 ++--
src/parse/read/expression.ts | 24 +-
src/parse/read/script.ts | 19 +-
src/parse/read/style.ts | 20 +-
src/parse/state/fragment.ts | 6 +-
src/parse/state/mustache.ts | 152 +++---
src/parse/state/tag.ts | 334 ++++++------
src/parse/state/text.ts | 12 +-
src/parse/utils/entities.ts | 2 +-
src/parse/utils/hash.ts | 4 +-
src/parse/utils/html.ts | 78 ++-
src/server-side-rendering/register.js | 12 +-
src/shared/_build.js | 41 +-
src/shared/dom.js | 66 +--
src/shared/index.js | 111 ++--
src/shared/transitions.js | 133 +++--
src/shared/utils.js | 13 +-
src/utils/CodeBuilder.ts | 58 ++-
src/utils/CompileError.ts | 28 +-
src/utils/__test__.ts | 132 +++--
src/utils/annotateWithScopes.ts | 106 ++--
src/utils/deindent.js | 45 +-
src/utils/flattenReference.ts | 16 +-
src/utils/getCodeFrame.ts | 37 +-
src/utils/globalWhitelist.ts | 27 +-
src/utils/isReference.ts | 22 +-
src/utils/isVoidElementName.ts | 4 +-
src/utils/namespaces.ts | 24 +-
src/utils/removeNode.ts | 42 +-
src/utils/reservedNames.ts | 55 +-
src/utils/spaces.js | 4 +-
src/utils/trim.ts | 12 +-
src/validate/html/index.ts | 52 +-
src/validate/html/validateElement.ts | 166 +++---
src/validate/html/validateEventHandler.ts | 43 +-
src/validate/html/validateWindow.ts | 46 +-
src/validate/index.ts | 53 +-
src/validate/js/index.ts | 86 +--
src/validate/js/propValidators/components.ts | 26 +-
src/validate/js/propValidators/computed.ts | 52 +-
src/validate/js/propValidators/data.ts | 10 +-
src/validate/js/propValidators/events.ts | 13 +-
src/validate/js/propValidators/helpers.ts | 62 ++-
src/validate/js/propValidators/methods.ts | 36 +-
src/validate/js/propValidators/namespace.ts | 24 +-
src/validate/js/propValidators/oncreate.ts | 11 +-
src/validate/js/propValidators/ondestroy.ts | 11 +-
src/validate/js/propValidators/onrender.ts | 9 +-
src/validate/js/propValidators/onteardown.ts | 9 +-
src/validate/js/propValidators/transitions.ts | 15 +-
src/validate/js/utils/checkForAccessors.ts | 12 +-
src/validate/js/utils/checkForComputedKeys.ts | 11 +-
src/validate/js/utils/checkForDupes.ts | 13 +-
src/validate/js/utils/usesThisOrArguments.ts | 20 +-
src/validate/utils/FuzzySet.ts | 72 ++-
src/validate/utils/fuzzymatch.ts | 12 +-
src/validate/utils/list.ts | 10 +-
.../expected-bundle.js | 122 ++---
.../computed-collapsed-if/expected-bundle.js | 98 ++--
.../expected-bundle.js | 130 ++---
.../each-block-changed-check/expected.js | 2 +-
.../event-handlers-custom/expected-bundle.js | 114 ++--
.../if-block-no-update/expected-bundle.js | 122 ++---
.../if-block-simple/expected-bundle.js | 122 ++---
.../non-imported-component/expected-bundle.js | 106 ++--
.../expected-bundle.js | 98 ++--
.../expected-bundle.js | 122 ++---
test/js/update.js | 10 +
116 files changed, 4787 insertions(+), 3345 deletions(-)
create mode 100644 test/js/update.js
diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts
index 6ee4171cf1..09b7e3409a 100644
--- a/src/generators/Generator.ts
+++ b/src/generators/Generator.ts
@@ -42,7 +42,12 @@ export default class Generator {
aliases: Map;
usedNames: Set;
- constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
+ constructor(
+ parsed: Parsed,
+ source: string,
+ name: string,
+ options: CompileOptions
+ ) {
this.parsed = parsed;
this.source = source;
this.name = name;
@@ -61,9 +66,9 @@ export default class Generator {
// in dev mode
this.expectedProperties = new Set();
- this.code = new MagicString( source );
+ this.code = new MagicString(source);
this.cascade = options.cascade !== false; // TODO remove this option in v2
- this.css = parsed.css ? processCss( parsed, this.code, this.cascade ) : null;
+ this.css = parsed.css ? processCss(parsed, this.code, this.cascade) : null;
this.cssId = parsed.css ? `svelte-${parsed.hash}` : '';
this.usesRefs = false;
@@ -71,105 +76,112 @@ export default class Generator {
// Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`;
this.importedNames = new Set();
this.aliases = new Map();
- this.usedNames = new Set( [ name ] );
+ this.usedNames = new Set([name]);
}
- addSourcemapLocations ( node: Node ) {
- walk( node, {
- enter: ( node: Node ) => {
- this.code.addSourcemapLocation( node.start );
- this.code.addSourcemapLocation( node.end );
+ addSourcemapLocations(node: Node) {
+ walk(node, {
+ enter: (node: Node) => {
+ this.code.addSourcemapLocation(node.start);
+ this.code.addSourcemapLocation(node.end);
}
});
}
- alias ( name: string ) {
- if ( !this.aliases.has( name ) ) {
- this.aliases.set( name, this.getUniqueName( name ) );
+ alias(name: string) {
+ if (!this.aliases.has(name)) {
+ this.aliases.set(name, this.getUniqueName(name));
}
- return this.aliases.get( name );
+ return this.aliases.get(name);
}
- contextualise ( block: DomBlock | SsrBlock, expression: Node, context: string, isEventHandler: boolean ) {
- this.addSourcemapLocations( expression );
+ contextualise(
+ block: DomBlock | SsrBlock,
+ expression: Node,
+ context: string,
+ isEventHandler: boolean
+ ) {
+ this.addSourcemapLocations(expression);
const usedContexts: string[] = [];
const { code, helpers } = this;
const { contexts, indexes } = block;
- let scope = annotateWithScopes( expression ); // TODO this already happens in findDependencies
+ let scope = annotateWithScopes(expression); // TODO this already happens in findDependencies
let lexicalDepth = 0;
const self = this;
- walk( expression, {
- enter ( node: Node, parent: Node, key: string ) {
- if ( /^Function/.test( node.type ) ) lexicalDepth += 1;
+ walk(expression, {
+ enter(node: Node, parent: Node, key: string) {
+ if (/^Function/.test(node.type)) lexicalDepth += 1;
- if ( node._scope ) {
+ if (node._scope) {
scope = node._scope;
return;
}
- if ( node.type === 'ThisExpression' ) {
- if ( lexicalDepth === 0 && context ) code.overwrite( node.start, node.end, context, { storeName: true, contentOnly: false } );
- }
-
- else if ( isReference( node, parent ) ) {
- const { name } = flattenReference( node );
- if ( scope.has( name ) ) return;
-
- if ( name === 'event' && isEventHandler ) {
+ if (node.type === 'ThisExpression') {
+ if (lexicalDepth === 0 && context)
+ code.overwrite(node.start, node.end, context, {
+ storeName: true,
+ contentOnly: false
+ });
+ } else if (isReference(node, parent)) {
+ const { name } = flattenReference(node);
+ if (scope.has(name)) return;
+
+ if (name === 'event' && isEventHandler) {
// noop
- }
-
- else if ( contexts.has( name ) ) {
- const contextName = contexts.get( name );
- if ( contextName !== name ) {
+ } else if (contexts.has(name)) {
+ const contextName = contexts.get(name);
+ if (contextName !== name) {
// this is true for 'reserved' names like `state` and `component`
- code.overwrite( node.start, node.start + name.length, contextName, { storeName: true, contentOnly: false } );
+ code.overwrite(
+ node.start,
+ node.start + name.length,
+ contextName,
+ { storeName: true, contentOnly: false }
+ );
}
- if ( !~usedContexts.indexOf( name ) ) usedContexts.push( 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 );
- }
-
- else {
+ if (!~usedContexts.indexOf(name)) usedContexts.push(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);
+ } else {
// handle shorthand properties
- if ( parent && parent.type === 'Property' && parent.shorthand ) {
- if ( key === 'key' ) {
- code.appendLeft( node.start, `${name}: ` );
+ if (parent && parent.type === 'Property' && parent.shorthand) {
+ if (key === 'key') {
+ code.appendLeft(node.start, `${name}: `);
return;
}
}
- if ( globalWhitelist.has( name ) ) {
- code.prependRight( node.start, `( '${name}' in state ? state.` );
- code.appendLeft( node.object ? node.object.end : node.end, ` : ${name} )` );
+ if (globalWhitelist.has(name)) {
+ code.prependRight(node.start, `( '${name}' in state ? state.`);
+ code.appendLeft(
+ node.object ? node.object.end : node.end,
+ ` : ${name} )`
+ );
} else {
- code.prependRight( node.start, `state.` );
+ code.prependRight(node.start, `state.`);
}
- if ( !~usedContexts.indexOf( 'state' ) ) usedContexts.push( 'state' );
+ if (!~usedContexts.indexOf('state')) usedContexts.push('state');
}
this.skip();
}
},
- leave ( node: Node ) {
- if ( /^Function/.test( node.type ) ) lexicalDepth -= 1;
- if ( node._scope ) scope = scope.parent;
+ leave(node: Node) {
+ if (/^Function/.test(node.type)) lexicalDepth -= 1;
+ if (node._scope) scope = scope.parent;
}
});
@@ -180,112 +192,133 @@ export default class Generator {
};
}
- findDependencies ( contextDependencies: Map, indexes: Map, expression: Node ) {
- if ( expression._dependencies ) return expression._dependencies;
+ findDependencies(
+ contextDependencies: Map,
+ indexes: Map,
+ expression: Node
+ ) {
+ if (expression._dependencies) return expression._dependencies;
- let scope = annotateWithScopes( expression );
+ let scope = annotateWithScopes(expression);
const dependencies: string[] = [];
const generator = this; // can't use arrow functions, because of this.skip()
- walk( expression, {
- enter ( node: Node, parent: Node ) {
- if ( node._scope ) {
+ walk(expression, {
+ enter(node: Node, parent: Node) {
+ if (node._scope) {
scope = node._scope;
return;
}
- if ( isReference( node, parent ) ) {
- const { name } = flattenReference( node );
- if ( scope.has( name ) || generator.helpers.has( name ) ) return;
+ if (isReference(node, parent)) {
+ const { name } = flattenReference(node);
+ if (scope.has(name) || generator.helpers.has(name)) return;
- if ( contextDependencies.has( name ) ) {
- dependencies.push( ...contextDependencies.get( name ) );
- } else if ( !indexes.has( name ) ) {
- dependencies.push( name );
+ if (contextDependencies.has(name)) {
+ dependencies.push(...contextDependencies.get(name));
+ } else if (!indexes.has(name)) {
+ dependencies.push(name);
}
this.skip();
}
},
- leave ( node: Node ) {
- if ( node._scope ) scope = scope.parent;
+ leave(node: Node) {
+ if (node._scope) scope = scope.parent;
}
});
- dependencies.forEach( name => {
- if ( !globalWhitelist.has( name ) ) {
- this.expectedProperties.add( name );
+ dependencies.forEach(name => {
+ if (!globalWhitelist.has(name)) {
+ this.expectedProperties.add(name);
}
});
- return ( expression._dependencies = dependencies );
+ return (expression._dependencies = dependencies);
}
- generate ( result, options, { name, format } ) {
- if ( this.imports.length ) {
+ generate(result, options, { name, format }) {
+ if (this.imports.length) {
const statements: string[] = [];
- this.imports.forEach( ( declaration, i ) => {
- if ( format === 'es' ) {
- statements.push( this.source.slice( declaration.start, declaration.end ) );
+ this.imports.forEach((declaration, i) => {
+ if (format === 'es') {
+ statements.push(
+ this.source.slice(declaration.start, declaration.end)
+ );
return;
}
- const defaultImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
- const namespaceImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportNamespaceSpecifier' );
- const namedImports = declaration.specifiers.filter( ( x: Node ) => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
-
- const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`;
+ const defaultImport = declaration.specifiers.find(
+ (x: Node) =>
+ x.type === 'ImportDefaultSpecifier' ||
+ (x.type === 'ImportSpecifier' && x.imported.name === 'default')
+ );
+ const namespaceImport = declaration.specifiers.find(
+ (x: Node) => x.type === 'ImportNamespaceSpecifier'
+ );
+ const namedImports = declaration.specifiers.filter(
+ (x: Node) =>
+ x.type === 'ImportSpecifier' && x.imported.name !== 'default'
+ );
+
+ const name = defaultImport || namespaceImport
+ ? (defaultImport || namespaceImport).local.name
+ : `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later
- namedImports.forEach( ( specifier: Node ) => {
- statements.push( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` );
+ namedImports.forEach((specifier: Node) => {
+ statements.push(
+ `var ${specifier.local.name} = ${name}.${specifier.imported.name}`
+ );
});
- if ( defaultImport ) {
- statements.push( `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};` );
+ if (defaultImport) {
+ statements.push(
+ `${name} = ( ${name} && ${name}.__esModule ) ? ${name}['default'] : ${name};`
+ );
}
});
- result = `${statements.join( '\n' )}\n\n${result}`;
+ result = `${statements.join('\n')}\n\n${result}`;
}
const pattern = /\[✂(\d+)-(\d+)$/;
- const parts = result.split( '✂]' );
+ const parts = result.split('✂]');
const finalChunk = parts.pop();
const compiled = new Bundle({ separator: '' });
- function addString ( str: string ) {
+ function addString(str: string) {
compiled.addSource({
- content: new MagicString( str )
+ content: new MagicString(str)
});
}
- const intro = getIntro( format, options, this.imports );
- if ( intro ) addString( intro );
+ const intro = getIntro(format, options, this.imports);
+ if (intro) addString(intro);
const { filename } = options;
// special case — the source file doesn't actually get used anywhere. we need
// to add an empty file to populate map.sources and map.sourcesContent
- if ( !parts.length ) {
+ if (!parts.length) {
compiled.addSource({
filename,
- content: new MagicString( this.source ).remove( 0, this.source.length )
+ content: new MagicString(this.source).remove(0, this.source.length)
});
}
- parts.forEach( ( str: string ) => {
- const chunk = str.replace( pattern, '' );
- if ( chunk ) addString( chunk );
+ parts.forEach((str: string) => {
+ const chunk = str.replace(pattern, '');
+ if (chunk) addString(chunk);
- const match = pattern.exec( str );
+ const match = pattern.exec(str);
- const snippet = this.code.snip( +match[1], +match[2] );
+ const snippet = this.code.snip(+match[1], +match[2]);
compiled.addSource({
filename,
@@ -293,36 +326,52 @@ export default class Generator {
});
});
- addString( finalChunk );
- addString( '\n\n' + getOutro( format, name, options, this.imports ) );
+ addString(finalChunk);
+ addString('\n\n' + getOutro(format, name, options, this.imports));
return {
code: compiled.toString(),
- map: compiled.generateMap({ includeContent: true, file: options.outputFilename }),
+ map: compiled.generateMap({
+ includeContent: true,
+ file: options.outputFilename
+ }),
css: this.css
};
}
- getUniqueName ( name: string ) {
- if ( test ) name = `${name}$`;
+ getUniqueName(name: string) {
+ if (test) name = `${name}$`;
let alias = name;
- for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ); alias = `${name}_${i++}` );
- this.usedNames.add( alias );
+ for (
+ let i = 1;
+ reservedNames.has(alias) ||
+ this.importedNames.has(alias) ||
+ this.usedNames.has(alias);
+ alias = `${name}_${i++}`
+ );
+ this.usedNames.add(alias);
return alias;
}
- getUniqueNameMaker ( params ) {
- const localUsedNames = new Set( params );
+ getUniqueNameMaker(params) {
+ const localUsedNames = new Set(params);
return name => {
- if ( test ) name = `${name}$`;
+ if (test) name = `${name}$`;
let alias = name;
- for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` );
- localUsedNames.add( alias );
+ for (
+ let i = 1;
+ reservedNames.has(alias) ||
+ this.importedNames.has(alias) ||
+ this.usedNames.has(alias) ||
+ localUsedNames.has(alias);
+ alias = `${name}_${i++}`
+ );
+ localUsedNames.add(alias);
return alias;
};
}
- parseJs ( ssr: boolean = false ) {
+ parseJs(ssr: boolean = false) {
const { source } = this;
const { js } = this.parsed;
@@ -333,154 +382,213 @@ export default class Generator {
let namespace = null;
let hasJs = !!js;
- if ( js ) {
- this.addSourcemapLocations( js.content );
+ if (js) {
+ this.addSourcemapLocations(js.content);
const body = js.content.body.slice(); // slice, because we're going to be mutating the original
// imports need to be hoisted out of the IIFE
- for ( let i = 0; i < body.length; i += 1 ) {
+ for (let i = 0; i < body.length; i += 1) {
const node = body[i];
- if ( node.type === 'ImportDeclaration' ) {
- removeNode( this.code, js.content, node );
- imports.push( node );
+ if (node.type === 'ImportDeclaration') {
+ removeNode(this.code, js.content, node);
+ imports.push(node);
- node.specifiers.forEach( ( specifier: Node ) => {
- this.importedNames.add( specifier.local.name );
+ node.specifiers.forEach((specifier: Node) => {
+ this.importedNames.add(specifier.local.name);
});
}
}
- const defaultExport = body.find( ( node: Node ) => node.type === 'ExportDefaultDeclaration' );
+ const defaultExport = body.find(
+ (node: Node) => node.type === 'ExportDefaultDeclaration'
+ );
- if ( defaultExport ) {
- defaultExport.declaration.properties.forEach( ( prop: Node ) => {
- templateProperties[ prop.key.name ] = prop;
+ if (defaultExport) {
+ defaultExport.declaration.properties.forEach((prop: Node) => {
+ templateProperties[prop.key.name] = prop;
});
}
- [ 'helpers', 'events', 'components', 'transitions' ].forEach( key => {
- if ( templateProperties[ key ] ) {
- templateProperties[ key ].value.properties.forEach( ( prop: node ) => {
- this[ key ].add( prop.key.name );
+ ['helpers', 'events', 'components', 'transitions'].forEach(key => {
+ if (templateProperties[key]) {
+ templateProperties[key].value.properties.forEach((prop: node) => {
+ this[key].add(prop.key.name);
});
}
});
- if ( templateProperties.computed ) {
+ if (templateProperties.computed) {
const dependencies = new Map();
- templateProperties.computed.value.properties.forEach( ( prop: Node ) => {
+ templateProperties.computed.value.properties.forEach((prop: Node) => {
const key = prop.key.name;
const value = prop.value;
- const deps = value.params.map( ( param: Node ) => param.type === 'AssignmentPattern' ? param.left.name : param.name );
- dependencies.set( key, deps );
+ const deps = value.params.map(
+ (param: Node) =>
+ param.type === 'AssignmentPattern' ? param.left.name : param.name
+ );
+ dependencies.set(key, deps);
});
const visited = new Set();
- function visit ( key ) {
- if ( !dependencies.has( key ) ) return; // not a computation
+ function visit(key) {
+ if (!dependencies.has(key)) return; // not a computation
- if ( visited.has( key ) ) return;
- visited.add( key );
+ if (visited.has(key)) return;
+ visited.add(key);
- const deps = dependencies.get( key );
- deps.forEach( visit );
+ const deps = dependencies.get(key);
+ deps.forEach(visit);
computations.push({ key, deps });
}
- templateProperties.computed.value.properties.forEach( ( prop: Node ) => visit( prop.key.name ) );
+ templateProperties.computed.value.properties.forEach((prop: Node) =>
+ visit(prop.key.name)
+ );
}
- if ( templateProperties.namespace ) {
+ if (templateProperties.namespace) {
const ns = templateProperties.namespace.value.value;
- namespace = namespaces[ ns ] || ns;
+ namespace = namespaces[ns] || ns;
- removeObjectKey( this.code, defaultExport.declaration, 'namespace' );
+ removeObjectKey(this.code, defaultExport.declaration, 'namespace');
}
- if ( templateProperties.components ) {
+ if (templateProperties.components) {
let hasNonImportedComponent = false;
- templateProperties.components.value.properties.forEach( ( property: Node ) => {
- const key = property.key.name;
- const value = source.slice( property.value.start, property.value.end );
- if ( this.importedNames.has( value ) ) {
- this.importedComponents.set( key, value );
- } else {
- hasNonImportedComponent = true;
+ templateProperties.components.value.properties.forEach(
+ (property: Node) => {
+ const key = property.key.name;
+ const value = source.slice(
+ property.value.start,
+ property.value.end
+ );
+ if (this.importedNames.has(value)) {
+ this.importedComponents.set(key, value);
+ } else {
+ hasNonImportedComponent = true;
+ }
}
- });
- if ( hasNonImportedComponent ) {
+ );
+ if (hasNonImportedComponent) {
// remove the specific components that were imported, as we'll refer to them directly
- Array.from( this.importedComponents.keys() ).forEach( key => {
- removeObjectKey( this.code, templateProperties.components.value, key );
+ Array.from(this.importedComponents.keys()).forEach(key => {
+ removeObjectKey(
+ this.code,
+ templateProperties.components.value,
+ key
+ );
});
} else {
// remove the entire components portion of the export
- removeObjectKey( this.code, defaultExport.declaration, 'components' );
+ removeObjectKey(this.code, defaultExport.declaration, 'components');
}
}
// Remove these after version 2
- if ( templateProperties.onrender ) {
+ if (templateProperties.onrender) {
const { key } = templateProperties.onrender;
- this.code.overwrite( key.start, key.end, 'oncreate', { storeName: true, contentOnly: false } );
+ this.code.overwrite(key.start, key.end, 'oncreate', {
+ storeName: true,
+ contentOnly: false
+ });
templateProperties.oncreate = templateProperties.onrender;
}
- if ( templateProperties.onteardown ) {
+ if (templateProperties.onteardown) {
const { key } = templateProperties.onteardown;
- this.code.overwrite( key.start, key.end, 'ondestroy', { storeName: true, contentOnly: false } );
+ this.code.overwrite(key.start, key.end, 'ondestroy', {
+ storeName: true,
+ contentOnly: false
+ });
templateProperties.ondestroy = templateProperties.onteardown;
}
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
- if ( ssr ) {
- if ( templateProperties.oncreate ) removeNode( this.code, defaultExport.declaration, templateProperties.oncreate );
- if ( templateProperties.ondestroy ) removeNode( this.code, defaultExport.declaration, templateProperties.ondestroy );
- if ( templateProperties.methods ) removeNode( this.code, defaultExport.declaration, templateProperties.methods );
- if ( templateProperties.events ) removeNode( this.code, defaultExport.declaration, templateProperties.events );
+ if (ssr) {
+ if (templateProperties.oncreate)
+ removeNode(
+ this.code,
+ defaultExport.declaration,
+ templateProperties.oncreate
+ );
+ if (templateProperties.ondestroy)
+ removeNode(
+ this.code,
+ defaultExport.declaration,
+ templateProperties.ondestroy
+ );
+ if (templateProperties.methods)
+ removeNode(
+ this.code,
+ defaultExport.declaration,
+ templateProperties.methods
+ );
+ if (templateProperties.events)
+ removeNode(
+ this.code,
+ defaultExport.declaration,
+ templateProperties.events
+ );
}
// now that we've analysed the default export, we can determine whether or not we need to keep it
let hasDefaultExport = !!defaultExport;
- if ( defaultExport && defaultExport.declaration.properties.length === 0 ) {
+ if (defaultExport && defaultExport.declaration.properties.length === 0) {
hasDefaultExport = false;
- removeNode( this.code, js.content, defaultExport );
+ removeNode(this.code, js.content, defaultExport);
}
// if we do need to keep it, then we need to generate a return statement
- if ( hasDefaultExport ) {
- const finalNode = body[ body.length - 1 ];
- if ( defaultExport === finalNode ) {
+ if (hasDefaultExport) {
+ const finalNode = body[body.length - 1];
+ if (defaultExport === finalNode) {
// export is last property, we can just return it
- this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `return ` );
+ this.code.overwrite(
+ defaultExport.start,
+ defaultExport.declaration.start,
+ `return `
+ );
} else {
- const { declarations } = annotateWithScopes( js );
+ const { declarations } = annotateWithScopes(js);
let template = 'template';
- for ( let i = 1; declarations.has( template ); template = `template_${i++}` );
-
- this.code.overwrite( defaultExport.start, defaultExport.declaration.start, `var ${template} = ` );
+ for (
+ let i = 1;
+ declarations.has(template);
+ template = `template_${i++}`
+ );
+
+ this.code.overwrite(
+ defaultExport.start,
+ defaultExport.declaration.start,
+ `var ${template} = `
+ );
let i = defaultExport.start;
- while ( /\s/.test( source[ i - 1 ] ) ) i--;
+ while (/\s/.test(source[i - 1])) i--;
- const indentation = source.slice( i, defaultExport.start );
- this.code.appendLeft( finalNode.end, `\n\n${indentation}return ${template};` );
+ const indentation = source.slice(i, defaultExport.start);
+ this.code.appendLeft(
+ finalNode.end,
+ `\n\n${indentation}return ${template};`
+ );
}
}
// user code gets wrapped in an IIFE
- if ( js.content.body.length ) {
- const prefix = hasDefaultExport ? `var ${this.alias( 'template' )} = (function () {` : `(function () {`;
- this.code.prependRight( js.content.start, prefix ).appendLeft( js.content.end, '}());' );
- }
-
- // if there's no need to include user code, remove it altogether
- else {
- this.code.remove( js.content.start, js.content.end );
+ if (js.content.body.length) {
+ const prefix = hasDefaultExport
+ ? `var ${this.alias('template')} = (function () {`
+ : `(function () {`;
+ this.code
+ .prependRight(js.content.start, prefix)
+ .appendLeft(js.content.end, '}());');
+ } else {
+ // if there's no need to include user code, remove it altogether
+ this.code.remove(js.content.start, js.content.end);
hasJs = false;
}
}
diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts
index 0131ca1e12..f99c1d52c9 100644
--- a/src/generators/dom/Block.ts
+++ b/src/generators/dom/Block.ts
@@ -48,7 +48,7 @@ export default class Block {
unmount: CodeBuilder;
detachRaw: CodeBuilder;
destroy: CodeBuilder;
- }
+ };
hasIntroMethod: boolean;
hasOutroMethod: boolean;
@@ -64,7 +64,7 @@ export default class Block {
hasUpdateMethod: boolean;
autofocus: string;
- constructor ( options: BlockOptions ) {
+ constructor(options: BlockOptions) {
this.generator = options.generator;
this.name = options.name;
this.expression = options.expression;
@@ -102,144 +102,169 @@ export default class Block {
this.aliases = new Map();
this.variables = new Map();
- this.getUniqueName = this.generator.getUniqueNameMaker( options.params );
+ this.getUniqueName = this.generator.getUniqueNameMaker(options.params);
// unique names
- this.component = this.getUniqueName( 'component' );
- this.target = this.getUniqueName( 'target' );
+ this.component = this.getUniqueName('component');
+ this.target = this.getUniqueName('target');
this.hasUpdateMethod = false; // determined later
}
- addDependencies ( dependencies ) {
- dependencies.forEach( dependency => {
- this.dependencies.add( dependency );
+ addDependencies(dependencies) {
+ dependencies.forEach(dependency => {
+ this.dependencies.add(dependency);
});
}
- addElement ( name: string, renderStatement: string, parentNode: string, needsIdentifier = false ) {
+ addElement(
+ name: string,
+ renderStatement: string,
+ parentNode: string,
+ needsIdentifier = false
+ ) {
const isToplevel = !parentNode;
- if ( needsIdentifier || isToplevel ) {
- this.builders.create.addLine(
- `var ${name} = ${renderStatement};`
- );
+ if (needsIdentifier || isToplevel) {
+ this.builders.create.addLine(`var ${name} = ${renderStatement};`);
- this.mount( name, parentNode );
+ this.mount(name, parentNode);
} else {
- this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${renderStatement}, ${parentNode} );` );
+ this.builders.create.addLine(
+ `${this.generator.helper(
+ 'appendNode'
+ )}( ${renderStatement}, ${parentNode} );`
+ );
}
- if ( isToplevel ) {
- this.builders.unmount.addLine( `${this.generator.helper( 'detachNode' )}( ${name} );` );
+ if (isToplevel) {
+ this.builders.unmount.addLine(
+ `${this.generator.helper('detachNode')}( ${name} );`
+ );
}
}
- addVariable ( name: string, init?: string ) {
- if ( this.variables.has( name ) && this.variables.get( name ) !== init ) {
- throw new Error( `Variable '${name}' already initialised with a different value` );
+ addVariable(name: string, init?: string) {
+ if (this.variables.has(name) && this.variables.get(name) !== init) {
+ throw new Error(
+ `Variable '${name}' already initialised with a different value`
+ );
}
- this.variables.set( name, init );
+ this.variables.set(name, init);
}
- alias ( name: string ) {
- if ( !this.aliases.has( name ) ) {
- this.aliases.set( name, this.getUniqueName( name ) );
+ alias(name: string) {
+ if (!this.aliases.has(name)) {
+ this.aliases.set(name, this.getUniqueName(name));
}
- return this.aliases.get( name );
+ return this.aliases.get(name);
}
- child ( options: BlockOptions ) {
- return new Block( Object.assign( {}, this, options, { parent: this } ) );
+ child(options: BlockOptions) {
+ return new Block(Object.assign({}, this, options, { parent: this }));
}
- contextualise ( expression: Node, context?: string, isEventHandler?: boolean ) {
- return this.generator.contextualise( this, expression, context, isEventHandler );
+ contextualise(expression: Node, context?: string, isEventHandler?: boolean) {
+ return this.generator.contextualise(
+ this,
+ expression,
+ context,
+ isEventHandler
+ );
}
- findDependencies ( expression ) {
- return this.generator.findDependencies( this.contextDependencies, this.indexes, expression );
+ findDependencies(expression) {
+ return this.generator.findDependencies(
+ this.contextDependencies,
+ this.indexes,
+ expression
+ );
}
- mount ( name: string, parentNode: string ) {
- if ( parentNode ) {
- this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
+ mount(name: string, parentNode: string) {
+ if (parentNode) {
+ this.builders.create.addLine(
+ `${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
+ );
} else {
- this.builders.mount.addLine( `${this.generator.helper( 'insertNode' )}( ${name}, ${this.target}, anchor );` );
+ this.builders.mount.addLine(
+ `${this.generator.helper('insertNode')}( ${name}, ${this
+ .target}, anchor );`
+ );
}
}
- render () {
+ render() {
let introing;
const hasIntros = !this.builders.intro.isEmpty();
- if ( hasIntros ) {
- introing = this.getUniqueName( 'introing' );
- this.addVariable( introing );
+ if (hasIntros) {
+ introing = this.getUniqueName('introing');
+ this.addVariable(introing);
}
let outroing;
const hasOutros = !this.builders.outro.isEmpty();
- if ( hasOutros ) {
- outroing = this.getUniqueName( 'outroing' );
- this.addVariable( outroing );
+ if (hasOutros) {
+ outroing = this.getUniqueName('outroing');
+ this.addVariable(outroing);
}
- if ( this.variables.size ) {
- const variables = Array.from( this.variables.keys() )
- .map( key => {
- const init = this.variables.get( key );
+ if (this.variables.size) {
+ const variables = Array.from(this.variables.keys())
+ .map(key => {
+ const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
})
- .join( ', ' );
+ .join(', ');
- this.builders.create.addBlockAtStart( `var ${variables};` );
+ this.builders.create.addBlockAtStart(`var ${variables};`);
}
- if ( this.autofocus ) {
- this.builders.create.addLine( `${this.autofocus}.focus();` );
+ if (this.autofocus) {
+ this.builders.create.addLine(`${this.autofocus}.focus();`);
}
// minor hack – we need to ensure that any {{{triples}}} are detached first
- this.builders.unmount.addBlockAtStart( this.builders.detachRaw );
+ this.builders.unmount.addBlockAtStart(this.builders.detachRaw);
const properties = new CodeBuilder();
let localKey;
- if ( this.key ) {
- localKey = this.getUniqueName( 'key' );
- properties.addBlock( `key: ${localKey},` );
+ if (this.key) {
+ localKey = this.getUniqueName('key');
+ properties.addBlock(`key: ${localKey},`);
}
- if ( this.first ) {
- properties.addBlock( `first: ${this.first},` );
+ if (this.first) {
+ properties.addBlock(`first: ${this.first},`);
}
- if ( this.builders.mount.isEmpty() ) {
- properties.addBlock( `mount: ${this.generator.helper( 'noop' )},` );
+ if (this.builders.mount.isEmpty()) {
+ properties.addBlock(`mount: ${this.generator.helper('noop')},`);
} else {
- properties.addBlock( deindent`
+ properties.addBlock(deindent`
mount: function ( ${this.target}, anchor ) {
${this.builders.mount}
},
- ` );
+ `);
}
- if ( this.hasUpdateMethod ) {
- if ( this.builders.update.isEmpty() ) {
- properties.addBlock( `update: ${this.generator.helper( 'noop' )},` );
+ if (this.hasUpdateMethod) {
+ if (this.builders.update.isEmpty()) {
+ properties.addBlock(`update: ${this.generator.helper('noop')},`);
} else {
- properties.addBlock( deindent`
- update: function ( changed, ${this.params.join( ', ' )} ) {
+ properties.addBlock(deindent`
+ update: function ( changed, ${this.params.join(', ')} ) {
${this.builders.update}
},
- ` );
+ `);
}
}
- if ( this.hasIntroMethod ) {
- if ( hasIntros ) {
- properties.addBlock( deindent`
+ if (this.hasIntroMethod) {
+ if (hasIntros) {
+ properties.addBlock(deindent`
intro: function ( ${this.target}, anchor ) {
if ( ${introing} ) return;
${introing} = true;
@@ -249,60 +274,63 @@ export default class Block {
this.mount( ${this.target}, anchor );
},
- ` );
+ `);
} else {
- properties.addBlock( deindent`
+ properties.addBlock(deindent`
intro: function ( ${this.target}, anchor ) {
this.mount( ${this.target}, anchor );
},
- ` );
+ `);
}
}
- if ( this.hasOutroMethod ) {
- if ( hasOutros ) {
- properties.addBlock( deindent`
- outro: function ( ${this.alias( 'outrocallback' )} ) {
+ if (this.hasOutroMethod) {
+ if (hasOutros) {
+ properties.addBlock(deindent`
+ outro: function ( ${this.alias('outrocallback')} ) {
if ( ${outroing} ) return;
${outroing} = true;
${hasIntros && `${introing} = false;`}
- var ${this.alias( 'outros' )} = ${this.outros};
+ var ${this.alias('outros')} = ${this.outros};
${this.builders.outro}
},
- ` );
+ `);
} else {
- properties.addBlock( deindent`
+ properties.addBlock(deindent`
outro: function ( outrocallback ) {
outrocallback();
},
- ` );
+ `);
}
}
- if ( this.builders.unmount.isEmpty() ) {
- properties.addBlock( `unmount: ${this.generator.helper('noop')},`);
+ if (this.builders.unmount.isEmpty()) {
+ properties.addBlock(`unmount: ${this.generator.helper('noop')},`);
} else {
- properties.addBlock( deindent`
+ properties.addBlock(deindent`
unmount: function () {
${this.builders.unmount}
},
- ` );
+ `);
}
- if ( this.builders.destroy.isEmpty() ) {
- properties.addBlock( `destroy: ${this.generator.helper( 'noop' )}` );
+ if (this.builders.destroy.isEmpty()) {
+ properties.addBlock(`destroy: ${this.generator.helper('noop')}`);
} else {
- properties.addBlock( deindent`
+ properties.addBlock(deindent`
destroy: function () {
${this.builders.destroy}
}
- ` );
+ `);
}
return deindent`
- function ${this.name} ( ${this.params.join( ', ' )}, ${this.component}${this.key ? `, ${localKey}` : ''} ) {
+ function ${this.name} ( ${this.params.join(', ')}, ${this.component}${this
+ .key
+ ? `, ${localKey}`
+ : ''} ) {
${this.builders.create}
return {
@@ -311,4 +339,4 @@ export default class Block {
}
`;
}
-}
\ No newline at end of file
+}
diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts
index 341608d428..cfb3bacdca 100644
--- a/src/generators/dom/index.ts
+++ b/src/generators/dom/index.ts
@@ -13,7 +13,7 @@ import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator {
- blocks: Block[]
+ blocks: Block[];
uses: Set;
readonly: Set;
metaBindings: string[];
@@ -22,8 +22,13 @@ export class DomGenerator extends Generator {
hasOutroTransitions: boolean;
hasComplexBindings: boolean;
- constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
- super( parsed, source, name, options );
+ constructor(
+ parsed: Parsed,
+ source: string,
+ name: string,
+ options: CompileOptions
+ ) {
+ super(parsed, source, name, options);
this.blocks = [];
this.uses = new Set();
@@ -33,29 +38,38 @@ export class DomGenerator extends Generator {
this.metaBindings = [];
}
- helper ( name: string ) {
- if ( this.options.dev && `${name}Dev` in shared ) {
+ helper(name: string) {
+ if (this.options.dev && `${name}Dev` in shared) {
name = `${name}Dev`;
}
- this.uses.add( name );
+ this.uses.add(name);
- return this.alias( name );
+ return this.alias(name);
}
}
-export default function dom ( parsed: Parsed, source: string, options: CompileOptions ) {
+export default function dom(
+ parsed: Parsed,
+ source: string,
+ options: CompileOptions
+) {
const format = options.format || 'es';
const name = options.name || 'SvelteComponent';
- const generator = new DomGenerator( parsed, source, name, options );
+ const generator = new DomGenerator(parsed, source, name, options);
- const { computations, hasJs, templateProperties, namespace } = generator.parseJs();
+ const {
+ computations,
+ hasJs,
+ templateProperties,
+ namespace
+ } = generator.parseJs();
- const { block, state } = preprocess( generator, namespace, parsed.html );
+ const { block, state } = preprocess(generator, namespace, parsed.html);
- parsed.html.children.forEach( ( node: Node ) => {
- visit( generator, block, state, node );
+ parsed.html.children.forEach((node: Node) => {
+ visit(generator, block, state, node);
});
const builders = {
@@ -63,94 +77,139 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
_set: new CodeBuilder()
};
- if ( computations.length ) {
+ if (computations.length) {
const builder = new CodeBuilder();
- const differs = generator.helper( 'differs' );
+ const differs = generator.helper('differs');
- computations.forEach( ({ key, deps }) => {
- if ( generator.readonly.has( key ) ) {
+ computations.forEach(({ key, deps }) => {
+ if (generator.readonly.has(key)) {
// <:Window> bindings
- throw new Error( `Cannot have a computed value '${key}' that clashes with a read-only property` );
+ throw new Error(
+ `Cannot have a computed value '${key}' that clashes with a read-only property`
+ );
}
- generator.readonly.add( key );
+ 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} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );`;
+ const condition = `isInitial || ${deps
+ .map(
+ dep =>
+ `( '${dep}' in newState && ${differs}( state.${dep}, oldState.${dep} ) )`
+ )
+ .join(' || ')}`;
+ const statement = `state.${key} = newState.${key} = ${generator.alias(
+ 'template'
+ )}.computed.${key}( ${deps.map(dep => `state.${dep}`).join(', ')} );`;
- builder.addConditionalLine( condition, statement );
+ builder.addConditionalLine(condition, statement);
});
- builders.main.addBlock( deindent`
- function ${generator.alias( 'recompute' )} ( state, newState, oldState, isInitial ) {
+ builders.main.addBlock(deindent`
+ function ${generator.alias(
+ 'recompute'
+ )} ( state, newState, oldState, isInitial ) {
${builder}
}
- ` );
+ `);
}
- builders._set.addBlock( deindent`
- ${options.dev && deindent`
+ builders._set.addBlock(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}'" );`
+ ${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 = ${generator.helper( 'assign' )}( {}, oldState, newState );
- ${computations.length && `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )`}
- ${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );
+ this._state = ${generator.helper('assign')}( {}, oldState, newState );
+ ${computations.length &&
+ `${generator.alias(
+ 'recompute'
+ )}( this._state, newState, oldState, false )`}
+ ${generator.helper(
+ 'dispatchObservers'
+ )}( this, this._observers.pre, newState, oldState );
${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`}
- ${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );
- ${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
- ${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
- ` );
-
- if ( hasJs ) {
- builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
+ ${generator.helper(
+ 'dispatchObservers'
+ )}( this, this._observers.post, newState, oldState );
+ ${generator.hasComplexBindings &&
+ `while ( this._bindings.length ) this._bindings.pop()();`}
+ ${(generator.hasComponents || generator.hasIntroTransitions) &&
+ `this._flush();`}
+ `);
+
+ if (hasJs) {
+ builders.main.addBlock(
+ `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`
+ );
}
- if ( generator.css && options.css !== false ) {
- builders.main.addBlock( deindent`
- function ${generator.alias( 'add_css' )} () {
- var style = ${generator.helper( 'createElement' )}( 'style' );
- style.id = ${JSON.stringify( generator.cssId + '-style' )};
- style.textContent = ${JSON.stringify( generator.css )};
- ${generator.helper( 'appendNode' )}( style, document.head );
+ if (generator.css && options.css !== false) {
+ builders.main.addBlock(deindent`
+ function ${generator.alias('add_css')} () {
+ var style = ${generator.helper('createElement')}( 'style' );
+ style.id = ${JSON.stringify(generator.cssId + '-style')};
+ style.textContent = ${JSON.stringify(generator.css)};
+ ${generator.helper('appendNode')}( style, document.head );
}
- ` );
+ `);
}
- generator.blocks.forEach( block => {
- builders.main.addBlock( block.render() );
+ generator.blocks.forEach(block => {
+ builders.main.addBlock(block.render());
});
- const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared;
-
- const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' );
- const proto = sharedPath ? `${generator.helper( 'proto' )} ` : deindent`
+ const sharedPath = options.shared === true
+ ? 'svelte/shared.js'
+ : options.shared;
+
+ const prototypeBase =
+ `${name}.prototype` +
+ (templateProperties.methods
+ ? `, ${generator.alias('template')}.methods`
+ : '');
+ const proto = sharedPath
+ ? `${generator.helper('proto')} `
+ : deindent`
{
- ${
- [ 'get', 'fire', 'observe', 'on', 'set', '_flush' ]
- .map( n => `${n}: ${generator.helper( n )}` )
- .join( ',\n' )
- }
+ ${['get', 'fire', 'observe', 'on', 'set', '_flush']
+ .map(n => `${n}: ${generator.helper(n)}`)
+ .join(',\n')}
}`;
// TODO deprecate component.teardown()
- builders.main.addBlock( deindent`
+ builders.main.addBlock(deindent`
function ${name} ( options ) {
options = options || {};
- ${options.dev && `if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
+ ${options.dev &&
+ `if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
${generator.usesRefs && `this.refs = {};`}
- this._state = ${templateProperties.data ? `${generator.helper( 'assign' )}( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};
+ this._state = ${templateProperties.data
+ ? `${generator.helper('assign')}( ${generator.alias(
+ 'template'
+ )}.data(), options.data )`
+ : `options.data || {}`};
${generator.metaBindings}
- ${computations.length && `${generator.alias( 'recompute' )}( this._state, this._state, {}, true );`}
- ${options.dev && Array.from( generator.expectedProperties ).map( prop => `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`)}
- ${generator.bindingGroups.length && `this._bindingGroups = [ ${Array( generator.bindingGroups.length ).fill( '[]' ).join( ', ' )} ];`}
+ ${computations.length &&
+ `${generator.alias(
+ 'recompute'
+ )}( this._state, this._state, {}, true );`}
+ ${options.dev &&
+ Array.from(generator.expectedProperties).map(
+ prop =>
+ `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`
+ )}
+ ${generator.bindingGroups.length &&
+ `this._bindingGroups = [ ${Array(generator.bindingGroups.length)
+ .fill('[]')
+ .join(', ')} ];`}
this._observers = {
pre: Object.create( null ),
@@ -163,25 +222,37 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
this._yield = options._yield;
this._torndown = false;
- ${parsed.css && options.css !== false && `if ( !document.getElementById( ${JSON.stringify( generator.cssId + '-style' )} ) ) ${generator.alias( 'add_css' )}();`}
- ${( generator.hasComponents || generator.hasIntroTransitions ) && `this._renderHooks = [];`}
+ ${parsed.css &&
+ options.css !== false &&
+ `if ( !document.getElementById( ${JSON.stringify(
+ generator.cssId + '-style'
+ )} ) ) ${generator.alias('add_css')}();`}
+ ${(generator.hasComponents || generator.hasIntroTransitions) &&
+ `this._renderHooks = [];`}
${generator.hasComplexBindings && `this._bindings = [];`}
- this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
+ this._fragment = ${generator.alias(
+ 'create_main_fragment'
+ )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
- ${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
- ${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
+ ${generator.hasComplexBindings &&
+ `while ( this._bindings.length ) this._bindings.pop()();`}
+ ${(generator.hasComponents || generator.hasIntroTransitions) &&
+ `this._flush();`}
- ${templateProperties.oncreate && deindent`
+ ${templateProperties.oncreate &&
+ deindent`
if ( options._root ) {
- options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
+ options._root._renderHooks.push( ${generator.alias(
+ 'template'
+ )}.oncreate.bind( this ) );
} else {
- ${generator.alias( 'template' )}.oncreate.call( this );
+ ${generator.alias('template')}.oncreate.call( this );
}
`}
}
- ${generator.helper( 'assign' )}( ${prototypeBase}, ${proto});
+ ${generator.helper('assign')}( ${prototypeBase}, ${proto});
${name}.prototype._set = function _set ( newState ) {
${builders._set}
@@ -189,7 +260,8 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
${name}.prototype.teardown = ${name}.prototype.destroy = function destroy ( detach ) {
this.fire( 'destroy' );
- ${templateProperties.ondestroy && `${generator.alias( 'template' )}.ondestroy.call( this );`}
+ ${templateProperties.ondestroy &&
+ `${generator.alias('template')}.ondestroy.call( this );`}
if ( detach !== false ) this._fragment.unmount();
this._fragment.destroy();
@@ -198,63 +270,79 @@ export default function dom ( parsed: Parsed, source: string, options: CompileOp
this._state = {};
this._torndown = true;
};
- ` );
+ `);
- if ( sharedPath ) {
- if ( format !== 'es' ) {
- throw new Error( `Components with shared helpers must be compiled to ES2015 modules (format: 'es')` );
+ if (sharedPath) {
+ if (format !== 'es') {
+ throw new Error(
+ `Components with shared helpers must be compiled to ES2015 modules (format: 'es')`
+ );
}
- const names = Array.from( generator.uses ).sort().map( name => {
- return name !== generator.alias( name ) ? `${name} as ${generator.alias( name )}` : name;
+ const names = Array.from(generator.uses).sort().map(name => {
+ return name !== generator.alias(name)
+ ? `${name} as ${generator.alias(name)}`
+ : name;
});
builders.main.addLineAtStart(
- `import { ${names.join( ', ' )} } from ${JSON.stringify( sharedPath )};`
+ `import { ${names.join(', ')} } from ${JSON.stringify(sharedPath)};`
);
} else {
- generator.uses.forEach( key => {
- const str = shared[ key ];
- const code = new MagicString( str );
- const expression = parseExpressionAt( str, 0 );
-
- let scope = annotateWithScopes( expression );
-
- walk( expression, {
- enter ( node, parent ) {
- if ( node._scope ) scope = node._scope;
-
- if ( node.type === 'Identifier' && isReference( node, parent ) && !scope.has( node.name ) ) {
- if ( node.name in shared ) {
+ generator.uses.forEach(key => {
+ const str = shared[key];
+ const code = new MagicString(str);
+ const expression = parseExpressionAt(str, 0);
+
+ let scope = annotateWithScopes(expression);
+
+ walk(expression, {
+ enter(node, parent) {
+ if (node._scope) scope = node._scope;
+
+ if (
+ node.type === 'Identifier' &&
+ isReference(node, parent) &&
+ !scope.has(node.name)
+ ) {
+ if (node.name in shared) {
// this helper function depends on another one
const dependency = node.name;
- generator.uses.add( dependency );
+ generator.uses.add(dependency);
- const alias = generator.alias( dependency );
- if ( alias !== node.name ) code.overwrite( node.start, node.end, alias );
+ const alias = generator.alias(dependency);
+ if (alias !== node.name)
+ code.overwrite(node.start, node.end, alias);
}
}
},
- leave ( node ) {
- if ( node._scope ) scope = scope.parent;
+ leave(node) {
+ if (node._scope) scope = scope.parent;
}
});
- if ( key === 'transitionManager' ) { // special case
+ if (key === 'transitionManager') {
+ // special case
const global = `_svelteTransitionManager`;
builders.main.addBlock(
- `var ${generator.alias( 'transitionManager' )} = window.${global} || ( window.${global} = ${code});`
+ `var ${generator.alias(
+ 'transitionManager'
+ )} = window.${global} || ( window.${global} = ${code});`
);
} else {
- const alias = generator.alias( expression.id.name );
- if ( alias !== expression.id.name ) code.overwrite( expression.id.start, expression.id.end, alias );
+ const alias = generator.alias(expression.id.name);
+ if (alias !== expression.id.name)
+ code.overwrite(expression.id.start, expression.id.end, alias);
- builders.main.addBlock( code.toString() );
+ builders.main.addBlock(code.toString());
}
});
}
- return generator.generate( builders.main.toString(), options, { name, format } );
+ return generator.generate(builders.main.toString(), options, {
+ name,
+ format
+ });
}
diff --git a/src/generators/dom/interfaces.ts b/src/generators/dom/interfaces.ts
index a58069da90..8c460a4733 100644
--- a/src/generators/dom/interfaces.ts
+++ b/src/generators/dom/interfaces.ts
@@ -2,7 +2,7 @@ export interface State {
name: string;
namespace: string;
parentNode: string;
- isTopLevel: boolean
+ isTopLevel: boolean;
parentNodeName?: string;
basename?: string;
inEachBlock?: boolean;
diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts
index 3a96aca3f2..595b753d57 100644
--- a/src/generators/dom/preprocess.ts
+++ b/src/generators/dom/preprocess.ts
@@ -5,12 +5,14 @@ import { DomGenerator } from './index';
import { Node } from '../../interfaces';
import { State } from './interfaces';
-function isElseIf ( node: Node ) {
- return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
+function isElseIf(node: Node) {
+ return (
+ node && node.children.length === 1 && node.children[0].type === 'IfBlock'
+ );
}
-function getChildState ( parent: State, child = {} ) {
- return assign( {}, parent, { name: null, parentNode: null }, child || {} );
+function getChildState(parent: State, child = {}) {
+ return assign({}, parent, { name: null, parentNode: null }, child || {});
}
// Whitespace inside one of these elements will not result in
@@ -28,118 +30,144 @@ const elementsWithoutText = new Set([
]);
const preprocessors = {
- MustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
- const dependencies = block.findDependencies( node.expression );
- block.addDependencies( dependencies );
-
- node._state = getChildState( state, {
- name: block.getUniqueName( 'text' )
+ MustacheTag: (
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node
+ ) => {
+ const dependencies = block.findDependencies(node.expression);
+ block.addDependencies(dependencies);
+
+ node._state = getChildState(state, {
+ name: block.getUniqueName('text')
});
},
- RawMustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
- const dependencies = block.findDependencies( node.expression );
- block.addDependencies( dependencies );
+ RawMustacheTag: (
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node
+ ) => {
+ const dependencies = block.findDependencies(node.expression);
+ block.addDependencies(dependencies);
- const basename = block.getUniqueName( 'raw' );
- const name = block.getUniqueName( `${basename}_before` );
+ const basename = block.getUniqueName('raw');
+ const name = block.getUniqueName(`${basename}_before`);
- node._state = getChildState( state, { basename, name });
+ node._state = getChildState(state, { basename, name });
},
- Text: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
- node._state = getChildState( state );
+ Text: (generator: DomGenerator, block: Block, state: State, node: Node) => {
+ node._state = getChildState(state);
- if ( !/\S/.test( node.data ) ) {
- if ( state.namespace ) return;
- if ( elementsWithoutText.has( state.parentNodeName ) ) return;
+ if (!/\S/.test(node.data)) {
+ if (state.namespace) return;
+ if (elementsWithoutText.has(state.parentNodeName)) return;
}
node._state.shouldCreate = true;
- node._state.name = block.getUniqueName( `text` );
+ node._state.name = block.getUniqueName(`text`);
},
- IfBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
+ IfBlock: (
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node
+ ) => {
const blocks: Block[] = [];
let dynamic = false;
let hasIntros = false;
let hasOutros = false;
- function attachBlocks ( node: Node ) {
- const dependencies = block.findDependencies( node.expression );
- block.addDependencies( dependencies );
+ function attachBlocks(node: Node) {
+ const dependencies = block.findDependencies(node.expression);
+ block.addDependencies(dependencies);
node._block = block.child({
- name: generator.getUniqueName( `create_if_block` )
+ name: generator.getUniqueName(`create_if_block`)
});
- node._state = getChildState( state );
+ node._state = getChildState(state);
- blocks.push( node._block );
- preprocessChildren( generator, node._block, node._state, node );
+ blocks.push(node._block);
+ preprocessChildren(generator, node._block, node._state, node);
- if ( node._block.dependencies.size > 0 ) {
+ if (node._block.dependencies.size > 0) {
dynamic = true;
- block.addDependencies( node._block.dependencies );
+ block.addDependencies(node._block.dependencies);
}
- if ( node._block.hasIntroMethod ) hasIntros = true;
- if ( node._block.hasOutroMethod ) hasOutros = true;
+ if (node._block.hasIntroMethod) hasIntros = true;
+ if (node._block.hasOutroMethod) hasOutros = true;
- if ( isElseIf( node.else ) ) {
- attachBlocks( node.else.children[0] );
- } else if ( node.else ) {
+ if (isElseIf(node.else)) {
+ attachBlocks(node.else.children[0]);
+ } else if (node.else) {
node.else._block = block.child({
- name: generator.getUniqueName( `create_if_block` )
+ name: generator.getUniqueName(`create_if_block`)
});
- node.else._state = getChildState( state );
+ node.else._state = getChildState(state);
- blocks.push( node.else._block );
- preprocessChildren( generator, node.else._block, node.else._state, node.else );
+ blocks.push(node.else._block);
+ preprocessChildren(
+ generator,
+ node.else._block,
+ node.else._state,
+ node.else
+ );
- if ( node.else._block.dependencies.size > 0 ) {
+ if (node.else._block.dependencies.size > 0) {
dynamic = true;
- block.addDependencies( node.else._block.dependencies );
+ block.addDependencies(node.else._block.dependencies);
}
}
}
- attachBlocks( node );
+ attachBlocks(node);
- blocks.forEach( block => {
+ blocks.forEach(block => {
block.hasUpdateMethod = dynamic;
block.hasIntroMethod = hasIntros;
block.hasOutroMethod = hasOutros;
});
- generator.blocks.push( ...blocks );
+ generator.blocks.push(...blocks);
},
- EachBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
- const dependencies = block.findDependencies( node.expression );
- block.addDependencies( dependencies );
+ EachBlock: (
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node
+ ) => {
+ const dependencies = block.findDependencies(node.expression);
+ block.addDependencies(dependencies);
- const indexNames = new Map( block.indexNames );
- const indexName = node.index || block.getUniqueName( `${node.context}_index` );
- indexNames.set( node.context, indexName );
+ const indexNames = new Map(block.indexNames);
+ const indexName =
+ node.index || block.getUniqueName(`${node.context}_index`);
+ indexNames.set(node.context, indexName);
- const listNames = new Map( block.listNames );
- const listName = block.getUniqueName( `each_block_value` );
- listNames.set( node.context, listName );
+ const listNames = new Map(block.listNames);
+ const listName = block.getUniqueName(`each_block_value`);
+ listNames.set(node.context, listName);
- const context = generator.getUniqueName( node.context );
- const contexts = new Map( block.contexts );
- contexts.set( node.context, context );
+ const context = generator.getUniqueName(node.context);
+ const contexts = new Map(block.contexts);
+ contexts.set(node.context, context);
- const indexes = new Map( block.indexes );
- if ( node.index ) indexes.set( indexName, node.context );
+ const indexes = new Map(block.indexes);
+ if (node.index) indexes.set(indexName, node.context);
- const contextDependencies = new Map( block.contextDependencies );
- contextDependencies.set( node.context, dependencies );
+ const contextDependencies = new Map(block.contextDependencies);
+ contextDependencies.set(node.context, dependencies);
node._block = block.child({
- name: generator.getUniqueName( 'create_each_block' ),
+ name: generator.getUniqueName('create_each_block'),
expression: node.expression,
context: node.context,
key: node.key,
@@ -153,134 +181,152 @@ const preprocessors = {
indexNames,
listNames,
- params: block.params.concat( listName, context, indexName )
+ params: block.params.concat(listName, context, indexName)
});
- node._state = getChildState( state, {
+ node._state = getChildState(state, {
inEachBlock: true
});
- generator.blocks.push( node._block );
- preprocessChildren( generator, node._block, node._state, node );
- block.addDependencies( node._block.dependencies );
+ generator.blocks.push(node._block);
+ preprocessChildren(generator, node._block, node._state, node);
+ block.addDependencies(node._block.dependencies);
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
- if ( node.else ) {
+ if (node.else) {
node.else._block = block.child({
- name: generator.getUniqueName( `${node._block.name}_else` )
+ name: generator.getUniqueName(`${node._block.name}_else`)
});
- node.else._state = getChildState( state );
+ node.else._state = getChildState(state);
- generator.blocks.push( node.else._block );
- preprocessChildren( generator, node.else._block, node.else._state, node.else );
+ generator.blocks.push(node.else._block);
+ preprocessChildren(
+ generator,
+ node.else._block,
+ node.else._state,
+ node.else
+ );
node.else._block.hasUpdateMethod = node.else._block.dependencies.size > 0;
}
},
- Element: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
- const isComponent = generator.components.has( node.name ) || node.name === ':Self';
-
- if ( isComponent ) {
- node._state = getChildState( state );
+ Element: (
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node
+ ) => {
+ const isComponent =
+ generator.components.has(node.name) || node.name === ':Self';
+
+ if (isComponent) {
+ node._state = getChildState(state);
} else {
- const name = block.getUniqueName( node.name.replace( /[^a-zA-Z0-9_$]/g, '_' ) );
+ const name = block.getUniqueName(
+ node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
+ );
- node._state = getChildState( state, {
+ node._state = getChildState(state, {
isTopLevel: false,
name,
parentNode: name,
parentNodeName: node.name,
- namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' : state.namespace,
+ namespace: node.name === 'svg'
+ ? 'http://www.w3.org/2000/svg'
+ : state.namespace,
allUsedContexts: []
});
}
- node.attributes.forEach( ( attribute: Node ) => {
- if ( attribute.type === 'Attribute' && attribute.value !== true ) {
- attribute.value.forEach( ( chunk: Node ) => {
- if ( chunk.type !== 'Text' ) {
- const dependencies = block.findDependencies( chunk.expression );
- block.addDependencies( dependencies );
+ node.attributes.forEach((attribute: Node) => {
+ if (attribute.type === 'Attribute' && attribute.value !== true) {
+ attribute.value.forEach((chunk: Node) => {
+ if (chunk.type !== 'Text') {
+ const dependencies = block.findDependencies(chunk.expression);
+ block.addDependencies(dependencies);
}
});
- }
-
- else if ( attribute.type === 'Binding' ) {
- const dependencies = block.findDependencies( attribute.value );
- block.addDependencies( dependencies );
- }
-
- else if ( attribute.type === 'Transition' ) {
- if ( attribute.intro ) generator.hasIntroTransitions = block.hasIntroMethod = true;
- if ( attribute.outro ) {
+ } else if (attribute.type === 'Binding') {
+ const dependencies = block.findDependencies(attribute.value);
+ block.addDependencies(dependencies);
+ } else if (attribute.type === 'Transition') {
+ if (attribute.intro)
+ generator.hasIntroTransitions = block.hasIntroMethod = true;
+ if (attribute.outro) {
generator.hasOutroTransitions = block.hasOutroMethod = true;
block.outros += 1;
}
}
});
- if ( node.children.length ) {
- if ( isComponent ) {
- const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );
+ if (node.children.length) {
+ if (isComponent) {
+ const name = block.getUniqueName(
+ (node.name === ':Self' ? generator.name : node.name).toLowerCase()
+ );
node._block = block.child({
- name: generator.getUniqueName( `create_${name}_yield_fragment` )
+ name: generator.getUniqueName(`create_${name}_yield_fragment`)
});
- generator.blocks.push( node._block );
- preprocessChildren( generator, node._block, node._state, node );
- block.addDependencies( node._block.dependencies );
+ generator.blocks.push(node._block);
+ preprocessChildren(generator, node._block, node._state, node);
+ block.addDependencies(node._block.dependencies);
node._block.hasUpdateMethod = node._block.dependencies.size > 0;
- }
-
- else {
- preprocessChildren( generator, block, node._state, node );
+ } else {
+ preprocessChildren(generator, block, node._state, node);
}
}
}
};
-function preprocessChildren ( generator: DomGenerator, block: Block, state: State, node: Node, isTopLevel: boolean = false ) {
+function preprocessChildren(
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node,
+ isTopLevel: boolean = false
+) {
// glue text nodes together
const cleaned: Node[] = [];
let lastChild: Node;
- node.children.forEach( ( child: Node ) => {
- if ( child.type === 'Comment' ) return;
+ node.children.forEach((child: Node) => {
+ if (child.type === 'Comment') return;
- if ( child.type === 'Text' && lastChild && lastChild.type === 'Text' ) {
+ if (child.type === 'Text' && lastChild && lastChild.type === 'Text') {
lastChild.data += child.data;
lastChild.end = child.end;
} else {
- cleaned.push( child );
+ cleaned.push(child);
}
lastChild = child;
});
- if ( isTopLevel ) {
+ if (isTopLevel) {
// trim leading and trailing whitespace from the top level
const firstChild = cleaned[0];
- if ( firstChild && firstChild.type === 'Text' ) {
- firstChild.data = trimStart( firstChild.data );
- if ( !firstChild.data ) cleaned.shift();
+ if (firstChild && firstChild.type === 'Text') {
+ firstChild.data = trimStart(firstChild.data);
+ if (!firstChild.data) cleaned.shift();
}
- const lastChild = cleaned[ cleaned.length - 1 ];
- if ( lastChild && lastChild.type === 'Text' ) {
- lastChild.data = trimEnd( lastChild.data );
- if ( !lastChild.data ) cleaned.pop();
+ const lastChild = cleaned[cleaned.length - 1];
+ if (lastChild && lastChild.type === 'Text') {
+ lastChild.data = trimEnd(lastChild.data);
+ if (!lastChild.data) cleaned.pop();
}
}
lastChild = null;
- cleaned.forEach( ( child: Node ) => {
- const preprocess = preprocessors[ child.type ];
- if ( preprocess ) preprocess( generator, block, state, child );
+ cleaned.forEach((child: Node) => {
+ const preprocess = preprocessors[child.type];
+ if (preprocess) preprocess(generator, block, state, child);
- if ( lastChild ) {
+ if (lastChild) {
lastChild.next = child;
lastChild.needsAnchor = !child._state || !child._state.name;
}
@@ -288,24 +334,28 @@ function preprocessChildren ( generator: DomGenerator, block: Block, state: Stat
lastChild = child;
});
- if ( lastChild ) {
+ if (lastChild) {
lastChild.needsAnchor = !state.parentNode;
}
node.children = cleaned;
}
-export default function preprocess ( generator: DomGenerator, namespace: string, node: Node ) {
+export default function preprocess(
+ generator: DomGenerator,
+ namespace: string,
+ node: Node
+) {
const block = new Block({
generator,
- name: generator.alias( 'create_main_fragment' ),
+ name: generator.alias('create_main_fragment'),
key: null,
contexts: new Map(),
indexes: new Map(),
contextDependencies: new Map(),
- params: [ 'state' ],
+ params: ['state'],
indexNames: new Map(),
listNames: new Map(),
@@ -318,9 +368,9 @@ export default function preprocess ( generator: DomGenerator, namespace: string,
isTopLevel: true
};
- generator.blocks.push( block );
- preprocessChildren( generator, block, state, node, true );
+ generator.blocks.push(block);
+ preprocessChildren(generator, block, state, node, true);
block.hasUpdateMethod = block.dependencies.size > 0;
return { block, state };
-}
\ No newline at end of file
+}
diff --git a/src/generators/dom/visit.ts b/src/generators/dom/visit.ts
index 1443a32049..91601fa8fd 100644
--- a/src/generators/dom/visit.ts
+++ b/src/generators/dom/visit.ts
@@ -3,7 +3,12 @@ import { DomGenerator } from './index';
import Block from './Block';
import { Node } from '../../interfaces';
-export default function visit ( generator: DomGenerator, block: Block, state, node: Node ) {
- const visitor = visitors[ node.type ];
- visitor( generator, block, state, node );
-}
\ No newline at end of file
+export default function visit(
+ generator: DomGenerator,
+ block: Block,
+ state,
+ node: Node
+) {
+ const visitor = visitors[node.type];
+ visitor(generator, block, state, node);
+}
diff --git a/src/generators/dom/visitors/Component/Attribute.ts b/src/generators/dom/visitors/Component/Attribute.ts
index 690cb88639..1bd477a396 100644
--- a/src/generators/dom/visitors/Component/Attribute.ts
+++ b/src/generators/dom/visitors/Component/Attribute.ts
@@ -3,37 +3,40 @@ import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
-export default function visitAttribute ( generator: DomGenerator, block: Block, state: State, node: Node, attribute, local ) {
- if ( attribute.value === true ) {
+export default function visitAttribute(
+ generator: DomGenerator,
+ block: Block,
+ state: State,
+ node: Node,
+ attribute,
+ local
+) {
+ if (attribute.value === true) {
// attributes without values, e.g.