diff --git a/src/generators/Generator.js b/src/generators/Generator.js
index 12081a03699b..feebc13263d6 100644
--- a/src/generators/Generator.js
+++ b/src/generators/Generator.js
@@ -9,9 +9,10 @@ import getOutro from './shared/utils/getOutro.js';
import annotateWithScopes from './annotateWithScopes.js';
export default class Generator {
- constructor ( parsed, source, names, visitors ) {
+ constructor ( parsed, source, name, names, visitors ) {
this.parsed = parsed;
this.source = source;
+ this.name = name;
this.names = names;
this.visitors = visitors;
diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js
index 709b5d84000d..5ac11c9414c8 100644
--- a/src/generators/dom/index.js
+++ b/src/generators/dom/index.js
@@ -8,8 +8,8 @@ import Generator from '../Generator.js';
import * as shared from '../../shared/index.js';
class DomGenerator extends Generator {
- constructor ( parsed, source, names, visitors ) {
- super( parsed, source, names, visitors );
+ constructor ( parsed, source, name, names, visitors ) {
+ super( parsed, source, name, names, visitors );
this.renderers = [];
this.uses = {};
@@ -152,7 +152,7 @@ export default function dom ( parsed, source, options, names ) {
const format = options.format || 'es';
const name = options.name || 'SvelteComponent';
- const generator = new DomGenerator( parsed, source, names, visitors );
+ const generator = new DomGenerator( parsed, source, name, names, visitors );
const { computations, templateProperties } = generator.parseJs();
diff --git a/src/generators/dom/visitors/Component.js b/src/generators/dom/visitors/Component.js
index edd6a5209c16..f0d0b53d9542 100644
--- a/src/generators/dom/visitors/Component.js
+++ b/src/generators/dom/visitors/Component.js
@@ -2,10 +2,14 @@ import deindent from '../../../utils/deindent.js';
import CodeBuilder from '../../../utils/CodeBuilder.js';
import addComponentAttributes from './attributes/addComponentAttributes.js';
+function capDown ( name ) {
+ return `${name[0].toLowerCase()}${name.slice( 1 )}`;
+}
+
export default {
enter ( generator, node ) {
const hasChildren = node.children.length > 0;
- const name = generator.current.getUniqueName( `${node.name[0].toLowerCase()}${node.name.slice( 1 )}` );
+ const name = generator.current.getUniqueName( capDown( node.name === ':Self' ? generator.name : node.name ) );
const local = {
name,
@@ -104,9 +108,11 @@ export default {
componentInitProperties.push(`data: ${name}_initialData`);
}
+ const expression = node.name === ':Self' ? generator.name : `template.components.${node.name}`;
+
local.init.addBlockAtStart( deindent`
${statements.join( '\n\n' )}
- var ${name} = new template.components.${node.name}({
+ var ${name} = new ${expression}({
${componentInitProperties.join(',\n')}
});
` );
diff --git a/src/generators/dom/visitors/Element.js b/src/generators/dom/visitors/Element.js
index 21da9897b761..3338c088ef2b 100644
--- a/src/generators/dom/visitors/Element.js
+++ b/src/generators/dom/visitors/Element.js
@@ -5,7 +5,7 @@ import Component from './Component.js';
export default {
enter ( generator, node ) {
- const isComponent = node.name in generator.components;
+ const isComponent = node.name in generator.components || node.name === ':Self';
if ( isComponent ) {
return Component.enter( generator, node );
}
diff --git a/src/generators/server-side-rendering/index.js b/src/generators/server-side-rendering/index.js
index 0e42b49dfa73..0ff75221fd93 100644
--- a/src/generators/server-side-rendering/index.js
+++ b/src/generators/server-side-rendering/index.js
@@ -5,8 +5,8 @@ import visitors from './visitors/index.js';
import Generator from '../Generator.js';
class SsrGenerator extends Generator {
- constructor ( parsed, source, names, visitors ) {
- super( parsed, source, names, visitors );
+ constructor ( parsed, source, name, names, visitors ) {
+ super( parsed, source, name, names, visitors );
this.bindings = [];
this.renderCode = '';
}
@@ -18,7 +18,7 @@ class SsrGenerator extends Generator {
this.bindings.push( deindent`
if ( ${conditions.join( '&&' )} ) {
- tmp = template.components.${name}.data();
+ tmp = ${name}.data();
if ( '${binding.value}' in tmp ) {
root.${binding.name} = tmp.${binding.value};
settled = false;
@@ -36,7 +36,7 @@ export default function ssr ( parsed, source, options, names ) {
const format = options.format || 'cjs';
const name = options.name || 'SvelteComponent';
- const generator = new SsrGenerator( parsed, source, names, visitors );
+ const generator = new SsrGenerator( parsed, source, name, names, visitors );
const { computations, templateProperties } = generator.parseJs();
diff --git a/src/generators/server-side-rendering/visitors/Component.js b/src/generators/server-side-rendering/visitors/Component.js
index b75409e356ec..b736af39982e 100644
--- a/src/generators/server-side-rendering/visitors/Component.js
+++ b/src/generators/server-side-rendering/visitors/Component.js
@@ -43,11 +43,13 @@ export default {
})
.join( ', ' );
+ const expression = node.name === ':Self' ? generator.name : `template.components.${node.name}`;
+
bindings.forEach( binding => {
- generator.addBinding( binding, node.name );
+ generator.addBinding( binding, expression );
});
- let open = `\${template.components.${node.name}.render({${props}}`;
+ let open = `\${${expression}.render({${props}}`;
if ( node.children.length ) {
open += `, { yield: () => \``;
diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js
index 863abf27db38..c5fd7abf31bd 100644
--- a/src/generators/server-side-rendering/visitors/Element.js
+++ b/src/generators/server-side-rendering/visitors/Element.js
@@ -3,7 +3,7 @@ import voidElementNames from '../../../utils/voidElementNames.js';
export default {
enter ( generator, node ) {
- if ( node.name in generator.components ) {
+ if ( node.name in generator.components || node.name === ':Self' ) {
Component.enter( generator, node );
return;
}
@@ -39,7 +39,7 @@ export default {
},
leave ( generator, node ) {
- if ( node.name in generator.components ) {
+ if ( node.name in generator.components || node.name === ':Self' ) {
Component.leave( generator, node );
return;
}
diff --git a/src/parse/state/tag.js b/src/parse/state/tag.js
index 11177514b512..210f7b243440 100644
--- a/src/parse/state/tag.js
+++ b/src/parse/state/tag.js
@@ -9,6 +9,8 @@ import voidElementNames from '../../utils/voidElementNames.js';
const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/;
+const SELF = ':Self';
+
const specials = {
script: {
read: readScript,
@@ -170,6 +172,27 @@ export default function tag ( parser ) {
function readTagName ( parser ) {
const start = parser.index;
+ if ( parser.eat( SELF ) ) {
+ // check we're inside a block, otherwise this
+ // will cause infinite recursion
+ let i = parser.stack.length;
+ let legal = false;
+
+ while ( i-- ) {
+ const fragment = parser.stack[i];
+ if ( fragment.type === 'IfBlock' || fragment.type === 'ElseBlock' ) {
+ legal = true;
+ break;
+ }
+ }
+
+ if ( !legal ) {
+ parser.error( `<${SELF}> components can only exist inside if-blocks or each-blocks`, start );
+ }
+
+ return SELF;
+ }
+
const name = parser.readUntil( /(\s|\/|>)/ );
if ( !validTagName.test( name ) ) {
parser.error( `Expected valid tag name`, start );
diff --git a/test/generator/self-reference/_config.js b/test/generator/self-reference/_config.js
new file mode 100644
index 000000000000..dabb46783384
--- /dev/null
+++ b/test/generator/self-reference/_config.js
@@ -0,0 +1,14 @@
+export default {
+ data: {
+ depth: 5
+ },
+
+ html: `
+ 5
+ 4
+ 3
+ 2
+ 1
+ 0
+ `
+};
\ No newline at end of file
diff --git a/test/generator/self-reference/main.html b/test/generator/self-reference/main.html
new file mode 100644
index 000000000000..d310b340cec3
--- /dev/null
+++ b/test/generator/self-reference/main.html
@@ -0,0 +1,4 @@
+{{depth}}
+{{#if depth > 0}}
+ <:Self depth='{{depth - 1}}'/>
+{{/if}}
\ No newline at end of file
diff --git a/test/parser/error-self-reference/error.json b/test/parser/error-self-reference/error.json
new file mode 100644
index 000000000000..37d0dee1ab50
--- /dev/null
+++ b/test/parser/error-self-reference/error.json
@@ -0,0 +1,8 @@
+{
+ "message": "<:Self> components can only exist inside if-blocks or each-blocks",
+ "loc": {
+ "line": 1,
+ "column": 1
+ },
+ "pos": 1
+}
\ No newline at end of file
diff --git a/test/parser/error-self-reference/input.html b/test/parser/error-self-reference/input.html
new file mode 100644
index 000000000000..21b76e81bff0
--- /dev/null
+++ b/test/parser/error-self-reference/input.html
@@ -0,0 +1 @@
+<:Self/>
\ No newline at end of file
diff --git a/test/parser/self-reference/input.html b/test/parser/self-reference/input.html
new file mode 100644
index 000000000000..cda950d2e2a1
--- /dev/null
+++ b/test/parser/self-reference/input.html
@@ -0,0 +1,3 @@
+{{#if depth > 1}}
+ <:Self depth='{{depth - 1}}'/>
+{{/if}}
\ No newline at end of file
diff --git a/test/parser/self-reference/output.json b/test/parser/self-reference/output.json
new file mode 100644
index 000000000000..fd5c1543e4e0
--- /dev/null
+++ b/test/parser/self-reference/output.json
@@ -0,0 +1,79 @@
+{
+ "hash": 1792372370,
+ "html": {
+ "start": 0,
+ "end": 57,
+ "type": "Fragment",
+ "children": [
+ {
+ "start": 0,
+ "end": 57,
+ "type": "IfBlock",
+ "expression": {
+ "type": "BinaryExpression",
+ "start": 6,
+ "end": 15,
+ "left": {
+ "type": "Identifier",
+ "start": 6,
+ "end": 11,
+ "name": "depth"
+ },
+ "operator": ">",
+ "right": {
+ "type": "Literal",
+ "start": 14,
+ "end": 15,
+ "value": 1,
+ "raw": "1"
+ }
+ },
+ "children": [
+ {
+ "start": 19,
+ "end": 49,
+ "type": "Element",
+ "name": ":Self",
+ "attributes": [
+ {
+ "start": 26,
+ "end": 47,
+ "type": "Attribute",
+ "name": "depth",
+ "value": [
+ {
+ "start": 33,
+ "end": 46,
+ "type": "MustacheTag",
+ "expression": {
+ "type": "BinaryExpression",
+ "start": 35,
+ "end": 44,
+ "left": {
+ "type": "Identifier",
+ "start": 35,
+ "end": 40,
+ "name": "depth"
+ },
+ "operator": "-",
+ "right": {
+ "type": "Literal",
+ "start": 43,
+ "end": 44,
+ "value": 1,
+ "raw": "1"
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "children": []
+ }
+ ]
+ }
+ ]
+ },
+ "css": null,
+ "js": null
+}
\ No newline at end of file