Merge pull request #772 from sveltejs/gh-760

Implement batched bindings
pull/778/head
Rich Harris 8 years ago committed by GitHub
commit 64cedfad19

@ -122,11 +122,16 @@ export default class Generator {
expression: Node,
context: string,
isEventHandler: boolean
) {
): {
dependencies: string[],
contexts: Set<string>,
indexes: Set<string>,
snippet: string
} {
this.addSourcemapLocations(expression);
const usedContexts = new Set();
const usedIndexes = new Set();
const usedContexts: Set<string> = new Set();
const usedIndexes: Set<string> = new Set();
const { code, helpers } = this;
const { contexts, indexes } = block;
@ -208,7 +213,7 @@ export default class Generator {
},
});
const dependencies = new Set(expression._dependencies || []);
const dependencies: Set<string> = new Set(expression._dependencies || []);
if (expression._dependencies) {
expression._dependencies.forEach((prop: string) => {

@ -178,6 +178,7 @@ export default function dom(
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
${generator.stylesheet.hasStyles &&
options.css !== false &&

@ -0,0 +1,474 @@
import deindent from '../../../utils/deindent';
import CodeBuilder from '../../../utils/CodeBuilder';
import visit from '../visit';
import { DomGenerator } from '../index';
import Block from '../Block';
import getTailSnippet from '../../../utils/getTailSnippet';
import getObject from '../../../utils/getObject';
import { stringify } from '../../../utils/stringify';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
function stringifyProps(props: string[]) {
if (!props.length) return '{}';
const joined = props.join(', ');
if (joined.length > 40) {
// make larger data objects readable
return `{\n\t${props.join(',\n\t')}\n}`;
}
return `{ ${joined} }`;
}
interface Attribute {
name: string;
value: any;
dynamic: boolean;
dependencies?: string[]
}
interface Binding {
name: string;
value: Node;
contexts: Set<string>;
snippet: string;
obj: string;
prop: string;
dependencies: string[];
}
export default function visitComponent(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
elementStack: Node[]
) {
generator.hasComponents = true;
const name = block.getUniqueName(
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
);
const componentInitProperties = [`_root: #component._root`];
// Component has children, put them in a separate {{yield}} block
if (node.children.length > 0) {
const params = block.params.join(', ');
const childBlock = node._block;
node.children.forEach((child: Node) => {
visit(generator, childBlock, node._state, child, elementStack);
});
const yield_fragment = block.getUniqueName(`${name}_yield_fragment`);
block.builders.init.addLine(
`var ${yield_fragment} = ${childBlock.name}( ${params}, #component );`
);
block.builders.create.addLine(`${yield_fragment}.create();`);
block.builders.claim.addLine(
`${yield_fragment}.claim( ${state.parentNodes} );`
);
if (childBlock.hasUpdateMethod) {
block.builders.update.addLine(
`${yield_fragment}.update( changed, ${params} );`
);
}
block.builders.destroy.addLine(`${yield_fragment}.destroy();`);
componentInitProperties.push(`_yield: ${yield_fragment}`);
}
const allContexts = new Set();
const statements: string[] = [];
const name_context = block.getUniqueName(`${name}_context`);
let name_updating: string;
let name_initial_data: string;
let beforecreate: string = null;
const attributes = node.attributes
.filter((a: Node) => a.type === 'Attribute')
.map((a: Node) => mungeAttribute(a, block));
const bindings = node.attributes
.filter((a: Node) => a.type === 'Binding')
.map((a: Node) => mungeBinding(a, block));
if (attributes.length || bindings.length) {
const initialProps = attributes
.map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`);
const initialPropString = stringifyProps(initialProps);
const updates: string[] = [];
attributes
.filter((attribute: Attribute) => attribute.dynamic)
.forEach((attribute: Attribute) => {
if (attribute.dependencies.length) {
updates.push(deindent`
if ( ${attribute.dependencies
.map(dependency => `changed.${dependency}`)
.join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value};
`);
}
else {
// TODO this is an odd situation to encounter I *think* it should only happen with
// each block indices, in which case it may be possible to optimise this
updates.push(`${name}_changes.${attribute.name} = ${attribute.value};`);
}
});
if (bindings.length) {
generator.hasComplexBindings = true;
name_updating = block.alias(`${name}_updating`);
name_initial_data = block.getUniqueName(`${name}_initial_data`);
block.addVariable(name_updating, '{}');
statements.push(`var ${name_initial_data} = ${initialPropString};`);
const setParentFromChildOnChange = new CodeBuilder();
const setParentFromChildOnInit = new CodeBuilder();
bindings.forEach((binding: Binding) => {
let setParentFromChild;
binding.contexts.forEach(context => {
allContexts.add(context);
});
const { name: key } = getObject(binding.value);
if (block.contexts.has(key)) {
const prop = binding.dependencies[0];
const computed = isComputed(binding.value);
const tail = binding.value.type === 'MemberExpression' ? getTailSnippet(binding.value) : '';
setParentFromChild = deindent`
var list = ${name_context}.${block.listNames.get(key)};
var index = ${name_context}.${block.indexNames.get(key)};
list[index]${tail} = childState.${binding.name};
${binding.dependencies
.map((prop: string) => `newState.${prop} = state.${prop};`)
.join('\n')}
`;
}
else if (binding.value.type === 'MemberExpression') {
setParentFromChild = deindent`
${binding.snippet} = childState.${binding.name};
${binding.dependencies.map((prop: string) => `newState.${prop} = state.${prop};`).join('\n')}
`;
}
else {
setParentFromChild = `newState.${binding.value.name} = childState.${binding.name};`;
}
statements.push(deindent`
if ( ${binding.prop} in ${binding.obj} ) {
${name_initial_data}.${binding.name} = ${binding.snippet};
${name_updating}.${binding.name} = true;
}`
);
setParentFromChildOnChange.addConditional(
`!${name_updating}.${binding.name} && changed.${binding.name}`,
setParentFromChild
);
setParentFromChildOnInit.addConditional(
`!${name_updating}.${binding.name}`,
setParentFromChild
);
// TODO could binding.dependencies.length ever be 0?
if (binding.dependencies.length) {
updates.push(deindent`
if ( !${name_updating}.${binding.name} && ${binding.dependencies.map((dependency: string) => `changed.${dependency}`).join(' || ')} ) {
${name}_changes.${binding.name} = ${binding.snippet};
${name_updating}.${binding.name} = true;
}
`);
}
});
componentInitProperties.push(`data: ${name_initial_data}`);
componentInitProperties.push(deindent`
_bind: function(changed, childState) {
var state = #component.get(), newState = {};
${setParentFromChildOnChange}
${name_updating} = changed;
#component._set(newState);
${name_updating} = {};
}
`);
beforecreate = deindent`
#component._root._beforecreate.push(function () {
var state = #component.get(), childState = ${name}.get(), newState = {};
if (!childState) return;
${setParentFromChildOnInit}
${name_updating} = { ${bindings.map((binding: Binding) => `${binding.name}: true`).join(', ')} };
#component._set(newState);
${name_updating} = {};
});
`;
} else if (initialProps.length) {
componentInitProperties.push(`data: ${initialPropString}`);
}
block.builders.update.addBlock(deindent`
var ${name}_changes = {};
${updates.join('\n')}
${name}._set( ${name}_changes );
${bindings.length && `${name_updating} = {};`}
`);
}
const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`@template.components.${node.name}`;
block.builders.init.addBlock(deindent`
${statements.join('\n')}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
${beforecreate}
`);
block.builders.create.addLine(`${name}._fragment.create();`);
block.builders.claim.addLine(
`${name}._fragment.claim( ${state.parentNodes} );`
);
block.builders.mount.addLine(
`${name}._fragment.mount( ${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'} );`
);
if (!state.parentNode) block.builders.unmount.addLine(`${name}._fragment.unmount();`);
block.builders.destroy.addLine(`${name}.destroy( false );`);
// event handlers
node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => {
const usedContexts: string[] = [];
if (handler.expression) {
generator.addSourcemapLocations(handler.expression);
generator.code.prependRight(
handler.expression.start,
`${block.alias('component')}.`
);
handler.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
allContexts.add(context);
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return `var state = ${name_context}.state;`;
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = ${name_context}.${listName}, ${indexName} = ${name_context}.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
(handler.expression ?
`[✂${handler.expression.start}-${handler.expression.end}✂];` :
`${block.alias('component')}.fire('${handler.name}', event);`);
block.builders.init.addBlock(deindent`
${name}.on( '${handler.name}', function ( event ) {
${handlerBody}
});
`);
});
// refs
node.attributes.filter((a: Node) => a.type === 'Ref').forEach((ref: Node) => {
generator.usesRefs = true;
block.builders.init.addLine(`#component.refs.${ref.name} = ${name};`);
block.builders.destroy.addLine(deindent`
if ( #component.refs.${ref.name} === ${name} ) #component.refs.${ref.name} = null;
`);
});
// maintain component context
if (allContexts.size) {
const contexts = Array.from(allContexts);
const initialProps = contexts
.map(contextName => {
if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
})
.join(',\n');
const updates = contexts
.map(contextName => {
if (contextName === 'state') return `${name_context}.state = state;`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${name_context}.${listName} = ${listName};\n${name_context}.${indexName} = ${indexName};`;
})
.join('\n');
block.builders.init.addBlock(deindent`
var ${name_context} = {
${initialProps}
};
`);
block.builders.update.addBlock(updates);
}
}
function mungeAttribute(attribute: Node, block: Block): Attribute {
if (attribute.value === true) {
// attributes without values, e.g. <textarea readonly>
return {
name: attribute.name,
value: true,
dynamic: false
};
}
if (attribute.value.length === 0) {
return {
name: attribute.name,
value: `''`,
dynamic: false
};
}
if (attribute.value.length === 1) {
const value = attribute.value[0];
if (value.type === 'Text') {
// static attributes
return {
name: attribute.name,
value: isNaN(value.data) ? stringify(value.data) : value.data,
dynamic: false
};
}
// simple dynamic attributes
const { dependencies, snippet } = block.contextualise(value.expression);
// TODO only update attributes that have changed
return {
name: attribute.name,
value: snippet,
dependencies,
dynamic: true
};
}
// otherwise we're dealing with a complex dynamic attribute
const allDependencies = new Set();
const value =
(attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = block.contextualise(
chunk.expression
);
dependencies.forEach(dependency => {
allDependencies.add(dependency);
});
return `( ${snippet} )`; // TODO only parenthesize if necessary
}
})
.join(' + ');
return {
name: attribute.name,
value,
dependencies: Array.from(allDependencies),
dynamic: true
};
}
function mungeBinding(binding: Node, block: Block): Binding {
const { name } = getObject(binding.value);
const { snippet, contexts, dependencies } = block.contextualise(
binding.value
);
const contextual = block.contexts.has(name);
let obj;
let prop;
if (contextual) {
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} else if (binding.value.type === 'MemberExpression') {
prop = `[✂${binding.value.property.start}-${binding.value.property.end}✂]`;
if (!binding.value.computed) prop = `'${prop}'`;
obj = `[✂${binding.value.object.start}-${binding.value.object.end}✂]`;
} else {
obj = 'state';
prop = `'${name}'`;
}
return {
name: binding.name,
value: binding.value,
contexts,
snippet,
obj,
prop,
dependencies
};
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -1,77 +0,0 @@
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,
block: Block,
state: State,
node: Node,
attribute,
local
) {
if (attribute.value === true) {
// attributes without values, e.g. <textarea readonly>
local.staticAttributes.push({
name: attribute.name,
value: true,
});
} else if (attribute.value.length === 0) {
local.staticAttributes.push({
name: attribute.name,
value: `''`,
});
} else if (attribute.value.length === 1) {
const value = attribute.value[0];
if (value.type === 'Text') {
// static attributes
const result = isNaN(value.data) ? stringify(value.data) : value.data;
local.staticAttributes.push({
name: attribute.name,
value: result,
});
} else {
// simple dynamic attributes
const { dependencies, snippet } = block.contextualise(value.expression);
// TODO only update attributes that have changed
local.dynamicAttributes.push({
name: attribute.name,
value: snippet,
dependencies,
});
}
} else {
// complex dynamic attributes
const allDependencies = [];
const value =
(attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map(chunk => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { dependencies, snippet } = block.contextualise(
chunk.expression
);
dependencies.forEach(dependency => {
if (!~allDependencies.indexOf(dependency))
allDependencies.push(dependency);
});
return `( ${snippet} )`;
}
})
.join(' + ');
local.dynamicAttributes.push({
name: attribute.name,
value,
dependencies: allDependencies,
});
}
}

@ -1,99 +0,0 @@
import deindent from '../../../../utils/deindent';
import flattenReference from '../../../../utils/flattenReference';
import getSetter from '../shared/binding/getSetter';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
import getObject from '../../../../utils/getObject';
export default function visitBinding(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute,
local
) {
const { name } = getObject(attribute.value);
const { snippet, contexts, dependencies } = block.contextualise(
attribute.value
);
contexts.forEach(context => {
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
const contextual = block.contexts.has(name);
let obj;
let prop;
if (contextual) {
obj = block.listNames.get(name);
prop = block.indexNames.get(name);
} else if (attribute.value.type === 'MemberExpression') {
prop = `[✂${attribute.value.property.start}-${attribute.value.property
.end}]`;
if (!attribute.value.computed) prop = `'${prop}'`;
obj = `[✂${attribute.value.object.start}-${attribute.value.object.end}✂]`;
} else {
obj = 'state';
prop = `'${name}'`;
}
local.bindings.push({
name: attribute.name,
value: snippet,
obj,
prop,
});
const setter = getSetter({
block,
name,
snippet,
_this: 'this',
props: '_context',
attribute,
dependencies,
value: 'value',
});
generator.hasComplexBindings = true;
const updating = block.getUniqueName(`${local.name}_updating`);
block.addVariable(updating, 'false');
const observer = block.getUniqueName('observer');
const value = block.getUniqueName('value');
local.create.addBlock(deindent`
function ${observer} ( value ) {
if ( ${updating} ) return;
${updating} = true;
${setter}
${updating} = false;
}
${local.name}.observe( '${attribute.name}', ${observer}, { init: false });
#component._root._beforecreate.push( function () {
var value = ${local.name}.get( '${attribute.name}' );
if ( @differs( value, ${snippet} ) ) {
${observer}.call( ${local.name}, value );
}
});
`);
local.update.addBlock(deindent`
if ( !${updating} && ${dependencies
.map(dependency => `changed.${dependency}`)
.join(' || ')} ) {
${updating} = true;
${local.name}._set({ ${attribute.name}: ${snippet} });
${updating} = false;
}
`);
}

@ -1,235 +0,0 @@
import deindent from '../../../../utils/deindent';
import CodeBuilder from '../../../../utils/CodeBuilder';
import visit from '../../visit';
import visitAttribute from './Attribute';
import visitEventHandler from './EventHandler';
import visitBinding from './Binding';
import visitRef from './Ref';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
function stringifyProps(props: string[]) {
if (!props.length) return '{}';
const joined = props.join(', ');
if (joined.length > 40) {
// make larger data objects readable
return `{\n\t${props.join(',\n\t')}\n}`;
}
return `{ ${joined} }`;
}
const order = {
Attribute: 1,
EventHandler: 2,
Binding: 3,
Ref: 4,
};
const visitors = {
Attribute: visitAttribute,
EventHandler: visitEventHandler,
Binding: visitBinding,
Ref: visitRef,
};
export default function visitComponent(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
elementStack: Node[]
) {
const hasChildren = node.children.length > 0;
const name = block.getUniqueName(
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
);
const childState = node._state;
const local = {
name,
namespace: state.namespace,
isComponent: true,
allUsedContexts: [],
staticAttributes: [],
dynamicAttributes: [],
bindings: [],
create: new CodeBuilder(),
update: new CodeBuilder(),
};
const isTopLevel = !state.parentNode;
generator.hasComponents = true;
node.attributes
.sort((a, b) => order[a.type] - order[b.type])
.forEach(attribute => {
visitors[attribute.type](
generator,
block,
childState,
node,
attribute,
local
);
});
if (local.allUsedContexts.length) {
const initialProps = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `state: state`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${listName}: ${listName},\n${indexName}: ${indexName}`;
})
.join(',\n');
const updates = local.allUsedContexts
.map(contextName => {
if (contextName === 'state') return `${name}._context.state = state;`;
const listName = block.listNames.get(contextName);
const indexName = block.indexNames.get(contextName);
return `${name}._context.${listName} = ${listName};\n${name}._context.${indexName} = ${indexName};`;
})
.join('\n');
local.create.addBlock(deindent`
${name}._context = {
${initialProps}
};
`);
local.update.addBlock(updates);
}
const componentInitProperties = [`_root: #component._root`];
// Component has children, put them in a separate {{yield}} block
if (hasChildren) {
const params = block.params.join(', ');
const childBlock = node._block;
node.children.forEach((child: Node) => {
visit(generator, childBlock, childState, child, elementStack);
});
const yieldFragment = block.getUniqueName(`${name}_yield_fragment`);
block.builders.init.addLine(
`var ${yieldFragment} = ${childBlock.name}( ${params}, #component );`
);
block.builders.create.addLine(`${yieldFragment}.create();`);
block.builders.claim.addLine(
`${yieldFragment}.claim( ${state.parentNodes} );`
);
if (childBlock.hasUpdateMethod) {
block.builders.update.addLine(
`${yieldFragment}.update( changed, ${params} );`
);
}
block.builders.destroy.addLine(`${yieldFragment}.destroy();`);
componentInitProperties.push(`_yield: ${yieldFragment}`);
}
const statements: string[] = [];
if (
local.staticAttributes.length ||
local.dynamicAttributes.length ||
local.bindings.length
) {
const initialProps = local.staticAttributes
.concat(local.dynamicAttributes)
.map(attribute => `${attribute.name}: ${attribute.value}`);
const initialPropString = stringifyProps(initialProps);
if (local.bindings.length) {
const initialData = block.getUniqueName(`${name}_initial_data`);
statements.push(`var ${initialData} = ${initialPropString};`);
local.bindings.forEach(binding => {
statements.push(
`if ( ${binding.prop} in ${binding.obj} ) ${initialData}.${binding.name} = ${binding.value};`
);
});
componentInitProperties.push(`data: ${initialData}`);
} else if (initialProps.length) {
componentInitProperties.push(`data: ${initialPropString}`);
}
}
const expression = node.name === ':Self'
? generator.name
: generator.importedComponents.get(node.name) ||
`@template.components.${node.name}`;
local.create.addBlockAtStart(deindent`
${statements.join('\n')}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
`);
if (local.dynamicAttributes.length) {
const updates = local.dynamicAttributes.map(attribute => {
if (attribute.dependencies.length) {
return deindent`
if ( ${attribute.dependencies
.map(dependency => `changed.${dependency}`)
.join(' || ')} ) ${name}_changes.${attribute.name} = ${attribute.value};
`;
}
// TODO this is an odd situation to encounter I *think* it should only happen with
// each block indices, in which case it may be possible to optimise this
return `${name}_changes.${attribute.name} = ${attribute.value};`;
});
local.update.addBlock(deindent`
var ${name}_changes = {};
${updates.join('\n')}
${name}._set( ${name}_changes );
`);
}
if (isTopLevel)
block.builders.unmount.addLine(`${name}._fragment.unmount();`);
block.builders.destroy.addLine(`${name}.destroy( false );`);
block.builders.init.addBlock(local.create);
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} );`
);
if (!local.update.isEmpty()) block.builders.update.addBlock(local.update);
}

@ -1,57 +0,0 @@
import deindent from '../../../../utils/deindent';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitEventHandler(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
const usedContexts: string[] = [];
if (attribute.expression) {
generator.addSourcemapLocations(attribute.expression);
generator.code.prependRight(
attribute.expression.start,
`${block.alias('component')}.`
);
attribute.expression.arguments.forEach((arg: Node) => {
const { contexts } = block.contextualise(arg, null, true);
contexts.forEach(context => {
if (!~usedContexts.indexOf(context)) usedContexts.push(context);
if (!~local.allUsedContexts.indexOf(context))
local.allUsedContexts.push(context);
});
});
}
// TODO hoist event handlers? can do `this.__component.method(...)`
const declarations = usedContexts.map(name => {
if (name === 'state') return 'var state = this._context.state;';
const listName = block.listNames.get(name);
const indexName = block.indexNames.get(name);
return `var ${listName} = this._context.${listName}, ${indexName} = this._context.${indexName}, ${name} = ${listName}[${indexName}]`;
});
const handlerBody =
(declarations.length ? declarations.join('\n') + '\n\n' : '') +
(attribute.expression ?
`[✂${attribute.expression.start}-${attribute.expression.end}✂];` :
`${block.alias('component')}.fire('${attribute.name}', event);`);
local.create.addBlock(deindent`
${local.name}.on( '${attribute.name}', function ( event ) {
${handlerBody}
});
`);
}

@ -1,22 +0,0 @@
import deindent from '../../../../utils/deindent';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitRef(
generator: DomGenerator,
block: Block,
state: State,
node: Node,
attribute: Node,
local
) {
generator.usesRefs = true;
local.create.addLine(`#component.refs.${attribute.name} = ${local.name};`);
block.builders.destroy.addLine(deindent`
if ( #component.refs.${attribute.name} === ${local.name} ) #component.refs.${attribute.name} = null;
`);
}

@ -1,6 +1,5 @@
import deindent from '../../../../utils/deindent';
import flattenReference from '../../../../utils/flattenReference';
import getSetter from '../shared/binding/getSetter';
import getStaticAttributeValue from './getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
@ -50,16 +49,7 @@ export default function visitBinding(
type
);
let setter = getSetter({
block,
name,
snippet,
_this: state.parentNode,
props: '_svelte',
attribute,
dependencies,
value,
});
let setter = getSetter(block, name, snippet, state.parentNode, attribute, dependencies, value);
let updateElement = `${state.parentNode}.${attribute.name} = ${snippet};`;
const lock = `#${state.parentNode}_updating`;
let updateCondition = `!${lock}`;
@ -271,3 +261,58 @@ function getBindingGroup(generator: DomGenerator, value: Node) {
return index;
}
function getSetter(
block: Block,
name: string,
snippet: string,
_this: string,
attribute: Node,
dependencies: string[],
value: string,
) {
const tail = attribute.value.type === 'MemberExpression'
? getTailSnippet(attribute.value)
: '';
if (block.contexts.has(name)) {
const prop = dependencies[0];
const computed = isComputed(attribute.value);
return deindent`
var list = ${_this}._svelte.${block.listNames.get(name)};
var index = ${_this}._svelte.${block.indexNames.get(name)};
${computed && `var state = #component.get();`}
list[index]${tail} = ${value};
${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') {
return deindent`
var state = #component.get();
${snippet} = ${value};
#component.set({ ${dependencies
.map((prop: string) => `${prop}: state.${prop}`)
.join(', ')} });
`;
}
return `#component.set({ ${name}: ${value} });`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -1,6 +1,6 @@
import deindent from '../../../../utils/deindent';
import visit from '../../visit';
import visitComponent from '../Component/Component';
import visitComponent from '../Component';
import visitWindow from './meta/Window';
import visitAttribute from './Attribute';
import visitEventHandler from './EventHandler';

@ -1,59 +0,0 @@
import deindent from '../../../../../utils/deindent';
import getTailSnippet from '../../../../../utils/getTailSnippet';
import { Node } from '../../../../../interfaces';
export default function getSetter({
block,
name,
snippet,
_this,
props,
attribute,
dependencies,
value,
}) {
const tail = attribute.value.type === 'MemberExpression'
? getTailSnippet(attribute.value)
: '';
if (block.contexts.has(name)) {
const prop = dependencies[0];
const computed = isComputed(attribute.value);
return deindent`
var list = ${_this}.${props}.${block.listNames.get(name)};
var index = ${_this}.${props}.${block.indexNames.get(name)};
${computed && `var state = #component.get();`}
list[index]${tail} = ${value};
${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') {
return deindent`
var state = #component.get();
${snippet} = ${value};
#component.set({ ${dependencies
.map((prop: string) => `${prop}: state.${prop}`)
.join(', ')} });
`;
}
return `#component.set({ ${name}: ${value} });`;
}
function isComputed(node: Node) {
while (node.type === 'MemberExpression') {
if (node.computed) return true;
node = node.object;
}
return false;
}

@ -5,14 +5,12 @@ export * from './transitions.js';
export * from './utils.js';
export function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
export function destroyDev(detach) {
@ -143,6 +141,7 @@ export function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);

@ -38,14 +38,12 @@ function setAttribute(node, attribute, value) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -144,6 +142,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -230,6 +229,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css();

@ -65,6 +65,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !document.getElementById( 'svelte-3590263702-style' ) ) add_css();

@ -14,14 +14,12 @@ function assign(target) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -120,6 +118,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -179,6 +178,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -38,6 +38,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -34,14 +34,12 @@ function setAttribute(node, attribute, value) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -140,6 +138,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -212,6 +211,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !document.getElementById( 'svelte-2363328337-style' ) ) add_css();

@ -51,6 +51,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !document.getElementById( 'svelte-2363328337-style' ) ) add_css();

@ -47,14 +47,12 @@ function createText(data) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -153,6 +151,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -326,6 +325,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -152,6 +152,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -34,14 +34,12 @@ function createText(data) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -140,6 +138,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -223,6 +222,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -62,6 +62,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -38,14 +38,12 @@ function createComment() {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -144,6 +142,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -265,6 +264,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -100,6 +100,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -38,14 +38,12 @@ function createComment() {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -144,6 +142,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -241,6 +240,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -76,6 +76,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -28,14 +28,12 @@ function createText(data) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -134,6 +132,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -215,6 +214,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !options._root ) {
this._oncreate = [];

@ -62,6 +62,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
if ( !options._root ) {
this._oncreate = [];

@ -14,14 +14,12 @@ function assign(target) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -120,6 +118,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -178,6 +177,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
var oncreate = template.oncreate.bind( this );

@ -37,6 +37,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
var oncreate = template.oncreate.bind( this );

@ -14,14 +14,12 @@ function assign(target) {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -120,6 +118,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -188,6 +187,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -47,6 +47,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -38,14 +38,12 @@ function createComment() {
}
function destroy(detach) {
this.destroy = this.set = noop;
this.destroy = this.set = this.get = noop;
this.fire('destroy');
if (detach !== false) this._fragment.unmount();
this._fragment.destroy();
this._fragment = null;
this._state = {};
this._fragment = this._state = null;
}
function differs(a, b) {
@ -144,6 +142,7 @@ function _set(newState) {
this._state = assign({}, oldState, newState);
this._recompute(changed, this._state, oldState, false);
if (this._bind) this._bind(changed, this._state);
dispatchObservers(this, this._observers.pre, changed, this._state, oldState);
this._fragment.update(changed, this._state);
dispatchObservers(this, this._observers.post, changed, this._state, oldState);
@ -425,6 +424,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -260,6 +260,7 @@ function SvelteComponent ( options ) {
this._root = options._root || this;
this._yield = options._yield;
this._bind = options._bind;
this._fragment = create_main_fragment( this._state, this );

@ -0,0 +1,20 @@
<p>bar in Foo: {{bar}}</p>
<p>baz in Foo: {{baz}}</p>
<script>
export default {
data() {
return {
bar: 1,
baz: 2
};
},
methods: {
double() {
const { bar, baz } = this.get();
this.set({ bar: bar * 2, baz: baz * 2 });
}
}
};
</script>

@ -0,0 +1,17 @@
export default {
test(assert, component) {
const { foo, p } = component.refs;
const values = [];
Object.defineProperty(p.childNodes[0], 'data', {
set(value) {
values.push(value);
}
});
foo.double();
assert.deepEqual(values, [6]);
}
};

@ -0,0 +1,12 @@
<Foo ref:foo bind:bar bind:baz/>
<p ref:p>{{bar + baz}}</p>
<script>
import Foo from './Foo.html';
export default {
components: {
Foo
}
};
</script>
Loading…
Cancel
Save