Skip to content

Commit

Permalink
Merge pull request #559 from sveltejs/simpler-codegen
Browse files Browse the repository at this point in the history
Simpler codegen
  • Loading branch information
Rich-Harris authored May 4, 2017
2 parents c71cb29 + d8364f6 commit 3a7f7e2
Show file tree
Hide file tree
Showing 19 changed files with 173 additions and 301 deletions.
188 changes: 61 additions & 127 deletions src/generators/dom/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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' );
Expand All @@ -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}'" );` );
});
}

if ( computations.length ) {
builders._set.addLine( `${generator.alias( 'recompute' )}( this._state, newState, oldState, false )` );
}
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.' );
}
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 );` );
${Array.from( generator.readonly ).map( prop =>
`if ( '${prop}' in newState && !this._updatingReadonlyProperty ) throw new Error( "Cannot set read-only property '${prop}'" );`
)}
`}
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}✂]` );
Expand All @@ -130,104 +123,6 @@ 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}'" );`
);
});

constructorBlock.addBlock(
`if ( !options.target && !options._root ) throw new Error( "'target' is a required option" );`
);
}

if ( generator.bindingGroups.length ) {
constructorBlock.addLine( `this._bindingGroups = [ ${Array( generator.bindingGroups.length ).fill( '[]' ).join( ', ' )} ];` );
}

constructorBlock.addBlock( deindent`
this._observers = {
pre: Object.create( null ),
post: Object.create( null )
};
this._handlers = Object.create( null );
this._root = options._root || this;
this._yield = options._yield;
${builders.init}
` );

builders.main.addBlock( deindent`
function ${name} ( options ) {
${constructorBlock}
}
` );

const sharedPath = options.shared === true ? 'svelte/shared.js' : options.shared;

const prototypeBase = `${name}.prototype` + ( templateProperties.methods ? `, ${generator.alias( 'template' )}.methods` : '' );
Expand All @@ -240,10 +135,49 @@ export default function dom ( parsed, source, options ) {
}
}`;

builders.main.addBlock( `${generator.helper( 'assign' )}( ${prototypeBase}, ${proto});` );

// 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( ', ' )} ];`}
this._observers = {
pre: Object.create( null ),
post: Object.create( null )
};
this._handlers = Object.create( null );
this._root = options._root || this;
this._yield = options._yield;
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 = [];`}
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();`}
${templateProperties.oncreate && deindent`
if ( options._root ) {
options._root._renderHooks.push( ${generator.alias( 'template' )}.oncreate.bind( this ) );
} else {
${generator.alias( 'template' )}.oncreate.call( this );
}
`}
}
${generator.helper( 'assign' )}( ${prototypeBase}, ${proto});
${name}.prototype._set = function _set ( newState ) {
${builders._set}
};
Expand Down
37 changes: 7 additions & 30 deletions src/generators/dom/visitors/EachBlock.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';

Expand Down Expand Up @@ -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();

create.addBlock( deindent`
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`
var ${lookup} = Object.create( null );
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${create}
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} );
${state.parentNode && `${iterations}[${i}].${mountOrIntro}( ${state.parentNode}, null );`}
}
` );

Expand Down Expand Up @@ -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 );`}
}
` );

Expand Down
18 changes: 5 additions & 13 deletions src/generators/dom/visitors/Element/EventHandler.js
Original file line number Diff line number Diff line change
@@ -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 ) {
Expand Down Expand Up @@ -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`
Expand Down
27 changes: 12 additions & 15 deletions src/generators/dom/visitors/Element/meta/Window.js
Original file line number Diff line number Diff line change
@@ -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',
Expand Down Expand Up @@ -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 ) {
Expand Down Expand Up @@ -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};`
);
}
Expand All @@ -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 ) {
Expand Down Expand Up @@ -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;`
);

Expand Down
Loading

0 comments on commit 3a7f7e2

Please sign in to comment.