Skip to content

Commit dba32df

Browse files
committed
client-side dynamic components mostly working (#640)
1 parent 4f99153 commit dba32df

File tree

10 files changed

+162
-33
lines changed

10 files changed

+162
-33
lines changed

src/generators/Generator.ts

+4
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,10 @@ export default class Generator {
763763
node.metadata = contextualise(node.expression, contextDependencies, indexes);
764764
this.skip();
765765
}
766+
767+
if (node.type === 'Element' && node.name === ':Switch') {
768+
node.metadata = contextualise(node.expression, contextDependencies, indexes);
769+
}
766770
},
767771

768772
leave(node: Node, parent: Node) {

src/generators/dom/preprocess.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -436,13 +436,17 @@ const preprocessors = {
436436
}
437437

438438
const isComponent =
439-
generator.components.has(node.name) || node.name === ':Self';
439+
generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch';
440440

441441
if (isComponent) {
442442
cannotUseInnerHTML(node);
443443

444444
node.var = block.getUniqueName(
445-
(node.name === ':Self' ? generator.name : node.name).toLowerCase()
445+
(
446+
node.name === ':Self' ? generator.name :
447+
node.name === ':Switch' ? 'switch_instance' :
448+
node.name
449+
).toLowerCase()
446450
);
447451

448452
node._state = getChildState(state, {

src/generators/dom/visitors/Component.ts

+113-28
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import CodeBuilder from '../../../utils/CodeBuilder';
33
import visit from '../visit';
44
import { DomGenerator } from '../index';
55
import Block from '../Block';
6+
import isDomNode from './shared/isDomNode';
67
import getTailSnippet from '../../../utils/getTailSnippet';
78
import getObject from '../../../utils/getObject';
89
import getExpressionPrecedence from '../../../utils/getExpressionPrecedence';
@@ -67,6 +68,9 @@ export default function visitComponent(
6768
.filter((a: Node) => a.type === 'Binding')
6869
.map((a: Node) => mungeBinding(a, block));
6970

71+
const ref = node.attributes.find((a: Node) => a.type === 'Ref');
72+
if (ref) generator.usesRefs = true;
73+
7074
if (attributes.length || bindings.length) {
7175
const initialProps = attributes
7276
.map((attribute: Attribute) => `${attribute.name}: ${attribute.value}`);
@@ -205,30 +209,122 @@ export default function visitComponent(
205209
}
206210
}
207211

208-
const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`;
212+
const isSwitch = node.name === ':Switch';
209213

210-
block.builders.init.addBlock(deindent`
211-
${statements.join('\n')}
212-
var ${name} = new ${expression}({
213-
${componentInitProperties.join(',\n')}
214-
});
214+
const switch_vars = isSwitch && {
215+
value: block.getUniqueName('switch_value'),
216+
props: block.getUniqueName('switch_props')
217+
};
215218

216-
${beforecreate}
217-
`);
219+
const expression = (
220+
node.name === ':Self' ? generator.name :
221+
isSwitch ? switch_vars.value :
222+
`%components-${node.name}`
223+
);
218224

219-
block.builders.create.addLine(`${name}._fragment.c();`);
225+
if (isSwitch) {
226+
block.contextualise(node.expression);
227+
const { dependencies, snippet } = node.metadata;
228+
229+
const needsAnchor = node.next ? !isDomNode(node.next, generator) : !state.parentNode || !isDomNode(node.parent, generator);
230+
const anchor = needsAnchor
231+
? block.getUniqueName(`${name}_anchor`)
232+
: (node.next && node.next.var) || 'null';
233+
234+
if (needsAnchor) {
235+
block.addElement(
236+
anchor,
237+
`@createComment()`,
238+
`@createComment()`,
239+
state.parentNode
240+
);
241+
}
220242

221-
block.builders.claim.addLine(
222-
`${name}._fragment.l(${state.parentNodes});`
223-
);
243+
const params = block.params.join(', ');
224244

225-
block.builders.mount.addLine(
226-
`${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
227-
);
245+
block.builders.init.addBlock(deindent`
246+
var ${switch_vars.value} = ${snippet};
247+
`);
228248

229-
if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`);
249+
block.builders.init.addBlock(deindent`
250+
function ${switch_vars.props}(${params}) {
251+
return {
252+
${componentInitProperties.join(',\n')}
253+
};
254+
}
255+
256+
if (${switch_vars.value}) {
257+
${statements.length > 0 && statements.join('\n')}
258+
var ${name} = new ${expression}(${switch_vars.props}(${params}));
259+
260+
${beforecreate}
261+
}
262+
`);
263+
264+
block.builders.create.addLine(
265+
`if (${name}) ${name}._fragment.c();`
266+
);
267+
268+
block.builders.claim.addLine(
269+
`if (${name}) ${name}._fragment.l(${state.parentNodes});`
270+
);
271+
272+
block.builders.mount.addLine(
273+
`if (${name}) ${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
274+
);
275+
276+
block.builders.update.addBlock(deindent`
277+
if (${switch_vars.value} !== (${switch_vars.value} = ${snippet})) {
278+
if (${name}) ${name}.destroy();
230279
231-
block.builders.destroy.addLine(`${name}.destroy(false);`);
280+
if (${switch_vars.value}) {
281+
${name} = new ${switch_vars.value}(${switch_vars.props}(${params}));
282+
${name}._fragment.c();
283+
${name}._mount(${anchor}.parentNode, ${anchor});
284+
${ref && `#component.refs.${ref.name} = ${name};`}
285+
}
286+
287+
${ref && deindent`
288+
else if (#component.refs.${ref.name} === ${name}) {
289+
#component.refs.${ref.name} = null;
290+
}`}
291+
} else {
292+
// normal update
293+
}
294+
`);
295+
296+
if (!state.parentNode) block.builders.unmount.addLine(`if (${name}) ${name}._unmount();`);
297+
298+
block.builders.destroy.addLine(`if (${name}) ${name}.destroy(false);`);
299+
} else {
300+
block.builders.init.addBlock(deindent`
301+
${statements.join('\n')}
302+
var ${name} = new ${expression}({
303+
${componentInitProperties.join(',\n')}
304+
});
305+
306+
${beforecreate}
307+
308+
${ref && `#component.refs.${ref.name} = ${name};`}
309+
`);
310+
311+
block.builders.create.addLine(`${name}._fragment.c();`);
312+
313+
block.builders.claim.addLine(
314+
`${name}._fragment.l(${state.parentNodes});`
315+
);
316+
317+
block.builders.mount.addLine(
318+
`${name}._mount(${state.parentNode || '#target'}, ${state.parentNode ? 'null' : 'anchor'});`
319+
);
320+
321+
if (!state.parentNode) block.builders.unmount.addLine(`${name}._unmount();`);
322+
323+
block.builders.destroy.addLine(deindent`
324+
${name}.destroy(false);
325+
${ref && `if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null;`}
326+
`);
327+
}
232328

233329
// event handlers
234330
node.attributes.filter((a: Node) => a.type === 'EventHandler').forEach((handler: Node) => {
@@ -274,17 +370,6 @@ export default function visitComponent(
274370
`);
275371
});
276372

277-
// refs
278-
node.attributes.filter((a: Node) => a.type === 'Ref').forEach((ref: Node) => {
279-
generator.usesRefs = true;
280-
281-
block.builders.init.addLine(`#component.refs.${ref.name} = ${name};`);
282-
283-
block.builders.destroy.addLine(deindent`
284-
if (#component.refs.${ref.name} === ${name}) #component.refs.${ref.name} = null;
285-
`);
286-
});
287-
288373
// maintain component context
289374
if (allContexts.size) {
290375
const contexts = Array.from(allContexts);

src/generators/dom/visitors/Element/Element.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export default function visitElement(
4343
}
4444
}
4545

46-
if (generator.components.has(node.name) || node.name === ':Self') {
46+
if (generator.components.has(node.name) || node.name === ':Self' || node.name === ':Switch') {
4747
return visitComponent(generator, block, state, node, elementStack, componentStack);
4848
}
4949

src/parse/state/tag.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ export default function tag(parser: Parser) {
171171
element.expression = readExpression(parser);
172172
parser.allowWhitespace();
173173
parser.eat('}', true);
174+
parser.allowWhitespace();
174175
}
175176

176177
const uniqueNames = new Set();
@@ -181,8 +182,6 @@ export default function tag(parser: Parser) {
181182
parser.allowWhitespace();
182183
}
183184

184-
parser.allowWhitespace();
185-
186185
// special cases – top-level <script> and <style>
187186
if (specials.has(name) && parser.stack.length === 1) {
188187
const special = specials.get(name);

test/runtime/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import assert from "assert";
2+
import chalk from 'chalk';
23
import * as path from "path";
34
import * as fs from "fs";
45
import * as acorn from "acorn";
@@ -89,6 +90,9 @@ describe("runtime", () => {
8990
}
9091
} catch (err) {
9192
failed.add(dir);
93+
if (err.frame) {
94+
console.error(chalk.red(err.frame)); // eslint-disable-line no-console
95+
}
9296
showOutput(cwd, { shared, format: 'cjs', store: !!compileOptions.store }, svelte); // eslint-disable-line no-console
9397
throw err;
9498
}

test/runtime/samples/switch/Bar.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>{{x}}, therefore Bar</p>

test/runtime/samples/switch/Foo.html

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>{{x}}, therefore Foo</p>
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default {
2+
data: {
3+
x: true
4+
},
5+
6+
html: `
7+
<p>true, therefore Foo</p>
8+
`,
9+
10+
test(assert, component, target) {
11+
component.set({
12+
x: false
13+
});
14+
15+
assert.htmlEqual(target.innerHTML, `
16+
<p>false, therefore Bar</p>
17+
`);
18+
}
19+
};

test/runtime/samples/switch/main.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<:Switch { x ? Foo : Bar } x='{{x}}'/>
2+
3+
<script>
4+
import Foo from './Foo.html';
5+
import Bar from './Bar.html';
6+
7+
export default {
8+
data() {
9+
return { Foo, Bar };
10+
}
11+
};
12+
</script>

0 commit comments

Comments
 (0)