Merge pull request #559 from sveltejs/simpler-codegen

Simpler codegen
pull/563/head
Rich Harris 8 years ago committed by GitHub
commit 3a7f7e29c2

@ -19,9 +19,7 @@ class DomGenerator extends Generator {
this.readonly = new Set();
// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
this.builders = {
metaBindings: new CodeBuilder()
};
this.metaBindings = [];
}
helper ( name ) {
@ -57,21 +55,9 @@ export default function dom ( parsed, source, options ) {
const builders = {
main: new CodeBuilder(),
init: new CodeBuilder(),
_set: new CodeBuilder()
};
if ( options.dev ) {
builders._set.addBlock( deindent`
if ( typeof newState !== 'object' ) {
throw new Error( 'Component .set was called without an object of data key-values to update.' );
}
`);
}
builders._set.addLine( 'var oldState = this._state;' );
builders._set.addLine( `this._state = ${generator.helper( 'assign' )}( {}, oldState, newState );` );
if ( computations.length ) {
const builder = new CodeBuilder();
const differs = generator.helper( 'differs' );
@ -97,19 +83,26 @@ export default function dom ( parsed, source, options ) {
` );
}
if ( options.dev ) {
Array.from( generator.readonly ).forEach( prop => {
builders._set.addLine( `if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );` );
});
builders._set.addBlock( deindent`
${options.dev && deindent`
if ( typeof newState !== 'object' ) {
throw new Error( 'Component .set was called without an object of data key-values to update.' );
}
if ( computations.length ) {
builders._set.addLine( `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )` );
}
${Array.from( generator.readonly ).map( prop =>
`if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );`
)}
`}
builders._set.addLine( `${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );` );
if ( block.hasUpdateMethod ) builders._set.addLine( `if ( this._fragment ) this._fragment.update( newState, this._state );` ); // TODO is the condition necessary?
builders._set.addLine( `${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );` );
var oldState = this._state;
this._state = ${generator.helper( 'assign' )}( {}, oldState, newState );
${computations.length && `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )`}
${generator.helper( 'dispatchObservers' )}( this, this._observers.pre, newState, oldState );
${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`}
${generator.helper( 'dispatchObservers' )}( this, this._observers.post, newState, oldState );
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
` );
if ( hasJs ) {
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
@ -130,85 +123,30 @@ export default function dom ( parsed, source, options ) {
builders.main.addBlock( block.render() );
});
builders.init.addLine( `this._torndown = false;` );
if ( parsed.css && options.css !== false ) {
builders.init.addLine( `if ( !document.getElementById( ${JSON.stringify( generator.cssId + '-style' )} ) ) ${generator.alias( 'add_css' )}();` );
}
if ( generator.hasComponents || generator.hasIntroTransitions ) {
builders.init.addLine( `this._renderHooks = [];` );
}
if ( generator.hasComplexBindings ) {
builders.init.addBlock( deindent`
this._bindings = [];
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
while ( this._bindings.length ) this._bindings.pop()();
` );
builders._set.addLine( `while ( this._bindings.length ) this._bindings.pop()();` );
} else {
builders.init.addBlock( deindent`
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
` );
}
if ( generator.hasComponents || generator.hasIntroTransitions ) {
const statement = `this._flush();`;
builders.init.addBlock( statement );
builders._set.addBlock( statement );
}
if ( templateProperties.oncreate ) {
builders.init.addBlock( deindent`
if ( options._root ) {
options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
} else {
${generator.alias( 'template' )}.oncreate.call( this );
}
` );
}
const constructorBlock = new CodeBuilder();
constructorBlock.addLine( `options = options || {};` );
if ( generator.usesRefs ) constructorBlock.addLine( `this.refs = {};` );
constructorBlock.addLine(
`this._state = ${templateProperties.data ? `${generator.helper( 'assign' )}( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};`
);
if ( !generator.builders.metaBindings.isEmpty() ) {
constructorBlock.addBlock( generator.builders.metaBindings );
}
if ( computations.length ) {
constructorBlock.addLine(
`${generator.alias( 'recompute' )}( this._state, this._state, {}, true );`
);
}
if ( options.dev ) {
generator.expectedProperties.forEach( prop => {
constructorBlock.addLine(
`if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`
);
});
const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared;
constructorBlock.addBlock(
`if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`
);
const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' );
const proto = sharedPath ? `${generator.helper( 'proto' )} ` : deindent`
{
${
[ 'get', 'fire', 'observe', 'on', 'set', '_flush' ]
.map( n => `${n}: ${generator.helper( n )}` )
.join( ',\n' )
}
}`;
if ( generator.bindingGroups.length ) {
constructorBlock.addLine( `this._bindingGroups = [ ${Array( generator.bindingGroups.length ).fill( '[]' ).join( ', ' )} ];` );
}
// TODO deprecate component.teardown()
builders.main.addBlock( deindent`
function ${name} ( options ) {
options = options || {};
${options.dev && `if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`}
${generator.usesRefs && `this.refs = {};`}
this._state = ${templateProperties.data ? `${generator.helper( 'assign' )}( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};
${generator.metaBindings}
${computations.length && `${generator.alias( 'recompute' )}( this._state, this._state, {}, true );`}
${options.dev && Array.from( generator.expectedProperties ).map( prop => `if ( !( '${prop}' in this._state ) ) console.warn( "Component was created without expected data property '${prop}'" );`)}
${generator.bindingGroups.length && `this._bindingGroups = [ ${Array( generator.bindingGroups.length ).fill( '[]' ).join( ', ' )} ];`}
constructorBlock.addBlock( deindent`
this._observers = {
pre: Object.create( null ),
post: Object.create( null )
@ -219,31 +157,27 @@ export default function dom ( parsed, source, options ) {
this._root = options._root || this;
this._yield = options._yield;
${builders.init}
` );
this._torndown = false;
${parsed.css && options.css !== false && `if ( !document.getElementById( ${JSON.stringify( generator.cssId + '-style' )} ) ) ${generator.alias( 'add_css' )}();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._renderHooks = [];`}
${generator.hasComplexBindings && `this._bindings = [];`}
builders.main.addBlock( deindent`
function ${name} ( options ) {
${constructorBlock}
}
` );
const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared;
this._fragment = ${generator.alias( 'create_main_fragment' )}( this._state, this );
if ( options.target ) this._fragment.mount( options.target, null );
${generator.hasComplexBindings && `while ( this._bindings.length ) this._bindings.pop()();`}
${( generator.hasComponents || generator.hasIntroTransitions ) && `this._flush();`}
const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' );
const proto = sharedPath ? `${generator.helper( 'proto' )} ` : deindent`
{
${
[ 'get', 'fire', 'observe', 'on', 'set', '_flush' ]
.map( n => `${n}: ${generator.helper( n )}` )
.join( ',\n' )
${templateProperties.oncreate && deindent`
if ( options._root ) {
options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
} else {
${generator.alias( 'template' )}.oncreate.call( this );
}
`}
}
}`;
builders.main.addBlock( `${generator.helper( 'assign' )}( ${prototypeBase}, ${proto});` );
${generator.helper( 'assign' )}( ${prototypeBase}, ${proto});
// TODO deprecate component.teardown()
builders.main.addBlock( deindent`
${name}.prototype._set = function _set ( newState ) {
${builders._set}
};

@ -1,4 +1,3 @@
import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
@ -119,24 +118,13 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
const iteration = block.getUniqueName( `${each_block}_iteration` );
const _iterations = block.getUniqueName( `_${each_block}_iterations` );
block.builders.create.addLine( `var ${lookup} = Object.create( null );` );
const create = new CodeBuilder();
block.builders.create.addBlock( deindent`
var ${lookup} = Object.create( null );
create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
${iterations}[${i}] = ${lookup}[ ${key} ] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
` );
if ( state.parentNode ) {
create.addLine(
`${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`
);
}
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${create}
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
}
` );
@ -206,21 +194,10 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
}
function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
const create = new CodeBuilder();
create.addLine(
`${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );`
);
if ( state.parentNode ) {
create.addLine(
`${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`
);
}
block.builders.create.addBlock( deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${create}
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
}
` );

@ -1,5 +1,4 @@
import deindent from '../../../../utils/deindent.js';
import CodeBuilder from '../../../../utils/CodeBuilder.js';
import flattenReference from '../../../../utils/flattenReference.js';
export default function visitEventHandler ( generator, block, state, node, attribute ) {
@ -49,18 +48,11 @@ export default function visitEventHandler ( generator, block, state, node, attri
block.getUniqueName( `${name}_handler` );
// create the handler body
const handlerBody = new CodeBuilder();
if ( state.usesComponent ) {
// TODO the element needs to know to create `thing._svelte = { component: component }`
handlerBody.addLine( `var ${block.component} = this._svelte.component;` );
}
declarations.forEach( declaration => {
handlerBody.addLine( declaration );
});
handlerBody.addLine( `[✂${attribute.expression.start}-${attribute.expression.end}✂];` );
const handlerBody = deindent`
${state.usesComponent && `var ${block.component} = this._svelte.component;`}
${declarations}
[${attribute.expression.start}-${attribute.expression.end}];
`;
const handler = isCustomEvent ?
deindent`

@ -1,6 +1,5 @@
import flattenReference from '../../../../../utils/flattenReference.js';
import deindent from '../../../../../utils/deindent.js';
import CodeBuilder from '../../../../../utils/CodeBuilder.js';
const associatedEvents = {
innerWidth: 'resize',
@ -43,8 +42,10 @@ export default function visitWindow ( generator, block, node ) {
}
const handlerName = block.getUniqueName( `onwindow${attribute.name}` );
const handlerBody = ( usesState ? `var state = ${block.component}.get();\n` : '' ) +
`[✂${attribute.expression.start}-${attribute.expression.end}✂];`;
const handlerBody = deindent`
${usesState && `var state = ${block.component}.get();`}
[${attribute.expression.start}-${attribute.expression.end}];
`;
block.builders.create.addBlock( deindent`
function ${handlerName} ( event ) {
@ -84,7 +85,7 @@ export default function visitWindow ( generator, block, node ) {
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
// add initial value
generator.builders.metaBindings.addLine(
generator.metaBindings.push(
`this._state.${attribute.value.name} = window.${attribute.name};`
);
}
@ -96,25 +97,21 @@ export default function visitWindow ( generator, block, node ) {
const handlerName = block.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' );
const handlerBody = new CodeBuilder();
if ( event === 'scroll' ) { // TODO other bidirectional bindings...
block.addVariable( lock, 'false' );
handlerBody.addLine( `${lock} = true;` );
}
if ( generator.options.dev ) handlerBody.addLine( `component._updatingReadonlyProperty = true;` );
const handlerBody = deindent`
${event === 'scroll' && `${lock} = true;`}
${generator.options.dev && `component._updatingReadonlyProperty = true;`}
handlerBody.addBlock( deindent`
${block.component}.set({
${props}
});
` );
if ( generator.options.dev ) handlerBody.addLine( `component._updatingReadonlyProperty = false;` );
if ( event === 'scroll' ) {
handlerBody.addLine( `${lock} = false;` );
}
${generator.options.dev && `component._updatingReadonlyProperty = false;`}
${event === 'scroll' && `${lock} = false;`}
`;
block.builders.create.addBlock( deindent`
function ${handlerName} ( event ) {
@ -166,7 +163,7 @@ export default function visitWindow ( generator, block, node ) {
` );
// add initial value
generator.builders.metaBindings.addLine(
generator.metaBindings.push(
`this._state.${bindings.online} = navigator.onLine;`
);

@ -1,5 +1,4 @@
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import Generator from '../Generator.js';
import Block from './Block.js';
import visit from './visit.js';
@ -24,13 +23,6 @@ export default function ssr ( parsed, source, options ) {
const { computations, hasJs, templateProperties } = generator.parseJs( true );
const builders = {
main: new CodeBuilder(),
bindings: new CodeBuilder(),
render: new CodeBuilder(),
renderCss: new CodeBuilder()
};
// create main render() function
const mainBlock = new Block({
generator,
@ -43,52 +35,50 @@ export default function ssr ( parsed, source, options ) {
visit( generator, mainBlock, node );
});
builders.render.addLine(
templateProperties.data ? `state = Object.assign( ${generator.alias( 'template' )}.data(), state || {} );` : `state = state || {};`
);
const result = deindent`
${hasJs && `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`}
computations.forEach( ({ key, deps }) => {
builders.render.addLine(
`state.${key} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );`
);
});
var ${name} = {};
${name}.filename = ${JSON.stringify( options.filename )};
${name}.data = function () {
return ${templateProperties.data ? `${generator.alias( 'template' )}.data()` : `{}`};
};
if ( generator.bindings.length ) {
const bindings = generator.bindings.join( '\n\n' );
${name}.render = function ( state, options ) {
${templateProperties.data ? `state = Object.assign( ${generator.alias( 'template' )}.data(), state || {} );` : `state = state || {};`}
${computations.map( ({ key, deps }) =>
`state.${key} = ${generator.alias( 'template' )}.computed.${key}( ${deps.map( dep => `state.${dep}` ).join( ', ' )} );`
)}
builders.render.addBlock( deindent`
${generator.bindings.length && deindent`
var settled = false;
var tmp;
while ( !settled ) {
settled = true;
${bindings}
}
` );
${generator.bindings.join( '\n\n' )}
}
`}
builders.render.addBlock(
`return \`${generator.renderCode}\`;`
);
return \`${generator.renderCode}\`;
};
// create renderCss() function
builders.renderCss.addBlock(
`var components = [];`
);
${name}.renderCss = function () {
var components = [];
if ( generator.css ) {
builders.renderCss.addBlock( deindent`
${generator.css && deindent`
components.push({
filename: ${name}.filename,
css: ${JSON.stringify( generator.css )},
map: null // TODO
});
` );
}
`}
if ( templateProperties.components ) {
builders.renderCss.addBlock( deindent`
${templateProperties.components && deindent`
var seen = {};
function addComponent ( component ) {
@ -99,42 +89,21 @@ export default function ssr ( parsed, source, options ) {
components.push( x );
});
}
` );
templateProperties.components.value.properties.forEach( prop => {
${
templateProperties.components.value.properties.map( prop => {
const { name } = prop.key;
const expression = generator.importedComponents.get( name ) || `${generator.alias( 'template' )}.components.${name}`;
builders.renderCss.addLine( `addComponent( ${expression} );` );
});
return `addComponent( ${expression} );`;
})
}
`}
builders.renderCss.addBlock( deindent`
return {
css: components.map( x => x.css ).join( '\\n' ),
map: null,
components
};
` );
if ( hasJs ) {
builders.main.addBlock( `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]` );
}
builders.main.addBlock( deindent`
var ${name} = {};
${name}.filename = ${JSON.stringify( options.filename )};
${name}.data = function () {
return ${templateProperties.data ? `${generator.alias( 'template' )}.data()` : `{}`};
};
${name}.render = function ( state, options ) {
${builders.render}
};
${name}.renderCss = function () {
${builders.renderCss}
};
var escaped = {
@ -148,9 +117,7 @@ export default function ssr ( parsed, source, options ) {
function __escape ( html ) {
return String( html ).replace( /["'&<>]/g, match => escaped[ match ] );
}
` );
const result = builders.main.toString();
`;
return generator.generate( result, options, { name, format } );
}

@ -2,11 +2,12 @@ const LINE = {};
const BLOCK = {};
export default class CodeBuilder {
constructor () {
this.result = '';
constructor ( str = '' ) {
this.result = str;
this.first = null;
this.last = null;
const initial = str ? ( /\n/.test( str ) ? BLOCK : LINE ) : null;
this.first = initial;
this.last = initial;
this.lastCondition = null;
}

@ -9,9 +9,13 @@ export default function deindent ( strings, ...values ) {
let trailingIndentation = getTrailingIndentation( result );
for ( let i = 1; i < strings.length; i += 1 ) {
const expression = values[ i - 1 ];
let expression = values[ i - 1 ];
const string = strings[i].replace( pattern, '' );
if ( Array.isArray( expression ) ) {
expression = expression.length ? expression.join( '\n' ) : null;
}
if ( expression || expression === '' ) {
const value = String( expression ).replace( /\n/g, `\n${trailingIndentation}` );
result += value + string;

@ -69,7 +69,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -145,7 +145,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -95,7 +95,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -76,7 +76,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -226,7 +226,7 @@ SvelteComponent.prototype._set = function _set ( newState ) {
var oldState = this._state;
this._state = assign( {}, oldState, newState );
dispatchObservers( this, this._observers.pre, newState, oldState );
if ( this._fragment ) this._fragment.update( newState, this._state );
this._fragment.update( newState, this._state );
dispatchObservers( this, this._observers.post, newState, oldState );
};

@ -1,3 +1,3 @@
<p>before</p>
<p>after</p>
<p>after</p>

@ -1,4 +1,4 @@
<div><p>foo: lol</p>
<p>baz: 42 (number)</p>
<p>qux: this is a piece of string</p>
<p>quux: core</p></div>
<p>baz: 42 (number)</p>
<p>qux: this is a piece of string</p>
<p>quux: core</p></div>

@ -1,2 +1,2 @@
<div><p>foo: bar</p>
<p>baz: 42 (number)</p></div>
<p>baz: 42 (number)</p></div>

@ -1,2 +1,2 @@
<p>1 + 2 = 3</p>
<p>3 * 3 = 9</p>
<p>3 * 3 = 9</p>

@ -1,2 +1,2 @@
<div>i got 99 problems</div>
<div>the answer is 42</div>
<div>the answer is 42</div>

@ -1,5 +1,5 @@
<div svelte-4188175681>red</div>
<div svelte-146600313>green: foo</div>
<div svelte-1506185237>blue: foo</div>
<div svelte-146600313>green: bar</div>
<div svelte-1506185237>blue: bar</div>
<div svelte-146600313>green: foo</div>
<div svelte-1506185237>blue: foo</div>
<div svelte-146600313>green: bar</div>
<div svelte-1506185237>blue: bar</div>
Loading…
Cancel
Save