Skip to content

Commit

Permalink
client-side dynamic components mostly working (#640)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Dec 3, 2017
1 parent 4f99153 commit dba32df
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 33 deletions.
4 changes: 4 additions & 0 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions src/generators/dom/preprocess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand Down
141 changes: 113 additions & 28 deletions src/generators/dom/visitors/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/generators/dom/visitors/Element/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
3 changes: 1 addition & 2 deletions src/parse/state/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions test/runtime/index.js
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions test/runtime/samples/switch/Bar.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{{x}}, therefore Bar</p>
1 change: 1 addition & 0 deletions test/runtime/samples/switch/Foo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>{{x}}, therefore Foo</p>
19 changes: 19 additions & 0 deletions test/runtime/samples/switch/_config.js
Original file line number Diff line number Diff line change
@@ -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>
`);
}
};
12 changes: 12 additions & 0 deletions test/runtime/samples/switch/main.html
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit dba32df

Please sign in to comment.