Merge pull request #1 from sveltejs/spread-attribute-updates

Updating and component-friendly spread attributes
pull/280/head
Paul Sauve 9 years ago committed by GitHub
commit 24212cf102

@ -12,7 +12,7 @@ export default {
nodeResolve({ jsnext: true, module: true }), nodeResolve({ jsnext: true, module: true }),
commonjs() commonjs()
], ],
external: [ path.resolve( 'src/index.js' ), 'fs', 'magic-string' ], external: [ path.resolve( 'src/index.js' ), 'fs', 'path', 'magic-string' ],
paths: { paths: {
[ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js' [ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js'
}, },

@ -49,7 +49,7 @@ export default {
const statements = []; 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 const initialProps = local.staticAttributes
.concat( local.dynamicAttributes ) .concat( local.dynamicAttributes )
.map( attribute => `${attribute.name}: ${attribute.value}` ); .map( attribute => `${attribute.name}: ${attribute.value}` );
@ -73,6 +73,13 @@ export default {
statements.push( bindings.join( '\n' ) ); statements.push( bindings.join( '\n' ) );
} }
if ( local.spreadAttributes.length ) {
local.spreadAttributes.forEach( name => {
statements.push( `Object.assign( ${local.name}_initialData, root.${name} );` );
});
}
componentInitProperties.push( `data: ${name}_initialData` ); componentInitProperties.push( `data: ${name}_initialData` );
} }

@ -4,6 +4,7 @@ import deindent from '../../../../utils/deindent.js';
export default function addComponentAttributes ( generator, node, local ) { export default function addComponentAttributes ( generator, node, local ) {
local.staticAttributes = []; local.staticAttributes = [];
local.dynamicAttributes = []; local.dynamicAttributes = [];
local.spreadAttributes = [];
local.bindings = []; local.bindings = [];
node.attributes.forEach( attribute => { 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 { else {
throw new Error( `Not implemented: ${attribute.type}` ); throw new Error( `Not implemented: ${attribute.type}` );
} }

@ -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 to include setXLinkAttibute and setAttribute because the data is dynamic
// we have no idea at compile time what is being used // 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 { else {

@ -7,9 +7,26 @@ import Generator from '../Generator.js';
class SsrGenerator extends Generator { class SsrGenerator extends Generator {
constructor ( parsed, source, names, visitors ) { constructor ( parsed, source, names, visitors ) {
super( parsed, source, names, visitors ); super( parsed, source, names, visitors );
this.bindings = [];
this.renderCode = ''; 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 ) { append ( code ) {
this.renderCode += code; this.renderCode += code;
} }
@ -25,6 +42,7 @@ export default function ssr ( parsed, source, options, names ) {
const builders = { const builders = {
main: new CodeBuilder(), main: new CodeBuilder(),
bindings: new CodeBuilder(),
render: new CodeBuilder(), render: new CodeBuilder(),
renderCss: new CodeBuilder() renderCss: new CodeBuilder()
}; };
@ -32,7 +50,8 @@ export default function ssr ( parsed, source, options, names ) {
// create main render() function // create main render() function
generator.push({ generator.push({
contexts: {}, contexts: {},
indexes: {} indexes: {},
conditions: []
}); });
parsed.html.children.forEach( node => generator.visit( node ) ); 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( builders.render.addBlock(
`return \`${generator.renderCode}\`;` `return \`${generator.renderCode}\`;`
); );
@ -102,6 +136,10 @@ export default function ssr ( parsed, source, options, names ) {
${name}.filename = ${JSON.stringify( options.filename )}; ${name}.filename = ${JSON.stringify( options.filename )};
${name}.data = function () {
return ${templateProperties.data ? `template.data()` : `{}`};
};
${name}.render = function ( root, options ) { ${name}.render = function ( root, options ) {
${builders.render} ${builders.render}
}; };

@ -8,10 +8,22 @@ export default {
} }
} }
const props = node.attributes const attributes = [];
.map( attribute => { const bindings = [];
if ( attribute.type !== 'Attribute' ) return; 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; let value;
if ( attribute.value === true ) { if ( attribute.value === true ) {
@ -32,10 +44,17 @@ export default {
return `${attribute.name}: ${value}`; return `${attribute.name}: ${value}`;
}) })
.filter( Boolean )
.join( ', ' ); .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 ) { if ( node.children.length ) {
open += `, { yield: () => \``; open += `, { yield: () => \``;

@ -15,6 +15,7 @@ export default {
openingTag += ` \${Object.keys( root.${attribute.name} ).map( prop => \`\${prop}="\${root.${attribute.name}[prop]}"\` ).join( ' ' )}`; openingTag += ` \${Object.keys( root.${attribute.name} ).map( prop => \`\${prop}="\${root.${attribute.name}[prop]}"\` ).join( ' ' )}`;
return; return;
} }
if ( attribute.type !== 'Attribute' ) return; if ( attribute.type !== 'Attribute' ) return;
let str = ` ${attribute.name}`; let str = ` ${attribute.name}`;

@ -1,12 +1,19 @@
export default { export default {
enter ( generator, node ) { enter ( generator, node ) {
const { snippet } = generator.contextualise( node.expression ); const { snippet } = generator.contextualise( node.expression );
generator.append( '${ ' + snippet + ' ? `' ); generator.append( '${ ' + snippet + ' ? `' );
generator.push({
conditions: generator.current.conditions.concat( snippet )
});
}, },
leave ( generator, node ) { leave ( generator, node ) {
generator.append( '` : `' ); generator.append( '` : `' );
if ( node.else ) node.else.children.forEach( child => generator.visit( child ) ); if ( node.else ) node.else.children.forEach( child => generator.visit( child ) );
generator.append( '` }' ); generator.append( '` }' );
generator.pop();
} }
}; };

@ -1,10 +1,17 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
import { compile } from '../index.js'; import { compile } from '../index.js';
function capitalise ( name ) {
return name[0].toUpperCase() + name.slice( 1 );
}
require.extensions[ '.html' ] = function ( module, filename ) { require.extensions[ '.html' ] = function ( module, filename ) {
const { code } = compile( fs.readFileSync( filename, 'utf-8' ), { const { code } = compile( fs.readFileSync( filename, 'utf-8' ), {
filename, filename,
name: capitalise( path.basename( filename ).replace( /\.html$/, '' ) ),
generate: 'ssr' generate: 'ssr'
}); });
return module._compile( code, filename ); return module._compile( code, filename );
}; };

@ -1,3 +1,15 @@
export default { export default {
html: '<input type="text" value="Hello World"/>', html: '<input type="text" value="Hello World"/>',
test ( assert, component, target ) {
component.set({
options: {
type: 'text',
value: 'changed'
}
});
assert.htmlEqual( target.innerHTML, `<input type="text" value="changed"/>` );
component.teardown();
}
}; };

@ -1 +1,2 @@
<input {{options}}/> <p>foo: {{foo}}</p>
<p>bar: {{bar}}</p>

@ -1,3 +1,22 @@
export default { export default {
html: '<input type="text" value="Hello World"/>', html: `
<p>foo: 1</p>
<p>bar: 2</p>
`,
test ( assert, component, target ) {
component.set({
options: {
foo: 3,
bar: 4
}
});
assert.equal( component.refs.widget.get( 'foo' ), 3 );
assert.htmlEqual( target.innerHTML, `
<p>foo: 3</p>
<p>bar: 4</p>
` );
component.teardown();
}
}; };

@ -1,4 +1,4 @@
<Widget options="{{options}}"/> <Widget ref:widget {{options}}/>
<script> <script>
import Widget from './Widget.html'; import Widget from './Widget.html';
@ -6,10 +6,11 @@
export default { export default {
data: () => ({ data: () => ({
options: { options: {
type: 'text', foo: 1,
value: 'Hello World' bar: 2
} }
}), }),
components: { components: {
Widget Widget
} }

@ -0,0 +1,11 @@
:foo:
<script>
export default {
data () {
return {
x: 1
};
}
};
</script>

@ -0,0 +1,11 @@
{{y}}<Foo bind:y='x'/>{{y}}
<script>
import Foo from './Foo.html';
export default {
components: {
Foo
}
};
</script>

@ -0,0 +1,11 @@
:foo:
<script>
export default {
data () {
return {
x: 1
};
}
};
</script>

@ -0,0 +1,11 @@
{{x}}<Foo bind:x/>{{x}}
<script>
import Foo from './Foo.html';
export default {
components: {
Foo
}
};
</script>
Loading…
Cancel
Save