Merge pull request #673 from sveltejs/codegen

Simpler codegen
pull/683/head
Rich Harris 8 years ago committed by GitHub
commit 252fa5b5a2

@ -1 +1,2 @@
test/test.js
--bail
test/test.js

@ -2,6 +2,7 @@ import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
import shared from './shared';
export interface BlockOptions {
name: string;
@ -61,9 +62,6 @@ export default class Block {
variables: Map<string, string>;
getUniqueName: (name: string) => string;
component: string;
target: string;
hasUpdateMethod: boolean;
autofocus: string;
@ -110,10 +108,6 @@ export default class Block {
this.variables = new Map();
this.getUniqueName = this.generator.getUniqueNameMaker(options.params);
// unique names
this.component = this.getUniqueName('component');
this.target = this.getUniqueName('target');
this.hasUpdateMethod = false; // determined later
}
@ -134,14 +128,12 @@ export default class Block {
this.addVariable(name);
this.builders.create.addLine(`${name} = ${renderStatement};`);
this.builders.claim.addLine(`${name} = ${claimStatement};`)
this.builders.claim.addLine(`${name} = ${claimStatement};`);
this.mount(name, parentNode);
if (isToplevel) {
this.builders.unmount.addLine(
`${this.generator.helper('detachNode')}( ${name} );`
);
this.builders.unmount.addLine(`@detachNode( ${name} );`);
}
}
@ -186,14 +178,9 @@ export default class Block {
mount(name: string, parentNode: string) {
if (parentNode) {
this.builders.mount.addLine(
`${this.generator.helper('appendNode')}( ${name}, ${parentNode} );`
);
this.builders.mount.addLine(`@appendNode( ${name}, ${parentNode} );`);
} else {
this.builders.mount.addLine(
`${this.generator.helper('insertNode')}( ${name}, ${this
.target}, anchor );`
);
this.builders.mount.addLine(`@insertNode( ${name}, #target, anchor );`);
}
}
@ -229,11 +216,11 @@ export default class Block {
if (this.first) {
properties.addBlock(`first: null,`);
this.builders.hydrate.addLine( `this.first = ${this.first};` );
this.builders.hydrate.addLine(`this.first = ${this.first};`);
}
if (this.builders.create.isEmpty()) {
properties.addBlock(`create: ${this.generator.helper('noop')},`);
properties.addBlock(`create: @noop,`);
} else {
properties.addBlock(deindent`
create: function () {
@ -245,7 +232,7 @@ export default class Block {
if (this.generator.hydratable) {
if (this.builders.claim.isEmpty()) {
properties.addBlock(`claim: ${this.generator.helper('noop')},`);
properties.addBlock(`claim: @noop,`);
} else {
properties.addBlock(deindent`
claim: function ( nodes ) {
@ -265,10 +252,10 @@ export default class Block {
}
if (this.builders.mount.isEmpty()) {
properties.addBlock(`mount: ${this.generator.helper('noop')},`);
properties.addBlock(`mount: @noop,`);
} else {
properties.addBlock(deindent`
mount: function ( ${this.target}, anchor ) {
mount: function ( #target, anchor ) {
${this.builders.mount}
},
`);
@ -276,7 +263,7 @@ export default class Block {
if (this.hasUpdateMethod) {
if (this.builders.update.isEmpty()) {
properties.addBlock(`update: ${this.generator.helper('noop')},`);
properties.addBlock(`update: @noop,`);
} else {
properties.addBlock(deindent`
update: function ( changed, ${this.params.join(', ')} ) {
@ -289,20 +276,20 @@ export default class Block {
if (this.hasIntroMethod) {
if (hasIntros) {
properties.addBlock(deindent`
intro: function ( ${this.target}, anchor ) {
intro: function ( #target, anchor ) {
if ( ${introing} ) return;
${introing} = true;
${hasOutros && `${outroing} = false;`}
${this.builders.intro}
this.mount( ${this.target}, anchor );
this.mount( #target, anchor );
},
`);
} else {
properties.addBlock(deindent`
intro: function ( ${this.target}, anchor ) {
this.mount( ${this.target}, anchor );
intro: function ( #target, anchor ) {
this.mount( #target, anchor );
},
`);
}
@ -331,7 +318,7 @@ export default class Block {
}
if (this.builders.unmount.isEmpty()) {
properties.addBlock(`unmount: ${this.generator.helper('noop')},`);
properties.addBlock(`unmount: @noop,`);
} else {
properties.addBlock(deindent`
unmount: function () {
@ -341,7 +328,7 @@ export default class Block {
}
if (this.builders.destroy.isEmpty()) {
properties.addBlock(`destroy: ${this.generator.helper('noop')}`);
properties.addBlock(`destroy: @noop`);
} else {
properties.addBlock(deindent`
destroy: function () {
@ -351,15 +338,16 @@ export default class Block {
}
return deindent`
function ${this.name} ( ${this.params.join(', ')}, ${this.component}${this
.key
function ${this.name} ( ${this.params.join(', ')}, #component${this.key
? `, ${localKey}`
: ''} ) {
${this.variables.size > 0 && (
`var ${Array.from(this.variables.keys()).map(key => {
const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
}).join(', ')};`)}
${this.variables.size > 0 &&
`var ${Array.from(this.variables.keys())
.map(key => {
const init = this.variables.get(key);
return init !== undefined ? `${key} = ${init}` : key;
})
.join(', ')};`}
${!this.builders.init.isEmpty() && this.builders.init}
@ -367,6 +355,8 @@ export default class Block {
${properties}
};
}
`;
`.replace(/(\\)?#(\w*)/g, (match, escaped, name) => {
return escaped ? match.slice(1) : this.alias(name);
});
}
}

@ -4,6 +4,7 @@ import annotateWithScopes from '../../utils/annotateWithScopes';
import isReference from '../../utils/isReference';
import { walk } from 'estree-walker';
import deindent from '../../utils/deindent';
import stringify from '../../utils/stringify';
import CodeBuilder from '../../utils/CodeBuilder';
import visit from './visit';
import shared from './shared';
@ -14,7 +15,6 @@ import { Parsed, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator {
blocks: Block[];
uses: Set<string>;
readonly: Set<string>;
metaBindings: string[];
@ -32,7 +32,6 @@ export class DomGenerator extends Generator {
) {
super(parsed, source, name, options);
this.blocks = [];
this.uses = new Set();
this.readonly = new Set();
@ -41,16 +40,6 @@ export class DomGenerator extends Generator {
// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
this.metaBindings = [];
}
helper(name: string) {
if (this.options.dev && `${name}Dev` in shared) {
name = `${name}Dev`;
}
this.uses.add(name);
return this.alias(name);
}
}
export default function dom(
@ -76,14 +65,10 @@ export default function dom(
visit(generator, block, state, node);
});
const builders = {
main: new CodeBuilder(),
_set: new CodeBuilder(),
};
const builder = new CodeBuilder();
if (computations.length) {
const builder = new CodeBuilder();
const differs = generator.helper('differs');
const computationBuilder = new CodeBuilder();
computations.forEach(({ key, deps }) => {
if (generator.readonly.has(key)) {
@ -98,26 +83,24 @@ export default function dom(
const condition = `isInitial || ${deps
.map(
dep =>
`( '${dep}' in newState && ${differs}( state.${dep}, oldState.${dep} ) )`
`( '${dep}' in newState && @differs( state.${dep}, oldState.${dep} ) )`
)
.join(' || ')}`;
const statement = `state.${key} = newState.${key} = ${generator.alias(
'template'
)}.computed.${key}( ${deps.map(dep => `state.${dep}`).join(', ')} );`;
const statement = `state.${key} = newState.${key} = @template.computed.${key}( ${deps
.map(dep => `state.${dep}`)
.join(', ')} );`;
builder.addConditionalLine(condition, statement);
computationBuilder.addConditionalLine(condition, statement);
});
builders.main.addBlock(deindent`
function ${generator.alias(
'recompute'
)} ( state, newState, oldState, isInitial ) {
${builder}
builder.addBlock(deindent`
function @recompute ( state, newState, oldState, isInitial ) {
${computationBuilder}
}
`);
}
builders._set.addBlock(deindent`
const _set = deindent`
${options.dev &&
deindent`
if ( typeof newState !== 'object' ) {
@ -131,43 +114,35 @@ export default function dom(
`}
var oldState = this._state;
this._state = ${generator.helper('assign')}( {}, oldState, newState );
this._state = @assign( {}, oldState, newState );
${computations.length &&
`${generator.alias(
'recompute'
)}( this._state, newState, oldState, false )`}
${generator.helper(
'dispatchObservers'
)}( this, this._observers.pre, newState, oldState );
`@recompute( this._state, newState, oldState, false )`}
@dispatchObservers( this, this._observers.pre, newState, oldState );
${block.hasUpdateMethod && `this._fragment.update( newState, this._state );`}
${generator.helper(
'dispatchObservers'
)}( this, this._observers.post, newState, oldState );
@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}✂]`
);
builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`);
}
if (generator.css && options.css !== false) {
builders.main.addBlock(deindent`
function ${generator.alias('add_css')} () {
var style = ${generator.helper('createElement')}( 'style' );
style.id = ${JSON.stringify(generator.cssId + '-style')};
style.textContent = ${JSON.stringify(generator.css)};
${generator.helper('appendNode')}( style, document.head );
builder.addBlock(deindent`
function @add_css () {
var style = @createElement( 'style' );
style.id = '${generator.cssId}-style';
style.textContent = ${stringify(generator.css)};
@appendNode( style, document.head );
}
`);
}
generator.blocks.forEach(block => {
builders.main.addBlock(block.render());
builder.addBlock(block.render());
});
const sharedPath = options.shared === true
@ -176,35 +151,28 @@ export default function dom(
const prototypeBase =
`${name}.prototype` +
(templateProperties.methods
? `, ${generator.alias('template')}.methods`
: '');
(templateProperties.methods ? `, @template.methods` : '');
const proto = sharedPath
? `${generator.helper('proto')} `
? `@proto `
: deindent`
{
${['get', 'fire', 'observe', 'on', 'set', '_flush']
.map(n => `${n}: ${generator.helper(n)}`)
.map(n => `${n}: @${n}`)
.join(',\n')}
}`;
// TODO deprecate component.teardown()
builders.main.addBlock(deindent`
builder.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 )`
? `@assign( @template.data(), options.data )`
: `options.data || {}`};
${generator.metaBindings}
${computations.length &&
`${generator.alias(
'recompute'
)}( this._state, this._state, {}, true );`}
${computations.length && `@recompute( this._state, this._state, {}, true );`}
${options.dev &&
Array.from(generator.expectedProperties).map(
prop =>
@ -228,23 +196,19 @@ export default function dom(
this._torndown = false;
${generator.css &&
options.css !== false &&
`if ( !document.getElementById( ${JSON.stringify(
generator.cssId + '-style'
)} ) ) ${generator.alias('add_css')}();`}
`if ( !document.getElementById( '${generator.cssId}-style' ) ) @add_css();`}
${(generator.hasComponents || generator.hasIntroTransitions) &&
`this._renderHooks = [];`}
${generator.hasComplexBindings && `this._bindings = [];`}
this._fragment = ${generator.alias(
'create_main_fragment'
)}( this._state, this );
this._fragment = @create_main_fragment( this._state, this );
if ( options.target ) {
${generator.hydratable ?
deindent`
var nodes = ${generator.helper('children')}( options.target );
${generator.hydratable
? deindent`
var nodes = @children( options.target );
options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create();
nodes.forEach( ${generator.helper('detachNode')} );
nodes.forEach( @detachNode );
` :
deindent`
${options.dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );`}
@ -261,25 +225,22 @@ export default function dom(
${templateProperties.oncreate &&
deindent`
if ( options._root ) {
options._root._renderHooks.push( ${generator.alias(
'template'
)}.oncreate.bind( this ) );
options._root._renderHooks.push( @template.oncreate.bind( this ) );
} else {
${generator.alias('template')}.oncreate.call( this );
@template.oncreate.call( this );
}
`}
}
${generator.helper('assign')}( ${prototypeBase}, ${proto});
@assign( ${prototypeBase}, ${proto});
${name}.prototype._set = function _set ( newState ) {
${builders._set}
${_set}
};
${name}.prototype.teardown = ${name}.prototype.destroy = function destroy ( detach ) {
this.fire( 'destroy' );
${templateProperties.ondestroy &&
`${generator.alias('template')}.ondestroy.call( this );`}
${templateProperties.ondestroy && `@template.ondestroy.call( this );`}
if ( detach !== false ) this._fragment.unmount();
this._fragment.destroy();
@ -290,6 +251,21 @@ export default function dom(
};
`);
const usedHelpers = new Set();
let result = builder
.toString()
.replace(/(\\)?@(\w*)/g, (match: string, escaped: string, name: string) => {
if (escaped) return match.slice(1);
if (name in shared) {
if (options.dev && `${name}Dev` in shared) name = `${name}Dev`;
usedHelpers.add(name);
}
return generator.alias(name);
});
if (sharedPath) {
if (format !== 'es') {
throw new Error(
@ -297,17 +273,17 @@ export default function dom(
);
}
const names = Array.from(generator.uses).sort().map(name => {
const names = Array.from(usedHelpers).sort().map(name => {
return name !== generator.alias(name)
? `${name} as ${generator.alias(name)}`
: name;
});
builders.main.addLineAtStart(
`import { ${names.join(', ')} } from ${JSON.stringify(sharedPath)};`
);
result =
`import { ${names.join(', ')} } from ${stringify(sharedPath)};\n\n` +
result;
} else {
generator.uses.forEach(key => {
usedHelpers.forEach(key => {
const str = shared[key];
const code = new MagicString(str);
const expression = parseExpressionAt(str, 0);
@ -326,7 +302,7 @@ export default function dom(
if (node.name in shared) {
// this helper function depends on another one
const dependency = node.name;
generator.uses.add(dependency);
usedHelpers.add(dependency);
const alias = generator.alias(dependency);
if (alias !== node.name)
@ -344,22 +320,20 @@ export default function dom(
// special case
const global = `_svelteTransitionManager`;
builders.main.addBlock(
`var ${generator.alias(
'transitionManager'
)} = window.${global} || ( window.${global} = ${code});`
);
result += `\n\nvar ${generator.alias(
'transitionManager'
)} = window.${global} || ( window.${global} = ${code});`;
} else {
const alias = generator.alias(expression.id.name);
if (alias !== expression.id.name)
code.overwrite(expression.id.start, expression.id.end, alias);
builders.main.addBlock(code.toString());
result += `\n\n${code}`;
}
});
}
return generator.generate(builders.main.toString(), options, {
return generator.generate(result, options, {
name,
format,
});

@ -12,7 +12,12 @@ function isElseIf(node: Node) {
}
function getChildState(parent: State, child = {}) {
return assign({}, parent, { name: null, parentNode: null, parentNodes: 'nodes' }, child || {});
return assign(
{},
parent,
{ name: null, parentNode: null, parentNodes: 'nodes' },
child || {}
);
}
// Whitespace inside one of these elements will not result in
@ -225,7 +230,11 @@ const preprocessors = {
block.addDependencies(dependencies);
// special case — <option value='{{foo}}'> — see below
if (node.name === 'option' && attribute.name === 'value' && state.selectBindingDependencies) {
if (
node.name === 'option' &&
attribute.name === 'value' &&
state.selectBindingDependencies
) {
state.selectBindingDependencies.forEach(prop => {
dependencies.forEach((dependency: string) => {
generator.indirectDependencies.get(prop).add(dependency);
@ -258,7 +267,9 @@ const preprocessors = {
// so that if `foo.qux` changes, we know that we need to
// mark `bar` and `baz` as dirty too
if (node.name === 'select') {
const value = node.attributes.find((attribute: Node) => attribute.name === 'value');
const value = node.attributes.find(
(attribute: Node) => attribute.name === 'value'
);
if (value) {
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
const dependencies = block.findDependencies(value.value);
@ -382,7 +393,7 @@ export default function preprocess(
) {
const block = new Block({
generator,
name: generator.alias('create_main_fragment'),
name: '@create_main_fragment',
key: null,
contexts: new Map(),

@ -2,6 +2,7 @@ import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import stringify from '../../../../utils/stringify';
export default function visitAttribute(
generator: DomGenerator,
@ -27,9 +28,7 @@ export default function visitAttribute(
if (value.type === 'Text') {
// static attributes
const result = isNaN(value.data)
? JSON.stringify(value.data)
: value.data;
const result = isNaN(value.data) ? stringify(value.data) : value.data;
local.staticAttributes.push({
name: attribute.name,
value: result,
@ -54,7 +53,7 @@ export default function visitAttribute(
attribute.value
.map(chunk => {
if (chunk.type === 'Text') {
return JSON.stringify(chunk.data);
return stringify(chunk.data);
} else {
const { dependencies, snippet } = block.contextualise(
chunk.expression

@ -66,16 +66,14 @@ export default function visitBinding(
block.addVariable(updating, 'false');
local.create.addBlock(deindent`
${block.component}._bindings.push( function () {
#component._bindings.push( function () {
if ( ${local.name}._torndown ) return;
${local.name}.observe( '${attribute.name}', function ( value ) {
if ( ${updating} ) return;
${updating} = true;
${setter}
${updating} = false;
}, { init: ${generator.helper(
'differs'
)}( ${local.name}.get( '${attribute.name}' ), ${snippet} ) });
}, { init: @differs( ${local.name}.get( '${attribute.name}' ), ${snippet} ) });
});
`);

@ -112,9 +112,7 @@ export default function visitComponent(
local.update.addBlock(updates);
}
const componentInitProperties = [
`_root: ${block.component}._root`,
];
const componentInitProperties = [`_root: #component._root`];
// Component has children, put them in a separate {{yield}} block
if (hasChildren) {
@ -129,12 +127,10 @@ export default function visitComponent(
const yieldFragment = block.getUniqueName(`${name}_yield_fragment`);
block.builders.init.addLine(
`var ${yieldFragment} = ${childBlock.name}( ${params}, ${block.component} );`
`var ${yieldFragment} = ${childBlock.name}( ${params}, #component );`
);
block.builders.create.addLine(
`${yieldFragment}.create();`
);
block.builders.create.addLine(`${yieldFragment}.create();`);
block.builders.claim.addLine(
`${yieldFragment}.claim( ${state.parentNodes} );`
@ -184,7 +180,7 @@ export default function visitComponent(
const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`${generator.alias('template')}.components.${node.name}`;
`@template.components.${node.name}`;
local.create.addBlockAtStart(deindent`
${statements.join('\n')}
@ -225,12 +221,16 @@ export default function visitComponent(
block.builders.init.addBlock(local.create);
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.create.addLine(`${name}._fragment.create();`);
block.builders.claim.addLine(`${name}._fragment.claim( ${state.parentNodes} );`);
block.builders.mount.addLine(`${name}._fragment.mount( ${targetNode}, ${anchorNode} );` );
block.builders.claim.addLine(
`${name}._fragment.claim( ${state.parentNodes} );`
);
block.builders.mount.addLine(
`${name}._fragment.mount( ${targetNode}, ${anchorNode} );`
);
if (!local.update.isEmpty()) block.builders.update.addBlock(local.update);
}

@ -16,7 +16,7 @@ export default function visitEventHandler(
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
`${block.alias('component')}.`
);
const usedContexts: string[] = [];

@ -14,11 +14,9 @@ export default function visitRef(
) {
generator.usesRefs = true;
local.create.addLine(
`${block.component}.refs.${attribute.name} = ${local.name};`
);
local.create.addLine(`#component.refs.${attribute.name} = ${local.name};`);
block.builders.destroy.addLine(deindent`
if ( ${block.component}.refs.${attribute.name} === ${local.name} ) ${block.component}.refs.${attribute.name} = null;
if ( #component.refs.${attribute.name} === ${local.name} ) #component.refs.${attribute.name} = null;
`);
}

@ -15,7 +15,6 @@ export default function visitEachBlock(
const create_each_block = node._block.name;
const each_block_value = node._block.listName;
const iterations = block.getUniqueName(`${each_block}_iterations`);
const i = block.alias(`i`);
const params = block.params.join(', ');
const anchor = node.needsAnchor
? block.getUniqueName(`${each_block}_anchor`)
@ -27,7 +26,6 @@ export default function visitEachBlock(
create_each_block,
each_block_value,
iterations,
i,
params,
anchor,
mountOrIntro,
@ -48,8 +46,8 @@ export default function visitEachBlock(
if (node.needsAnchor) {
block.addElement(
anchor,
`${generator.helper('createComment')}()`,
`${generator.helper('createComment')}()`,
`@createComment()`,
`@createComment()`,
state.parentNode,
true
);
@ -65,14 +63,15 @@ export default function visitEachBlock(
// TODO neaten this up... will end up with an empty line in the block
block.builders.init.addBlock(deindent`
if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${each_block_else} = ${node.else._block.name}( ${params}, #component );
${each_block_else}.create();
}
`);
block.builders.mount.addBlock(deindent`
if ( ${each_block_else} ) {
${each_block_else}.${mountOrIntro}( ${state.parentNode || block.target}, null );
${each_block_else}.${mountOrIntro}( ${state.parentNode ||
'#target'}, null );
}
`);
@ -83,7 +82,7 @@ export default function visitEachBlock(
if ( !${each_block_value}.length && ${each_block_else} ) {
${each_block_else}.update( changed, ${params} );
} else if ( !${each_block_value}.length ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${each_block_else} = ${node.else._block.name}( ${params}, #component );
${each_block_else}.create();
${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
} else if ( ${each_block_else} ) {
@ -101,7 +100,7 @@ export default function visitEachBlock(
${each_block_else} = null;
}
} else if ( !${each_block_else} ) {
${each_block_else} = ${node.else._block.name}( ${params}, ${block.component} );
${each_block_else} = ${node.else._block.name}( ${params}, #component );
${each_block_else}.create();
${each_block_else}.${mountOrIntro}( ${parentNode}, ${anchor} );
}
@ -138,7 +137,6 @@ function keyed(
each_block,
create_each_block,
each_block_value,
i,
params,
anchor,
mountOrIntro,
@ -162,27 +160,27 @@ function keyed(
node._block.first = node._block.getUniqueName('first');
node._block.addElement(
node._block.first,
`${generator.helper('createComment')}()`,
`${generator.helper('createComment')}()`,
`@createComment()`,
`@createComment()`,
null,
true
);
}
block.builders.init.addBlock(deindent`
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
for ( var #i = 0; #i < ${each_block_value}.length; #i += 1 ) {
var ${key} = ${each_block_value}[#i].${node.key};
var ${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component, ${key} );
if ( ${last} ) ${last}.next = ${iteration};
${iteration}.last = ${last};
${last} = ${iteration};
if ( ${i} === 0 ) ${head} = ${iteration};
if ( #i === 0 ) ${head} = ${iteration};
}
`);
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
@ -231,9 +229,9 @@ function keyed(
${expected} = ${expected}.next;
}
for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
if ( discard_pile[${i}].discard ) {
${fn}( discard_pile[${i}] );
for ( #i = 0; #i < discard_pile.length; #i += 1 ) {
if ( discard_pile[#i].discard ) {
${fn}( discard_pile[#i] );
}
}
`;
@ -253,8 +251,8 @@ function keyed(
${expected} = ${expected}.next;
}
for ( ${i} = 0; ${i} < discard_pile.length; ${i} += 1 ) {
var ${iteration} = discard_pile[${i}];
for ( #i = 0; #i < discard_pile.length; #i += 1 ) {
var ${iteration} = discard_pile[#i];
if ( ${iteration}.discard ) {
${fn}( ${iteration} );
}
@ -270,12 +268,12 @@ function keyed(
var discard_pile = [];
for ( ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
var ${key} = ${each_block_value}[${i}].${node.key};
for ( #i = 0; #i < ${each_block_value}.length; #i += 1 ) {
var ${key} = ${each_block_value}[#i].${node.key};
var ${iteration} = ${lookup}[${key}];
${dynamic &&
`if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );`}
`if ( ${iteration} ) ${iteration}.update( changed, ${params}, ${each_block_value}, ${each_block_value}[#i], #i );`}
if ( ${expected} ) {
if ( ${key} === ${expected}.key ) {
@ -296,7 +294,7 @@ function keyed(
if (!${expected}) ${iteration}.mount( ${parentNode}, ${anchor} );
} else {
// key is being inserted
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component, ${key} );
${iteration}.create();
${iteration}.${mountOrIntro}( ${parentNode}, ${expected}.first );
@ -311,7 +309,7 @@ function keyed(
${iteration}.next = null;
${iteration}.mount( ${parentNode}, ${anchor} );
} else {
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component, ${key} );
${iteration}.create();
${iteration}.${mountOrIntro}( ${parentNode}, ${anchor} );
}
@ -360,7 +358,6 @@ function unkeyed(
create_each_block,
each_block_value,
iterations,
i,
params,
anchor,
mountOrIntro,
@ -369,29 +366,29 @@ function unkeyed(
block.builders.init.addBlock(deindent`
var ${iterations} = [];
for ( var ${i} = 0; ${i} < ${each_block_value}.length; ${i} += 1 ) {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
for ( var #i = 0; #i < ${each_block_value}.length; #i += 1 ) {
${iterations}[#i] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component );
}
`);
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].create();
for ( var #i = 0; #i < ${iterations}.length; #i += 1 ) {
${iterations}[#i].create();
}
`);
block.builders.claim.addBlock(deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].claim( ${state.parentNodes} );
for ( var #i = 0; #i < ${iterations}.length; #i += 1 ) {
${iterations}[#i].claim( ${state.parentNodes} );
}
`);
block.builders.mount.addBlock(deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].${mountOrIntro}( ${targetNode}, ${anchorNode} );
for ( var #i = 0; #i < ${iterations}.length; #i += 1 ) {
${iterations}[#i].${mountOrIntro}( ${targetNode}, ${anchorNode} );
}
`);
@ -412,26 +409,26 @@ function unkeyed(
const forLoopBody = node._block.hasUpdateMethod
? node._block.hasIntroMethod
? deindent`
if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
if ( ${iterations}[#i] ) {
${iterations}[#i].update( changed, ${params}, ${each_block_value}, ${each_block_value}[#i], #i );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].create();
${iterations}[#i] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component );
${iterations}[#i].create();
}
${iterations}[${i}].intro( ${parentNode}, ${anchor} );
${iterations}[#i].intro( ${parentNode}, ${anchor} );
`
: deindent`
if ( ${iterations}[${i}] ) {
${iterations}[${i}].update( changed, ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i} );
if ( ${iterations}[#i] ) {
${iterations}[#i].update( changed, ${params}, ${each_block_value}, ${each_block_value}[#i], #i );
} else {
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].create();
${iterations}[${i}].mount( ${parentNode}, ${anchor} );
${iterations}[#i] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component );
${iterations}[#i].create();
${iterations}[#i].mount( ${parentNode}, ${anchor} );
}
`
: deindent`
${iterations}[${i}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component} );
${iterations}[${i}].${mountOrIntro}( ${parentNode}, ${anchor} );
${iterations}[#i] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[#i], #i, #component );
${iterations}[#i].${mountOrIntro}( ${parentNode}, ${anchor} );
`;
const start = node._block.hasUpdateMethod ? '0' : `${iterations}.length`;
@ -449,12 +446,12 @@ function unkeyed(
}
}
for ( ; ${i} < ${iterations}.length; ${i} += 1 ) ${outro}( ${i} );
for ( ; #i < ${iterations}.length; #i += 1 ) ${outro}( #i );
`
: deindent`
for ( ; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].unmount();
${iterations}[${i}].destroy();
for ( ; #i < ${iterations}.length; #i += 1 ) {
${iterations}[#i].unmount();
${iterations}[#i].destroy();
}
${iterations}.length = ${each_block_value}.length;
`;
@ -463,7 +460,7 @@ function unkeyed(
var ${each_block_value} = ${snippet};
if ( ${condition} ) {
for ( var ${i} = ${start}; ${i} < ${each_block_value}.length; ${i} += 1 ) {
for ( var #i = ${start}; #i < ${each_block_value}.length; #i += 1 ) {
${forLoopBody}
}
@ -473,12 +470,10 @@ function unkeyed(
}
block.builders.unmount.addBlock(deindent`
for ( var ${i} = 0; ${i} < ${iterations}.length; ${i} += 1 ) {
${iterations}[${i}].unmount();
for ( var #i = 0; #i < ${iterations}.length; #i += 1 ) {
${iterations}[#i].unmount();
}
`);
block.builders.destroy.addBlock(
`${generator.helper('destroyEach')}( ${iterations}, false, 0 );`
);
block.builders.destroy.addBlock(`@destroyEach( ${iterations}, false, 0 );`);
}

@ -1,5 +1,6 @@
import attributeLookup from './lookup';
import deindent from '../../../../utils/deindent';
import stringify from '../../../../utils/stringify';
import getStaticAttributeValue from './getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
@ -36,8 +37,8 @@ export default function visitAttribute(
// namespaced attributes but I'm not sure that's applicable in
// HTML5?
const method = name.slice(0, 6) === 'xlink:'
? 'setXlinkAttribute'
: 'setAttribute';
? '@setXlinkAttribute'
: '@setAttribute';
const isDynamic =
(attribute.value !== true && attribute.value.length > 1) ||
@ -57,7 +58,7 @@ export default function visitAttribute(
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return JSON.stringify(chunk.data);
return stringify(chunk.data);
} else {
const { snippet } = block.contextualise(chunk.expression);
return `( ${snippet} )`;
@ -80,7 +81,9 @@ export default function visitAttribute(
// annoying special case
const isMultipleSelect =
node.name === 'select' &&
node.attributes.find((attr: Node) => attr.name.toLowerCase() === 'multiple'); // TODO use getStaticAttributeValue
node.attributes.find(
(attr: Node) => attr.name.toLowerCase() === 'multiple'
); // TODO use getStaticAttributeValue
const i = block.getUniqueName('i');
const option = block.getUniqueName('option');
@ -112,13 +115,9 @@ export default function visitAttribute(
updater = `${state.parentNode}.${propertyName} = ${last};`;
} else {
block.builders.hydrate.addLine(
`${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} = ${value} );`
`${method}( ${state.parentNode}, '${name}', ${last} = ${value} );`
);
updater = `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${last} );`;
updater = `${method}( ${state.parentNode}, '${name}', ${last} );`;
}
block.builders.update.addBlock(deindent`
@ -131,13 +130,11 @@ export default function visitAttribute(
? 'true'
: attribute.value.length === 0
? `''`
: JSON.stringify(attribute.value[0].data);
: stringify(attribute.value[0].data);
const statement = propertyName
? `${state.parentNode}.${propertyName} = ${value};`
: `${generator.helper(
method
)}( ${state.parentNode}, '${name}', ${value} );`;
: `${method}( ${state.parentNode}, '${name}', ${value} );`;
block.builders.hydrate.addLine(statement);

@ -16,7 +16,9 @@ export default function visitBinding(
attribute: Node
) {
const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
contexts.forEach(context => {
if (!~state.allUsedContexts.indexOf(context))
@ -57,7 +59,7 @@ export default function visitBinding(
value,
});
let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const lock = block.alias(`${state.parentNode}_updating`);
const lock = `#${state.parentNode}_updating`;
let updateCondition = `!${lock}`;
block.addVariable(lock, 'false');
@ -69,7 +71,6 @@ export default function visitBinding(
}
const value = block.getUniqueName('value');
const i = block.alias('i');
const option = block.getUniqueName('option');
const ifStatement = isMultipleSelect
@ -83,8 +84,8 @@ export default function visitBinding(
updateElement = deindent`
var ${value} = ${snippet};
for ( var ${i} = 0; ${i} < ${state.parentNode}.options.length; ${i} += 1 ) {
var ${option} = ${state.parentNode}.options[${i}];
for ( var #i = 0; #i < ${state.parentNode}.options.length; #i += 1 ) {
var ${option} = ${state.parentNode}.options[#i];
${ifStatement}
}
@ -92,7 +93,7 @@ export default function visitBinding(
generator.hasComplexBindings = true;
block.builders.hydrate.addBlock(
`if ( !('${name}' in state) ) ${block.component}._bindings.push( ${handler} );`
`if ( !('${name}' in state) ) #component._bindings.push( ${handler} );`
);
} else if (attribute.name === 'group') {
// <input type='checkbox|radio' bind:group='selected'> special case
@ -108,19 +109,17 @@ export default function visitBinding(
: `${state.parentNode}.__value === ${snippet}`;
block.builders.hydrate.addLine(
`${block.component}._bindingGroups[${bindingGroup}].push( ${state.parentNode} );`
`#component._bindingGroups[${bindingGroup}].push( ${state.parentNode} );`
);
block.builders.destroy.addBlock(
`${block.component}._bindingGroups[${bindingGroup}].splice( ${block.component}._bindingGroups[${bindingGroup}].indexOf( ${state.parentNode} ), 1 );`
`#component._bindingGroups[${bindingGroup}].splice( #component._bindingGroups[${bindingGroup}].indexOf( ${state.parentNode} ), 1 );`
);
updateElement = `${state.parentNode}.checked = ${condition};`;
} else if (node.name === 'audio' || node.name === 'video') {
generator.hasComplexBindings = true;
block.builders.hydrate.addBlock(
`${block.component}._bindings.push( ${handler} );`
);
block.builders.hydrate.addBlock(`#component._bindings.push( ${handler} );`);
if (attribute.name === 'currentTime') {
const frame = block.getUniqueName(`${state.parentNode}_animationframe`);
@ -152,11 +151,9 @@ export default function visitBinding(
}
`);
block.builders.hydrate.addBlock(deindent`
${generator.helper(
'addListener'
)}( ${state.parentNode}, '${eventName}', ${handler} );
`);
block.builders.hydrate.addBlock(
`@addListener( ${state.parentNode}, '${eventName}', ${handler} );`
);
if (node.name !== 'audio' && node.name !== 'video')
node.initialUpdate = updateElement;
@ -170,22 +167,16 @@ export default function visitBinding(
`);
}
block.builders.destroy.addLine(deindent`
${generator.helper(
'removeListener'
)}( ${state.parentNode}, '${eventName}', ${handler} );
`);
block.builders.destroy.addLine(
`@removeListener( ${state.parentNode}, '${eventName}', ${handler} );`
);
if (attribute.name === 'paused') {
block.builders.create.addLine(
`${generator.helper(
'addListener'
)}( ${state.parentNode}, 'play', ${handler} );`
`@addListener( ${state.parentNode}, 'play', ${handler} );`
);
block.builders.destroy.addLine(
`${generator.helper(
'removeListener'
)}( ${state.parentNode}, 'play', ${handler} );`
`@removeListener( ${state.parentNode}, 'play', ${handler} );`
);
}
}
@ -231,9 +222,7 @@ function getBindingValue(
// <input type='checkbox' bind:group='foo'>
if (attribute.name === 'group') {
if (type === 'checkbox') {
return `${generator.helper(
'getBindingGroupValue'
)}( ${block.component}._bindingGroups[${bindingGroup}] )`;
return `@getBindingGroupValue( #component._bindingGroups[${bindingGroup}] )`;
}
return `${state.parentNode}.__value`;
@ -241,9 +230,7 @@ function getBindingValue(
// <input type='range|number' bind:value>
if (type === 'range' || type === 'number') {
return `${generator.helper(
'toNumber'
)}( ${state.parentNode}.${attribute.name} )`;
return `@toNumber( ${state.parentNode}.${attribute.name} )`;
}
// everything else

@ -51,27 +51,38 @@ export default function visitElement(
const isToplevel = !state.parentNode;
block.addVariable(name);
block.builders.create.addLine(`${name} = ${getRenderStatement(generator, childState.namespace, node.name)};`);
block.builders.create.addLine(
`${name} = ${getRenderStatement(
generator,
childState.namespace,
node.name
)};`
);
if (generator.hydratable) {
block.builders.claim.addBlock(deindent`
${name} = ${getClaimStatement(generator, childState.namespace, state.parentNodes, node)};
var ${childState.parentNodes} = ${generator.helper('children')}( ${name} );
${name} = ${getClaimStatement(
generator,
childState.namespace,
state.parentNodes,
node
)};
var ${childState.parentNodes} = @children( ${name} );
`);
}
if (state.parentNode) {
block.builders.mount.addLine(`${block.generator.helper('appendNode')}( ${name}, ${state.parentNode} );`);
block.builders.mount.addLine(
`@appendNode( ${name}, ${state.parentNode} );`
);
} else {
block.builders.mount.addLine(`${block.generator.helper('insertNode')}( ${name}, ${block.target}, anchor );`);
block.builders.mount.addLine(`@insertNode( ${name}, #target, anchor );`);
}
// add CSS encapsulation attribute
if (generator.cssId && (!generator.cascade || state.isTopLevel)) {
block.builders.hydrate.addLine(
`${generator.helper(
'setAttribute'
)}( ${name}, '${generator.cssId}', '' );`
`@setAttribute( ${name}, '${generator.cssId}', '' );`
);
}
@ -99,7 +110,7 @@ export default function visitElement(
const updates: string[] = [];
if (childState.usesComponent) {
initialProps.push(`component: ${block.component}`);
initialProps.push(`component: #component`);
}
childState.allUsedContexts.forEach((contextName: string) => {
@ -133,9 +144,7 @@ export default function visitElement(
if (isToplevel) {
// TODO we eventually need to consider what happens to elements
// that belong to the same outgroup as an outroing element...
block.builders.unmount.addLine(
`${generator.helper('detachNode')}( ${name} );`
);
block.builders.unmount.addLine(`@detachNode( ${name} );`);
}
if (node.name !== 'select') {
@ -188,7 +197,7 @@ export default function visitElement(
}
block.builders.claim.addLine(
`${childState.parentNodes}.forEach( ${generator.helper('detachNode')} );`
`${childState.parentNodes}.forEach( @detachNode );`
);
}
@ -198,14 +207,14 @@ function getRenderStatement(
name: string
) {
if (namespace === 'http://www.w3.org/2000/svg') {
return `${generator.helper('createSvgElement')}( '${name}' )`;
return `@createSvgElement( '${name}' )`;
}
if (namespace) {
return `document.createElementNS( '${namespace}', '${name}' )`;
}
return `${generator.helper('createElement')}( '${name}' )`;
return `@createElement( '${name}' )`;
}
function getClaimStatement(
@ -221,10 +230,12 @@ function getClaimStatement(
const name = namespace ? node.name : node.name.toUpperCase();
return `${generator.helper('claimElement')}( ${nodes}, '${name}', ${attributes ? `{ ${attributes} }` : `{}`}, ${namespace === namespaces.svg ? true : false} )`;
return `@claimElement( ${nodes}, '${name}', ${attributes
? `{ ${attributes} }`
: `{}`}, ${namespace === namespaces.svg ? true : false} )`;
}
function quoteProp(name: string) {
if (/[^a-zA-Z_$0-9]/.test(name)) return `'${name}'`;
return name;
}
}

@ -24,7 +24,7 @@ export default function visitEventHandler(
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
`${block.alias('component')}.`
);
if (shouldHoist) state.usesComponent = true; // this feels a bit hacky but it works!
}
@ -45,7 +45,7 @@ export default function visitEventHandler(
const declarations = usedContexts.map(name => {
if (name === 'state') {
if (shouldHoist) state.usesComponent = true;
return `var state = ${block.component}.get();`;
return `var state = #component.get();`;
}
const listName = block.listNames.get(name);
@ -63,7 +63,8 @@ export default function visitEventHandler(
// create the handler body
const handlerBody = deindent`
${state.usesComponent && `var ${block.component} = this._svelte.component;`}
${state.usesComponent &&
`var ${block.alias('component')} = this._svelte.component;`}
${declarations}
[${attribute.expression.start}-${attribute.expression.end}];
`;
@ -72,9 +73,7 @@ export default function visitEventHandler(
block.addVariable(handlerName);
block.builders.hydrate.addBlock(deindent`
${handlerName} = ${generator.alias(
'template'
)}.events.${name}.call( ${block.component}, ${state.parentNode}, function ( event ) {
${handlerName} = @template.events.${name}.call( #component, ${state.parentNode}, function ( event ) {
${handlerBody}
});
`);
@ -99,16 +98,12 @@ export default function visitEventHandler(
block.builders.init.addBlock(handler);
}
block.builders.hydrate.addLine(deindent`
${generator.helper(
'addListener'
)}( ${state.parentNode}, '${name}', ${handlerName} );
`);
block.builders.hydrate.addLine(
`@addListener( ${state.parentNode}, '${name}', ${handlerName} );`
);
block.builders.destroy.addLine(deindent`
${generator.helper(
'removeListener'
)}( ${state.parentNode}, '${name}', ${handlerName} );
`);
block.builders.destroy.addLine(
`@removeListener( ${state.parentNode}, '${name}', ${handlerName} );`
);
}
}

@ -14,11 +14,11 @@ export default function visitRef(
const name = attribute.name;
block.builders.mount.addLine(
`${block.component}.refs.${name} = ${state.parentNode};`
`#component.refs.${name} = ${state.parentNode};`
);
block.builders.unmount.addLine(deindent`
if ( ${block.component}.refs.${name} === ${state.parentNode} ) ${block.component}.refs.${name} = null;
if ( #component.refs.${name} === ${state.parentNode} ) #component.refs.${name} = null;
`);
generator.usesRefs = true; // so this component.refs object is created

@ -12,8 +12,6 @@ export default function addTransitions(
intro,
outro
) {
const wrapTransition = generator.helper('wrapTransition');
if (intro === outro) {
const name = block.getUniqueName(`${state.name}_transition`);
const snippet = intro.expression
@ -22,21 +20,21 @@ export default function addTransitions(
block.addVariable(name);
const fn = `${generator.alias('template')}.transitions.${intro.name}`;
const fn = `@template.transitions.${intro.name}`;
block.builders.intro.addBlock(deindent`
${block.component}._renderHooks.push( function () {
if ( !${name} ) ${name} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null );
#component._renderHooks.push( function () {
if ( !${name} ) ${name} = @wrapTransition( ${state.name}, ${fn}, ${snippet}, true, null );
${name}.run( true, function () {
${block.component}.fire( 'intro.end', { node: ${state.name} });
#component.fire( 'intro.end', { node: ${state.name} });
});
});
`);
block.builders.outro.addBlock(deindent`
${name}.run( false, function () {
${block.component}.fire( 'outro.end', { node: ${state.name} });
if ( --${block.alias('outros')} === 0 ) ${block.alias('outrocallback')}();
#component.fire( 'outro.end', { node: ${state.name} });
if ( --#outros === 0 ) #outrocallback();
${name} = null;
});
`);
@ -50,7 +48,7 @@ export default function addTransitions(
? block.contextualise(intro.expression).snippet
: '{}';
const fn = `${generator.alias('template')}.transitions.${intro.name}`; // TODO add built-in transitions?
const fn = `@template.transitions.${intro.name}`; // TODO add built-in transitions?
if (outro) {
block.builders.intro.addBlock(deindent`
@ -60,10 +58,10 @@ export default function addTransitions(
}
block.builders.intro.addBlock(deindent`
${block.component}._renderHooks.push( function () {
${introName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, true, null );
#component._renderHooks.push( function () {
${introName} = @wrapTransition( ${state.name}, ${fn}, ${snippet}, true, null );
${introName}.run( true, function () {
${block.component}.fire( 'intro.end', { node: ${state.name} });
#component.fire( 'intro.end', { node: ${state.name} });
});
});
`);
@ -75,15 +73,15 @@ export default function addTransitions(
? block.contextualise(outro.expression).snippet
: '{}';
const fn = `${generator.alias('template')}.transitions.${outro.name}`;
const fn = `@template.transitions.${outro.name}`;
// TODO hide elements that have outro'd (unless they belong to a still-outroing
// group) prior to their removal from the DOM
block.builders.outro.addBlock(deindent`
${outroName} = ${wrapTransition}( ${state.name}, ${fn}, ${snippet}, false, null );
${outroName} = @wrapTransition( ${state.name}, ${fn}, ${snippet}, false, null );
${outroName}.run( false, function () {
${block.component}.fire( 'outro.end', { node: ${state.name} });
if ( --${block.alias('outros')} === 0 ) ${block.alias('outrocallback')}();
#component.fire( 'outro.end', { node: ${state.name} });
if ( --#outros === 0 ) #outrocallback();
});
`);
}

@ -47,13 +47,13 @@ export default function visitWindow(
// allow event.stopPropagation(), this.select() etc
generator.code.prependRight(
attribute.expression.start,
`${block.component}.`
`${block.alias('component')}.`
);
}
const handlerName = block.getUniqueName(`onwindow${attribute.name}`);
const handlerBody = deindent`
${usesState && `var state = ${block.component}.get();`}
${usesState && `var state = #component.get();`}
[${attribute.expression.start}-${attribute.expression.end}];
`;
@ -113,7 +113,7 @@ export default function visitWindow(
${event === 'scroll' && `${lock} = true;`}
${generator.options.dev && `component._updatingReadonlyProperty = true;`}
${block.component}.set({
#component.set({
${props}
});
@ -141,10 +141,10 @@ export default function visitWindow(
function ${observerCallback} () {
if ( ${lock} ) return;
var x = ${bindings.scrollX
? `${block.component}.get( '${bindings.scrollX}' )`
? `#component.get( '${bindings.scrollX}' )`
: `window.scrollX`};
var y = ${bindings.scrollY
? `${block.component}.get( '${bindings.scrollY}' )`
? `#component.get( '${bindings.scrollY}' )`
: `window.scrollY`};
window.scrollTo( x, y );
};
@ -152,18 +152,18 @@ export default function visitWindow(
if (bindings.scrollX)
block.builders.init.addLine(
`${block.component}.observe( '${bindings.scrollX}', ${observerCallback} );`
`#component.observe( '${bindings.scrollX}', ${observerCallback} );`
);
if (bindings.scrollY)
block.builders.init.addLine(
`${block.component}.observe( '${bindings.scrollY}', ${observerCallback} );`
`#component.observe( '${bindings.scrollY}', ${observerCallback} );`
);
} else if (bindings.scrollX || bindings.scrollY) {
const isX = !!bindings.scrollX;
block.builders.init.addBlock(deindent`
${block.component}.observe( '${bindings.scrollX ||
bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) {
#component.observe( '${bindings.scrollX ||
bindings.scrollY}', function ( ${isX ? 'x' : 'y'} ) {
if ( ${lock} ) return;
window.scrollTo( ${isX ? 'x, window.scrollY' : 'window.scrollX, y'} );
});
@ -175,7 +175,7 @@ export default function visitWindow(
const handlerName = block.getUniqueName(`onlinestatuschanged`);
block.builders.init.addBlock(deindent`
function ${handlerName} ( event ) {
${block.component}.set({ ${bindings.online}: navigator.onLine });
#component.set({ ${bindings.online}: navigator.onLine });
};
window.addEventListener( 'online', ${handlerName} );
window.addEventListener( 'offline', ${handlerName} );

@ -105,9 +105,7 @@ export default function visitIfBlock(
simple(generator, block, state, node, branches[0], dynamic, vars);
}
block.builders.create.addLine(
`${if_name}${name}.create();`
);
block.builders.create.addLine(`${if_name}${name}.create();`);
block.builders.claim.addLine(
`${if_name}${name}.claim( ${state.parentNodes} );`
@ -116,8 +114,8 @@ export default function visitIfBlock(
if (node.needsAnchor) {
block.addElement(
anchor,
`${generator.helper('createComment')}()`,
`${generator.helper('createComment')}()`,
`@createComment()`,
`@createComment()`,
state.parentNode,
true
);
@ -136,12 +134,12 @@ function simple(
{ name, anchor, params, if_name }
) {
block.builders.init.addBlock(deindent`
var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} );
var ${name} = (${branch.condition}) && ${branch.block}( ${params}, #component );
`);
const isTopLevel = !state.parentNode;
const mountOrIntro = branch.hasIntroMethod ? 'intro' : 'mount';
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
@ -156,7 +154,7 @@ function simple(
if ( ${name} ) {
${name}.update( changed, ${params} );
} else {
${name} = ${branch.block}( ${params}, ${block.component} );
${name} = ${branch.block}( ${params}, #component );
if ( ${name} ) ${name}.create();
}
@ -166,7 +164,7 @@ function simple(
if ( ${name} ) {
${name}.update( changed, ${params} );
} else {
${name} = ${branch.block}( ${params}, ${block.component} );
${name} = ${branch.block}( ${params}, #component );
${name}.create();
${name}.mount( ${parentNode}, ${anchor} );
}
@ -174,14 +172,14 @@ function simple(
: branch.hasIntroMethod
? deindent`
if ( !${name} ) {
${name} = ${branch.block}( ${params}, ${block.component} );
${name} = ${branch.block}( ${params}, #component );
${name}.create();
}
${name}.intro( ${parentNode}, ${anchor} );
`
: deindent`
if ( !${name} ) {
${name} = ${branch.block}( ${params}, ${block.component} );
${name} = ${branch.block}( ${params}, #component );
${name}.create();
${name}.mount( ${parentNode}, ${anchor} );
}
@ -239,13 +237,13 @@ function compound(
}
var ${current_block} = ${get_block}( ${params} );
var ${name} = ${current_block_and}${current_block}( ${params}, ${block.component} );
var ${name} = ${current_block_and}${current_block}( ${params}, #component );
`);
const isTopLevel = !state.parentNode;
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
`${if_name}${name}.${mountOrIntro}( ${targetNode}, ${anchorNode} );`
@ -254,17 +252,17 @@ function compound(
const parentNode = state.parentNode || `${anchor}.parentNode`;
const changeBlock = deindent`
${hasElse ?
deindent`
${hasElse
? deindent`
${name}.unmount();
${name}.destroy();
` :
deindent`
`
: deindent`
if ( ${name} ) {
${name}.unmount();
${name}.destroy();
}`}
${name} = ${current_block_and}${current_block}( ${params}, ${block.component} );
${name} = ${current_block_and}${current_block}( ${params}, #component );
${if_name}${name}.create();
${if_name}${name}.${mountOrIntro}( ${parentNode}, ${anchor} );
`;
@ -285,13 +283,9 @@ function compound(
`);
}
block.builders.unmount.addLine(
`${if_name}${name}.unmount();`
);
block.builders.unmount.addLine(`${if_name}${name}.unmount();`);
block.builders.destroy.addLine(
`${if_name}${name}.destroy();`
);
block.builders.destroy.addLine(`${if_name}${name}.destroy();`);
}
// if any of the siblings have outros, we need to keep references to the blocks
@ -339,19 +333,19 @@ function compoundWithOutros(
if (hasElse) {
block.builders.init.addBlock(deindent`
${current_block_index} = ${get_block}( ${params} );
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, #component );
`);
} else {
block.builders.init.addBlock(deindent`
if ( ~( ${current_block_index} = ${get_block}( ${params} ) ) ) {
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, #component );
}
`);
}
const isTopLevel = !state.parentNode;
const mountOrIntro = branches[0].hasIntroMethod ? 'intro' : 'mount';
const targetNode = state.parentNode || block.target;
const targetNode = state.parentNode || '#target';
const anchorNode = state.parentNode ? 'null' : 'anchor';
block.builders.mount.addLine(
@ -371,7 +365,7 @@ function compoundWithOutros(
const createNewBlock = deindent`
${name} = ${if_blocks}[ ${current_block_index} ];
if ( !${name} ) {
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, ${block.component} );
${name} = ${if_blocks}[ ${current_block_index} ] = ${if_block_creators}[ ${current_block_index} ]( ${params}, #component );
${name}.create();
}
${name}.${mountOrIntro}( ${parentNode}, ${anchor} );

@ -18,8 +18,10 @@ export default function visitMustacheTag(
block.addVariable(value);
block.addElement(
name,
`${generator.helper('createText')}( ${value} = ${snippet} )`,
generator.hydratable ? `${generator.helper('claimText')}( ${state.parentNodes}, ${value} = ${snippet} )` : '',
`@createText( ${value} = ${snippet} )`,
generator.hydratable
? `@claimText( ${state.parentNodes}, ${value} = ${snippet} )`
: '',
state.parentNode,
true
);

@ -23,15 +23,15 @@ export default function visitRawMustacheTag(
// exists for `Element`s.
block.addElement(
before,
`${generator.helper('createElement')}( 'noscript' )`,
`${generator.helper('createElement')}( 'noscript' )`,
`@createElement( 'noscript' )`,
`@createElement( 'noscript' )`,
state.parentNode,
true
);
block.addElement(
after,
`${generator.helper('createElement')}( 'noscript' )`,
`${generator.helper('createElement')}( 'noscript' )`,
`@createElement( 'noscript' )`,
`@createElement( 'noscript' )`,
state.parentNode,
true
);
@ -39,9 +39,7 @@ export default function visitRawMustacheTag(
const isToplevel = !state.parentNode;
const mountStatement = `${before}.insertAdjacentHTML( 'afterend', ${value} = ${snippet} );`;
const detachStatement = `${generator.helper(
'detachBetween'
)}( ${before}, ${after} );`;
const detachStatement = `@detachBetween( ${before}, ${after} );`;
block.builders.mount.addLine(mountStatement);

@ -2,6 +2,7 @@ import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
import stringify from '../../../utils/stringify';
export default function visitText(
generator: DomGenerator,
@ -12,8 +13,10 @@ export default function visitText(
if (!node._state.shouldCreate) return;
block.addElement(
node._state.name,
`${generator.helper('createText')}( ${JSON.stringify(node.data)} )`,
generator.hydratable ? `${generator.helper('claimText')}( ${state.parentNodes}, ${JSON.stringify(node.data)} )` : '',
`@createText( ${stringify(node.data)} )`,
generator.hydratable
? `@claimText( ${state.parentNodes}, ${stringify(node.data)} )`
: '',
state.parentNode,
node.usedAsAnchor
);

@ -7,13 +7,13 @@ export default function visitYieldTag(
block: Block,
state: State
) {
const parentNode = state.parentNode || block.target;
const parentNode = state.parentNode || '#target';
block.builders.mount.addLine(
`if ( ${block.component}._yield ) ${block.component}._yield.mount( ${parentNode}, null );`
`if ( #component._yield ) #component._yield.mount( ${parentNode}, null );`
);
block.builders.unmount.addLine(
`if ( ${block.component}._yield ) ${block.component}._yield.unmount();`
`if ( #component._yield ) #component._yield.unmount();`
);
}

@ -22,27 +22,30 @@ export default function getSetter({
return deindent`
var list = this.${context}.${block.listNames.get(name)};
var index = this.${context}.${block.indexNames.get(name)};
${computed && `var state = ${block.component}.get();`}
${computed && `var state = #component.get();`}
list[index]${tail} = ${value};
${computed ?
`${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });` :
`${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: ${block.component}.get( '${prop}' )`).join(', ')} });`
}
${computed
? `#component._set({ ${dependencies
.map((prop: string) => `${prop}: state.${prop}`)
.join(', ')} });`
: `#component._set({ ${dependencies
.map((prop: string) => `${prop}: #component.get( '${prop}' )`)
.join(', ')} });`}
`;
}
if (attribute.value.type === 'MemberExpression') {
const alias = block.alias(name);
return deindent`
var state = ${block.component}.get();
var state = #component.get();
${snippet} = ${value};
${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });
#component._set({ ${dependencies
.map((prop: string) => `${prop}: state.${prop}`)
.join(', ')} });
`;
}
return `${block.component}._set({ ${name}: ${value} });`;
return `#component._set({ ${name}: ${value} });`;
}
function isComputed(node: Node) {

@ -86,23 +86,19 @@ export default function ssr(
${name}.filename = ${JSON.stringify(options.filename)};
${name}.data = function () {
return ${templateProperties.data
? `${generator.alias('template')}.data()`
: `{}`};
return ${templateProperties.data ? `@template.data()` : `{}`};
};
${name}.render = function ( state, options ) {
${templateProperties.data
? `state = Object.assign( ${generator.alias(
'template'
)}.data(), state || {} );`
? `state = Object.assign( @template.data(), state || {} );`
: `state = state || {};`}
${computations.map(
({ key, deps }) =>
`state.${key} = ${generator.alias(
'template'
)}.computed.${key}( ${deps.map(dep => `state.${dep}`).join(', ')} );`
`state.${key} = @template.computed.${key}( ${deps
.map(dep => `state.${dep}`)
.join(', ')} );`
)}
${generator.bindings.length &&
@ -149,7 +145,7 @@ export default function ssr(
const { name } = prop.key;
const expression =
generator.importedComponents.get(name) ||
`${generator.alias('template')}.components.${name}`;
`@template.components.${name}`;
return `addComponent( ${expression} );`;
})}
`}
@ -172,7 +168,7 @@ export default function ssr(
function __escape ( html ) {
return String( html ).replace( /["'&<>]/g, match => escaped[ match ] );
}
`;
`.replace(/(\\)?@(\w*)/g, (match: string, escaped: string, name: string) => escaped ? match.slice(1) : generator.alias(name));
return generator.generate(result, options, { name, format });
}

@ -11,7 +11,7 @@ export default function visitComponent(
block: Block,
node: Node
) {
function stringify(chunk: Node) {
function stringifyAttribute(chunk: Node) {
if (chunk.type === 'Text') return chunk.data;
if (chunk.type === 'MustacheTag') {
const { snippet } = block.contextualise(chunk.expression);
@ -47,7 +47,7 @@ export default function visitComponent(
value = snippet;
}
} else {
value = '`' + attribute.value.map(stringify).join('') + '`';
value = '`' + attribute.value.map(stringifyAttribute).join('') + '`';
}
return `${attribute.name}: ${value}`;
@ -59,7 +59,9 @@ export default function visitComponent(
? getTailSnippet(binding.value)
: '';
const keypath = block.contexts.has(name) ? `${name}${tail}` : `state.${name}${tail}`;
const keypath = block.contexts.has(name)
? `${name}${tail}`
: `state.${name}${tail}`;
return `${binding.name}: ${keypath}`;
})
)
@ -68,7 +70,7 @@ export default function visitComponent(
const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`${generator.alias('template')}.components.${node.name}`;
`@template.components.${node.name}`;
bindings.forEach(binding => {
block.addBinding(binding, expression);

@ -10,7 +10,9 @@ export default function visitEachBlock(
) {
const { dependencies, snippet } = block.contextualise(node.expression);
const open = `\${ ${node.else ? `${snippet}.length ? ` : ''}${snippet}.map( ${node.index
const open = `\${ ${node.else
? `${snippet}.length ? `
: ''}${snippet}.map( ${node.index
? `( ${node.context}, ${node.index} )`
: node.context} => \``;
generator.append(open);

@ -7,5 +7,5 @@ export default function visitText(
block: Block,
node: Node
) {
generator.append(node.data.replace(/(\${|`|\\)/g, '\\$1'));
generator.append(node.data.replace(/(\${|`|\\)/g, '\\$1').replace(/([^\\])?([@#])/g, '$1\\$2'));
}

@ -85,9 +85,7 @@ export default function processCss(
}
shouldTransform = false;
}
else if (child.type === 'PseudoElementSelector') {
} else if (child.type === 'PseudoElementSelector') {
code.prependRight(c, attr);
shouldTransform = false;
}

@ -1,4 +1,5 @@
import { assign, noop } from './utils.js';
import { createElement } from './dom.js';
export function linear(t) {
return t;
@ -37,8 +38,9 @@ export function wrapTransition(node, fn, params, intro, outgroup) {
var ease = obj.easing || linear;
var cssText;
// TODO share <style> tag between all transitions?
if (obj.css && !transitionManager.stylesheet) {
var style = document.createElement('style');
var style = createElement('style');
document.head.appendChild(style);
transitionManager.stylesheet = style.sheet;
}

@ -15,4 +15,4 @@ export default function clone(node: Node) {
}
return cloned;
}
}

@ -1,6 +1,9 @@
const start = /\n(\t+)/;
export default function deindent(strings: TemplateStringsArray, ...values: any[]) {
export default function deindent(
strings: TemplateStringsArray,
...values: any[]
) {
const indentation = start.exec(strings[0])[1];
const pattern = new RegExp(`^${indentation}`, 'gm');

@ -3,4 +3,4 @@ import { Node } from '../interfaces';
export default function getObject(node: Node) {
while (node.type === 'MemberExpression') node = node.object;
return node;
}
}

@ -6,4 +6,4 @@ export default function getTailSnippet(node: Node) {
const start = node.end;
return `[✂${start}-${end}✂]`;
}
}

@ -0,0 +1,3 @@
export default function stringify(data: string) {
return JSON.stringify(data.replace(/([^\\])?([@#])/g, '$1\\$2'));
}

@ -146,7 +146,10 @@ function checkTypeAttribute(validator: Validator, node: Node) {
}
if (attribute.value.length > 1 || attribute.value[0].type !== 'Text') {
validator.error(`'type' attribute cannot be dynamic if input uses two-way binding`, attribute.start);
validator.error(
`'type' attribute cannot be dynamic if input uses two-way binding`,
attribute.start
);
}
return attribute.value[0].data;

@ -145,7 +145,7 @@ var template = (function () {
function add_css () {
var style = createElement( 'style' );
style.id = "svelte-3590263702-style";
style.id = 'svelte-3590263702-style';
style.textContent = "\n\tp[svelte-3590263702], [svelte-3590263702] p {\n\t\tcolor: red;\n\t}\n";
appendNode( style, document.head );
}
@ -198,7 +198,7 @@ function SvelteComponent ( options ) {
this._yield = options._yield;
this._torndown = false;
if ( !document.getElementById( "svelte-3590263702-style" ) ) add_css();
if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css();
this._fragment = create_main_fragment( this._state, this );

@ -10,7 +10,7 @@ var template = (function () {
function add_css () {
var style = createElement( 'style' );
style.id = "svelte-3590263702-style";
style.id = 'svelte-3590263702-style';
style.textContent = "\n\tp[svelte-3590263702], [svelte-3590263702] p {\n\t\tcolor: red;\n\t}\n";
appendNode( style, document.head );
}
@ -63,7 +63,7 @@ function SvelteComponent ( options ) {
this._yield = options._yield;
this._torndown = false;
if ( !document.getElementById( "svelte-3590263702-style" ) ) add_css();
if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css();
this._fragment = create_main_fragment( this._state, this );

@ -0,0 +1,3 @@
export default {
html: `#foo`
};

@ -0,0 +1,3 @@
export default {
html: `@foo`
};

@ -34,7 +34,7 @@ describe("ssr", () => {
// add .solo to a sample directory name to only run that test, or
// .show to always show the output. or both
const solo = /\.solo/.test(dir);
let show = /\.show/.test(dir);
const show = /\.show/.test(dir);
if (solo && process.env.CI) {
throw new Error("Forgot to remove `solo: true` from test");
@ -42,35 +42,31 @@ describe("ssr", () => {
(solo ? it.only : it)(dir, () => {
dir = path.resolve("test/server-side-rendering/samples", dir);
const component = require(`${dir}/main.html`);
try {
const component = require(`${dir}/main.html`);
const expectedHtml = tryToReadFile(`${dir}/_expected.html`);
const expectedCss = tryToReadFile(`${dir}/_expected.css`) || "";
const expectedHtml = tryToReadFile(`${dir}/_expected.html`);
const expectedCss = tryToReadFile(`${dir}/_expected.css`) || "";
const data = tryToLoadJson(`${dir}/data.json`);
let html;
let css;
let error;
const data = tryToLoadJson(`${dir}/data.json`);
try {
html = component.render(data);
css = component.renderCss().css;
} catch (e) {
show = true;
error = e;
}
const html = component.render(data);
const css = component.renderCss().css;
if (show) showOutput(dir, { generate: "ssr" });
if (error) throw error;
fs.writeFileSync(`${dir}/_actual.html`, html);
if (css) fs.writeFileSync(`${dir}/_actual.css`, css);
fs.writeFileSync(`${dir}/_actual.html`, html);
if (css) fs.writeFileSync(`${dir}/_actual.css`, css);
assert.htmlEqual(html, expectedHtml);
assert.equal(
css.replace(/^\s+/gm, ""),
expectedCss.replace(/^\s+/gm, "")
);
assert.htmlEqual(html, expectedHtml);
assert.equal(
css.replace(/^\s+/gm, ""),
expectedCss.replace(/^\s+/gm, "")
);
if (show) showOutput(dir, { generate: 'ssr' });
} catch (err) {
showOutput(dir, { generate: 'ssr' });
throw err;
}
});
});

Loading…
Cancel
Save