diff --git a/rollup/rollup.config.ssr.js b/rollup/rollup.config.ssr.js index 988554341d..6f8922a854 100644 --- a/rollup/rollup.config.ssr.js +++ b/rollup/rollup.config.ssr.js @@ -12,7 +12,7 @@ export default { nodeResolve({ jsnext: true, module: true }), commonjs() ], - external: [ path.resolve( 'src/index.js' ), 'fs', 'magic-string' ], + external: [ path.resolve( 'src/index.js' ), 'fs', 'path', 'magic-string' ], paths: { [ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js' }, diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js index 05ea49fad1..ff67eb475f 100644 --- a/src/generators/dom/visitors/Component.js +++ b/src/generators/dom/visitors/Component.js @@ -49,7 +49,7 @@ export default { const statements = []; - if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) { + if ( local.staticAttributes.length || local.dynamicAttributes.length || local.spreadAttributes.length || local.bindings.length ) { const initialProps = local.staticAttributes .concat( local.dynamicAttributes ) .map( attribute => `${attribute.name}: ${attribute.value}` ); @@ -73,7 +73,14 @@ export default { statements.push( bindings.join( '\n' ) ); } - componentInitProperties.push(`data: ${name}_initialData`); + + if ( local.spreadAttributes.length ) { + local.spreadAttributes.forEach( name => { + statements.push( `Object.assign( ${local.name}_initialData, root.${name} );` ); + }); + } + + componentInitProperties.push( `data: ${name}_initialData` ); } local.init.addBlockAtStart( deindent` diff --git a/src/generators/dom/visitors/attributes/addComponentAttributes.js b/src/generators/dom/visitors/attributes/addComponentAttributes.js index e3471c6d37..34f74bc85c 100644 --- a/src/generators/dom/visitors/attributes/addComponentAttributes.js +++ b/src/generators/dom/visitors/attributes/addComponentAttributes.js @@ -4,6 +4,7 @@ import deindent from '../../../../utils/deindent.js'; export default function addComponentAttributes ( generator, node, local ) { local.staticAttributes = []; local.dynamicAttributes = []; + local.spreadAttributes = []; local.bindings = []; node.attributes.forEach( attribute => { @@ -127,6 +128,11 @@ export default function addComponentAttributes ( generator, node, local ) { ` ); } + else if ( attribute.type === 'Spread' ) { + local.spreadAttributes.push( attribute.name ); + local.update.addLine( `${local.name}.set( root.${attribute.name} );` ); + } + else { throw new Error( `Not implemented: ${attribute.type}` ); } diff --git a/src/generators/dom/visitors/attributes/addElementAttributes.js b/src/generators/dom/visitors/attributes/addElementAttributes.js index 36869a202f..45a66e6419 100644 --- a/src/generators/dom/visitors/attributes/addElementAttributes.js +++ b/src/generators/dom/visitors/attributes/addElementAttributes.js @@ -221,7 +221,10 @@ export default function addElementAttributes ( generator, node, local ) { // we have to include setXLinkAttibute and setAttribute because the data is dynamic // we have no idea at compile time what is being used - local.init.addLine( `spreadAttributes( ${local.name}, root.${name} );` ); + const statement = `spreadAttributes( ${local.name}, root.${name} );`; + + local.init.addLine( statement ); + local.update.addLine( statement ); } else { diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js index 3187622f79..d2e8cd961c 100644 --- a/src/generators/server-side-rendering/index.js +++ b/src/generators/server-side-rendering/index.js @@ -7,9 +7,26 @@ import Generator from '../Generator.js'; class SsrGenerator extends Generator { constructor ( parsed, source, names, visitors ) { super( parsed, source, names, visitors ); + this.bindings = []; this.renderCode = ''; } + addBinding ( binding, name ) { + const conditions = [ `!( '${binding.name}' in root )`].concat( // TODO handle contextual bindings... + this.current.conditions.map( c => `(${c})` ) + ); + + this.bindings.push( deindent` + if ( ${conditions.join( '&&' )} ) { + tmp = template.components.${name}.data(); + if ( '${binding.value}' in tmp ) { + root.${binding.name} = tmp.${binding.value}; + settled = false; + } + } + ` ); + } + append ( code ) { this.renderCode += code; } @@ -25,6 +42,7 @@ export default function ssr ( parsed, source, options, names ) { const builders = { main: new CodeBuilder(), + bindings: new CodeBuilder(), render: new CodeBuilder(), renderCss: new CodeBuilder() }; @@ -32,7 +50,8 @@ export default function ssr ( parsed, source, options, names ) { // create main render() function generator.push({ contexts: {}, - indexes: {} + indexes: {}, + conditions: [] }); parsed.html.children.forEach( node => generator.visit( node ) ); @@ -47,6 +66,21 @@ export default function ssr ( parsed, source, options, names ) { ); }); + if ( generator.bindings.length ) { + const bindings = generator.bindings.join( '\n\n' ); + + builders.render.addBlock( deindent` + var settled = false; + var tmp; + + while ( !settled ) { + settled = true; + + ${bindings} + } + ` ); + } + builders.render.addBlock( `return \`${generator.renderCode}\`;` ); @@ -102,6 +136,10 @@ export default function ssr ( parsed, source, options, names ) { ${name}.filename = ${JSON.stringify( options.filename )}; + ${name}.data = function () { + return ${templateProperties.data ? `template.data()` : `{}`}; + }; + ${name}.render = function ( root, options ) { ${builders.render} }; diff --git a/src/generators/server-side-rendering/visitors/Component.js b/src/generators/server-side-rendering/visitors/Component.js index 55a7a11399..3dc43a1709 100644 --- a/src/generators/server-side-rendering/visitors/Component.js +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -8,10 +8,22 @@ export default { } } - const props = node.attributes - .map( attribute => { - if ( attribute.type !== 'Attribute' ) return; + const attributes = []; + const bindings = []; + const spreads = []; + + node.attributes.forEach( attribute => { + if ( attribute.type === 'Attribute' ) { + attributes.push( attribute ); + } else if ( attribute.type === 'Binding' ) { + bindings.push( attribute ); + } else if ( attribute.type === 'Spread' ) { + spreads.push( attribute ); + } + }); + const props = attributes + .map( attribute => { let value; if ( attribute.value === true ) { @@ -32,10 +44,17 @@ export default { return `${attribute.name}: ${value}`; }) - .filter( Boolean ) .join( ', ' ); - let open = `\${template.components.${node.name}.render({${props}}`; + bindings.forEach( binding => { + generator.addBinding( binding, node.name ); + }); + + const spreadProps = spreads.map( spread => `root.${spread.name}` ).join( ', ' ); + + const data = spreads.length ? `Object.assign({${props}}, ${spreadProps})` : `{${props}}`; + + let open = `\${template.components.${node.name}.render(${data}`; if ( node.children.length ) { open += `, { yield: () => \``; diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js index e9adec7e4e..d7828fea99 100644 --- a/src/generators/server-side-rendering/visitors/Element.js +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -15,6 +15,7 @@ export default { openingTag += ` \${Object.keys( root.${attribute.name} ).map( prop => \`\${prop}="\${root.${attribute.name}[prop]}"\` ).join( ' ' )}`; return; } + if ( attribute.type !== 'Attribute' ) return; let str = ` ${attribute.name}`; diff --git a/src/generators/server-side-rendering/visitors/IfBlock.js b/src/generators/server-side-rendering/visitors/IfBlock.js index a473965afd..dd21ff8b4f 100644 --- a/src/generators/server-side-rendering/visitors/IfBlock.js +++ b/src/generators/server-side-rendering/visitors/IfBlock.js @@ -1,12 +1,19 @@ export default { enter ( generator, node ) { const { snippet } = generator.contextualise( node.expression ); + generator.append( '${ ' + snippet + ' ? `' ); + + generator.push({ + conditions: generator.current.conditions.concat( snippet ) + }); }, leave ( generator, node ) { generator.append( '` : `' ); if ( node.else ) node.else.children.forEach( child => generator.visit( child ) ); generator.append( '` }' ); + + generator.pop(); } }; diff --git a/src/server-side-rendering/register.js b/src/server-side-rendering/register.js index cb17b9a710..95c2a63fad 100644 --- a/src/server-side-rendering/register.js +++ b/src/server-side-rendering/register.js @@ -1,10 +1,17 @@ import * as fs from 'fs'; +import * as path from 'path'; import { compile } from '../index.js'; +function capitalise ( name ) { + return name[0].toUpperCase() + name.slice( 1 ); +} + require.extensions[ '.html' ] = function ( module, filename ) { const { code } = compile( fs.readFileSync( filename, 'utf-8' ), { filename, + name: capitalise( path.basename( filename ).replace( /\.html$/, '' ) ), generate: 'ssr' }); + return module._compile( code, filename ); }; diff --git a/test/generator/attribute-spread/_config.js b/test/generator/attribute-spread/_config.js index c9c42a1e3a..849ccdb406 100644 --- a/test/generator/attribute-spread/_config.js +++ b/test/generator/attribute-spread/_config.js @@ -1,3 +1,15 @@ export default { html: '', + + test ( assert, component, target ) { + component.set({ + options: { + type: 'text', + value: 'changed' + } + }); + + assert.htmlEqual( target.innerHTML, `` ); + component.teardown(); + } }; diff --git a/test/generator/attribute-spread/main.html b/test/generator/attribute-spread/main.html index eabb52e0a8..48d2bdec24 100644 --- a/test/generator/attribute-spread/main.html +++ b/test/generator/attribute-spread/main.html @@ -4,8 +4,8 @@ export default { data: () => ({ options: { - type: 'text', - value: 'Hello World' + type: 'text', + value: 'Hello World' } }) }; diff --git a/test/generator/component-attribute-spread/Widget.html b/test/generator/component-attribute-spread/Widget.html index d767d2c07a..6821108309 100644 --- a/test/generator/component-attribute-spread/Widget.html +++ b/test/generator/component-attribute-spread/Widget.html @@ -1 +1,2 @@ - +
foo: {{foo}}
+bar: {{bar}}
diff --git a/test/generator/component-attribute-spread/_config.js b/test/generator/component-attribute-spread/_config.js index c9c42a1e3a..b15a0b935d 100644 --- a/test/generator/component-attribute-spread/_config.js +++ b/test/generator/component-attribute-spread/_config.js @@ -1,3 +1,22 @@ export default { - html: '', + html: ` +foo: 1
+bar: 2
+ `, + + test ( assert, component, target ) { + component.set({ + options: { + foo: 3, + bar: 4 + } + }); + + assert.equal( component.refs.widget.get( 'foo' ), 3 ); + assert.htmlEqual( target.innerHTML, ` +foo: 3
+bar: 4
+ ` ); + component.teardown(); + } }; diff --git a/test/generator/component-attribute-spread/main.html b/test/generator/component-attribute-spread/main.html index 946a1c4cf5..98ef2bcaef 100644 --- a/test/generator/component-attribute-spread/main.html +++ b/test/generator/component-attribute-spread/main.html @@ -1,4 +1,4 @@ -