Skip to content

Simpler codegen #559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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