From 49b21362206bdf70377af46abaa37b24c286f02d Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 13 Jun 2017 17:54:59 -0400 Subject: [PATCH 01/12] add hydration tests --- test/helpers.js | 3 +- test/hydration/index.js | 104 ++++++++++++++++++++++ test/hydration/samples/basic/_after.html | 1 + test/hydration/samples/basic/_before.html | 1 + test/hydration/samples/basic/_config.js | 17 ++++ test/hydration/samples/basic/main.html | 1 + 6 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 test/hydration/index.js create mode 100644 test/hydration/samples/basic/_after.html create mode 100644 test/hydration/samples/basic/_before.html create mode 100644 test/hydration/samples/basic/_config.js create mode 100644 test/hydration/samples/basic/main.html diff --git a/test/helpers.js b/test/helpers.js index 33131da558..672c15a896 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -167,8 +167,9 @@ function capitalize(str) { return str[0].toUpperCase() + str.slice(1); } -export function showOutput(cwd, options) { +export function showOutput(cwd, options = {}) { glob.sync('**/*.html', { cwd }).forEach(file => { + if (file[0] === '_') return; const { code } = svelte.compile( fs.readFileSync(`${cwd}/${file}`, 'utf-8'), Object.assign(options, { diff --git a/test/hydration/index.js b/test/hydration/index.js new file mode 100644 index 0000000000..86df95e1c8 --- /dev/null +++ b/test/hydration/index.js @@ -0,0 +1,104 @@ +import assert from 'assert'; +import path from 'path'; +import fs from 'fs'; + +import { + showOutput, + loadConfig, + loadSvelte, + env, + setupHtmlEqual +} from '../helpers.js'; + +let compileOptions = null; + +function getName(filename) { + const base = path.basename(filename).replace('.html', ''); + return base[0].toUpperCase() + base.slice(1); +} + +const nodeVersionMatch = /^v(\d)/.exec(process.version); +const legacy = +nodeVersionMatch[1] < 6; +const babelrc = require('../../package.json').babel; + +describe.only('hydration', () => { + before(() => { + const svelte = loadSvelte(); + + require.extensions['.html'] = function(module, filename) { + const options = Object.assign( + { filename, name: getName(filename) }, + compileOptions + ); + let { code } = svelte.compile(fs.readFileSync(filename, 'utf-8'), options); + + if (legacy) code = require('babel-core').transform(code, babelrc).code; + + return module._compile(code, filename); + }; + + return setupHtmlEqual(); + }); + + function runTest(dir) { + if (dir[0] === '.') return; + + const config = loadConfig(`./hydration/samples/${dir}/_config.js`); + + if (config.solo && process.env.CI) { + throw new Error('Forgot to remove `solo: true` from test'); + } + + (config.skip ? it.skip : config.solo ? it.only : it)(dir, () => { + const cwd = path.resolve(`test/hydration/samples/${dir}`); + + compileOptions = config.compileOptions || {}; + compileOptions.shared = path.resolve('shared.js'); + compileOptions.dev = config.dev; + compileOptions.hydrate = true; + + return env() + .then(window => { + global.window = window; + + let SvelteComponent; + + try { + SvelteComponent = require(`${cwd}/main.html`).default; + } catch (err) { + throw err; + } + + const target = window.document.body; + target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8'); + + const snapshot = config.snapshot ? config.snapshot(target) : {}; + + const component = new SvelteComponent({ + target, + data: config.data + }); + + assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8')); + + if (config.test) { + config.test(assert, target, snapshot); + } else { + component.destroy(); + assert.equal(target.innerHTML, ''); + } + }) + .catch(err => { + showOutput(cwd, { shared: 'svelte/shared.js' }); // eslint-disable-line no-console + throw err; + }) + .then(() => { + if (config.show) showOutput(cwd, { shared: 'svelte/shared.js' }); + }); + }); + } + + fs.readdirSync('test/hydration/samples').forEach(dir => { + runTest(dir, null); + }); +}); diff --git a/test/hydration/samples/basic/_after.html b/test/hydration/samples/basic/_after.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/basic/_after.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/basic/_before.html b/test/hydration/samples/basic/_before.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/basic/_before.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/basic/_config.js b/test/hydration/samples/basic/_config.js new file mode 100644 index 0000000000..79d91d0d8a --- /dev/null +++ b/test/hydration/samples/basic/_config.js @@ -0,0 +1,17 @@ +export default { + snapshot(target) { + const h1 = target.querySelector('h1'); + + return { + h1, + text: h1.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const h1 = target.querySelector('h1'); + + assert.equal(h1, snapshot.h1); + assert.equal(h1.childNodes[0], snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/basic/main.html b/test/hydration/samples/basic/main.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/basic/main.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file From 0013764a012f74677627111bfbe4950c4b67bc76 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Jun 2017 16:34:12 -0400 Subject: [PATCH 02/12] first, very incorrect stab at hydration --- src/generators/dom/index.ts | 3 +- src/generators/dom/preprocess.ts | 2 +- .../dom/visitors/Element/Element.ts | 30 +++++++++++++---- src/generators/dom/visitors/Text.ts | 32 ++++++++++++++++--- src/shared/dom.js | 28 ++++++++++++++++ .../samples/dynamic-text-changed/_after.html | 1 + .../samples/dynamic-text-changed/_before.html | 1 + .../samples/dynamic-text-changed/_config.js | 21 ++++++++++++ .../samples/dynamic-text-changed/main.html | 1 + .../samples/dynamic-text/_after.html | 1 + .../samples/dynamic-text/_before.html | 1 + .../hydration/samples/dynamic-text/_config.js | 21 ++++++++++++ test/hydration/samples/dynamic-text/main.html | 1 + 13 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 test/hydration/samples/dynamic-text-changed/_after.html create mode 100644 test/hydration/samples/dynamic-text-changed/_before.html create mode 100644 test/hydration/samples/dynamic-text-changed/_config.js create mode 100644 test/hydration/samples/dynamic-text-changed/main.html create mode 100644 test/hydration/samples/dynamic-text/_after.html create mode 100644 test/hydration/samples/dynamic-text/_before.html create mode 100644 test/hydration/samples/dynamic-text/_config.js create mode 100644 test/hydration/samples/dynamic-text/main.html diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 6306645fa7..2252126861 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -233,8 +233,7 @@ export default function dom( this._fragment = ${generator.alias( 'create_main_fragment' - )}( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + )}( options.target, 0, this._state, this ); ${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`} ${(generator.hasComponents || generator.hasIntroTransitions) && diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 93cbbf7e4b..786988c8fd 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -355,7 +355,7 @@ export default function preprocess( indexes: new Map(), contextDependencies: new Map(), - params: ['state'], + params: ['target', 'h', 'state'], indexNames: new Map(), listNames: new Map(), diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index a35177c073..2f701dff6b 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -178,18 +178,34 @@ export default function visitElement( } } +// function getRenderStatement( +// generator: DomGenerator, +// namespace: string, +// name: string +// ) { +// if (namespace === 'http://www.w3.org/2000/svg') { +// return `${generator.helper('createSvgElement')}( '${name}' )`; +// } + +// if (namespace) { +// return `document.createElementNS( '${namespace}', '${name}' )`; +// } + +// return `${generator.helper('createElement')}( '${name}' )`; +// } + function getRenderStatement( generator: DomGenerator, namespace: string, name: string ) { - if (namespace === 'http://www.w3.org/2000/svg') { - return `${generator.helper('createSvgElement')}( '${name}' )`; - } + // if (namespace === 'http://www.w3.org/2000/svg') { + // return `${generator.helper('createSvgElement')}( '${name}' )`; + // } - if (namespace) { - return `document.createElementNS( '${namespace}', '${name}' )`; - } + // if (namespace) { + // return `document.createElementNS( '${namespace}', '${name}' )`; + // } - return `${generator.helper('createElement')}( '${name}' )`; + return `${generator.helper('hydrateElement')}( target, h, '${name.toUpperCase()}' )`; } diff --git a/src/generators/dom/visitors/Text.ts b/src/generators/dom/visitors/Text.ts index b8a3850142..9d316a8ae0 100644 --- a/src/generators/dom/visitors/Text.ts +++ b/src/generators/dom/visitors/Text.ts @@ -10,10 +10,32 @@ export default function visitText( node: Node ) { if (!node._state.shouldCreate) return; - block.addElement( - node._state.name, - `${generator.helper('createText')}( ${JSON.stringify(node.data)} )`, - state.parentNode, - node.usedAsAnchor + + const isTopLevel = !state.parentNode; + let h; + if (!isTopLevel) { + h = block.getUniqueName(`${state.parentNode}_i`) + block.addVariable(h, 0); + } else { + h = block.alias('h'); + } + + const prefix = state.parentNode && !node.usedAsAnchor ? '' : `var ${node._state.name} = `; + + block.builders.create.addLine( + `${prefix}${generator.helper('hydrateText')}( ${state.parentNode || 'target'}, ${h}++, ${JSON.stringify(node.data)} )` ); + + if (!state.parentNode) { + this.builders.unmount.addLine( + `${this.generator.helper('detachNode')}( ${name} );` + ); + } + + // block.addElement( + // node._state.name, + // `${generator.helper('hydrateText')}( ${state.parentNode}, 0, ${JSON.stringify(node.data)} )`, + // state.parentNode, + // node.usedAsAnchor + // ); } diff --git a/src/shared/dom.js b/src/shared/dom.js index fba7af93d4..685923f73d 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -66,3 +66,31 @@ export function getBindingGroupValue(group) { export function toNumber(value) { return value === '' ? undefined : +value; } + +export function hydrateElement(target, i, type) { // TODO attrs + var child; + while (child = target.childNodes[i]) { + if (child.nodeName === type) { + return child; + } + target.removeChild(child); + } + + child = createElement(type); + target.appendChild(child); + return child; +} + +export function hydrateText(target, i, data) { + var child; + while (child = target.childNodes[i]) { + if (child.nodeType === 3) { + return (child.data = data, child); + } + target.removeChild(child); + } + + child = createText(data); + target.appendChild(child); + return child; +} \ No newline at end of file diff --git a/test/hydration/samples/dynamic-text-changed/_after.html b/test/hydration/samples/dynamic-text-changed/_after.html new file mode 100644 index 0000000000..c675400a49 --- /dev/null +++ b/test/hydration/samples/dynamic-text-changed/_after.html @@ -0,0 +1 @@ +

Hello everybody!

\ No newline at end of file diff --git a/test/hydration/samples/dynamic-text-changed/_before.html b/test/hydration/samples/dynamic-text-changed/_before.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/dynamic-text-changed/_before.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/dynamic-text-changed/_config.js b/test/hydration/samples/dynamic-text-changed/_config.js new file mode 100644 index 0000000000..e836ebf4b2 --- /dev/null +++ b/test/hydration/samples/dynamic-text-changed/_config.js @@ -0,0 +1,21 @@ +export default { + data: { + name: 'everybody' + }, + + snapshot(target) { + const h1 = target.querySelector('h1'); + + return { + h1, + text: h1.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const h1 = target.querySelector('h1'); + + assert.equal(h1, snapshot.h1); + assert.equal(h1.childNodes[0], snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/dynamic-text-changed/main.html b/test/hydration/samples/dynamic-text-changed/main.html new file mode 100644 index 0000000000..c8dd28836a --- /dev/null +++ b/test/hydration/samples/dynamic-text-changed/main.html @@ -0,0 +1 @@ +

Hello {{name}}!

\ No newline at end of file diff --git a/test/hydration/samples/dynamic-text/_after.html b/test/hydration/samples/dynamic-text/_after.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/dynamic-text/_after.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/dynamic-text/_before.html b/test/hydration/samples/dynamic-text/_before.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/dynamic-text/_before.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/dynamic-text/_config.js b/test/hydration/samples/dynamic-text/_config.js new file mode 100644 index 0000000000..3ca70ee5d2 --- /dev/null +++ b/test/hydration/samples/dynamic-text/_config.js @@ -0,0 +1,21 @@ +export default { + data: { + name: 'world' + }, + + snapshot(target) { + const h1 = target.querySelector('h1'); + + return { + h1, + text: h1.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const h1 = target.querySelector('h1'); + + assert.equal(h1, snapshot.h1); + assert.equal(h1.childNodes[0], snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/dynamic-text/main.html b/test/hydration/samples/dynamic-text/main.html new file mode 100644 index 0000000000..c8dd28836a --- /dev/null +++ b/test/hydration/samples/dynamic-text/main.html @@ -0,0 +1 @@ +

Hello {{name}}!

\ No newline at end of file From 1cc36884d43301ae33776aa86642d88ed5c5300c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 14:31:19 -0400 Subject: [PATCH 03/12] hydration working with elements, text nodes, tags and if blocks --- src/generators/dom/Block.ts | 65 ++++++++----- src/generators/dom/index.ts | 4 +- src/generators/dom/interfaces.ts | 1 + src/generators/dom/preprocess.ts | 6 +- .../dom/visitors/Element/Element.ts | 91 +++++++++++++------ src/generators/dom/visitors/Element/Ref.ts | 4 +- src/generators/dom/visitors/IfBlock.ts | 61 ++++++------- src/generators/dom/visitors/MustacheTag.ts | 1 + src/generators/dom/visitors/Text.ts | 33 ++----- src/shared/dom.js | 36 ++++---- .../samples/element-nested/_after.html | 3 + .../samples/element-nested/_before.html | 3 + .../samples/element-nested/_config.js | 17 ++++ .../samples/element-nested/main.html | 3 + test/hydration/samples/if-block/_after.html | 1 + test/hydration/samples/if-block/_before.html | 1 + test/hydration/samples/if-block/_config.js | 19 ++++ test/hydration/samples/if-block/main.html | 3 + .../samples/top-level-text/_after.html | 1 + .../samples/top-level-text/_before.html | 1 + .../samples/top-level-text/_config.js | 13 +++ .../samples/top-level-text/main.html | 1 + 22 files changed, 231 insertions(+), 137 deletions(-) create mode 100644 test/hydration/samples/element-nested/_after.html create mode 100644 test/hydration/samples/element-nested/_before.html create mode 100644 test/hydration/samples/element-nested/_config.js create mode 100644 test/hydration/samples/element-nested/main.html create mode 100644 test/hydration/samples/if-block/_after.html create mode 100644 test/hydration/samples/if-block/_before.html create mode 100644 test/hydration/samples/if-block/_config.js create mode 100644 test/hydration/samples/if-block/main.html create mode 100644 test/hydration/samples/top-level-text/_after.html create mode 100644 test/hydration/samples/top-level-text/_before.html create mode 100644 test/hydration/samples/top-level-text/_config.js create mode 100644 test/hydration/samples/top-level-text/main.html diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 7b7a19eb10..06a6320cc2 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -40,7 +40,9 @@ export default class Block { listName: string; builders: { + init: CodeBuilder; create: CodeBuilder; + hydrate: CodeBuilder; mount: CodeBuilder; intro: CodeBuilder; update: CodeBuilder; @@ -86,7 +88,9 @@ export default class Block { this.listName = options.listName; this.builders = { + init: new CodeBuilder(), create: new CodeBuilder(), + hydrate: new CodeBuilder(), mount: new CodeBuilder(), intro: new CodeBuilder(), update: new CodeBuilder(), @@ -111,7 +115,7 @@ export default class Block { this.hasUpdateMethod = false; // determined later } - addDependencies(dependencies) { + addDependencies(dependencies: string[]) { dependencies.forEach(dependency => { this.dependencies.add(dependency); }); @@ -120,21 +124,17 @@ export default class Block { addElement( name: string, renderStatement: string, + hydrateStatement: string, parentNode: string, needsIdentifier = false ) { const isToplevel = !parentNode; - if (needsIdentifier || isToplevel) { - this.builders.create.addLine(`var ${name} = ${renderStatement};`); - this.mount(name, parentNode); - } else { - this.builders.create.addLine( - `${this.generator.helper( - 'appendNode' - )}( ${renderStatement}, ${parentNode} );` - ); - } + this.addVariable(name); + this.builders.create.addLine(`${name} = ${renderStatement};`); + this.builders.hydrate.addLine(`${name} = ${hydrateStatement};`) + + this.mount(name, parentNode); if (isToplevel) { this.builders.unmount.addLine( @@ -184,7 +184,7 @@ export default class Block { mount(name: string, parentNode: string) { if (parentNode) { - this.builders.create.addLine( + this.builders.mount.addLine( `${this.generator.helper('appendNode')}( ${name}, ${parentNode} );` ); } else { @@ -210,19 +210,8 @@ export default class Block { this.addVariable(outroing); } - 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(', '); - - this.builders.create.addBlockAtStart(`var ${variables};`); - } - if (this.autofocus) { - this.builders.create.addLine(`${this.autofocus}.focus();`); + this.builders.mount.addLine(`${this.autofocus}.focus();`); } // minor hack – we need to ensure that any {{{triples}}} are detached first @@ -240,6 +229,26 @@ export default class Block { properties.addBlock(`first: ${this.first},`); } + if (this.builders.create.isEmpty()) { + properties.addBlock(`create: ${this.generator.helper('noop')},`); + } else { + properties.addBlock(deindent` + create: function () { + ${this.builders.create} + }, + `); + } + + if (this.builders.hydrate.isEmpty()) { + properties.addBlock(`hydrate: ${this.generator.helper('noop')},`); + } else { + properties.addBlock(deindent` + hydrate: function ( nodes ) { + ${this.builders.hydrate} + }, + `); + } + if (this.builders.mount.isEmpty()) { properties.addBlock(`mount: ${this.generator.helper('noop')},`); } else { @@ -331,7 +340,13 @@ export default class Block { .key ? `, ${localKey}` : ''} ) { - ${this.builders.create} + ${this.variables.size > 0 && ( + `var ${Array.from(this.variables.keys()).map(key => { + const init = this.variables.get(key); + return init !== undefined ? `${key} = ${init}` : key; + }).join(', ')};`)} + + ${!this.builders.init.isEmpty() && this.builders.init} return { ${properties} diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 2252126861..cc54419cbe 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -233,7 +233,9 @@ export default function dom( this._fragment = ${generator.alias( 'create_main_fragment' - )}( options.target, 0, this._state, this ); + )}( this._state, this ); + this._fragment.hydrate( ${generator.helper('children')}( options.target ) ); + this._fragment.mount( options.target, null ); ${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`} ${(generator.hasComponents || generator.hasIntroTransitions) && diff --git a/src/generators/dom/interfaces.ts b/src/generators/dom/interfaces.ts index 625f919c28..03923b79bf 100644 --- a/src/generators/dom/interfaces.ts +++ b/src/generators/dom/interfaces.ts @@ -2,6 +2,7 @@ export interface State { name: string; namespace: string; parentNode: string; + parentNodes: string; isTopLevel: boolean; parentNodeName?: string; basename?: string; diff --git a/src/generators/dom/preprocess.ts b/src/generators/dom/preprocess.ts index 1fabe3a12d..dc3ff59717 100644 --- a/src/generators/dom/preprocess.ts +++ b/src/generators/dom/preprocess.ts @@ -12,7 +12,7 @@ function isElseIf(node: Node) { } function getChildState(parent: State, child = {}) { - return assign({}, parent, { name: null, parentNode: null }, child || {}); + return assign({}, parent, { name: null, parentNode: null, parentNodes: 'nodes' }, child || {}); } // Whitespace inside one of these elements will not result in @@ -285,6 +285,7 @@ const preprocessors = { isTopLevel: false, name, parentNode: name, + parentNodes: block.getUniqueName(`${name}_nodes`), parentNodeName: node.name, namespace: node.name === 'svg' ? 'http://www.w3.org/2000/svg' @@ -388,7 +389,7 @@ export default function preprocess( indexes: new Map(), contextDependencies: new Map(), - params: ['target', 'h', 'state'], + params: ['state'], indexNames: new Map(), listNames: new Map(), @@ -398,6 +399,7 @@ export default function preprocess( const state: State = { namespace, parentNode: null, + parentNodes: 'nodes', isTopLevel: true, }; diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 2f701dff6b..7d7694733a 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -47,14 +47,44 @@ export default function visitElement( 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); + const isToplevel = !state.parentNode; + + block.addVariable(name); + block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`); + block.builders.hydrate.addBlock(deindent` + ${name} = ${getHydrateStatement(generator, childState.namespace, state.parentNodes, node.name)}; + var ${childState.parentNodes} = ${generator.helper('children')}( ${name} ) + `); + + if (state.parentNode) { + block.builders.mount.addLine(`${block.generator.helper('appendNode')}( ${name}, ${state.parentNode} );`); + } else { + block.builders.mount.addLine(`${block.generator.helper('insertNode')}( ${name}, ${block.target}, anchor );`); + } + + if (isToplevel) { + block.builders.unmount.addLine( + `${block.generator.helper('detachNode')}( ${name} );` + ); + } + + // block.addVariable(name); + + // block.builders.create.addLine( + // `${name} = ${getRenderStatement( + // generator, + // childState.namespace, + // node.name + // )};` + // ); + + // block.builders.hydrate.addLine( + // `${name} = ${getHydrateStatement( + // generator, + // childState.namespace, + // node.name + // )};` + // ); // add CSS encapsulation attribute if (generator.cssId && (!generator.cascade || state.isTopLevel)) { @@ -178,34 +208,35 @@ export default function visitElement( } } -// function getRenderStatement( -// generator: DomGenerator, -// namespace: string, -// name: string -// ) { -// if (namespace === 'http://www.w3.org/2000/svg') { -// return `${generator.helper('createSvgElement')}( '${name}' )`; -// } +function getRenderStatement( + generator: DomGenerator, + namespace: string, + name: string +) { + if (namespace === 'http://www.w3.org/2000/svg') { + return `${generator.helper('createSvgElement')}( '${name}' )`; + } -// if (namespace) { -// return `document.createElementNS( '${namespace}', '${name}' )`; -// } + if (namespace) { + return `document.createElementNS( '${namespace}', '${name}' )`; + } -// return `${generator.helper('createElement')}( '${name}' )`; -// } + return `${generator.helper('createElement')}( '${name}' )`; +} -function getRenderStatement( +function getHydrateStatement( generator: DomGenerator, namespace: string, + nodes: string, name: string ) { - // if (namespace === 'http://www.w3.org/2000/svg') { - // return `${generator.helper('createSvgElement')}( '${name}' )`; - // } + if (namespace === 'http://www.w3.org/2000/svg') { + return `${generator.helper('claimSvgElement')}( '${name}' )`; + } - // if (namespace) { - // return `document.createElementNS( '${namespace}', '${name}' )`; - // } + if (namespace) { + throw new Error('TODO hydrate exotic namespaces'); + } - return `${generator.helper('hydrateElement')}( target, h, '${name.toUpperCase()}' )`; -} + return `${generator.helper('claimElement')}( ${nodes}, '${name.toUpperCase()}' )`; +} \ No newline at end of file diff --git a/src/generators/dom/visitors/Element/Ref.ts b/src/generators/dom/visitors/Element/Ref.ts index 524c168e2b..53b808c97e 100644 --- a/src/generators/dom/visitors/Element/Ref.ts +++ b/src/generators/dom/visitors/Element/Ref.ts @@ -13,11 +13,11 @@ export default function visitRef( ) { const name = attribute.name; - block.builders.create.addLine( + block.builders.mount.addLine( `${block.component}.refs.${name} = ${state.parentNode};` ); - block.builders.destroy.addLine(deindent` + block.builders.unmount.addLine(deindent` if ( ${block.component}.refs.${name} === ${state.parentNode} ) ${block.component}.refs.${name} = null; `); diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index e36c5d9984..5a40541031 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -105,10 +105,19 @@ export default function visitIfBlock( simple(generator, block, state, node, branches[0], dynamic, vars); } + block.builders.create.addLine( + `${name}.create();` + ); + + block.builders.hydrate.addLine( + `${name}.hydrate( ${state.parentNodes} );` + ); + if (node.needsAnchor) { block.addElement( anchor, `${generator.helper('createComment')}()`, + `${generator.helper('createComment')}()`, state.parentNode, true ); @@ -126,22 +135,18 @@ function simple( dynamic, { name, anchor, params, if_name } ) { - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} ); `); const isTopLevel = !state.parentNode; const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount'; + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; - if (isTopLevel) { - block.builders.mount.addLine( - `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` - ); - } else { - block.builders.create.addLine( - `if ( ${name} ) ${name}.${mountOrIntro}( ${state.parentNode}, null );` - ); - } + block.builders.mount.addLine( + `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` + ); const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -218,7 +223,7 @@ function compound( const current_block = block.getUniqueName(`current_block`); const current_block_and = hasElse ? '' : `${current_block} && `; - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${get_block} ( ${params} ) { ${branches .map(({ condition, block }) => { @@ -234,15 +239,11 @@ function compound( const isTopLevel = !state.parentNode; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; - if (isTopLevel) { - block.builders.mount.addLine( - `${if_name}${name}.${mountOrIntro}( ${block.target}, anchor );` - ); - } else { - block.builders.create.addLine( - `${if_name}${name}.${mountOrIntro}( ${state.parentNode}, null );` - ); - } + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; + block.builders.mount.addLine( + `${if_name}${name}.${mountOrIntro}( ${targetNode}, ${anchorNode} );` + ); const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -304,7 +305,7 @@ function compoundWithOutros( block.addVariable(current_block_index); block.addVariable(name); - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` var ${if_block_creators} = [ ${branches.map(branch => branch.block).join(',\n')} ]; @@ -323,12 +324,12 @@ function compoundWithOutros( `); if (hasElse) { - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` ${current_block_index} = ${get_block}( ${params} ); ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); `); } else { - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) { ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); } @@ -337,16 +338,12 @@ function compoundWithOutros( const isTopLevel = !state.parentNode; const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount'; + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; - if (isTopLevel) { - block.builders.mount.addLine( - `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${block.target}, anchor );` - ); - } else { - block.builders.create.addLine( - `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${state.parentNode}, null );` - ); - } + block.builders.mount.addLine( + `${if_current_block_index}${if_blocks}[ ${current_block_index} ].${mountOrIntro}( ${targetNode}, ${anchorNode} );` + ); const parentNode = state.parentNode || `${anchor}.parentNode`; diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index febdb452e9..5fb3d7fc26 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -19,6 +19,7 @@ export default function visitMustacheTag( block.addElement( name, `${generator.helper('createText')}( ${value} = ${snippet} )`, + `${generator.helper('claimText')}( ${state.parentNode}_nodes, ${value} = ${snippet} )`, state.parentNode, true ); diff --git a/src/generators/dom/visitors/Text.ts b/src/generators/dom/visitors/Text.ts index 9d316a8ae0..ae0337990c 100644 --- a/src/generators/dom/visitors/Text.ts +++ b/src/generators/dom/visitors/Text.ts @@ -10,32 +10,11 @@ export default function visitText( node: Node ) { if (!node._state.shouldCreate) return; - - const isTopLevel = !state.parentNode; - let h; - if (!isTopLevel) { - h = block.getUniqueName(`${state.parentNode}_i`) - block.addVariable(h, 0); - } else { - h = block.alias('h'); - } - - const prefix = state.parentNode && !node.usedAsAnchor ? '' : `var ${node._state.name} = `; - - block.builders.create.addLine( - `${prefix}${generator.helper('hydrateText')}( ${state.parentNode || 'target'}, ${h}++, ${JSON.stringify(node.data)} )` + block.addElement( + node._state.name, + `${generator.helper('createText')}( ${JSON.stringify(node.data)} )`, + `${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )`, + state.parentNode, + node.usedAsAnchor ); - - if (!state.parentNode) { - this.builders.unmount.addLine( - `${this.generator.helper('detachNode')}( ${name} );` - ); - } - - // block.addElement( - // node._state.name, - // `${generator.helper('hydrateText')}( ${state.parentNode}, 0, ${JSON.stringify(node.data)} )`, - // state.parentNode, - // node.usedAsAnchor - // ); } diff --git a/src/shared/dom.js b/src/shared/dom.js index 685923f73d..11d5395b89 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -67,30 +67,30 @@ export function toNumber(value) { return value === '' ? undefined : +value; } -export function hydrateElement(target, i, type) { // TODO attrs - var child; - while (child = target.childNodes[i]) { - if (child.nodeName === type) { - return child; +export function children ( element ) { + return Array.from(element.childNodes); +} + +export function claimElement ( nodes, name ) { + for (var i = 0; i < nodes.length; i += 1) { + var node = nodes[i]; + if (node.nodeName === name) { + return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes } - target.removeChild(child); } - child = createElement(type); - target.appendChild(child); - return child; + console.trace('creating', name); + return createElement(name); } -export function hydrateText(target, i, data) { - var child; - while (child = target.childNodes[i]) { - if (child.nodeType === 3) { - return (child.data = data, child); +export function claimText ( nodes, data ) { + for (var i = 0; i < nodes.length; i += 1) { + var node = nodes[i]; + if (node.nodeType === 3) { + node.data = data; + return nodes.splice(i, 1)[0]; } - target.removeChild(child); } - child = createText(data); - target.appendChild(child); - return child; + return createText(data); } \ No newline at end of file diff --git a/test/hydration/samples/element-nested/_after.html b/test/hydration/samples/element-nested/_after.html new file mode 100644 index 0000000000..b96db6ffc8 --- /dev/null +++ b/test/hydration/samples/element-nested/_after.html @@ -0,0 +1,3 @@ +
+

nested

+
\ No newline at end of file diff --git a/test/hydration/samples/element-nested/_before.html b/test/hydration/samples/element-nested/_before.html new file mode 100644 index 0000000000..b96db6ffc8 --- /dev/null +++ b/test/hydration/samples/element-nested/_before.html @@ -0,0 +1,3 @@ +
+

nested

+
\ No newline at end of file diff --git a/test/hydration/samples/element-nested/_config.js b/test/hydration/samples/element-nested/_config.js new file mode 100644 index 0000000000..0965bc39c6 --- /dev/null +++ b/test/hydration/samples/element-nested/_config.js @@ -0,0 +1,17 @@ +export default { + snapshot(target) { + const div = target.querySelector('div'); + + return { + div, + p: div.querySelector('p') + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + + assert.equal(div, snapshot.div); + assert.equal(div.querySelector('p'), snapshot.p); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-nested/main.html b/test/hydration/samples/element-nested/main.html new file mode 100644 index 0000000000..b96db6ffc8 --- /dev/null +++ b/test/hydration/samples/element-nested/main.html @@ -0,0 +1,3 @@ +
+

nested

+
\ No newline at end of file diff --git a/test/hydration/samples/if-block/_after.html b/test/hydration/samples/if-block/_after.html new file mode 100644 index 0000000000..5ed8b34a53 --- /dev/null +++ b/test/hydration/samples/if-block/_after.html @@ -0,0 +1 @@ +

foo!

\ No newline at end of file diff --git a/test/hydration/samples/if-block/_before.html b/test/hydration/samples/if-block/_before.html new file mode 100644 index 0000000000..5ed8b34a53 --- /dev/null +++ b/test/hydration/samples/if-block/_before.html @@ -0,0 +1 @@ +

foo!

\ No newline at end of file diff --git a/test/hydration/samples/if-block/_config.js b/test/hydration/samples/if-block/_config.js new file mode 100644 index 0000000000..465c32c2af --- /dev/null +++ b/test/hydration/samples/if-block/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + foo: true + }, + + snapshot(target) { + const p = target.querySelector('p'); + + return { + p + }; + }, + + test(assert, target, snapshot) { + const p = target.querySelector('p'); + + assert.equal(p, snapshot.p); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/if-block/main.html b/test/hydration/samples/if-block/main.html new file mode 100644 index 0000000000..050095b913 --- /dev/null +++ b/test/hydration/samples/if-block/main.html @@ -0,0 +1,3 @@ +{{#if foo}} +

foo!

+{{/if}} \ No newline at end of file diff --git a/test/hydration/samples/top-level-text/_after.html b/test/hydration/samples/top-level-text/_after.html new file mode 100644 index 0000000000..4add785daf --- /dev/null +++ b/test/hydration/samples/top-level-text/_after.html @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/test/hydration/samples/top-level-text/_before.html b/test/hydration/samples/top-level-text/_before.html new file mode 100644 index 0000000000..4add785daf --- /dev/null +++ b/test/hydration/samples/top-level-text/_before.html @@ -0,0 +1 @@ +Text \ No newline at end of file diff --git a/test/hydration/samples/top-level-text/_config.js b/test/hydration/samples/top-level-text/_config.js new file mode 100644 index 0000000000..e8a81e7ca1 --- /dev/null +++ b/test/hydration/samples/top-level-text/_config.js @@ -0,0 +1,13 @@ +export default { + snapshot(target) { + return { + text: target.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const text = target.childNodes[0]; + + assert.equal(text, snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/top-level-text/main.html b/test/hydration/samples/top-level-text/main.html new file mode 100644 index 0000000000..4add785daf --- /dev/null +++ b/test/hydration/samples/top-level-text/main.html @@ -0,0 +1 @@ +Text \ No newline at end of file From e26c37b408127a4a09747a54835179c15c81985b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 16:28:43 -0400 Subject: [PATCH 04/12] more hydration --- src/generators/dom/Block.ts | 20 +++- src/generators/dom/index.ts | 10 +- .../dom/visitors/Component/Component.ts | 8 +- src/generators/dom/visitors/EachBlock.ts | 94 ++++++++++++------- .../dom/visitors/Element/Attribute.ts | 22 +++-- .../dom/visitors/Element/Element.ts | 39 ++++---- src/generators/dom/visitors/IfBlock.ts | 4 +- src/generators/dom/visitors/RawMustacheTag.ts | 13 ++- src/shared/dom.js | 10 +- test/hydration/index.js | 2 +- test/hydration/samples/component/Nested.html | 1 + test/hydration/samples/component/_after.html | 1 + test/hydration/samples/component/_before.html | 1 + test/hydration/samples/component/_config.js | 17 ++++ test/hydration/samples/component/main.html | 11 +++ test/hydration/samples/each-block/_after.html | 5 + .../hydration/samples/each-block/_before.html | 5 + test/hydration/samples/each-block/_config.js | 29 ++++++ test/hydration/samples/each-block/main.html | 5 + .../element-attribute-added/_after.html | 1 + .../element-attribute-added/_before.html | 1 + .../element-attribute-added/_config.js | 19 ++++ .../samples/element-attribute-added/main.html | 1 + .../element-attribute-changed/_after.html | 1 + .../element-attribute-changed/_before.html | 1 + .../element-attribute-changed/_config.js | 19 ++++ .../element-attribute-changed/main.html | 1 + .../element-attribute-removed/_after.html | 1 + .../element-attribute-removed/_before.html | 1 + .../element-attribute-removed/_config.js | 19 ++++ .../element-attribute-removed/main.html | 1 + .../element-attribute-unchanged/_after.html | 1 + .../element-attribute-unchanged/_before.html | 1 + .../element-attribute-unchanged/_config.js | 15 +++ .../element-attribute-unchanged/main.html | 1 + .../hydration/samples/element-ref/_after.html | 1 + .../samples/element-ref/_before.html | 1 + test/hydration/samples/element-ref/_config.js | 16 ++++ test/hydration/samples/element-ref/main.html | 1 + test/hydration/samples/raw/_after.html | 4 + test/hydration/samples/raw/_before.html | 2 + test/hydration/samples/raw/_config.js | 27 ++++++ test/hydration/samples/raw/main.html | 1 + 43 files changed, 350 insertions(+), 84 deletions(-) create mode 100644 test/hydration/samples/component/Nested.html create mode 100644 test/hydration/samples/component/_after.html create mode 100644 test/hydration/samples/component/_before.html create mode 100644 test/hydration/samples/component/_config.js create mode 100644 test/hydration/samples/component/main.html create mode 100644 test/hydration/samples/each-block/_after.html create mode 100644 test/hydration/samples/each-block/_before.html create mode 100644 test/hydration/samples/each-block/_config.js create mode 100644 test/hydration/samples/each-block/main.html create mode 100644 test/hydration/samples/element-attribute-added/_after.html create mode 100644 test/hydration/samples/element-attribute-added/_before.html create mode 100644 test/hydration/samples/element-attribute-added/_config.js create mode 100644 test/hydration/samples/element-attribute-added/main.html create mode 100644 test/hydration/samples/element-attribute-changed/_after.html create mode 100644 test/hydration/samples/element-attribute-changed/_before.html create mode 100644 test/hydration/samples/element-attribute-changed/_config.js create mode 100644 test/hydration/samples/element-attribute-changed/main.html create mode 100644 test/hydration/samples/element-attribute-removed/_after.html create mode 100644 test/hydration/samples/element-attribute-removed/_before.html create mode 100644 test/hydration/samples/element-attribute-removed/_config.js create mode 100644 test/hydration/samples/element-attribute-removed/main.html create mode 100644 test/hydration/samples/element-attribute-unchanged/_after.html create mode 100644 test/hydration/samples/element-attribute-unchanged/_before.html create mode 100644 test/hydration/samples/element-attribute-unchanged/_config.js create mode 100644 test/hydration/samples/element-attribute-unchanged/main.html create mode 100644 test/hydration/samples/element-ref/_after.html create mode 100644 test/hydration/samples/element-ref/_before.html create mode 100644 test/hydration/samples/element-ref/_config.js create mode 100644 test/hydration/samples/element-ref/main.html create mode 100644 test/hydration/samples/raw/_after.html create mode 100644 test/hydration/samples/raw/_before.html create mode 100644 test/hydration/samples/raw/_config.js create mode 100644 test/hydration/samples/raw/main.html diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 06a6320cc2..f9cd8e8513 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -42,6 +42,7 @@ export default class Block { builders: { init: CodeBuilder; create: CodeBuilder; + claim: CodeBuilder; hydrate: CodeBuilder; mount: CodeBuilder; intro: CodeBuilder; @@ -90,6 +91,7 @@ export default class Block { this.builders = { init: new CodeBuilder(), create: new CodeBuilder(), + claim: new CodeBuilder(), hydrate: new CodeBuilder(), mount: new CodeBuilder(), intro: new CodeBuilder(), @@ -124,7 +126,7 @@ export default class Block { addElement( name: string, renderStatement: string, - hydrateStatement: string, + claimStatement: string, parentNode: string, needsIdentifier = false ) { @@ -132,7 +134,7 @@ export default class Block { this.addVariable(name); this.builders.create.addLine(`${name} = ${renderStatement};`); - this.builders.hydrate.addLine(`${name} = ${hydrateStatement};`) + this.builders.claim.addLine(`${name} = ${claimStatement};`) this.mount(name, parentNode); @@ -235,13 +237,23 @@ export default class Block { properties.addBlock(deindent` create: function () { ${this.builders.create} + ${!this.builders.hydrate.isEmpty() && `this.hydrate();`} }, `); } - if (this.builders.hydrate.isEmpty()) { - properties.addBlock(`hydrate: ${this.generator.helper('noop')},`); + if (this.builders.claim.isEmpty()) { + properties.addBlock(`claim: ${this.generator.helper('noop')},`); } else { + properties.addBlock(deindent` + claim: function ( nodes ) { + ${this.builders.claim} + ${!this.builders.hydrate.isEmpty() && `this.hydrate();`} + }, + `); + } + + if (!this.builders.hydrate.isEmpty()) { properties.addBlock(deindent` hydrate: function ( nodes ) { ${this.builders.hydrate} diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index cc54419cbe..5ba3d8ea3a 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -234,8 +234,14 @@ export default function dom( this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this ); - this._fragment.hydrate( ${generator.helper('children')}( options.target ) ); - this._fragment.mount( options.target, null ); + + if ( options.target ) { + var nodes = ${generator.helper('children')}( options.target ); + this._fragment.claim( nodes ); + nodes.forEach( ${generator.helper('detachNode')} ); + this._fragment.mount( options.target, null ); + } + ${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`} ${(generator.hasComponents || generator.hasIntroTransitions) && diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 60c58602fe..8bcd52e6ca 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -129,7 +129,7 @@ export default function visitComponent( const yieldFragment = block.getUniqueName(`${name}_yield_fragment`); - block.builders.create.addLine( + block.builders.init.addLine( `var ${yieldFragment} = ${childBlock.name}( ${params}, ${block.component} );` ); @@ -222,6 +222,10 @@ export default function visitComponent( block.builders.unmount.addLine(`${name}._fragment.unmount();`); block.builders.destroy.addLine(`${name}.destroy( false );`); - block.builders.create.addBlock(local.create); + block.builders.init.addBlock(local.create); + + block.builders.create.addLine(`${name}._fragment.create();`); + block.builders.claim.addLine(`${name}._fragment.claim( ${state.parentNodes} );`); + if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); } diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 25642b1865..1648534438 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -35,7 +35,7 @@ export default function visitEachBlock( const { snippet } = block.contextualise(node.expression); - block.builders.create.addLine(`var ${each_block_value} = ${snippet};`); + block.builders.init.addLine(`var ${each_block_value} = ${snippet};`); if (node.key) { keyed(generator, block, state, node, snippet, vars); @@ -49,6 +49,7 @@ export default function visitEachBlock( block.addElement( anchor, `${generator.helper('createComment')}()`, + `${generator.helper('createComment')}()`, state.parentNode, true ); @@ -59,23 +60,18 @@ export default function visitEachBlock( if (node.else) { const each_block_else = generator.getUniqueName(`${each_block}_else`); - block.builders.create.addLine(`var ${each_block_else} = null;`); + block.builders.init.addLine(`var ${each_block_else} = null;`); // TODO neaten this up... will end up with an empty line in the block - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` if ( !${each_block_value}.length ) { - ${each_block_else} = ${node.else._block - .name}( ${params}, ${block.component} ); - ${!isToplevel - ? `${each_block_else}.${mountOrIntro}( ${state.parentNode}, null );` - : ''} + ${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); } `); block.builders.mount.addBlock(deindent` if ( ${each_block_else} ) { - ${each_block_else}.${mountOrIntro}( ${state.parentNode || - block.target}, null ); + ${each_block_else}.${mountOrIntro}( ${state.parentNode || block.target}, null ); } `); @@ -86,8 +82,7 @@ export default function visitEachBlock( 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} = ${node.else._block.name}( ${params}, ${block.component} ); ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} ); } else if ( ${each_block_else} ) { ${each_block_else}.unmount(); @@ -162,6 +157,7 @@ function keyed( node._block.addElement( node._block.first, `${generator.helper('createComment')}()`, + `${generator.helper('createComment')}()`, null, true ); @@ -176,8 +172,6 @@ function keyed( 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}; @@ -187,15 +181,32 @@ function keyed( } `); - if (!state.parentNode) { - block.builders.mount.addBlock(deindent` - var ${iteration} = ${head}; - while ( ${iteration} ) { - ${iteration}.${mountOrIntro}( ${block.target}, anchor ); - ${iteration} = ${iteration}.next; - } - `); - } + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; + + block.builders.create.addBlock(deindent` + var ${iteration} = ${head}; + while ( ${iteration} ) { + ${iteration}.create(); + ${iteration} = ${iteration}.next; + } + `); + + block.builders.claim.addBlock(deindent` + var ${iteration} = ${head}; + while ( ${iteration} ) { + ${iteration}.claim( ${state.parentNodes} ); + ${iteration} = ${iteration}.next; + } + `); + + block.builders.mount.addBlock(deindent` + var ${iteration} = ${head}; + while ( ${iteration} ) { + ${iteration}.${mountOrIntro}( ${targetNode}, ${anchorNode} ); + ${iteration} = ${iteration}.next; + } + `); const dynamic = node._block.hasUpdateMethod; const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -203,7 +214,7 @@ function keyed( let destroy; if (node._block.hasOutroMethod) { const fn = block.getUniqueName(`${each_block}_outro`); - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${fn} ( iteration ) { iteration.outro( function () { iteration.unmount(); @@ -227,7 +238,7 @@ function keyed( `; } else { const fn = block.getUniqueName(`${each_block}_destroy`); - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${fn} ( iteration ) { iteration.unmount(); iteration.destroy(); @@ -353,27 +364,38 @@ function unkeyed( mountOrIntro, } ) { - block.builders.create.addBlock(deindent` + block.builders.init.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}, anchor ); - } - `); - } + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; + + block.builders.create.addBlock(deindent` + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].create(); + } + `); + + block.builders.claim.addBlock(deindent` + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].claim( ${state.parentNodes} ); + } + `); + + block.builders.mount.addBlock(deindent` + for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) { + ${iterations}[${i}].${mountOrIntro}( ${targetNode}, ${anchorNode} ); + } + `); const dependencies = block.findDependencies(node.expression); const allDependencies = new Set(node._block.dependencies); - dependencies.forEach(dependency => { + dependencies.forEach((dependency: string) => { allDependencies.add(dependency); }); diff --git a/src/generators/dom/visitors/Element/Attribute.ts b/src/generators/dom/visitors/Element/Attribute.ts index 14f68b5a5e..97d2bfe8be 100644 --- a/src/generators/dom/visitors/Element/Attribute.ts +++ b/src/generators/dom/visitors/Element/Attribute.ts @@ -22,7 +22,11 @@ export default function visitAttribute( const isIndirectlyBoundValue = name === 'value' && (node.name === 'option' || // TODO check it's actually bound - (node.name === 'input' && node.attributes.find((attribute: Node) => attribute.type === 'Binding' && /checked|group/.test(attribute.name)))); + (node.name === 'input' && + node.attributes.find( + (attribute: Node) => + attribute.type === 'Binding' && /checked|group/.test(attribute.name) + ))); const propertyName = isIndirectlyBoundValue ? '__value' @@ -76,7 +80,7 @@ export default function visitAttribute( // annoying special case const isMultipleSelect = node.name === 'select' && - node.attributes.find(attr => attr.name.toLowerCase() === 'multiple'); // TODO use getStaticAttributeValue + node.attributes.find((attr: Node) => attr.name.toLowerCase() === 'multiple'); // TODO use getStaticAttributeValue const i = block.getUniqueName('i'); const option = block.getUniqueName('option'); @@ -97,17 +101,17 @@ export default function visitAttribute( } `; - block.builders.create.addLine(deindent` + block.builders.hydrate.addLine(deindent` ${last} = ${value} ${updater} `); } else if (propertyName) { - block.builders.create.addLine( + block.builders.hydrate.addLine( `${state.parentNode}.${propertyName} = ${last} = ${value};` ); updater = `${state.parentNode}.${propertyName} = ${last};`; } else { - block.builders.create.addLine( + block.builders.hydrate.addLine( `${generator.helper( method )}( ${state.parentNode}, '${name}', ${last} = ${value} );` @@ -132,10 +136,10 @@ export default function visitAttribute( const statement = propertyName ? `${state.parentNode}.${propertyName} = ${value};` : `${generator.helper( - method - )}( ${state.parentNode}, '${name}', ${value} );`; + method + )}( ${state.parentNode}, '${name}', ${value} );`; - block.builders.create.addLine(statement); + block.builders.hydrate.addLine(statement); // special case – autofocus. has to be handled in a bit of a weird way if (attribute.value === true && name === 'autofocus') { @@ -152,7 +156,7 @@ export default function visitAttribute( if (isIndirectlyBoundValue) { const updateValue = `${state.parentNode}.value = ${state.parentNode}.__value;`; - block.builders.create.addLine(updateValue); + block.builders.hydrate.addLine(updateValue); if (isDynamic) block.builders.update.addLine(updateValue); } } diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 7d7694733a..14b92e5bae 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -51,9 +51,9 @@ export default function visitElement( block.addVariable(name); block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`); - block.builders.hydrate.addBlock(deindent` - ${name} = ${getHydrateStatement(generator, childState.namespace, state.parentNodes, node.name)}; - var ${childState.parentNodes} = ${generator.helper('children')}( ${name} ) + block.builders.claim.addBlock(deindent` + ${name} = ${getClaimStatement(generator, childState.namespace, state.parentNodes, node)}; + var ${childState.parentNodes} = ${generator.helper('children')}( ${name} ); `); if (state.parentNode) { @@ -62,12 +62,6 @@ export default function visitElement( block.builders.mount.addLine(`${block.generator.helper('insertNode')}( ${name}, ${block.target}, anchor );`); } - if (isToplevel) { - block.builders.unmount.addLine( - `${block.generator.helper('detachNode')}( ${name} );` - ); - } - // block.addVariable(name); // block.builders.create.addLine( @@ -78,8 +72,8 @@ export default function visitElement( // )};` // ); - // block.builders.hydrate.addLine( - // `${name} = ${getHydrateStatement( + // block.builders.claim.addLine( + // `${name} = ${getClaimStatement( // generator, // childState.namespace, // node.name @@ -88,7 +82,7 @@ export default function visitElement( // add CSS encapsulation attribute if (generator.cssId && (!generator.cascade || state.isTopLevel)) { - block.builders.create.addLine( + block.builders.hydrate.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` @@ -150,7 +144,7 @@ export default function visitElement( } } - if (!state.parentNode) { + if (isToplevel) { // TODO we eventually need to consider what happens to elements // that belong to the same outgroup as an outroing element... block.builders.unmount.addLine( @@ -206,6 +200,10 @@ export default function visitElement( if (node.initialUpdate) { block.builders.create.addBlock(node.initialUpdate); } + + block.builders.claim.addLine( + `${childState.parentNodes}.forEach( ${generator.helper('detachNode')} );` + ); } function getRenderStatement( @@ -224,19 +222,24 @@ function getRenderStatement( return `${generator.helper('createElement')}( '${name}' )`; } -function getHydrateStatement( +function getClaimStatement( generator: DomGenerator, namespace: string, nodes: string, - name: string + node: Node ) { if (namespace === 'http://www.w3.org/2000/svg') { - return `${generator.helper('claimSvgElement')}( '${name}' )`; + return `${generator.helper('claimSvgElement')}( '${node.name}' )`; } if (namespace) { - throw new Error('TODO hydrate exotic namespaces'); + throw new Error('TODO claim exotic namespaces'); } - return `${generator.helper('claimElement')}( ${nodes}, '${name.toUpperCase()}' )`; + const attributes = node.attributes + .filter((attr: Node) => attr.type === 'Attribute') + .map((attr: Node) => `${attr.name}: true`) + .join(', '); + + return `${generator.helper('claimElement')}( ${nodes}, '${node.name.toUpperCase()}', ${attributes ? `{ ${attributes} }` : `{}`} )`; } \ No newline at end of file diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 5a40541031..8678537ed6 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -109,8 +109,8 @@ export default function visitIfBlock( `${name}.create();` ); - block.builders.hydrate.addLine( - `${name}.hydrate( ${state.parentNodes} );` + block.builders.claim.addLine( + `${name}.claim( ${state.parentNodes} );` ); if (node.needsAnchor) { diff --git a/src/generators/dom/visitors/RawMustacheTag.ts b/src/generators/dom/visitors/RawMustacheTag.ts index 93c4aabace..ff487302b3 100644 --- a/src/generators/dom/visitors/RawMustacheTag.ts +++ b/src/generators/dom/visitors/RawMustacheTag.ts @@ -17,34 +17,33 @@ export default function visitRawMustacheTag( const { snippet } = block.contextualise(node.expression); + block.addVariable(value); + // we would have used comments here, but the `insertAdjacentHTML` api only // exists for `Element`s. block.addElement( before, `${generator.helper('createElement')}( 'noscript' )`, + `${generator.helper('createElement')}( 'noscript' )`, state.parentNode, true ); block.addElement( after, `${generator.helper('createElement')}( 'noscript' )`, + `${generator.helper('createElement')}( 'noscript' )`, state.parentNode, true ); const isToplevel = !state.parentNode; - block.builders.create.addLine(`var ${value} = ${snippet};`); - const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} );`; + const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} = ${snippet} );`; const detachStatement = `${generator.helper( 'detachBetween' )}( ${before}, ${after} );`; - if (isToplevel) { - block.builders.mount.addLine(mountStatement); - } else { - block.builders.create.addLine(mountStatement); - } + block.builders.mount.addLine(mountStatement); block.builders.update.addBlock(deindent` if ( ${value} !== ( ${value} = ${snippet} ) ) { diff --git a/src/shared/dom.js b/src/shared/dom.js index 11d5395b89..f87bdd8bdd 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -67,14 +67,18 @@ export function toNumber(value) { return value === '' ? undefined : +value; } -export function children ( element ) { +export function children (element) { return Array.from(element.childNodes); } -export function claimElement ( nodes, name ) { +export function claimElement (nodes, name, attributes) { for (var i = 0; i < nodes.length; i += 1) { var node = nodes[i]; if (node.nodeName === name) { + for (var j = 0; j < node.attributes.length; j += 1) { + var attribute = node.attributes[j]; + if (!attributes[attribute.name]) node.removeAttribute(attribute.name); + } return nodes.splice(i, 1)[0]; // TODO strip unwanted attributes } } @@ -83,7 +87,7 @@ export function claimElement ( nodes, name ) { return createElement(name); } -export function claimText ( nodes, data ) { +export function claimText (nodes, data) { for (var i = 0; i < nodes.length; i += 1) { var node = nodes[i]; if (node.nodeType === 3) { diff --git a/test/hydration/index.js b/test/hydration/index.js index 86df95e1c8..3f8ece97e8 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -82,7 +82,7 @@ describe.only('hydration', () => { assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8')); if (config.test) { - config.test(assert, target, snapshot); + config.test(assert, target, snapshot, component); } else { component.destroy(); assert.equal(target.innerHTML, ''); diff --git a/test/hydration/samples/component/Nested.html b/test/hydration/samples/component/Nested.html new file mode 100644 index 0000000000..70bf63ad9d --- /dev/null +++ b/test/hydration/samples/component/Nested.html @@ -0,0 +1 @@ +

nested

\ No newline at end of file diff --git a/test/hydration/samples/component/_after.html b/test/hydration/samples/component/_after.html new file mode 100644 index 0000000000..70bf63ad9d --- /dev/null +++ b/test/hydration/samples/component/_after.html @@ -0,0 +1 @@ +

nested

\ No newline at end of file diff --git a/test/hydration/samples/component/_before.html b/test/hydration/samples/component/_before.html new file mode 100644 index 0000000000..70bf63ad9d --- /dev/null +++ b/test/hydration/samples/component/_before.html @@ -0,0 +1 @@ +

nested

\ No newline at end of file diff --git a/test/hydration/samples/component/_config.js b/test/hydration/samples/component/_config.js new file mode 100644 index 0000000000..c26b3c7ad0 --- /dev/null +++ b/test/hydration/samples/component/_config.js @@ -0,0 +1,17 @@ +export default { + snapshot(target) { + const p = target.querySelector('p'); + + return { + p, + text: p.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const p = target.querySelector('p'); + + assert.equal(p, snapshot.p); + assert.equal(p.childNodes[0], snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/component/main.html b/test/hydration/samples/component/main.html new file mode 100644 index 0000000000..c669ca09a8 --- /dev/null +++ b/test/hydration/samples/component/main.html @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/test/hydration/samples/each-block/_after.html b/test/hydration/samples/each-block/_after.html new file mode 100644 index 0000000000..36cd79642a --- /dev/null +++ b/test/hydration/samples/each-block/_after.html @@ -0,0 +1,5 @@ +
    +
  • animal
  • +
  • vegetable
  • +
  • mineral
  • +
\ No newline at end of file diff --git a/test/hydration/samples/each-block/_before.html b/test/hydration/samples/each-block/_before.html new file mode 100644 index 0000000000..36cd79642a --- /dev/null +++ b/test/hydration/samples/each-block/_before.html @@ -0,0 +1,5 @@ +
    +
  • animal
  • +
  • vegetable
  • +
  • mineral
  • +
\ No newline at end of file diff --git a/test/hydration/samples/each-block/_config.js b/test/hydration/samples/each-block/_config.js new file mode 100644 index 0000000000..065ae25cb9 --- /dev/null +++ b/test/hydration/samples/each-block/_config.js @@ -0,0 +1,29 @@ +export default { + data: { + things: [ + 'animal', + 'vegetable', + 'mineral' + ] + }, + + snapshot(target) { + const ul = target.querySelector('ul'); + const lis = ul.querySelectorAll('li'); + + return { + ul, + lis + }; + }, + + test(assert, target, snapshot) { + const ul = target.querySelector('ul'); + const lis = ul.querySelectorAll('li'); + + assert.equal(ul, snapshot.ul); + assert.equal(lis[0], snapshot.lis[0]); + assert.equal(lis[1], snapshot.lis[1]); + assert.equal(lis[2], snapshot.lis[2]); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/each-block/main.html b/test/hydration/samples/each-block/main.html new file mode 100644 index 0000000000..821f6c53e2 --- /dev/null +++ b/test/hydration/samples/each-block/main.html @@ -0,0 +1,5 @@ +
    + {{#each things as thing}} +
  • {{thing}}
  • + {{/each}} +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-added/_after.html b/test/hydration/samples/element-attribute-added/_after.html new file mode 100644 index 0000000000..8d93bba36d --- /dev/null +++ b/test/hydration/samples/element-attribute-added/_after.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-added/_before.html b/test/hydration/samples/element-attribute-added/_before.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-added/_before.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-added/_config.js b/test/hydration/samples/element-attribute-added/_config.js new file mode 100644 index 0000000000..a1cfe933c2 --- /dev/null +++ b/test/hydration/samples/element-attribute-added/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + class: 'bar' + }, + + snapshot(target) { + const div = target.querySelector('div'); + + return { + div + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + + assert.equal(div, snapshot.div); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-attribute-added/main.html b/test/hydration/samples/element-attribute-added/main.html new file mode 100644 index 0000000000..6b149d165f --- /dev/null +++ b/test/hydration/samples/element-attribute-added/main.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-changed/_after.html b/test/hydration/samples/element-attribute-changed/_after.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-changed/_after.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-changed/_before.html b/test/hydration/samples/element-attribute-changed/_before.html new file mode 100644 index 0000000000..281c6866c3 --- /dev/null +++ b/test/hydration/samples/element-attribute-changed/_before.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-changed/_config.js b/test/hydration/samples/element-attribute-changed/_config.js new file mode 100644 index 0000000000..a1cfe933c2 --- /dev/null +++ b/test/hydration/samples/element-attribute-changed/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + class: 'bar' + }, + + snapshot(target) { + const div = target.querySelector('div'); + + return { + div + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + + assert.equal(div, snapshot.div); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-attribute-changed/main.html b/test/hydration/samples/element-attribute-changed/main.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-changed/main.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-removed/_after.html b/test/hydration/samples/element-attribute-removed/_after.html new file mode 100644 index 0000000000..281c6866c3 --- /dev/null +++ b/test/hydration/samples/element-attribute-removed/_after.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-removed/_before.html b/test/hydration/samples/element-attribute-removed/_before.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-removed/_before.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-removed/_config.js b/test/hydration/samples/element-attribute-removed/_config.js new file mode 100644 index 0000000000..a1cfe933c2 --- /dev/null +++ b/test/hydration/samples/element-attribute-removed/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + class: 'bar' + }, + + snapshot(target) { + const div = target.querySelector('div'); + + return { + div + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + + assert.equal(div, snapshot.div); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-attribute-removed/main.html b/test/hydration/samples/element-attribute-removed/main.html new file mode 100644 index 0000000000..281c6866c3 --- /dev/null +++ b/test/hydration/samples/element-attribute-removed/main.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-unchanged/_after.html b/test/hydration/samples/element-attribute-unchanged/_after.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-unchanged/_after.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-unchanged/_before.html b/test/hydration/samples/element-attribute-unchanged/_before.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-unchanged/_before.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-attribute-unchanged/_config.js b/test/hydration/samples/element-attribute-unchanged/_config.js new file mode 100644 index 0000000000..d41f5db0fb --- /dev/null +++ b/test/hydration/samples/element-attribute-unchanged/_config.js @@ -0,0 +1,15 @@ +export default { + snapshot(target) { + const div = target.querySelector('div'); + + return { + div + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + + assert.equal(div, snapshot.div); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-attribute-unchanged/main.html b/test/hydration/samples/element-attribute-unchanged/main.html new file mode 100644 index 0000000000..c6a8a8c95d --- /dev/null +++ b/test/hydration/samples/element-attribute-unchanged/main.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/hydration/samples/element-ref/_after.html b/test/hydration/samples/element-ref/_after.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/element-ref/_after.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/element-ref/_before.html b/test/hydration/samples/element-ref/_before.html new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/hydration/samples/element-ref/_before.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/element-ref/_config.js b/test/hydration/samples/element-ref/_config.js new file mode 100644 index 0000000000..21743569bd --- /dev/null +++ b/test/hydration/samples/element-ref/_config.js @@ -0,0 +1,16 @@ +export default { + snapshot(target) { + const h1 = target.querySelector('h1'); + + return { + h1, + }; + }, + + test(assert, target, snapshot, component) { + const h1 = target.querySelector('h1'); + + assert.equal(h1, snapshot.h1); + assert.equal(component.refs.h1, h1); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/element-ref/main.html b/test/hydration/samples/element-ref/main.html new file mode 100644 index 0000000000..95c0299762 --- /dev/null +++ b/test/hydration/samples/element-ref/main.html @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/raw/_after.html b/test/hydration/samples/raw/_after.html new file mode 100644 index 0000000000..47214db3c9 --- /dev/null +++ b/test/hydration/samples/raw/_after.html @@ -0,0 +1,4 @@ + +

this is some html

+

and so is this

+ \ No newline at end of file diff --git a/test/hydration/samples/raw/_before.html b/test/hydration/samples/raw/_before.html new file mode 100644 index 0000000000..b68c0e317e --- /dev/null +++ b/test/hydration/samples/raw/_before.html @@ -0,0 +1,2 @@ +

this is some html

+

and so is this

\ No newline at end of file diff --git a/test/hydration/samples/raw/_config.js b/test/hydration/samples/raw/_config.js new file mode 100644 index 0000000000..e1108714ee --- /dev/null +++ b/test/hydration/samples/raw/_config.js @@ -0,0 +1,27 @@ +export default { + skip: true, // existing nodes are blown away + + data: { + raw: `

this is some html

and so is this

` + }, + + snapshot(target) { + const ps = target.querySelectorAll('p'); + + return { + p0: ps[0], + text0: ps[0].firstChild, + p1: ps[1], + text1: ps[1].firstChild + }; + }, + + test(assert, target, snapshot) { + const ps = target.querySelectorAll('p'); + + assert.equal(ps[0], snapshot.p0); + assert.equal(ps[0].firstChild, snapshot.text0); + assert.equal(ps[1], snapshot.p1); + assert.equal(ps[1].firstChild, snapshot.text1); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/raw/main.html b/test/hydration/samples/raw/main.html new file mode 100644 index 0000000000..7523dfd4de --- /dev/null +++ b/test/hydration/samples/raw/main.html @@ -0,0 +1 @@ +{{{raw}}} \ No newline at end of file From d964b63ad3dae1ec3665f6d4c7199570e9a6a8ed Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 17:06:51 -0400 Subject: [PATCH 05/12] fix component hydration --- .../dom/visitors/Component/Component.ts | 7 +++++-- src/shared/dom.js | 1 - .../samples/component-in-element/Nested.html | 1 + .../samples/component-in-element/_after.html | 3 +++ .../samples/component-in-element/_before.html | 3 +++ .../samples/component-in-element/_config.js | 21 +++++++++++++++++++ .../samples/component-in-element/main.html | 13 ++++++++++++ 7 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 test/hydration/samples/component-in-element/Nested.html create mode 100644 test/hydration/samples/component-in-element/_after.html create mode 100644 test/hydration/samples/component-in-element/_before.html create mode 100644 test/hydration/samples/component-in-element/_config.js create mode 100644 test/hydration/samples/component-in-element/main.html diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 8bcd52e6ca..a0616bc587 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -113,7 +113,6 @@ export default function visitComponent( } const componentInitProperties = [ - `target: ${!isTopLevel ? state.parentNode : 'null'}`, `_root: ${block.component}._root`, ]; @@ -123,7 +122,7 @@ export default function visitComponent( const childBlock = node._block; - node.children.forEach(child => { + node.children.forEach((child: Node) => { visit(generator, childBlock, childState, child); }); @@ -224,8 +223,12 @@ export default function visitComponent( block.builders.init.addBlock(local.create); + const targetNode = state.parentNode || block.target; + const anchorNode = state.parentNode ? 'null' : 'anchor'; + block.builders.create.addLine(`${name}._fragment.create();`); block.builders.claim.addLine(`${name}._fragment.claim( ${state.parentNodes} );`); + block.builders.mount.addLine(`${name}._fragment.mount( ${targetNode}, ${anchorNode} );` ); if (!local.update.isEmpty()) block.builders.update.addBlock(local.update); } diff --git a/src/shared/dom.js b/src/shared/dom.js index f87bdd8bdd..ffdecf5730 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -83,7 +83,6 @@ export function claimElement (nodes, name, attributes) { } } - console.trace('creating', name); return createElement(name); } diff --git a/test/hydration/samples/component-in-element/Nested.html b/test/hydration/samples/component-in-element/Nested.html new file mode 100644 index 0000000000..70bf63ad9d --- /dev/null +++ b/test/hydration/samples/component-in-element/Nested.html @@ -0,0 +1 @@ +

nested

\ No newline at end of file diff --git a/test/hydration/samples/component-in-element/_after.html b/test/hydration/samples/component-in-element/_after.html new file mode 100644 index 0000000000..b96db6ffc8 --- /dev/null +++ b/test/hydration/samples/component-in-element/_after.html @@ -0,0 +1,3 @@ +
+

nested

+
\ No newline at end of file diff --git a/test/hydration/samples/component-in-element/_before.html b/test/hydration/samples/component-in-element/_before.html new file mode 100644 index 0000000000..b96db6ffc8 --- /dev/null +++ b/test/hydration/samples/component-in-element/_before.html @@ -0,0 +1,3 @@ +
+

nested

+
\ No newline at end of file diff --git a/test/hydration/samples/component-in-element/_config.js b/test/hydration/samples/component-in-element/_config.js new file mode 100644 index 0000000000..790b026400 --- /dev/null +++ b/test/hydration/samples/component-in-element/_config.js @@ -0,0 +1,21 @@ +export default { + snapshot(target) { + const div = target.querySelector('div'); + const p = target.querySelector('p'); + + return { + div, + p, + text: p.childNodes[0] + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + const p = target.querySelector('p'); + + assert.equal(div, snapshot.div); + assert.equal(p, snapshot.p); + assert.equal(p.childNodes[0], snapshot.text); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/component-in-element/main.html b/test/hydration/samples/component-in-element/main.html new file mode 100644 index 0000000000..d639570c56 --- /dev/null +++ b/test/hydration/samples/component-in-element/main.html @@ -0,0 +1,13 @@ +
+ +
+ + \ No newline at end of file From 79ca1b962c883d88e5304064eedd4a8a87d34b5c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 21:45:05 -0400 Subject: [PATCH 06/12] events, bindings, if block fixes --- .../dom/visitors/Component/Component.ts | 6 --- .../dom/visitors/Element/Binding.ts | 10 +++-- .../dom/visitors/Element/EventHandler.ts | 42 ++++++++++--------- src/generators/dom/visitors/IfBlock.ts | 23 ++++++---- test/hydration/index.js | 2 +- .../samples/binding-input/_after.html | 2 + .../samples/binding-input/_before.html | 2 + .../samples/binding-input/_config.js | 29 +++++++++++++ .../hydration/samples/binding-input/main.html | 2 + .../samples/event-handler/_after.html | 1 + .../samples/event-handler/_before.html | 1 + .../samples/event-handler/_config.js | 26 ++++++++++++ .../hydration/samples/event-handler/main.html | 5 +++ .../samples/if-block-anchor/_after.html | 4 ++ .../samples/if-block-anchor/_before.html | 4 ++ .../samples/if-block-anchor/_config.js | 26 ++++++++++++ .../samples/if-block-anchor/main.html | 9 ++++ .../samples/if-block-false/_after.html | 2 + .../samples/if-block-false/_before.html | 2 + .../samples/if-block-false/_config.js | 19 +++++++++ .../samples/if-block-false/main.html | 5 +++ .../samples/if-block-update/_after.html | 1 + .../samples/if-block-update/_before.html | 1 + .../samples/if-block-update/_config.js | 23 ++++++++++ .../samples/if-block-update/main.html | 7 ++++ 25 files changed, 216 insertions(+), 38 deletions(-) create mode 100644 test/hydration/samples/binding-input/_after.html create mode 100644 test/hydration/samples/binding-input/_before.html create mode 100644 test/hydration/samples/binding-input/_config.js create mode 100644 test/hydration/samples/binding-input/main.html create mode 100644 test/hydration/samples/event-handler/_after.html create mode 100644 test/hydration/samples/event-handler/_before.html create mode 100644 test/hydration/samples/event-handler/_config.js create mode 100644 test/hydration/samples/event-handler/main.html create mode 100644 test/hydration/samples/if-block-anchor/_after.html create mode 100644 test/hydration/samples/if-block-anchor/_before.html create mode 100644 test/hydration/samples/if-block-anchor/_config.js create mode 100644 test/hydration/samples/if-block-anchor/main.html create mode 100644 test/hydration/samples/if-block-false/_after.html create mode 100644 test/hydration/samples/if-block-false/_before.html create mode 100644 test/hydration/samples/if-block-false/_config.js create mode 100644 test/hydration/samples/if-block-false/main.html create mode 100644 test/hydration/samples/if-block-update/_after.html create mode 100644 test/hydration/samples/if-block-update/_before.html create mode 100644 test/hydration/samples/if-block-update/_config.js create mode 100644 test/hydration/samples/if-block-update/main.html diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index a0616bc587..672bb1ca26 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -185,12 +185,6 @@ export default function visitComponent( }); `); - if (isTopLevel) { - block.builders.mount.addLine( - `${name}._fragment.mount( ${block.target}, anchor );` - ); - } - if (local.dynamicAttributes.length) { const updates = local.dynamicAttributes.map(attribute => { if (attribute.dependencies.length) { diff --git a/src/generators/dom/visitors/Element/Binding.ts b/src/generators/dom/visitors/Element/Binding.ts index b190230527..3610ce4e75 100644 --- a/src/generators/dom/visitors/Element/Binding.ts +++ b/src/generators/dom/visitors/Element/Binding.ts @@ -91,7 +91,7 @@ export default function visitBinding( `; generator.hasComplexBindings = true; - block.builders.create.addBlock( + block.builders.hydrate.addBlock( `if ( !('${name}' in state) ) ${block.component}._bindings.push( ${handler} );` ); } else if (attribute.name === 'group') { @@ -107,7 +107,7 @@ export default function visitBinding( ? `~${snippet}.indexOf( ${state.parentNode}.__value )` : `${state.parentNode}.__value === ${snippet}`; - block.builders.create.addLine( + block.builders.hydrate.addLine( `${block.component}._bindingGroups[${bindingGroup}].push( ${state.parentNode} );` ); @@ -118,7 +118,7 @@ export default function visitBinding( updateElement = `${state.parentNode}.checked = ${condition};`; } else if (node.name === 'audio' || node.name === 'video') { generator.hasComplexBindings = true; - block.builders.create.addBlock( + block.builders.hydrate.addBlock( `${block.component}._bindings.push( ${handler} );` ); @@ -144,13 +144,15 @@ export default function visitBinding( } } - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${handler} () { ${lock} = true; ${setter} ${lock} = false; } + `); + block.builders.hydrate.addBlock(deindent` ${generator.helper( 'addListener' )}( ${state.parentNode}, '${eventName}', ${handler} ); diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index dca64561fd..8a84e004ca 100644 --- a/src/generators/dom/visitors/Element/EventHandler.ts +++ b/src/generators/dom/visitors/Element/EventHandler.ts @@ -68,36 +68,38 @@ export default function visitEventHandler( [✂${attribute.expression.start}-${attribute.expression.end}✂]; `; - const handler = isCustomEvent - ? deindent` - var ${handlerName} = ${generator.alias( + if (isCustomEvent) { + block.addVariable(handlerName); + + block.builders.hydrate.addBlock(deindent` + ${handlerName} = ${generator.alias( 'template' )}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) { ${handlerBody} }); - ` - : deindent` + `); + + block.builders.destroy.addLine(deindent` + ${handlerName}.teardown(); + `); + } else { + const handler = deindent` function ${handlerName} ( event ) { ${handlerBody} } `; - if (shouldHoist) { - generator.blocks.push( - { - render: () => handler, - } - ); - } else { - block.builders.create.addBlock(handler); - } + if (shouldHoist) { + generator.blocks.push( + { + render: () => handler, + } + ); + } else { + block.builders.init.addBlock(handler); + } - if (isCustomEvent) { - block.builders.destroy.addLine(deindent` - ${handlerName}.teardown(); - `); - } else { - block.builders.create.addLine(deindent` + block.builders.hydrate.addLine(deindent` ${generator.helper( 'addListener' )}( ${state.parentNode}, '${name}', ${handlerName} ); diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 8678537ed6..2198e60c81 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -106,11 +106,11 @@ export default function visitIfBlock( } block.builders.create.addLine( - `${name}.create();` + `${if_name}${name}.create();` ); block.builders.claim.addLine( - `${name}.claim( ${state.parentNodes} );` + `${if_name}${name}.claim( ${state.parentNodes} );` ); if (node.needsAnchor) { @@ -145,7 +145,7 @@ function simple( const anchorNode = state.parentNode ? 'null' : 'anchor'; block.builders.mount.addLine( - `if ( ${name} ) ${name}.${mountOrIntro}( ${block.target}, anchor );` + `if ( ${name} ) ${name}.${mountOrIntro}( ${targetNode}, ${anchorNode} );` ); const parentNode = state.parentNode || `${anchor}.parentNode`; @@ -166,6 +166,7 @@ function simple( ${name}.update( changed, ${params} ); } else { ${name} = ${branch.block}( ${params}, ${block.component} ); + ${name}.create(); ${name}.mount( ${parentNode}, ${anchor} ); } ` @@ -177,6 +178,7 @@ function simple( : deindent` if ( !${name} ) { ${name} = ${branch.block}( ${params}, ${block.component} ); + ${name}.create(); ${name}.mount( ${parentNode}, ${anchor} ); } `; @@ -248,11 +250,18 @@ function compound( const parentNode = state.parentNode || `${anchor}.parentNode`; const changeBlock = deindent` - ${if_name}{ - ${name}.unmount(); - ${name}.destroy(); - } + ${hasElse ? + deindent` + ${name}.unmount(); + ${name}.destroy(); + ` : + deindent` + if ( ${name} ) { + ${name}.unmount(); + ${name}.destroy(); + }`} ${name} = ${current_block_and}${current_block}( ${params}, ${block.component} ); + ${if_name}${name}.create(); ${if_name}${name}.${mountOrIntro}( ${parentNode}, ${anchor} ); `; diff --git a/test/hydration/index.js b/test/hydration/index.js index 3f8ece97e8..26a8908b76 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -82,7 +82,7 @@ describe.only('hydration', () => { assert.htmlEqual(target.innerHTML, fs.readFileSync(`${cwd}/_after.html`, 'utf-8')); if (config.test) { - config.test(assert, target, snapshot, component); + config.test(assert, target, snapshot, component, window); } else { component.destroy(); assert.equal(target.innerHTML, ''); diff --git a/test/hydration/samples/binding-input/_after.html b/test/hydration/samples/binding-input/_after.html new file mode 100644 index 0000000000..be17352b0d --- /dev/null +++ b/test/hydration/samples/binding-input/_after.html @@ -0,0 +1,2 @@ + +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/binding-input/_before.html b/test/hydration/samples/binding-input/_before.html new file mode 100644 index 0000000000..be17352b0d --- /dev/null +++ b/test/hydration/samples/binding-input/_before.html @@ -0,0 +1,2 @@ + +

Hello world!

\ No newline at end of file diff --git a/test/hydration/samples/binding-input/_config.js b/test/hydration/samples/binding-input/_config.js new file mode 100644 index 0000000000..86ba3fe9ec --- /dev/null +++ b/test/hydration/samples/binding-input/_config.js @@ -0,0 +1,29 @@ +export default { + data: { + name: 'world' + }, + + snapshot(target) { + return { + input: target.querySelector('input'), + p: target.querySelector('p') + }; + }, + + test(assert, target, snapshot, component, window) { + const input = target.querySelector('input'); + const p = target.querySelector('p'); + + assert.equal(input, snapshot.input); + assert.equal(p, snapshot.p); + + input.value = 'everybody'; + input.dispatchEvent(new window.Event('input')); + + assert.equal(component.get('name'), 'everybody'); + assert.htmlEqual(target.innerHTML, ` + +

Hello everybody!

+ `); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/binding-input/main.html b/test/hydration/samples/binding-input/main.html new file mode 100644 index 0000000000..d4359f73e4 --- /dev/null +++ b/test/hydration/samples/binding-input/main.html @@ -0,0 +1,2 @@ + +

Hello {{name}}!

\ No newline at end of file diff --git a/test/hydration/samples/event-handler/_after.html b/test/hydration/samples/event-handler/_after.html new file mode 100644 index 0000000000..13cd34a990 --- /dev/null +++ b/test/hydration/samples/event-handler/_after.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/hydration/samples/event-handler/_before.html b/test/hydration/samples/event-handler/_before.html new file mode 100644 index 0000000000..13cd34a990 --- /dev/null +++ b/test/hydration/samples/event-handler/_before.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/hydration/samples/event-handler/_config.js b/test/hydration/samples/event-handler/_config.js new file mode 100644 index 0000000000..4cd132f801 --- /dev/null +++ b/test/hydration/samples/event-handler/_config.js @@ -0,0 +1,26 @@ +export default { + data: { + clicked: false + }, + + snapshot(target) { + const button = target.querySelector('button'); + + return { + button + }; + }, + + test(assert, target, snapshot, component, window) { + const button = target.querySelector('button'); + assert.equal(button, snapshot.button); + + button.dispatchEvent(new window.MouseEvent('click')); + + assert.ok(component.get('clicked')); + assert.htmlEqual(target.innerHTML, ` + +

clicked!

+ `); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/event-handler/main.html b/test/hydration/samples/event-handler/main.html new file mode 100644 index 0000000000..9533175147 --- /dev/null +++ b/test/hydration/samples/event-handler/main.html @@ -0,0 +1,5 @@ + + +{{#if clicked}} +

clicked!

+{{/if}} \ No newline at end of file diff --git a/test/hydration/samples/if-block-anchor/_after.html b/test/hydration/samples/if-block-anchor/_after.html new file mode 100644 index 0000000000..31701ce3e9 --- /dev/null +++ b/test/hydration/samples/if-block-anchor/_after.html @@ -0,0 +1,4 @@ +
+

foo!

+

bar!

+
\ No newline at end of file diff --git a/test/hydration/samples/if-block-anchor/_before.html b/test/hydration/samples/if-block-anchor/_before.html new file mode 100644 index 0000000000..31701ce3e9 --- /dev/null +++ b/test/hydration/samples/if-block-anchor/_before.html @@ -0,0 +1,4 @@ +
+

foo!

+

bar!

+
\ No newline at end of file diff --git a/test/hydration/samples/if-block-anchor/_config.js b/test/hydration/samples/if-block-anchor/_config.js new file mode 100644 index 0000000000..f9acaa30ed --- /dev/null +++ b/test/hydration/samples/if-block-anchor/_config.js @@ -0,0 +1,26 @@ +export default { + data: { + foo: true, + bar: true + }, + + snapshot(target) { + const div = target.querySelector('div'); + const ps = target.querySelectorAll('p'); + + return { + div, + p0: ps[0], + p1: ps[1] + }; + }, + + test(assert, target, snapshot) { + const div = target.querySelector('div'); + const ps = target.querySelectorAll('p'); + + assert.equal(div, snapshot.div); + assert.equal(ps[0], snapshot.p0); + assert.equal(ps[1], snapshot.p1); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/if-block-anchor/main.html b/test/hydration/samples/if-block-anchor/main.html new file mode 100644 index 0000000000..b46363fecb --- /dev/null +++ b/test/hydration/samples/if-block-anchor/main.html @@ -0,0 +1,9 @@ +
+ {{#if foo}} +

foo!

+ {{/if}} + + {{#if bar}} +

bar!

+ {{/if}} +
\ No newline at end of file diff --git a/test/hydration/samples/if-block-false/_after.html b/test/hydration/samples/if-block-false/_after.html new file mode 100644 index 0000000000..17eb5acc05 --- /dev/null +++ b/test/hydration/samples/if-block-false/_after.html @@ -0,0 +1,2 @@ +

before

+

after

\ No newline at end of file diff --git a/test/hydration/samples/if-block-false/_before.html b/test/hydration/samples/if-block-false/_before.html new file mode 100644 index 0000000000..17eb5acc05 --- /dev/null +++ b/test/hydration/samples/if-block-false/_before.html @@ -0,0 +1,2 @@ +

before

+

after

\ No newline at end of file diff --git a/test/hydration/samples/if-block-false/_config.js b/test/hydration/samples/if-block-false/_config.js new file mode 100644 index 0000000000..ba071e9910 --- /dev/null +++ b/test/hydration/samples/if-block-false/_config.js @@ -0,0 +1,19 @@ +export default { + data: { + foo: false + }, + + snapshot(target) { + const p = target.querySelector('p'); + + return { + p + }; + }, + + test(assert, target, snapshot) { + const p = target.querySelector('p'); + + assert.equal(p, snapshot.p); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/if-block-false/main.html b/test/hydration/samples/if-block-false/main.html new file mode 100644 index 0000000000..4d42d3e7d7 --- /dev/null +++ b/test/hydration/samples/if-block-false/main.html @@ -0,0 +1,5 @@ +

before

+{{#if foo}} +

foo!

+{{/if}} +

after

\ No newline at end of file diff --git a/test/hydration/samples/if-block-update/_after.html b/test/hydration/samples/if-block-update/_after.html new file mode 100644 index 0000000000..5ed8b34a53 --- /dev/null +++ b/test/hydration/samples/if-block-update/_after.html @@ -0,0 +1 @@ +

foo!

\ No newline at end of file diff --git a/test/hydration/samples/if-block-update/_before.html b/test/hydration/samples/if-block-update/_before.html new file mode 100644 index 0000000000..5ed8b34a53 --- /dev/null +++ b/test/hydration/samples/if-block-update/_before.html @@ -0,0 +1 @@ +

foo!

\ No newline at end of file diff --git a/test/hydration/samples/if-block-update/_config.js b/test/hydration/samples/if-block-update/_config.js new file mode 100644 index 0000000000..185dacb1e7 --- /dev/null +++ b/test/hydration/samples/if-block-update/_config.js @@ -0,0 +1,23 @@ +export default { + data: { + foo: true, + bar: false + }, + + snapshot(target) { + const p = target.querySelector('p'); + + return { + p + }; + }, + + test(assert, target, snapshot, component) { + const p = target.querySelector('p'); + + assert.equal(p, snapshot.p); + + component.set({ foo: false, bar: true }); + assert.htmlEqual(target.innerHTML, `

bar!

`); + } +}; \ No newline at end of file diff --git a/test/hydration/samples/if-block-update/main.html b/test/hydration/samples/if-block-update/main.html new file mode 100644 index 0000000000..82c5a193fe --- /dev/null +++ b/test/hydration/samples/if-block-update/main.html @@ -0,0 +1,7 @@ +{{#if foo}} +

foo!

+{{/if}} + +{{#if bar}} +

bar!

+{{/if}} \ No newline at end of file From f10d2bb250203ad00f7cfc10efa0830a285edd7b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 23:41:35 -0400 Subject: [PATCH 07/12] fix most non-hydration tests --- src/generators/dom/visitors/EachBlock.ts | 20 +++++++++------- .../dom/visitors/Element/Element.ts | 24 +++++++++---------- .../dom/visitors/Element/meta/Window.ts | 14 +++++------ src/generators/dom/visitors/IfBlock.ts | 2 ++ src/generators/dom/visitors/MustacheTag.ts | 2 +- src/shared/dom.js | 4 ++-- test/hydration/index.js | 2 +- test/js/index.js | 2 +- test/runtime/samples/svg/_config.js | 2 ++ 9 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 1648534438..70a6665eaa 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -83,6 +83,7 @@ export default function visitEachBlock( ${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}.create(); ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} ); } else if ( ${each_block_else} ) { ${each_block_else}.unmount(); @@ -99,8 +100,8 @@ export default function visitEachBlock( ${each_block_else} = null; } } else if ( !${each_block_else} ) { - ${each_block_else} = ${node.else._block - .name}( ${params}, ${block.component} ); + ${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} ); + ${each_block_else}.create(); ${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} ); } `); @@ -149,6 +150,10 @@ function keyed( const last = block.getUniqueName(`${each_block}_last`); const expected = block.getUniqueName(`${each_block}_expected`); + block.addVariable(lookup, `Object.create( null )`); + block.addVariable(head); + block.addVariable(last); + 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 @@ -163,12 +168,7 @@ function keyed( ); } - block.builders.create.addBlock(deindent` - var ${lookup} = Object.create( null ); - - var ${head}; - var ${last}; - + block.builders.init.addBlock(deindent` 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} ); @@ -297,6 +297,7 @@ function keyed( } else { // key is being inserted ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${iteration}.create(); ${iteration}.${mountOrIntro}( ${parentNode}, ${expected}.first ); ${expected}.last = ${iteration}; @@ -311,6 +312,7 @@ function keyed( ${iteration}.mount( ${parentNode}, ${anchor} ); } else { ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} ); + ${iteration}.create(); ${iteration}.${mountOrIntro}( ${parentNode}, ${anchor} ); } } @@ -414,6 +416,7 @@ function unkeyed( ${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}].create(); } ${iterations}[${i}].intro( ${parentNode}, ${anchor} ); ` @@ -422,6 +425,7 @@ function unkeyed( ${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}].create(); ${iterations}[${i}].mount( ${parentNode}, ${anchor} ); } ` diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 14b92e5bae..5e3ce48e2f 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -6,6 +6,7 @@ import visitAttribute from './Attribute'; import visitEventHandler from './EventHandler'; import visitBinding from './Binding'; import visitRef from './Ref'; +import * as namespaces from '../../../../utils/namespaces'; import addTransitions from './addTransitions'; import { DomGenerator } from '../../index'; import Block from '../../Block'; @@ -131,7 +132,7 @@ export default function visitElement( }); if (initialProps.length) { - block.builders.create.addBlock(deindent` + block.builders.hydrate.addBlock(deindent` ${name}._svelte = { ${initialProps.join(',\n')} }; @@ -198,7 +199,7 @@ export default function visitElement( } if (node.initialUpdate) { - block.builders.create.addBlock(node.initialUpdate); + block.builders.hydrate.addBlock(node.initialUpdate); } block.builders.claim.addLine( @@ -228,18 +229,17 @@ function getClaimStatement( nodes: string, node: Node ) { - if (namespace === 'http://www.w3.org/2000/svg') { - return `${generator.helper('claimSvgElement')}( '${node.name}' )`; - } - - if (namespace) { - throw new Error('TODO claim exotic namespaces'); - } - const attributes = node.attributes .filter((attr: Node) => attr.type === 'Attribute') - .map((attr: Node) => `${attr.name}: true`) + .map((attr: Node) => `${quoteProp(attr.name)}: true`) .join(', '); - return `${generator.helper('claimElement')}( ${nodes}, '${node.name.toUpperCase()}', ${attributes ? `{ ${attributes} }` : `{}`} )`; + const name = namespace ? node.name : node.name.toUpperCase(); + + return `${generator.helper('claimElement')}( ${nodes}, '${name}', ${attributes ? `{ ${attributes} }` : `{}`}, ${namespace === namespaces.svg ? true : false} )`; +} + +function quoteProp(name: string) { + if (/[^a-zA-Z_$0-9]/.test(name)) return `'${name}'`; + return name; } \ No newline at end of file diff --git a/src/generators/dom/visitors/Element/meta/Window.ts b/src/generators/dom/visitors/Element/meta/Window.ts index b1ddaa7709..867ecdfba6 100644 --- a/src/generators/dom/visitors/Element/meta/Window.ts +++ b/src/generators/dom/visitors/Element/meta/Window.ts @@ -57,7 +57,7 @@ export default function visitWindow( [✂${attribute.expression.start}-${attribute.expression.end}✂]; `; - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${handlerName} ( event ) { ${handlerBody} }; @@ -121,7 +121,7 @@ export default function visitWindow( ${event === 'scroll' && `${lock} = false;`} `; - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${handlerName} ( event ) { ${handlerBody} }; @@ -137,7 +137,7 @@ export default function visitWindow( if (bindings.scrollX && bindings.scrollY) { const observerCallback = block.getUniqueName(`scrollobserver`); - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${observerCallback} () { if ( ${lock} ) return; var x = ${bindings.scrollX @@ -151,17 +151,17 @@ export default function visitWindow( `); if (bindings.scrollX) - block.builders.create.addLine( + block.builders.init.addLine( `${block.component}.observe( '${bindings.scrollX}', ${observerCallback} );` ); if (bindings.scrollY) - block.builders.create.addLine( + block.builders.init.addLine( `${block.component}.observe( '${bindings.scrollY}', ${observerCallback} );` ); } else if (bindings.scrollX || bindings.scrollY) { const isX = !!bindings.scrollX; - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` ${block.component}.observe( '${bindings.scrollX || bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) { if ( ${lock} ) return; @@ -173,7 +173,7 @@ export default function visitWindow( // another special case. (I'm starting to think these are all special cases.) if (bindings.online) { const handlerName = block.getUniqueName(`onlinestatuschanged`); - block.builders.create.addBlock(deindent` + block.builders.init.addBlock(deindent` function ${handlerName} ( event ) { ${block.component}.set({ ${bindings.online}: navigator.onLine }); }; diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 2198e60c81..655070f00a 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -157,6 +157,7 @@ function simple( ${name}.update( changed, ${params} ); } else { ${name} = ${branch.block}( ${params}, ${block.component} ); + if ( ${name} ) ${name}.create(); } ${name}.intro( ${parentNode}, ${anchor} ); @@ -173,6 +174,7 @@ function simple( : branch.hasIntroMethod ? deindent` if ( !${name} ) ${name} = ${branch.block}( ${params}, ${block.component} ); + ${name}.create(); ${name}.intro( ${parentNode}, ${anchor} ); ` : deindent` diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index 5fb3d7fc26..f7747eedb2 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -19,7 +19,7 @@ export default function visitMustacheTag( block.addElement( name, `${generator.helper('createText')}( ${value} = ${snippet} )`, - `${generator.helper('claimText')}( ${state.parentNode}_nodes, ${value} = ${snippet} )`, + `${generator.helper('claimText')}( ${state.parentNodes}, ${value} = ${snippet} )`, state.parentNode, true ); diff --git a/src/shared/dom.js b/src/shared/dom.js index ffdecf5730..08a06be802 100644 --- a/src/shared/dom.js +++ b/src/shared/dom.js @@ -71,7 +71,7 @@ export function children (element) { return Array.from(element.childNodes); } -export function claimElement (nodes, name, attributes) { +export function claimElement (nodes, name, attributes, svg) { for (var i = 0; i < nodes.length; i += 1) { var node = nodes[i]; if (node.nodeName === name) { @@ -83,7 +83,7 @@ export function claimElement (nodes, name, attributes) { } } - return createElement(name); + return svg ? createSvgElement(name) : createElement(name); } export function claimText (nodes, data) { diff --git a/test/hydration/index.js b/test/hydration/index.js index 26a8908b76..696de66739 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -21,7 +21,7 @@ const nodeVersionMatch = /^v(\d)/.exec(process.version); const legacy = +nodeVersionMatch[1] < 6; const babelrc = require('../../package.json').babel; -describe.only('hydration', () => { +describe('hydration', () => { before(() => { const svelte = loadSvelte(); diff --git a/test/js/index.js b/test/js/index.js index 512cba41fa..c8afdbc2c3 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -4,7 +4,7 @@ import * as path from "path"; import { rollup } from "rollup"; import { svelte } from "../helpers.js"; -describe("js", () => { +describe.skip("js", () => { fs.readdirSync("test/js/samples").forEach(dir => { if (dir[0] === ".") return; diff --git a/test/runtime/samples/svg/_config.js b/test/runtime/samples/svg/_config.js index 44f6c8aa71..37d94a1609 100644 --- a/test/runtime/samples/svg/_config.js +++ b/test/runtime/samples/svg/_config.js @@ -5,7 +5,9 @@ export default { width: 100, height: 100 }, + html: ``, + test ( assert, component, target ) { const svg = target.querySelector( 'svg' ); const rect = target.querySelector( 'rect' ); From 93087403d3e22d80ae8bb26c9c5e7f02eb1f822b Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 17 Jun 2017 23:54:10 -0400 Subject: [PATCH 08/12] fix yield blocks --- src/generators/dom/visitors/Component/Component.ts | 8 ++++++++ src/generators/dom/visitors/YieldTag.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/generators/dom/visitors/Component/Component.ts b/src/generators/dom/visitors/Component/Component.ts index 672bb1ca26..96fdd4e31e 100644 --- a/src/generators/dom/visitors/Component/Component.ts +++ b/src/generators/dom/visitors/Component/Component.ts @@ -132,6 +132,14 @@ export default function visitComponent( `var ${yieldFragment} = ${childBlock.name}( ${params}, ${block.component} );` ); + block.builders.create.addLine( + `${yieldFragment}.create();` + ); + + block.builders.claim.addLine( + `${yieldFragment}.claim( ${state.parentNodes} );` + ); + if (childBlock.hasUpdateMethod) { block.builders.update.addLine( `${yieldFragment}.update( changed, ${params} );` diff --git a/src/generators/dom/visitors/YieldTag.ts b/src/generators/dom/visitors/YieldTag.ts index dd91d3ca3e..373414f9fc 100644 --- a/src/generators/dom/visitors/YieldTag.ts +++ b/src/generators/dom/visitors/YieldTag.ts @@ -9,7 +9,7 @@ export default function visitYieldTag( ) { const parentNode = state.parentNode || block.target; - (state.parentNode ? block.builders.create : block.builders.mount).addLine( + block.builders.mount.addLine( `if ( ${block.component}._yield ) ${block.component}._yield.mount( ${parentNode}, null );` ); From f9e17d6d0ea82d82216e7636cb083afa7c9182f1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 18 Jun 2017 00:35:43 -0400 Subject: [PATCH 09/12] keyed each block and select fixes --- src/generators/dom/Block.ts | 3 ++- src/generators/dom/visitors/EachBlock.ts | 3 +-- src/generators/dom/visitors/Element/Element.ts | 2 +- src/generators/dom/visitors/IfBlock.ts | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index f9cd8e8513..1a074e7825 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -228,7 +228,8 @@ export default class Block { } if (this.first) { - properties.addBlock(`first: ${this.first},`); + properties.addBlock(`first: null,`); + this.builders.hydrate.addLine( `this.first = ${this.first};` ); } if (this.builders.create.isEmpty()) { diff --git a/src/generators/dom/visitors/EachBlock.ts b/src/generators/dom/visitors/EachBlock.ts index 70a6665eaa..f5c642f5d1 100644 --- a/src/generators/dom/visitors/EachBlock.ts +++ b/src/generators/dom/visitors/EachBlock.ts @@ -132,7 +132,7 @@ function keyed( block: Block, state: State, node: Node, - snippet, + snippet: string, { each_block, create_each_block, @@ -282,7 +282,6 @@ function keyed( } else { if ( ${iteration} ) { // probably a deletion - while ( ${expected} && ${expected}.key !== ${key} ) { ${expected}.discard = true; discard_pile.push( ${expected} ); diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 5e3ce48e2f..75ec6ad83e 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -199,7 +199,7 @@ export default function visitElement( } if (node.initialUpdate) { - block.builders.hydrate.addBlock(node.initialUpdate); + block.builders.mount.addBlock(node.initialUpdate); } block.builders.claim.addLine( diff --git a/src/generators/dom/visitors/IfBlock.ts b/src/generators/dom/visitors/IfBlock.ts index 655070f00a..f0a54a4524 100644 --- a/src/generators/dom/visitors/IfBlock.ts +++ b/src/generators/dom/visitors/IfBlock.ts @@ -368,6 +368,7 @@ function compoundWithOutros( const createNewBlock = deindent` ${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} ); + ${name}.create(); ${name}.${mountOrIntro}( ${parentNode}, ${anchor} ); `; From 1bce6f22ced03c5011aae20e3656f166ec57a9d1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 18 Jun 2017 00:52:29 -0400 Subject: [PATCH 10/12] get all tests passing with hydration --- test/runtime/index.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/runtime/index.js b/test/runtime/index.js index 222ac8b5fb..3e16e0208e 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -26,23 +26,24 @@ const nodeVersionMatch = /^v(\d)/.exec(process.version); const legacy = +nodeVersionMatch[1] < 6; const babelrc = require("../../package.json").babel; -require.extensions[".html"] = function(module, filename) { - const options = Object.assign( - { filename, name: getName(filename) }, - compileOptions - ); - let { code } = svelte.compile(fs.readFileSync(filename, "utf-8"), options); - - if (legacy) code = require('babel-core').transform(code, babelrc).code; - - return module._compile(code, filename); -}; - const Object_assign = Object.assign; describe("runtime", () => { before(() => { svelte = loadSvelte(true); + + require.extensions[".html"] = function(module, filename) { + const options = Object.assign( + { filename, name: getName(filename) }, + compileOptions + ); + let { code } = svelte.compile(fs.readFileSync(filename, "utf-8"), options); + + if (legacy) code = require('babel-core').transform(code, babelrc).code; + + return module._compile(code, filename); + }; + return setupHtmlEqual(); }); From 30cc2664c70999a957453bfd8e9891c6831baa8c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 18 Jun 2017 01:04:38 -0400 Subject: [PATCH 11/12] test with hydratable: true and hydratable: false --- src/generators/dom/Block.ts | 20 +++++++------ src/generators/dom/index.ts | 16 ++++++++-- .../dom/visitors/Element/Element.ts | 29 +++++-------------- src/generators/dom/visitors/MustacheTag.ts | 2 +- src/generators/dom/visitors/Text.ts | 2 +- src/interfaces.ts | 1 + test/hydration/index.js | 3 +- test/runtime/index.js | 11 ++++--- 8 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index 1a074e7825..e7187e309d 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -243,15 +243,17 @@ export default class Block { `); } - if (this.builders.claim.isEmpty()) { - properties.addBlock(`claim: ${this.generator.helper('noop')},`); - } else { - properties.addBlock(deindent` - claim: function ( nodes ) { - ${this.builders.claim} - ${!this.builders.hydrate.isEmpty() && `this.hydrate();`} - }, - `); + if (this.generator.hydratable) { + if (this.builders.claim.isEmpty()) { + properties.addBlock(`claim: ${this.generator.helper('noop')},`); + } else { + properties.addBlock(deindent` + claim: function ( nodes ) { + ${this.builders.claim} + ${!this.builders.hydrate.isEmpty() && `this.hydrate();`} + }, + `); + } } if (!this.builders.hydrate.isEmpty()) { diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 5ba3d8ea3a..2c54e573a1 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -18,6 +18,8 @@ export class DomGenerator extends Generator { readonly: Set; metaBindings: string[]; + hydratable: boolean; + hasIntroTransitions: boolean; hasOutroTransitions: boolean; hasComplexBindings: boolean; @@ -34,6 +36,8 @@ export class DomGenerator extends Generator { this.readonly = new Set(); + this.hydratable = options.hydratable; + // initial values for e.g. window.innerWidth, if there's a <:Window> meta tag this.metaBindings = []; } @@ -236,9 +240,15 @@ export default function dom( )}( this._state, this ); if ( options.target ) { - var nodes = ${generator.helper('children')}( options.target ); - this._fragment.claim( nodes ); - nodes.forEach( ${generator.helper('detachNode')} ); + ${generator.hydratable ? + deindent` + var nodes = ${generator.helper('children')}( options.target ); + options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create(); + nodes.forEach( ${generator.helper('detachNode')} ); + ` : + deindent` + this._fragment.create(); + `} this._fragment.mount( options.target, null ); } diff --git a/src/generators/dom/visitors/Element/Element.ts b/src/generators/dom/visitors/Element/Element.ts index 75ec6ad83e..5e16abb3eb 100644 --- a/src/generators/dom/visitors/Element/Element.ts +++ b/src/generators/dom/visitors/Element/Element.ts @@ -52,10 +52,13 @@ export default function visitElement( block.addVariable(name); block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`); - block.builders.claim.addBlock(deindent` - ${name} = ${getClaimStatement(generator, childState.namespace, state.parentNodes, node)}; - var ${childState.parentNodes} = ${generator.helper('children')}( ${name} ); - `); + + if (generator.hydratable) { + block.builders.claim.addBlock(deindent` + ${name} = ${getClaimStatement(generator, childState.namespace, state.parentNodes, node)}; + var ${childState.parentNodes} = ${generator.helper('children')}( ${name} ); + `); + } if (state.parentNode) { block.builders.mount.addLine(`${block.generator.helper('appendNode')}( ${name}, ${state.parentNode} );`); @@ -63,24 +66,6 @@ export default function visitElement( block.builders.mount.addLine(`${block.generator.helper('insertNode')}( ${name}, ${block.target}, anchor );`); } - // block.addVariable(name); - - // block.builders.create.addLine( - // `${name} = ${getRenderStatement( - // generator, - // childState.namespace, - // node.name - // )};` - // ); - - // block.builders.claim.addLine( - // `${name} = ${getClaimStatement( - // generator, - // childState.namespace, - // node.name - // )};` - // ); - // add CSS encapsulation attribute if (generator.cssId && (!generator.cascade || state.isTopLevel)) { block.builders.hydrate.addLine( diff --git a/src/generators/dom/visitors/MustacheTag.ts b/src/generators/dom/visitors/MustacheTag.ts index f7747eedb2..7ab106a650 100644 --- a/src/generators/dom/visitors/MustacheTag.ts +++ b/src/generators/dom/visitors/MustacheTag.ts @@ -19,7 +19,7 @@ export default function visitMustacheTag( block.addElement( name, `${generator.helper('createText')}( ${value} = ${snippet} )`, - `${generator.helper('claimText')}( ${state.parentNodes}, ${value} = ${snippet} )`, + generator.hydratable ? `${generator.helper('claimText')}( ${state.parentNodes}, ${value} = ${snippet} )` : '', state.parentNode, true ); diff --git a/src/generators/dom/visitors/Text.ts b/src/generators/dom/visitors/Text.ts index ae0337990c..a14c25208c 100644 --- a/src/generators/dom/visitors/Text.ts +++ b/src/generators/dom/visitors/Text.ts @@ -13,7 +13,7 @@ export default function visitText( block.addElement( node._state.name, `${generator.helper('createText')}( ${JSON.stringify(node.data)} )`, - `${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )`, + generator.hydratable ? `${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )` : '', state.parentNode, node.usedAsAnchor ); diff --git a/src/interfaces.ts b/src/interfaces.ts index fd12939860..fbb745b7b8 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -41,6 +41,7 @@ export interface CompileOptions { dev?: boolean; shared?: boolean | string; cascade?: boolean; + hydratable?: boolean; onerror?: (error: Error) => void; onwarn?: (warning: Warning) => void; diff --git a/test/hydration/index.js b/test/hydration/index.js index 696de66739..1e291ed6b1 100644 --- a/test/hydration/index.js +++ b/test/hydration/index.js @@ -27,7 +27,7 @@ describe('hydration', () => { require.extensions['.html'] = function(module, filename) { const options = Object.assign( - { filename, name: getName(filename) }, + { filename, name: getName(filename), hydratable: true }, compileOptions ); let { code } = svelte.compile(fs.readFileSync(filename, 'utf-8'), options); @@ -76,6 +76,7 @@ describe('hydration', () => { const component = new SvelteComponent({ target, + hydrate: true, data: config.data }); diff --git a/test/runtime/index.js b/test/runtime/index.js index 3e16e0208e..6c599ce96d 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -49,7 +49,7 @@ describe("runtime", () => { const failed = new Set(); - function runTest(dir, shared) { + function runTest(dir, shared, hydrate) { if (dir[0] === ".") return; const config = loadConfig(`./runtime/samples/${dir}/_config.js`); @@ -61,13 +61,14 @@ describe("runtime", () => { (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers)`, () => { if (failed.has(dir)) { // this makes debugging easier, by only printing compiled output once - throw new Error('skipping inline helpers test'); + throw new Error('skipping test, already failed'); } const cwd = path.resolve(`test/runtime/samples/${dir}`); compileOptions = config.compileOptions || {}; compileOptions.shared = shared; + compileOptions.hydratable = hydrate; compileOptions.dev = config.dev; // check that no ES2015+ syntax slipped in @@ -157,6 +158,7 @@ describe("runtime", () => { const component = new SvelteComponent({ target, + hydrate, data: config.data }); @@ -208,8 +210,9 @@ describe("runtime", () => { const shared = path.resolve("shared.js"); fs.readdirSync("test/runtime/samples").forEach(dir => { - runTest(dir, shared); - runTest(dir, null); + runTest(dir, shared, false); + runTest(dir, shared, true); + runTest(dir, null, false); }); it("fails if options.target is missing in dev mode", () => { From 6a1248334b363a9d56788739654af69cadc99761 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sun, 18 Jun 2017 01:08:03 -0400 Subject: [PATCH 12/12] update tests --- test/js/index.js | 2 +- .../expected-bundle.js | 24 +++- .../expected.js | 24 +++- .../computed-collapsed-if/expected-bundle.js | 9 +- .../samples/computed-collapsed-if/expected.js | 9 +- .../expected-bundle.js | 86 ++++++++----- .../each-block-changed-check/expected.js | 86 ++++++++----- .../event-handlers-custom/expected-bundle.js | 27 ++-- .../samples/event-handlers-custom/expected.js | 27 ++-- .../if-block-no-update/expected-bundle.js | 40 ++++-- .../js/samples/if-block-no-update/expected.js | 42 ++++-- .../if-block-simple/expected-bundle.js | 25 +++- test/js/samples/if-block-simple/expected.js | 25 +++- .../non-imported-component/expected-bundle.js | 18 ++- .../non-imported-component/expected.js | 18 ++- .../expected-bundle.js | 9 +- .../onrender-onteardown-rewritten/expected.js | 9 +- .../expected-bundle.js | 120 ++++++++++++------ .../use-elements-as-anchors/expected.js | 120 ++++++++++++------ 19 files changed, 494 insertions(+), 226 deletions(-) diff --git a/test/js/index.js b/test/js/index.js index c8afdbc2c3..512cba41fa 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -4,7 +4,7 @@ import * as path from "path"; import { rollup } from "rollup"; import { svelte } from "../helpers.js"; -describe.skip("js", () => { +describe("js", () => { fs.readdirSync("test/js/samples").forEach(dir => { if (dir[0] === ".") return; 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 e8ad287400..35aa419e8b 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -151,16 +151,22 @@ function add_css () { } function create_main_fragment ( state, component ) { - var text_value; - - var p = createElement( 'p' ); - setAttribute( p, 'svelte-3842350206', '' ); - var text = createText( text_value = state.foo ); - appendNode( text, p ); + var p, text_value, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( text_value = state.foo ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setAttribute( p, 'svelte-3842350206', '' ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, update: function ( changed, state ) { @@ -195,7 +201,11 @@ function SvelteComponent ( options ) { if ( !document.getElementById( "svelte-3842350206-style" ) ) add_css(); this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 3d832bfd50..d3a818a9d0 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -16,16 +16,22 @@ function add_css () { } function create_main_fragment ( state, component ) { - var text_value; - - var p = createElement( 'p' ); - setAttribute( p, 'svelte-3842350206', '' ); - var text = createText( text_value = state.foo ); - appendNode( text, p ); + var p, text_value, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( text_value = state.foo ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + setAttribute( p, 'svelte-3842350206', '' ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, update: function ( changed, state ) { @@ -60,7 +66,11 @@ function SvelteComponent ( options ) { if ( !document.getElementById( "svelte-3842350206-style" ) ) add_css(); this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index f491d92da1..d82dbc3616 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -129,8 +129,9 @@ var template = (function () { function create_main_fragment ( state, component ) { - return { + create: noop, + mount: noop, unmount: noop, @@ -157,7 +158,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index f0475aeecf..e16d023bfb 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -18,8 +18,9 @@ var template = (function () { function create_main_fragment ( state, component ) { - return { + create: noop, + mount: noop, unmount: noop, @@ -46,7 +47,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); 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 4bfdc1e50b..6f3a1a886d 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -145,7 +145,7 @@ var proto = { }; function create_main_fragment ( state, component ) { - var text_1_value; + var text, p, text_1_value, text_1; var each_block_value = state.comments; @@ -155,12 +155,17 @@ function create_main_fragment ( state, component ) { each_block_iterations[i] = create_each_block( state, each_block_value, each_block_value[i], i, component ); } - var text = createText( "\n\n" ); - var p = createElement( 'p' ); - var text_1 = createText( text_1_value = state.foo ); - appendNode( text_1, p ); - return { + create: function () { + for ( var i = 0; i < each_block_iterations.length; i += 1 ) { + each_block_iterations[i].create(); + } + + text = createText( "\n\n" ); + p = createElement( 'p' ); + text_1 = createText( text_1_value = state.foo ); + }, + mount: function ( target, anchor ) { for ( var i = 0; i < each_block_iterations.length; i += 1 ) { each_block_iterations[i].mount( target, anchor ); @@ -168,6 +173,7 @@ function create_main_fragment ( state, component ) { insertNode( text, target, anchor ); insertNode( p, target, anchor ); + appendNode( text_1, p ); }, update: function ( changed, state ) { @@ -179,6 +185,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].create(); each_block_iterations[i].mount( text.parentNode, text ); } } @@ -211,35 +218,44 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var text_value, text_2_value, text_4_value; - - var div = createElement( 'div' ); - div.className = "comment"; - var strong = createElement( 'strong' ); - appendNode( strong, div ); - var text = createText( text_value = i ); - appendNode( text, strong ); - appendNode( createText( "\n\n\t\t" ), div ); - var span = createElement( 'span' ); - appendNode( span, div ); - span.className = "meta"; - var text_2 = createText( text_2_value = comment.author ); - appendNode( text_2, span ); - appendNode( createText( " wrote " ), span ); - var text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); - appendNode( text_4, span ); - appendNode( createText( " ago:" ), span ); - appendNode( createText( "\n\n\t\t" ), div ); - var raw_before = createElement( 'noscript' ); - appendNode( raw_before, div ); - var raw_after = createElement( 'noscript' ); - appendNode( raw_after, div ); - var raw_value = comment.html; - raw_before.insertAdjacentHTML( 'afterend', raw_value ); + var div, strong, text_value, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; return { + create: function () { + div = createElement( 'div' ); + strong = createElement( 'strong' ); + text = createText( text_value = i ); + text_1 = createText( "\n\n\t\t" ); + span = createElement( 'span' ); + text_2 = createText( text_2_value = comment.author ); + text_3 = createText( " wrote " ); + text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); + text_5 = createText( " ago:" ); + text_6 = createText( "\n\n\t\t" ); + raw_before = createElement( 'noscript' ); + raw_after = createElement( 'noscript' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + div.className = "comment"; + span.className = "meta"; + }, + mount: function ( target, anchor ) { insertNode( div, target, anchor ); + appendNode( strong, div ); + appendNode( text, strong ); + appendNode( text_1, div ); + appendNode( span, div ); + appendNode( text_2, span ); + appendNode( text_3, span ); + appendNode( text_4, span ); + appendNode( text_5, span ); + appendNode( text_6, div ); + appendNode( raw_before, div ); + appendNode( raw_after, div ); + raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); }, update: function ( changed, state, each_block_value, comment, i ) { @@ -257,7 +273,7 @@ function create_each_block ( state, each_block_value, comment, i, component ) { if ( raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); - raw_before.insertAdjacentHTML( 'afterend', raw_value ); + raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); } }, @@ -288,7 +304,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/each-block-changed-check/expected.js b/test/js/samples/each-block-changed-check/expected.js index e79b8f0659..4fa7680a77 100644 --- a/test/js/samples/each-block-changed-check/expected.js +++ b/test/js/samples/each-block-changed-check/expected.js @@ -1,7 +1,7 @@ import { appendNode, assign, createElement, createText, destroyEach, detachBetween, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var text_1_value; + var text, p, text_1_value, text_1; var each_block_value = state.comments; @@ -11,12 +11,17 @@ function create_main_fragment ( state, component ) { each_block_iterations[i] = create_each_block( state, each_block_value, each_block_value[i], i, component ); } - var text = createText( "\n\n" ); - var p = createElement( 'p' ); - var text_1 = createText( text_1_value = state.foo ); - appendNode( text_1, p ); - return { + create: function () { + for ( var i = 0; i < each_block_iterations.length; i += 1 ) { + each_block_iterations[i].create(); + } + + text = createText( "\n\n" ); + p = createElement( 'p' ); + text_1 = createText( text_1_value = state.foo ); + }, + mount: function ( target, anchor ) { for ( var i = 0; i < each_block_iterations.length; i += 1 ) { each_block_iterations[i].mount( target, anchor ); @@ -24,6 +29,7 @@ function create_main_fragment ( state, component ) { insertNode( text, target, anchor ); insertNode( p, target, anchor ); + appendNode( text_1, p ); }, update: function ( changed, state ) { @@ -35,6 +41,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].create(); each_block_iterations[i].mount( text.parentNode, text ); } } @@ -67,35 +74,44 @@ function create_main_fragment ( state, component ) { } function create_each_block ( state, each_block_value, comment, i, component ) { - var text_value, text_2_value, text_4_value; - - var div = createElement( 'div' ); - div.className = "comment"; - var strong = createElement( 'strong' ); - appendNode( strong, div ); - var text = createText( text_value = i ); - appendNode( text, strong ); - appendNode( createText( "\n\n\t\t" ), div ); - var span = createElement( 'span' ); - appendNode( span, div ); - span.className = "meta"; - var text_2 = createText( text_2_value = comment.author ); - appendNode( text_2, span ); - appendNode( createText( " wrote " ), span ); - var text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); - appendNode( text_4, span ); - appendNode( createText( " ago:" ), span ); - appendNode( createText( "\n\n\t\t" ), div ); - var raw_before = createElement( 'noscript' ); - appendNode( raw_before, div ); - var raw_after = createElement( 'noscript' ); - appendNode( raw_after, div ); - var raw_value = comment.html; - raw_before.insertAdjacentHTML( 'afterend', raw_value ); + var div, strong, text_value, text, text_1, span, text_2_value, text_2, text_3, text_4_value, text_4, text_5, text_6, raw_value, raw_before, raw_after; return { + create: function () { + div = createElement( 'div' ); + strong = createElement( 'strong' ); + text = createText( text_value = i ); + text_1 = createText( "\n\n\t\t" ); + span = createElement( 'span' ); + text_2 = createText( text_2_value = comment.author ); + text_3 = createText( " wrote " ); + text_4 = createText( text_4_value = state.elapsed(comment.time, state.time) ); + text_5 = createText( " ago:" ); + text_6 = createText( "\n\n\t\t" ); + raw_before = createElement( 'noscript' ); + raw_after = createElement( 'noscript' ); + this.hydrate(); + }, + + hydrate: function ( nodes ) { + div.className = "comment"; + span.className = "meta"; + }, + mount: function ( target, anchor ) { insertNode( div, target, anchor ); + appendNode( strong, div ); + appendNode( text, strong ); + appendNode( text_1, div ); + appendNode( span, div ); + appendNode( text_2, span ); + appendNode( text_3, span ); + appendNode( text_4, span ); + appendNode( text_5, span ); + appendNode( text_6, div ); + appendNode( raw_before, div ); + appendNode( raw_after, div ); + raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); }, update: function ( changed, state, each_block_value, comment, i ) { @@ -113,7 +129,7 @@ function create_each_block ( state, each_block_value, comment, i, component ) { if ( raw_value !== ( raw_value = comment.html ) ) { detachBetween( raw_before, raw_after ); - raw_before.insertAdjacentHTML( 'afterend', raw_value ); + raw_before.insertAdjacentHTML( 'afterend', raw_value = comment.html ); } }, @@ -144,7 +160,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index 128cc96c43..f7a9c0aa73 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -145,18 +145,25 @@ var template = (function () { }()); function create_main_fragment ( state, component ) { - var button = createElement( 'button' ); + var button, foo_handler, text; - var foo_handler = template.events.foo.call( component, button, function ( event ) { - var state = component.get(); - component.foo( state.bar ); - }); + return { + create: function () { + button = createElement( 'button' ); + text = createText( "foo" ); + this.hydrate(); + }, - appendNode( createText( "foo" ), button ); + hydrate: function ( nodes ) { + foo_handler = template.events.foo.call( component, button, function ( event ) { + var state = component.get(); + component.foo( state.bar ); + }); + }, - return { mount: function ( target, anchor ) { insertNode( button, target, anchor ); + appendNode( text, button ); }, unmount: function () { @@ -186,7 +193,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, template.methods, proto ); diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index c57bcd2870..882bc08e85 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -16,18 +16,25 @@ var template = (function () { }()); function create_main_fragment ( state, component ) { - var button = createElement( 'button' ); + var button, foo_handler, text; - var foo_handler = template.events.foo.call( component, button, function ( event ) { - var state = component.get(); - component.foo( state.bar ); - }); + return { + create: function () { + button = createElement( 'button' ); + text = createText( "foo" ); + this.hydrate(); + }, - appendNode( createText( "foo" ), button ); + hydrate: function ( nodes ) { + foo_handler = template.events.foo.call( component, button, function ( event ) { + var state = component.get(); + component.foo( state.bar ); + }); + }, - return { mount: function ( target, anchor ) { insertNode( button, target, anchor ); + appendNode( text, button ); }, unmount: function () { @@ -57,7 +64,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, template.methods, proto ); 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 3177df9b02..e017940f2e 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -136,6 +136,8 @@ var proto = { }; function create_main_fragment ( state, component ) { + var if_block_anchor; + function get_block ( state ) { if ( state.foo ) return create_if_block; return create_if_block_1; @@ -144,9 +146,12 @@ function create_main_fragment ( state, component ) { var current_block = get_block( state ); var if_block = current_block( state, component ); - var if_block_anchor = createComment(); - return { + create: function () { + if_block.create(); + if_block_anchor = createComment(); + }, + mount: function ( target, anchor ) { if_block.mount( target, anchor ); insertNode( if_block_anchor, target, anchor ); @@ -154,11 +159,10 @@ function create_main_fragment ( state, component ) { update: function ( changed, state ) { if ( current_block !== ( current_block = get_block( state ) ) ) { - { - if_block.unmount(); - if_block.destroy(); - } + if_block.unmount(); + if_block.destroy(); if_block = current_block( state, component ); + if_block.create(); if_block.mount( if_block_anchor.parentNode, if_block_anchor ); } }, @@ -175,12 +179,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -192,12 +201,17 @@ function create_if_block ( state, component ) { } function create_if_block_1 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "not foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "not foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -225,7 +239,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/if-block-no-update/expected.js b/test/js/samples/if-block-no-update/expected.js index ac25a686c6..fdedd6e4a7 100644 --- a/test/js/samples/if-block-no-update/expected.js +++ b/test/js/samples/if-block-no-update/expected.js @@ -1,6 +1,8 @@ import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { + var if_block_anchor; + function get_block ( state ) { if ( state.foo ) return create_if_block; return create_if_block_1; @@ -9,9 +11,12 @@ function create_main_fragment ( state, component ) { var current_block = get_block( state ); var if_block = current_block( state, component ); - var if_block_anchor = createComment(); - return { + create: function () { + if_block.create(); + if_block_anchor = createComment(); + }, + mount: function ( target, anchor ) { if_block.mount( target, anchor ); insertNode( if_block_anchor, target, anchor ); @@ -19,11 +24,10 @@ function create_main_fragment ( state, component ) { update: function ( changed, state ) { if ( current_block !== ( current_block = get_block( state ) ) ) { - { - if_block.unmount(); - if_block.destroy(); - } + if_block.unmount(); + if_block.destroy(); if_block = current_block( state, component ); + if_block.create(); if_block.mount( if_block_anchor.parentNode, if_block_anchor ); } }, @@ -40,12 +44,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -57,12 +66,17 @@ function create_if_block ( state, component ) { } function create_if_block_1 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "not foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "not foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -90,7 +104,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); @@ -114,4 +132,4 @@ SvelteComponent.prototype.teardown = SvelteComponent.prototype.destroy = functio this._torndown = true; }; -export default SvelteComponent; +export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index 03b2450bfc..13695550b0 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -136,11 +136,16 @@ var proto = { }; function create_main_fragment ( state, component ) { - var if_block = (state.foo) && create_if_block( state, component ); + var if_block_anchor; - var if_block_anchor = createComment(); + var if_block = (state.foo) && create_if_block( state, component ); return { + create: function () { + if ( if_block ) if_block.create(); + if_block_anchor = createComment(); + }, + mount: function ( target, anchor ) { if ( if_block ) if_block.mount( target, anchor ); insertNode( if_block_anchor, target, anchor ); @@ -150,6 +155,7 @@ function create_main_fragment ( state, component ) { if ( state.foo ) { if ( !if_block ) { if_block = create_if_block( state, component ); + if_block.create(); if_block.mount( if_block_anchor.parentNode, if_block_anchor ); } } else if ( if_block ) { @@ -171,12 +177,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -204,7 +215,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/if-block-simple/expected.js b/test/js/samples/if-block-simple/expected.js index 8f87d57802..548938d59c 100644 --- a/test/js/samples/if-block-simple/expected.js +++ b/test/js/samples/if-block-simple/expected.js @@ -1,11 +1,16 @@ import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var if_block = (state.foo) && create_if_block( state, component ); + var if_block_anchor; - var if_block_anchor = createComment(); + var if_block = (state.foo) && create_if_block( state, component ); return { + create: function () { + if ( if_block ) if_block.create(); + if_block_anchor = createComment(); + }, + mount: function ( target, anchor ) { if ( if_block ) if_block.mount( target, anchor ); insertNode( if_block_anchor, target, anchor ); @@ -15,6 +20,7 @@ function create_main_fragment ( state, component ) { if ( state.foo ) { if ( !if_block ) { if_block = create_if_block( state, component ); + if_block.create(); if_block.mount( if_block_anchor.parentNode, if_block_anchor ); } } else if ( if_block ) { @@ -36,12 +42,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "foo!" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "foo!" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -69,7 +80,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index 3a03995a16..06e2ea9a7d 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -132,19 +132,23 @@ var template = (function () { }()); function create_main_fragment ( state, component ) { + var text; + var imported = new Imported({ - target: null, _root: component._root }); - var text = createText( "\n" ); - var nonimported = new template.components.NonImported({ - target: null, _root: component._root }); return { + create: function () { + imported._fragment.create(); + text = createText( "\n" ); + nonimported._fragment.create(); + }, + mount: function ( target, anchor ) { imported._fragment.mount( target, anchor ); insertNode( text, target, anchor ); @@ -182,7 +186,11 @@ function SvelteComponent ( options ) { this._renderHooks = []; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } this._flush(); } diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index 52a943887b..c4ebb32172 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -11,19 +11,23 @@ var template = (function () { }()); function create_main_fragment ( state, component ) { + var text; + var imported = new Imported({ - target: null, _root: component._root }); - var text = createText( "\n" ); - var nonimported = new template.components.NonImported({ - target: null, _root: component._root }); return { + create: function () { + imported._fragment.create(); + text = createText( "\n" ); + nonimported._fragment.create(); + }, + mount: function ( target, anchor ) { imported._fragment.mount( target, anchor ); insertNode( text, target, anchor ); @@ -61,7 +65,11 @@ function SvelteComponent ( options ) { this._renderHooks = []; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } this._flush(); } diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 8a9044c8ca..6998c7b5e0 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -121,8 +121,9 @@ var template = (function () { function create_main_fragment ( state, component ) { - return { + create: noop, + mount: noop, unmount: noop, @@ -148,7 +149,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } if ( options._root ) { options._root._renderHooks.push( template.oncreate.bind( this ) ); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index f0b4cbd24b..3869d14cb7 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -10,8 +10,9 @@ var template = (function () { function create_main_fragment ( state, component ) { - return { + create: noop, + mount: noop, unmount: noop, @@ -37,7 +38,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } if ( options._root ) { options._root._renderHooks.push( template.oncreate.bind( this ) ); 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 30bf07a1e4..16764bf2cf 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -136,48 +136,56 @@ var proto = { }; function create_main_fragment ( state, component ) { - var div = createElement( 'div' ); + var div, text, p, text_1, text_2, text_3, text_4, p_1, text_5, text_6, text_7, text_8, if_block_4_anchor; var if_block = (state.a) && create_if_block( state, component ); - if ( if_block ) if_block.mount( div, null ); - 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 ); - 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 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 ); - 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 ); - 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 ); - var text_7 = createText( "\n\n\t" ); - appendNode( text_7, div ); - var text_8 = createText( "\n\n" ); - var if_block_4 = (state.e) && create_if_block_4( state, component ); - var if_block_4_anchor = createComment(); - return { + create: function () { + div = createElement( 'div' ); + if ( if_block ) if_block.create(); + text = createText( "\n\n\t" ); + p = createElement( 'p' ); + text_1 = createText( "this can be used as an anchor" ); + text_2 = createText( "\n\n\t" ); + if ( if_block_1 ) if_block_1.create(); + text_3 = createText( "\n\n\t" ); + if ( if_block_2 ) if_block_2.create(); + text_4 = createText( "\n\n\t" ); + p_1 = createElement( 'p' ); + text_5 = createText( "so can this" ); + text_6 = createText( "\n\n\t" ); + if ( if_block_3 ) if_block_3.create(); + text_7 = createText( "\n\n\t" ); + text_8 = createText( "\n\n" ); + if ( if_block_4 ) if_block_4.create(); + if_block_4_anchor = createComment(); + }, + mount: function ( target, anchor ) { insertNode( div, target, anchor ); + if ( if_block ) if_block.mount( div, null ); + appendNode( text, div ); + appendNode( p, div ); + appendNode( text_1, p ); + appendNode( text_2, div ); + if ( if_block_1 ) if_block_1.mount( div, null ); + appendNode( text_3, div ); + if ( if_block_2 ) if_block_2.mount( div, null ); + appendNode( text_4, div ); + appendNode( p_1, div ); + appendNode( text_5, p_1 ); + appendNode( text_6, div ); + if ( if_block_3 ) if_block_3.mount( div, null ); + appendNode( text_7, div ); insertNode( text_8, target, anchor ); if ( if_block_4 ) if_block_4.mount( target, anchor ); insertNode( if_block_4_anchor, target, anchor ); @@ -187,6 +195,7 @@ function create_main_fragment ( state, component ) { if ( state.a ) { if ( !if_block ) { if_block = create_if_block( state, component ); + if_block.create(); if_block.mount( div, text ); } } else if ( if_block ) { @@ -198,6 +207,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.create(); if_block_1.mount( div, text_3 ); } } else if ( if_block_1 ) { @@ -209,6 +219,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.create(); if_block_2.mount( div, text_4 ); } } else if ( if_block_2 ) { @@ -220,6 +231,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.create(); if_block_3.mount( div, text_7 ); } } else if ( if_block_3 ) { @@ -231,6 +243,7 @@ function create_main_fragment ( state, component ) { if ( state.e ) { if ( !if_block_4 ) { if_block_4 = create_if_block_4( state, component ); + if_block_4.create(); if_block_4.mount( if_block_4_anchor.parentNode, if_block_4_anchor ); } } else if ( if_block_4 ) { @@ -262,12 +275,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "a" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "a" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -279,12 +297,17 @@ function create_if_block ( state, component ) { } function create_if_block_1 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "b" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "b" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -296,12 +319,17 @@ function create_if_block_1 ( state, component ) { } function create_if_block_2 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "c" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "c" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -313,12 +341,17 @@ function create_if_block_2 ( state, component ) { } function create_if_block_3 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "d" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "d" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -330,12 +363,17 @@ function create_if_block_3 ( state, component ) { } function create_if_block_4 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "e" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "e" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -363,7 +401,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto ); diff --git a/test/js/samples/use-elements-as-anchors/expected.js b/test/js/samples/use-elements-as-anchors/expected.js index 2c1d8b18c5..df05566744 100644 --- a/test/js/samples/use-elements-as-anchors/expected.js +++ b/test/js/samples/use-elements-as-anchors/expected.js @@ -1,48 +1,56 @@ import { appendNode, assign, createComment, createElement, createText, detachNode, dispatchObservers, insertNode, noop, proto } from "svelte/shared.js"; function create_main_fragment ( state, component ) { - var div = createElement( 'div' ); + var div, text, p, text_1, text_2, text_3, text_4, p_1, text_5, text_6, text_7, text_8, if_block_4_anchor; var if_block = (state.a) && create_if_block( state, component ); - if ( if_block ) if_block.mount( div, null ); - 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 ); - 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 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 ); - 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 ); - 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 ); - var text_7 = createText( "\n\n\t" ); - appendNode( text_7, div ); - var text_8 = createText( "\n\n" ); - var if_block_4 = (state.e) && create_if_block_4( state, component ); - var if_block_4_anchor = createComment(); - return { + create: function () { + div = createElement( 'div' ); + if ( if_block ) if_block.create(); + text = createText( "\n\n\t" ); + p = createElement( 'p' ); + text_1 = createText( "this can be used as an anchor" ); + text_2 = createText( "\n\n\t" ); + if ( if_block_1 ) if_block_1.create(); + text_3 = createText( "\n\n\t" ); + if ( if_block_2 ) if_block_2.create(); + text_4 = createText( "\n\n\t" ); + p_1 = createElement( 'p' ); + text_5 = createText( "so can this" ); + text_6 = createText( "\n\n\t" ); + if ( if_block_3 ) if_block_3.create(); + text_7 = createText( "\n\n\t" ); + text_8 = createText( "\n\n" ); + if ( if_block_4 ) if_block_4.create(); + if_block_4_anchor = createComment(); + }, + mount: function ( target, anchor ) { insertNode( div, target, anchor ); + if ( if_block ) if_block.mount( div, null ); + appendNode( text, div ); + appendNode( p, div ); + appendNode( text_1, p ); + appendNode( text_2, div ); + if ( if_block_1 ) if_block_1.mount( div, null ); + appendNode( text_3, div ); + if ( if_block_2 ) if_block_2.mount( div, null ); + appendNode( text_4, div ); + appendNode( p_1, div ); + appendNode( text_5, p_1 ); + appendNode( text_6, div ); + if ( if_block_3 ) if_block_3.mount( div, null ); + appendNode( text_7, div ); insertNode( text_8, target, anchor ); if ( if_block_4 ) if_block_4.mount( target, anchor ); insertNode( if_block_4_anchor, target, anchor ); @@ -52,6 +60,7 @@ function create_main_fragment ( state, component ) { if ( state.a ) { if ( !if_block ) { if_block = create_if_block( state, component ); + if_block.create(); if_block.mount( div, text ); } } else if ( if_block ) { @@ -63,6 +72,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.create(); if_block_1.mount( div, text_3 ); } } else if ( if_block_1 ) { @@ -74,6 +84,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.create(); if_block_2.mount( div, text_4 ); } } else if ( if_block_2 ) { @@ -85,6 +96,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.create(); if_block_3.mount( div, text_7 ); } } else if ( if_block_3 ) { @@ -96,6 +108,7 @@ function create_main_fragment ( state, component ) { if ( state.e ) { if ( !if_block_4 ) { if_block_4 = create_if_block_4( state, component ); + if_block_4.create(); if_block_4.mount( if_block_4_anchor.parentNode, if_block_4_anchor ); } } else if ( if_block_4 ) { @@ -127,12 +140,17 @@ function create_main_fragment ( state, component ) { } function create_if_block ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "a" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "a" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -144,12 +162,17 @@ function create_if_block ( state, component ) { } function create_if_block_1 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "b" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "b" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -161,12 +184,17 @@ function create_if_block_1 ( state, component ) { } function create_if_block_2 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "c" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "c" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -178,12 +206,17 @@ function create_if_block_2 ( state, component ) { } function create_if_block_3 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "d" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "d" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -195,12 +228,17 @@ function create_if_block_3 ( state, component ) { } function create_if_block_4 ( state, component ) { - var p = createElement( 'p' ); - appendNode( createText( "e" ), p ); + var p, text; return { + create: function () { + p = createElement( 'p' ); + text = createText( "e" ); + }, + mount: function ( target, anchor ) { insertNode( p, target, anchor ); + appendNode( text, p ); }, unmount: function () { @@ -228,7 +266,11 @@ function SvelteComponent ( options ) { this._torndown = false; this._fragment = create_main_fragment( this._state, this ); - if ( options.target ) this._fragment.mount( options.target, null ); + + if ( options.target ) { + this._fragment.create(); + this._fragment.mount( options.target, null ); + } } assign( SvelteComponent.prototype, proto );