implement dynamic components etc

pull/1330/head
Rich Harris 7 years ago
parent e85eda8279
commit 9bba8d18d1

@ -875,7 +875,7 @@ export default class Generator {
this.skip();
}
if (node.type === 'Component' && node.name === ':Component') {
if (node.type === 'Component' && (node.name === ':Component' || node.name === 'svelte:component')) {
node.metadata = contextualise(node.expression, contextDependencies, indexes, false);
}

@ -292,7 +292,7 @@ export default class Component extends Node {
`;
}
if (this.name === ':Component') {
if (this.name === ':Component' || this.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props');

@ -87,7 +87,7 @@ export default function visitComponent(
.concat(bindingProps)
.join(', ')} }`;
const isDynamicComponent = node.name === ':Component';
const isDynamicComponent = node.name === ':Component' || node.name === 'svelte:component';
if (isDynamicComponent) block.contextualise(node.expression);
const expression = (

@ -169,7 +169,7 @@ export default function tag(parser: Parser) {
}
}
if (name === (parser.v2 ? 'svelte:component' : ':Component')) {
if (name === ':Component') {
parser.eat('{', true);
element.expression = readExpression(parser);
parser.allowWhitespace();
@ -189,6 +189,21 @@ export default function tag(parser: Parser) {
parser.allowWhitespace();
}
if (parser.v2 && name === 'svelte:component') {
// TODO post v2, treat this just as any other attribute
const index = element.attributes.findIndex(attr => attr.name === 'this');
if (!~index) {
parser.error(`<svelte:component> must have a 'this' attribute`, start);
}
const definition = element.attributes.splice(index, 1)[0];
if (definition.value === true || definition.value.length !== 1 || definition.value[0].type === 'Text') {
parser.error(`invalid component definition`, definition.start);
}
element.expression = definition.value[0].expression;
}
// special cases top-level <script> and <style>
if (specials.has(name) && parser.stack.length === 1) {
const special = specials.get(name);
@ -348,8 +363,8 @@ function readAttribute(parser: Parser, uniqueNames: Set<string>) {
parser.allowWhitespace();
const attribute = readDirective(parser, start, name);
if (attribute) return attribute;
const directive = readDirective(parser, start, name);
if (directive) return directive;
let value = parser.eat('=') ? readAttributeValue(parser) : true;

@ -178,6 +178,13 @@ export function showOutput(cwd, options = {}, compile = svelte.compile) {
glob.sync('**/*.html', { cwd }).forEach(file => {
if (file[0] === '_') return;
// TODO remove this post-v2
if (/-v2\.html$/.test(file)) {
if (!options.v2) return;
} else if (options.v2 && fs.existsSync(`${cwd}/${file.replace('.html', '-v2.html')}`)) {
return;
}
const name = path.basename(file)
.slice(0, -path.extname(file).length)
.replace(/^\d/, '_$&')
@ -187,7 +194,8 @@ export function showOutput(cwd, options = {}, compile = svelte.compile) {
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
Object.assign(options, {
filename: file,
name: capitalise(name)
name: capitalise(name),
parser: options.v2 ? 'v2' : 'v1'
})
);

@ -1 +1 @@
<svelte:component {foo ? Foo : Bar}></svelte:component>
<svelte:component this="{foo ? Foo : Bar}"></svelte:component>

@ -1,37 +1,37 @@
{
"hash": "7yh2k2",
"hash": "2u5ec3",
"html": {
"start": 0,
"end": 55,
"end": 62,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 55,
"end": 62,
"type": "Element",
"name": "svelte:component",
"attributes": [],
"children": [],
"expression": {
"type": "ConditionalExpression",
"start": 19,
"end": 34,
"start": 25,
"end": 40,
"test": {
"type": "Identifier",
"start": 19,
"end": 22,
"start": 25,
"end": 28,
"name": "foo"
},
"consequent": {
"type": "Identifier",
"start": 25,
"end": 28,
"start": 31,
"end": 34,
"name": "Foo"
},
"alternate": {
"type": "Identifier",
"start": 31,
"end": 34,
"start": 37,
"end": 40,
"name": "Bar"
}
}

@ -21,7 +21,7 @@ let compileOptions = null;
let compile = null;
function getName(filename) {
const base = path.basename(filename).replace(".html", "");
const base = path.basename(filename).replace('-v2', '').replace(".html", "");
return base[0].toUpperCase() + base.slice(1);
}
@ -46,7 +46,7 @@ describe("runtime", () => {
const failed = new Set();
function runTest(dir, shared, hydrate) {
function runTest(dir, shared, hydrate, v2) {
if (dir[0] === ".") return;
const config = loadConfig(`./runtime/samples/${dir}/_config.js`);
@ -55,7 +55,7 @@ describe("runtime", () => {
throw new Error("Forgot to remove `solo: true` from test");
}
(config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers${hydrate ? ' , hydration' : ''})`, () => {
(config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers${hydrate ? ', hydration' : ''}${v2 ? ', v2' : ''})`, () => {
if (failed.has(dir)) {
// this makes debugging easier, by only printing compiled output once
throw new Error('skipping test, already failed');
@ -72,12 +72,13 @@ describe("runtime", () => {
compileOptions.dev = config.dev;
compileOptions.store = !!config.store;
compileOptions.immutable = config.immutable;
compileOptions.parser = v2 ? 'v2' : 'v1';
// check that no ES2015+ syntax slipped in
if (!config.allowES2015) {
try {
const source = fs.readFileSync(
`test/runtime/samples/${dir}/main.html`,
`test/runtime/samples/${dir}/main${v2 ? '-v2' : ''}.html`,
"utf-8"
);
const { code } = compile(source, compileOptions);
@ -100,7 +101,7 @@ describe("runtime", () => {
if (err.frame) {
console.error(chalk.red(err.frame)); // eslint-disable-line no-console
}
showOutput(cwd, { shared, format: 'cjs', store: !!compileOptions.store }, compile); // eslint-disable-line no-console
showOutput(cwd, { shared, format: 'cjs', store: !!compileOptions.store, v2 }, compile); // eslint-disable-line no-console
throw err;
}
}
@ -143,7 +144,7 @@ describe("runtime", () => {
};
try {
SvelteComponent = require(`./samples/${dir}/main.html`);
SvelteComponent = require(`./samples/${dir}/main${v2 ? '-v2' : ''}.html`);
} catch (err) {
showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, compile); // eslint-disable-line no-console
throw err;
@ -203,12 +204,12 @@ describe("runtime", () => {
config.error(assert, err);
} else {
failed.add(dir);
showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, compile); // eslint-disable-line no-console
showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, v2 }, compile); // eslint-disable-line no-console
throw err;
}
})
.then(() => {
if (config.show) showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store }, compile);
if (config.show) showOutput(cwd, { shared, format: 'cjs', hydratable: hydrate, store: !!compileOptions.store, v2 }, compile);
});
});
}
@ -218,6 +219,10 @@ describe("runtime", () => {
runTest(dir, shared, false);
runTest(dir, shared, true);
runTest(dir, null, false);
if (fs.existsSync(`test/runtime/samples/${dir}/main-v2.html`)) {
runTest(dir, shared, false, true);
}
});
it("fails if options.target is missing in dev mode", () => {

@ -0,0 +1,52 @@
<button use:tooltip="tooltip">action</button>
<svelte:window on:keydown="checkForCtrl(event)" on:keyup="checkForCtrl(event)"/>
<script>
export default {
data() {
return { tooltip: 'Perform an Action' };
},
methods: {
checkForCtrl(event) {
if (event.ctrlKey) {
this.set({ tooltip: 'Perform an augmented Action' });
} else {
this.set({ tooltip: 'Perform an Action' });
}
}
},
actions: {
tooltip(node, text) {
let tooltip = null;
function onMouseEnter() {
tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.textContent = text;
node.parentNode.appendChild(tooltip);
}
function onMouseLeave() {
if (!tooltip) return;
tooltip.remove();
tooltip = null;
}
node.addEventListener('mouseenter', onMouseEnter);
node.addEventListener('mouseleave', onMouseLeave);
return {
update(text) {
if (tooltip) tooltip.textContent = text;
},
destroy() {
node.removeEventListener('mouseenter', onMouseEnter);
node.removeEventListener('mouseleave', onMouseLeave);
}
}
}
}
}
</script>

@ -0,0 +1,13 @@
<svelte:component this="{x ? Foo : Bar}"/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
components: {
Foo,
Bar
}
};
</script>

@ -0,0 +1,15 @@
<svelte:component this="{x ? Green : Red}" bind:foo />
<script>
import Green from './Green-v2.html';
import Red from './Red-v2.html';
export default {
data() {
return {
Green,
Red
};
}
};
</script>

@ -0,0 +1,12 @@
<svelte:component this="{ x ? Foo : Bar }" bind:y bind:z/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>

@ -0,0 +1,12 @@
<svelte:component this="{ x ? Foo : Bar }" on:select='set({ selected: event.id })'/>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>

@ -0,0 +1,14 @@
<div>
<svelte:component this="{ x ? Foo : Bar }" x='{x}'/>
</div>
<script>
import Foo from './Foo-v2.html';
import Bar from './Bar-v2.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>

@ -0,0 +1,11 @@
<svelte:component this={foo} ref:test/>
<script>
import Foo from './Foo.html';
export default {
data() {
return { foo: Foo };
}
};
</script>

@ -0,0 +1,45 @@
<svelte:component this="{ x ? Foo : Bar }" x='{x}'>
<p>element</p>
{tag}
{#if foo}
<p>foo</p>
{:elseif bar}
<p>bar</p>
{:else}
<p>neither foo nor bar</p>
{/if}
text
{#each things as thing}
<span>{thing}</span>
{/each}
<Baz/>
<div slot='other'>what goes up must come down</div>
</svelte:component>
<script>
import Foo from './Foo.html';
import Bar from './Bar.html';
import Baz from './Baz.html';
export default {
data() {
return {
Foo,
Bar,
tag: 'you\'re it',
things: ['a', 'b', 'c']
};
},
components: {
Baz
}
};
</script>

@ -0,0 +1,12 @@
<svelte:component this="{ x ? Foo : Bar }" x='{x}'/>
<script>
import Foo from './Foo-v2.html';
import Bar from './Bar-v2.html';
export default {
data() {
return { Foo, Bar };
}
};
</script>

@ -0,0 +1,4 @@
<svelte:head>
<title>a {adjective} title</title>
<meta name='twitter:creator' content='@sveltejs'>
</svelte:head>

@ -0,0 +1,4 @@
<svelte:head>
<title>changed</title>
<meta name='twitter:creator' content='@sveltejs'>
</svelte:head>

@ -0,0 +1,11 @@
<article class='file {file.type}'>
<span class='name'>{file.name}</span>
{#if file.type === 'folder'}
<ul>
{#each file.children as child}
<li><svelte:self file='{child}'/></li>
{/each}
</ul>
{/if}
</article>

@ -0,0 +1,4 @@
<span>{depth}</span>
{#if depth > 0}
<svelte:self depth='{depth - 1}'/>
{/if}

@ -0,0 +1,3 @@
<svelte:window bind:scrollY/>
<div style='width: 100%; height: 9999px;'></div>

@ -0,0 +1,3 @@
<svelte:window bind:innerWidth='width' bind:innerHeight='height'/>
<div>{width}x{height}</div>

@ -0,0 +1,3 @@
<svelte:window on:click='set({ foo: !foo })'/>
{foo}

@ -0,0 +1,25 @@
<svelte:window on:esc="set({ escaped: true })" />
<p>escaped: {escaped}</p>
<script>
export default {
data() {
return { escaped: false };
},
events: {
esc(node, callback) {
function onKeyDown(event) {
if (event.which === 27) callback(event);
}
node.addEventListener('keydown', onKeyDown);
return {
destroy() {
node.removeEventListener('keydown', onKeyDown);
}
};
}
}
};
</script>
Loading…
Cancel
Save