quote slot names if necessary - fixes #1461

pull/1464/head
Rich Harris 7 years ago
parent ee052dc7bd
commit c28f62a117

@ -5,7 +5,7 @@ import stringifyProps from '../../utils/stringifyProps';
import CodeBuilder from '../../utils/CodeBuilder'; import CodeBuilder from '../../utils/CodeBuilder';
import getTailSnippet from '../../utils/getTailSnippet'; import getTailSnippet from '../../utils/getTailSnippet';
import getObject from '../../utils/getObject'; import getObject from '../../utils/getObject';
import quoteIfNecessary from '../../utils/quoteIfNecessary'; import { quoteNameIfNecessary } from '../../utils/quoteIfNecessary';
import { escape, escapeTemplate, stringify } from '../../utils/stringify'; import { escape, escapeTemplate, stringify } from '../../utils/stringify';
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../dom/Block'; import Block from '../dom/Block';
@ -126,7 +126,7 @@ export default class Component extends Node {
const componentInitProperties = [`root: #component.root`]; const componentInitProperties = [`root: #component.root`];
if (this.children.length > 0) { if (this.children.length > 0) {
const slots = Array.from(this._slots).map(name => `${quoteIfNecessary(name)}: @createFragment()`); const slots = Array.from(this._slots).map(name => `${quoteNameIfNecessary(name)}: @createFragment()`);
componentInitProperties.push(`slots: { ${slots.join(', ')} }`); componentInitProperties.push(`slots: { ${slots.join(', ')} }`);
this.children.forEach((child: Node) => { this.children.forEach((child: Node) => {
@ -185,7 +185,7 @@ export default class Component extends Node {
changes.push(condition ? `${condition} && ${value}` : value); changes.push(condition ? `${condition} && ${value}` : value);
} else { } else {
const obj = `{ ${quoteIfNecessary(name)}: ${attr.getValue()} }`; const obj = `{ ${quoteNameIfNecessary(name)}: ${attr.getValue()} }`;
initialProps.push(obj); initialProps.push(obj);
changes.push(condition ? `${condition} && ${obj}` : obj); changes.push(condition ? `${condition} && ${obj}` : obj);
@ -602,7 +602,7 @@ export default class Component extends Node {
}); });
const slotted = Object.keys(appendTarget.slots) const slotted = Object.keys(appendTarget.slots)
.map(name => `${name}: () => \`${appendTarget.slots[name]}\``) .map(name => `${quoteNameIfNecessary(name)}: () => \`${appendTarget.slots[name]}\``)
.join(', '); .join(', ');
options.push(`slotted: { ${slotted} }`); options.push(`slotted: { ${slotted} }`);

@ -5,7 +5,7 @@ import isVoidElementName from '../../utils/isVoidElementName';
import validCalleeObjects from '../../utils/validCalleeObjects'; import validCalleeObjects from '../../utils/validCalleeObjects';
import reservedNames from '../../utils/reservedNames'; import reservedNames from '../../utils/reservedNames';
import fixAttributeCasing from '../../utils/fixAttributeCasing'; import fixAttributeCasing from '../../utils/fixAttributeCasing';
import quoteIfNecessary from '../../utils/quoteIfNecessary'; import { quoteNameIfNecessary, quotePropIfNecessary } from '../../utils/quoteIfNecessary';
import Compiler from '../Compiler'; import Compiler from '../Compiler';
import Node from './shared/Node'; import Node from './shared/Node';
import Block from '../dom/Block'; import Block from '../dom/Block';
@ -260,8 +260,9 @@ export default class Element extends Node {
const name = this.var; const name = this.var;
const slot = this.attributes.find((attribute: Node) => attribute.name === 'slot'); const slot = this.attributes.find((attribute: Node) => attribute.name === 'slot');
const prop = slot && quotePropIfNecessary(slot.chunks[0].data);
const initialMountNode = this.slotted ? const initialMountNode = this.slotted ?
`${this.findNearest(/^Component/).var}._slotted.${slot.chunks[0].data}` : // TODO this looks bonkers `${this.findNearest(/^Component/).var}._slotted${prop}` : // TODO this looks bonkers
parentNode; parentNode;
block.addVariable(name); block.addVariable(name);
@ -571,7 +572,7 @@ export default class Element extends Node {
updates.push(condition ? `${condition} && ${snippet}` : snippet); updates.push(condition ? `${condition} && ${snippet}` : snippet);
} else { } else {
const snippet = `{ ${quoteIfNecessary(attr.name)}: ${attr.getValue()} }`; const snippet = `{ ${quoteNameIfNecessary(attr.name)}: ${attr.getValue()} }`;
initialProps.push(snippet); initialProps.push(snippet);
updates.push(condition ? `${condition} && ${snippet}` : snippet); updates.push(condition ? `${condition} && ${snippet}` : snippet);
@ -824,7 +825,8 @@ export default class Element extends Node {
remount(name: string) { remount(name: string) {
const slot = this.attributes.find(attribute => attribute.name === 'slot'); const slot = this.attributes.find(attribute => attribute.name === 'slot');
if (slot) { if (slot) {
return `@appendNode(${this.var}, ${name}._slotted.${this.getStaticAttributeValue('slot')});`; const prop = quotePropIfNecessary(slot.chunks[0].data);
return `@appendNode(${this.var}, ${name}._slotted${prop});`;
} }
return `@appendNode(${this.var}, ${name}._slotted.default);`; return `@appendNode(${this.var}, ${name}._slotted.default);`;
@ -881,16 +883,16 @@ export default class Element extends Node {
if (attribute.name === 'value' && this.name === 'textarea') { if (attribute.name === 'value' && this.name === 'textarea') {
textareaContents = attribute.stringifyForSsr(); textareaContents = attribute.stringifyForSsr();
} else if (attribute.isTrue) { } else if (attribute.isTrue) {
args.push(`{ ${quoteIfNecessary(attribute.name)}: true }`); args.push(`{ ${quoteNameIfNecessary(attribute.name)}: true }`);
} else if ( } else if (
booleanAttributes.has(attribute.name) && booleanAttributes.has(attribute.name) &&
attribute.chunks.length === 1 && attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text' attribute.chunks[0].type !== 'Text'
) { ) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
args.push(`{ ${quoteIfNecessary(attribute.name)}: ${attribute.chunks[0].snippet} }`); args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${attribute.chunks[0].snippet} }`);
} else { } else {
args.push(`{ ${quoteIfNecessary(attribute.name)}: \`${attribute.stringifyForSsr()}\` }`); args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${attribute.stringifyForSsr()}\` }`);
} }
} }
}); });
@ -962,7 +964,7 @@ function getClaimStatement(
) { ) {
const attributes = node.attributes const attributes = node.attributes
.filter((attr: Node) => attr.type === 'Attribute') .filter((attr: Node) => attr.type === 'Attribute')
.map((attr: Node) => `${quoteIfNecessary(attr.name)}: true`) .map((attr: Node) => `${quoteNameIfNecessary(attr.name)}: true`)
.join(', '); .join(', ');
const name = namespace ? node.name : node.name.toUpperCase(); const name = namespace ? node.name : node.name.toUpperCase();

@ -5,6 +5,11 @@ import Node from './shared/Node';
import Element from './Element'; import Element from './Element';
import Attribute from './Attribute'; import Attribute from './Attribute';
import Block from '../dom/Block'; import Block from '../dom/Block';
import { quotePropIfNecessary } from '../../utils/quoteIfNecessary';
function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; type: 'Element';
@ -36,8 +41,8 @@ export default class Slot extends Element {
const slotName = this.getStaticAttributeValue('name') || 'default'; const slotName = this.getStaticAttributeValue('name') || 'default';
compiler.slots.add(slotName); compiler.slots.add(slotName);
const content_name = block.getUniqueName(`slot_content_${slotName}`); const content_name = block.getUniqueName(`slot_content_${sanitize(slotName)}`);
const prop = !isValidIdentifier(slotName) ? `["${slotName}"]` : `.${slotName}`; const prop = quotePropIfNecessary(slotName);
block.addVariable(content_name, `#component._slotted${prop}`); block.addVariable(content_name, `#component._slotted${prop}`);
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode; const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;
@ -151,9 +156,11 @@ export default class Slot extends Element {
ssr() { ssr() {
const name = this.attributes.find(attribute => attribute.name === 'name'); const name = this.attributes.find(attribute => attribute.name === 'name');
const slotName = name && name.chunks[0].data || 'default'; const slotName = name && name.chunks[0].data || 'default';
const prop = quotePropIfNecessary(slotName);
this.compiler.target.append(`\${options && options.slotted && options.slotted.${slotName} ? options.slotted.${slotName}() : \``); this.compiler.target.append(`\${options && options.slotted && options.slotted${prop} ? options.slotted${prop}() : \``);
this.children.forEach((child: Node) => { this.children.forEach((child: Node) => {
child.ssr(); child.ssr();

@ -1,7 +1,12 @@
import isValidIdentifier from './isValidIdentifier'; import isValidIdentifier from './isValidIdentifier';
import reservedNames from './reservedNames'; import reservedNames from './reservedNames';
export default function quoteIfNecessary(name) { export function quoteNameIfNecessary(name) {
if (!isValidIdentifier(name)) return `"${name}"`; if (!isValidIdentifier(name)) return `"${name}"`;
return name; return name;
}
export function quotePropIfNecessary(name) {
if (!isValidIdentifier(name)) return `["${name}"]`;
return `.${name}`;
} }

@ -0,0 +1,3 @@
export default {
html: '<div><p slot="foo-bar">Hello</p></div>'
};

@ -0,0 +1,13 @@
<Nested ref:nested>
<p slot="foo-bar">Hello</p>
</Nested>
<script>
import Nested from './Nested.html';
export default {
components: {
Nested
}
};
</script>
Loading…
Cancel
Save