client-side dynamic components mostly working ()

pull/971/head
Rich Harris 7 years ago
parent 4f991536d4
commit dba32df84e

@ -763,6 +763,10 @@ export default class Generator {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
this.skip();
}
if (node.type === 'Element' && node.name === ':Switch') {
node.metadata = contextualise(node.expression, contextDependencies, indexes);
}
},
leave(node: Node, parent: Node) {

@ -436,13 +436,17 @@ const preprocessors = {
}
const isComponent =
generator.components.has(node.name) || node.name === ':Self';
generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch';
if (isComponent) {
cannotUseInnerHTML(node);
node.var = block.getUniqueName(
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
(
node.name === ':Self' ? generator.name :
node.name === ':Switch' ? 'switch_instance' :
node.name
).toLowerCase()
);
node._state = getChildState(state, {

@ -3,6 +3,7 @@ import CodeBuilder from '../../../utils/CodeBuilder';
import visit from '../visit';
import { DomGenerator } from '../index';
import Block from '../Block';
import isDomNode from './shared/isDomNode';
import getTailSnippet from '../../../utils/getTailSnippet';
import getObject from '../../../utils/getObject';
import getExpressionPrecedence from '../../../utils/getExpressionPrecedence';
@ -67,6 +68,9 @@ export default function visitComponent(
.filter((a: Node) => a.type === 'Binding')
.map((a: Node) => mungeBinding(a, block));
const ref = node.attributes.find((a: Node) => a.type === 'Ref');
if (ref) generator.usesRefs = true;
if (attributes.length || bindings.length) {
const initialProps = attributes
.map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`);
@ -205,30 +209,122 @@ export default function visitComponent(
}
}
const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`;
const isSwitch = node.name === ':Switch';
block.builders.init.addBlock(deindent`
${statements.join('\n')}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
const switch_vars = isSwitch && {
value: block.getUniqueName('switch_value'),
props: block.getUniqueName('switch_props')
};
${beforecreate}
`);
const expression = (
node.name === ':Self' ? generator.name :
isSwitch ? switch_vars.value :
`%components-${node.name}`
);
block.builders.create.addLine(`${name}._fragment.c();`);
if (isSwitch) {
block.contextualise(node.expression);
const { dependencies, snippet } = node.metadata;
const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator);
const anchor = needsAnchor
? block.getUniqueName(`${name}_anchor`)
: (node.next && node.next.var) || 'null';
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
`@createComment()`,
state.parentNode
);
}
block.builders.claim.addLine(
`${name}._fragment.l(${state.parentNodes});`
);
const params = block.params.join(', ');
block.builders.mount.addLine(
`${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
);
block.builders.init.addBlock(deindent`
var ${switch_vars.value} = ${snippet};
`);
if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`);
block.builders.init.addBlock(deindent`
function ${switch_vars.props}(${params}) {
return {
${componentInitProperties.join(',\n')}
};
}
if (${switch_vars.value}) {
${statements.length > 0 && statements.join('\n')}
var ${name} = new ${expression}(${switch_vars.props}(${params}));
${beforecreate}
}
`);
block.builders.create.addLine(
`if (${name}) ${name}._fragment.c();`
);
block.builders.claim.addLine(
`if (${name}) ${name}._fragment.l(${state.parentNodes});`
);
block.builders.mount.addLine(
`if (${name}) ${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
);
block.builders.update.addBlock(deindent`
if (${switch_vars.value} !== (${switch_vars.value} = ${snippet})) {
if (${name}) ${name}.destroy();
block.builders.destroy.addLine(`${name}.destroy(false);`);
if (${switch_vars.value}) {
${name} = new ${switch_vars.value}(${switch_vars.props}(${params}));
${name}._fragment.c();
${name}._mount(${anchor}.parentNode, ${anchor});
${ref && `#component.refs.${ref.name} = ${name};`}
}
${ref && deindent`
else if (#component.refs.${ref.name} === ${name}) {
#component.refs.${ref.name} = null;
}`}
} else {
// normal update
}
`);
if (!state.parentNode) block.builders.unmount.addLine(`if (${name}) ${name}._unmount();`);
block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`);
} else {
block.builders.init.addBlock(deindent`
${statements.join('\n')}
var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
${beforecreate}
${ref && `#component.refs.${ref.name} = ${name};`}
`);
block.builders.create.addLine(`${name}._fragment.c();`);
block.builders.claim.addLine(
`${name}._fragment.l(${state.parentNodes});`
);
block.builders.mount.addLine(
`${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
);
if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`);
block.builders.destroy.addLine(deindent`
${name}.destroy(false);
${ref && `if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null;`}
`);
}
// event handlers
node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => {
@ -274,17 +370,6 @@ export default function visitComponent(
`);
});
// 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);

@ -43,7 +43,7 @@ export default function visitElement(
}
}
if (generator.components.has(node.name) || node.name === ':Self') {
if (generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch') {
return visitComponent(generator, block, state, node, elementStack, componentStack);
}

@ -171,6 +171,7 @@ export default function tag(parser: Parser) {
element.expression = readExpression(parser);
parser.allowWhitespace();
parser.eat('}', true);
parser.allowWhitespace();
}
const uniqueNames = new Set();
@ -181,8 +182,6 @@ export default function tag(parser: Parser) {
parser.allowWhitespace();
}
parser.allowWhitespace();
// special cases top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name);

@ -1,4 +1,5 @@
import assert from "assert";
import chalk from 'chalk';
import * as path from "path";
import * as fs from "fs";
import * as acorn from "acorn";
@ -89,6 +90,9 @@ describe("runtime", () => {
}
} catch (err) {
failed.add(dir);
if (err.frame) {
console.error(chalk.red(err.frame)); // eslint-disable-line no-console
}
showOutput(cwd, { shared, format: 'cjs', store: !!compileOptions.store }, svelte); // eslint-disable-line no-console
throw err;
}

@ -0,0 +1 @@
<p>{{x}}, therefore Bar</p>

@ -0,0 +1 @@
<p>{{x}}, therefore Foo</p>

@ -0,0 +1,19 @@
export default {
data: {
x: true
},
html: `
<p>true, therefore Foo</p>
`,
test(assert, component, target) {
component.set({
x: false
});
assert.htmlEqual(target.innerHTML, `
<p>false, therefore Bar</p>
`);
}
};

@ -0,0 +1,12 @@
<:Switch { x ? Foo : Bar } x='{{x}}'/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>
Loading…
Cancel
Save