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