From 23265d5dfdee3d81cd12c2bd8887727bcaf9df86 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 8 Feb 2017 12:13:02 -0500 Subject: [PATCH] two-way component binding in SSR (#275) --- rollup/rollup.config.ssr.js | 2 +- src/generators/server-side-rendering/index.js | 40 ++++++++++++++++++- .../visitors/Component.js | 20 ++++++++-- .../server-side-rendering/visitors/IfBlock.js | 7 ++++ src/server-side-rendering/register.js | 7 ++++ .../component-binding-renamed/Foo.html | 11 +++++ .../component-binding-renamed/_actual.html | 1 + .../component-binding-renamed/_expected.html | 1 + .../component-binding-renamed/main.html | 11 +++++ .../component-binding/Foo.html | 11 +++++ .../component-binding/_actual.html | 1 + .../component-binding/_expected.html | 1 + .../component-binding/main.html | 11 +++++ 13 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 test/server-side-rendering/component-binding-renamed/Foo.html create mode 100644 test/server-side-rendering/component-binding-renamed/_actual.html create mode 100644 test/server-side-rendering/component-binding-renamed/_expected.html create mode 100644 test/server-side-rendering/component-binding-renamed/main.html create mode 100644 test/server-side-rendering/component-binding/Foo.html create mode 100644 test/server-side-rendering/component-binding/_actual.html create mode 100644 test/server-side-rendering/component-binding/_expected.html create mode 100644 test/server-side-rendering/component-binding/main.html 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/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..b75409e356 100644 --- a/src/generators/server-side-rendering/visitors/Component.js +++ b/src/generators/server-side-rendering/visitors/Component.js @@ -8,10 +8,19 @@ export default { } } - const props = node.attributes - .map( attribute => { - if ( attribute.type !== 'Attribute' ) return; + const attributes = []; + const bindings = []; + + node.attributes.forEach( attribute => { + if ( attribute.type === 'Attribute' ) { + attributes.push( attribute ); + } else if ( attribute.type === 'Binding' ) { + bindings.push( attribute ); + } + }); + const props = attributes + .map( attribute => { let value; if ( attribute.value === true ) { @@ -32,9 +41,12 @@ export default { return `${attribute.name}: ${value}`; }) - .filter( Boolean ) .join( ', ' ); + bindings.forEach( binding => { + generator.addBinding( binding, node.name ); + }); + let open = `\${template.components.${node.name}.render({${props}}`; if ( node.children.length ) { 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/server-side-rendering/component-binding-renamed/Foo.html b/test/server-side-rendering/component-binding-renamed/Foo.html new file mode 100644 index 0000000000..b0f08ce1d2 --- /dev/null +++ b/test/server-side-rendering/component-binding-renamed/Foo.html @@ -0,0 +1,11 @@ +:foo: + + diff --git a/test/server-side-rendering/component-binding-renamed/_actual.html b/test/server-side-rendering/component-binding-renamed/_actual.html new file mode 100644 index 0000000000..8009a9607c --- /dev/null +++ b/test/server-side-rendering/component-binding-renamed/_actual.html @@ -0,0 +1 @@ +1:foo:1 \ No newline at end of file diff --git a/test/server-side-rendering/component-binding-renamed/_expected.html b/test/server-side-rendering/component-binding-renamed/_expected.html new file mode 100644 index 0000000000..6bd7771f11 --- /dev/null +++ b/test/server-side-rendering/component-binding-renamed/_expected.html @@ -0,0 +1 @@ +1:foo:1 diff --git a/test/server-side-rendering/component-binding-renamed/main.html b/test/server-side-rendering/component-binding-renamed/main.html new file mode 100644 index 0000000000..7445660354 --- /dev/null +++ b/test/server-side-rendering/component-binding-renamed/main.html @@ -0,0 +1,11 @@ +{{y}}{{y}} + + diff --git a/test/server-side-rendering/component-binding/Foo.html b/test/server-side-rendering/component-binding/Foo.html new file mode 100644 index 0000000000..b0f08ce1d2 --- /dev/null +++ b/test/server-side-rendering/component-binding/Foo.html @@ -0,0 +1,11 @@ +:foo: + + diff --git a/test/server-side-rendering/component-binding/_actual.html b/test/server-side-rendering/component-binding/_actual.html new file mode 100644 index 0000000000..8009a9607c --- /dev/null +++ b/test/server-side-rendering/component-binding/_actual.html @@ -0,0 +1 @@ +1:foo:1 \ No newline at end of file diff --git a/test/server-side-rendering/component-binding/_expected.html b/test/server-side-rendering/component-binding/_expected.html new file mode 100644 index 0000000000..6bd7771f11 --- /dev/null +++ b/test/server-side-rendering/component-binding/_expected.html @@ -0,0 +1 @@ +1:foo:1 diff --git a/test/server-side-rendering/component-binding/main.html b/test/server-side-rendering/component-binding/main.html new file mode 100644 index 0000000000..d466a1fb11 --- /dev/null +++ b/test/server-side-rendering/component-binding/main.html @@ -0,0 +1,11 @@ +{{x}}{{x}} + +