render functions that close over contextual data - fixes

pull/1942/head
Richard Harris 6 years ago
parent 45d375e15d
commit 329b013e40

@ -2,6 +2,7 @@ import Node from './shared/Node';
import Expression from './shared/Expression'; import Expression from './shared/Expression';
import Component from '../Component'; import Component from '../Component';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import Block from '../render-dom/Block';
export default class EventHandler extends Node { export default class EventHandler extends Node {
name: string; name: string;
@ -52,8 +53,9 @@ export default class EventHandler extends Node {
} }
} }
render() { // TODO move this? it is specific to render-dom
if (this.expression) return this.expression.render(); render(block: Block) {
if (this.expression) return this.expression.render(block);
this.component.template_references.add(this.handler_name); this.component.template_references.add(this.handler_name);
return `ctx.${this.handler_name}`; return `ctx.${this.handler_name}`;

@ -11,6 +11,7 @@ import sanitize from '../../../utils/sanitize';
import TemplateScope from './TemplateScope'; import TemplateScope from './TemplateScope';
import getObject from '../../../utils/getObject'; import getObject from '../../../utils/getObject';
import { nodes_match } from '../../../utils/nodes_match'; import { nodes_match } from '../../../utils/nodes_match';
import Block from '../../render-dom/Block';
const binaryOperators: Record<string, number> = { const binaryOperators: Record<string, number> = {
'**': 15, '**': 15,
@ -86,13 +87,6 @@ export default class Expression {
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
value: component value: component
},
// TODO remove this, is just for debugging
snippet: {
get: () => {
throw new Error(`cannot access expression.snippet, use expression.render() instead`)
}
} }
}); });
@ -181,7 +175,7 @@ export default class Expression {
} }
// TODO move this into a render-dom wrapper? // TODO move this into a render-dom wrapper?
render() { render(block: Block) {
if (this.rendered) return this.rendered; if (this.rendered) return this.rendered;
const { const {
@ -407,6 +401,13 @@ export default class Expression {
} }
}); });
if (declarations.length > 0) {
block.maintainContext = true;
declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
return this.rendered = `[✂${this.node.start}-${this.node.end}✂]`; return this.rendered = `[✂${this.node.start}-${this.node.end}✂]`;
} }
} }

@ -125,7 +125,7 @@ export default class AwaitBlockWrapper extends Wrapper {
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes); const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor); const updateMountNode = this.getUpdateMountNode(anchor);
const snippet = this.node.expression.render(); const snippet = this.node.expression.render(block);
const info = block.getUniqueName(`info`); const info = block.getUniqueName(`info`);
const promise = block.getUniqueName(`promise`); const promise = block.getUniqueName(`promise`);

@ -8,7 +8,7 @@ export default class DocumentWrapper extends Wrapper {
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parentNode: string, parentNodes: string) {
this.node.handlers.forEach(handler => { this.node.handlers.forEach(handler => {
const snippet = handler.render(); const snippet = handler.render(block);
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
document.addEventListener("${handler.name}", ${snippet}); document.addEventListener("${handler.name}", ${snippet});

@ -180,7 +180,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`); if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`);
if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`); if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`);
const snippet = this.node.expression.render(); const snippet = this.node.expression.render(block);
block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`); block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`);

@ -65,7 +65,7 @@ export default class BindingWrapper {
// view to model // view to model
this.handler = getEventHandler(this, parent.renderer, block, this.object, contextless_snippet); this.handler = getEventHandler(this, parent.renderer, block, this.object, contextless_snippet);
this.snippet = this.node.expression.render(); this.snippet = this.node.expression.render(block);
const type = parent.node.getStaticAttributeValue('type'); const type = parent.node.getStaticAttributeValue('type');

@ -534,7 +534,7 @@ export default class ElementWrapper extends Wrapper {
: null; : null;
if (attr.isSpread) { if (attr.isSpread) {
const snippet = attr.expression.render(); const snippet = attr.expression.render(block);
initialProps.push(snippet); initialProps.push(snippet);
@ -584,7 +584,7 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) { if (intro === outro) {
const name = block.getUniqueName(`${this.var}_transition`); const name = block.getUniqueName(`${this.var}_transition`);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.render() ? intro.expression.render(block)
: '{}'; : '{}';
block.addVariable(name); block.addVariable(name);
@ -616,7 +616,7 @@ export default class ElementWrapper extends Wrapper {
if (intro) { if (intro) {
block.addVariable(introName); block.addVariable(introName);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.render() ? intro.expression.render(block)
: '{}'; : '{}';
const fn = component.qualify(intro.name); // TODO add built-in transitions? const fn = component.qualify(intro.name); // TODO add built-in transitions?
@ -639,7 +639,7 @@ export default class ElementWrapper extends Wrapper {
if (outro) { if (outro) {
block.addVariable(outroName); block.addVariable(outroName);
const snippet = outro.expression const snippet = outro.expression
? outro.expression.render() ? outro.expression.render(block)
: '{}'; : '{}';
const fn = component.qualify(outro.name); const fn = component.qualify(outro.name);
@ -680,7 +680,7 @@ export default class ElementWrapper extends Wrapper {
${stop_animation}(); ${stop_animation}();
`); `);
const params = this.node.animation.expression ? this.node.animation.expression.render() : '{}'; const params = this.node.animation.expression ? this.node.animation.expression.render(block) : '{}';
const name = component.qualify(this.node.animation.name); const name = component.qualify(this.node.animation.name);
@ -699,7 +699,7 @@ export default class ElementWrapper extends Wrapper {
const { expression, name } = classDir; const { expression, name } = classDir;
let snippet, dependencies; let snippet, dependencies;
if (expression) { if (expression) {
snippet = expression.render(); snippet = expression.render(block);
dependencies = expression.dependencies; dependencies = expression.dependencies;
} else { } else {
snippet = `${quotePropIfNecessary(name)}`; snippet = `${quotePropIfNecessary(name)}`;

@ -32,7 +32,7 @@ class IfBlockBranch extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.condition = (<IfBlock>node).expression && (<IfBlock>node).expression.render(); this.condition = (<IfBlock>node).expression && (<IfBlock>node).expression.render(block);
this.block = block.child({ this.block = block.child({
comment: createDebuggingComment(node, parent.renderer.component), comment: createDebuggingComment(node, parent.renderer.component),

@ -150,7 +150,7 @@ export default class InlineComponentWrapper extends Wrapper {
: null; : null;
if (attr.isSpread) { if (attr.isSpread) {
const value = attr.expression.render(); const value = attr.expression.render(block);
initialProps.push(value); initialProps.push(value);
changes.push(condition ? `${condition} && ${value}` : value); changes.push(condition ? `${condition} && ${value}` : value);
@ -238,7 +238,7 @@ export default class InlineComponentWrapper extends Wrapper {
const updating = block.getUniqueName(`updating_${binding.name}`); const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating); block.addVariable(updating);
const snippet = binding.expression.render(); const snippet = binding.expression.render(block);
statements.push(deindent` statements.push(deindent`
if (${snippet} !== void 0) { if (${snippet} !== void 0) {
@ -305,13 +305,7 @@ export default class InlineComponentWrapper extends Wrapper {
const munged_handlers = this.node.handlers.map(handler => { const munged_handlers = this.node.handlers.map(handler => {
// TODO return declarations from handler.render()? // TODO return declarations from handler.render()?
const snippet = handler.render(); const snippet = handler.render(block);
if (handler.expression) {
handler.expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
return `${name}.$on("${handler.name}", ${snippet});`; return `${name}.$on("${handler.name}", ${snippet});`;
}); });
@ -320,7 +314,7 @@ export default class InlineComponentWrapper extends Wrapper {
const switch_value = block.getUniqueName('switch_value'); const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props'); const switch_props = block.getUniqueName('switch_props');
const snippet = this.node.expression.render(); const snippet = this.node.expression.render(block);
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
var ${switch_value} = ${snippet}; var ${switch_value} = ${snippet};

@ -33,7 +33,7 @@ export default class TitleWrapper extends Wrapper {
if (this.node.children.length === 1) { if (this.node.children.length === 1) {
// single {tag} — may be a non-string // single {tag} — may be a non-string
const { expression } = this.node.children[0]; const { expression } = this.node.children[0];
value = expression.render(); value = expression.render(block);
addToSet(allDependencies, expression.dynamic_dependencies); addToSet(allDependencies, expression.dynamic_dependencies);
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
@ -44,7 +44,7 @@ export default class TitleWrapper extends Wrapper {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
const snippet = chunk.expression.render(); const snippet = chunk.expression.render(block);
chunk.expression.dynamic_dependencies.forEach(d => { chunk.expression.dynamic_dependencies.forEach(d => {
allDependencies.add(d); allDependencies.add(d);

@ -19,7 +19,7 @@ export default class Tag extends Wrapper {
update: ((value: string) => string) update: ((value: string) => string)
) { ) {
const dependencies = this.node.expression.dynamic_dependencies; const dependencies = this.node.expression.dynamic_dependencies;
const snippet = this.node.expression.render(); const snippet = this.node.expression.render(block);
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`); const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`);
const content = this.node.shouldCache ? value : snippet; const content = this.node.shouldCache ? value : snippet;

@ -12,13 +12,10 @@ export default function addActions(
actions.forEach(action => { actions.forEach(action => {
const { expression } = action; const { expression } = action;
let snippet, dependencies; let snippet, dependencies;
if (expression) { if (expression) {
snippet = expression.render(); snippet = expression.render(block);
dependencies = expression.dynamic_dependencies; dependencies = expression.dynamic_dependencies;
expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
} }
const name = block.getUniqueName( const name = block.getUniqueName(

@ -7,7 +7,7 @@ export default function addEventHandlers(
handlers: EventHandler[] handlers: EventHandler[]
) { ) {
handlers.forEach(handler => { handlers.forEach(handler => {
let snippet = handler.render(); let snippet = handler.render(block);
if (handler.modifiers.has('preventDefault')) snippet = `@preventDefault(${snippet})`; if (handler.modifiers.has('preventDefault')) snippet = `@preventDefault(${snippet})`;
if (handler.modifiers.has('stopPropagation')) snippet = `@stopPropagation(${snippet})`; if (handler.modifiers.has('stopPropagation')) snippet = `@stopPropagation(${snippet})`;
@ -26,11 +26,5 @@ export default function addEventHandlers(
`@addListener(${target}, "${handler.name}", ${snippet})` `@addListener(${target}, "${handler.name}", ${snippet})`
); );
} }
if (handler.expression) {
handler.expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
}); });
} }

@ -0,0 +1,15 @@
export default {
html: `
<p>1, 2, 3</p>
<p>2, 4, 6</p>
<p>3, 6, 9</p>
`,
test({ assert, component, target }) {
component.numbers = [4, 5];
assert.htmlEqual( target.innerHTML, `
<p>16, 20</p>
<p>20, 25</p>
` );
}
};

@ -0,0 +1,7 @@
{#each numbers as i}
<p>{numbers.map(j => i * j).join(', ')}</p>
{/each}
<script>
export let numbers = [1, 2, 3];
</script>
Loading…
Cancel
Save