diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 54263c3eb9..b12bcd88cc 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -208,7 +208,7 @@ export default { }, invalid_attribute_value: (name: string) => ({ code: `invalid-${name}-value`, - message: `${name} attribute must be true or false` + message: `${name} attribute must be true or false` }), invalid_options_attribute_unknown: { code: 'invalid-options-attribute', diff --git a/src/compiler/compile/nodes/Animation.ts b/src/compiler/compile/nodes/Animation.ts index 132688f402..ac9dddd275 100644 --- a/src/compiler/compile/nodes/Animation.ts +++ b/src/compiler/compile/nodes/Animation.ts @@ -5,7 +5,6 @@ import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; import EachBlock from './EachBlock'; -import DynamicElement from './DynamicElement'; import compiler_errors from '../compiler_errors'; export default class Animation extends Node { @@ -13,7 +12,7 @@ export default class Animation extends Node { name: string; expression: Expression; - constructor(component: Component, parent: Element | DynamicElement, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); component.warn_if_undefined(info.name, info, scope); diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 4861ebe605..1efc1a3038 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -10,7 +10,6 @@ import Element from './Element'; import InlineComponent from './InlineComponent'; import Window from './Window'; import { clone } from '../../utils/clone'; -import DynamicElement from './DynamicElement'; import compiler_errors from '../compiler_errors'; // TODO this should live in a specific binding @@ -33,7 +32,7 @@ export default class Binding extends Node { is_contextual: boolean; is_readonly: boolean; - constructor(component: Component, parent: Element | InlineComponent | Window | DynamicElement, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element | InlineComponent | Window, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); if (info.expression.type !== 'Identifier' && info.expression.type !== 'MemberExpression') { diff --git a/src/compiler/compile/nodes/DynamicElement.ts b/src/compiler/compile/nodes/DynamicElement.ts deleted file mode 100644 index eae94f47e4..0000000000 --- a/src/compiler/compile/nodes/DynamicElement.ts +++ /dev/null @@ -1,151 +0,0 @@ -import Node from './shared/Node'; -import Attribute from './Attribute'; -import Binding from './Binding'; -import EventHandler from './EventHandler'; -import Let from './Let'; -import TemplateScope from './shared/TemplateScope'; -import { INode } from './interfaces'; -import Expression from './shared/Expression'; -import Component from '../Component'; -import map_children from './shared/map_children'; -import Class from './Class'; -import Transition from './Transition'; -import Animation from './Animation'; -import Action from './Action'; -import { string_literal } from '../utils/stringify'; -import { Literal } from 'estree'; -import Text from './Text'; - -export default class DynamicElement extends Node { - type: 'DynamicElement'; - name: string; - tag: Expression; - attributes: Attribute[] = []; - actions: Action[] = []; - bindings: Binding[] = []; - classes: Class[] = []; - handlers: EventHandler[] = []; - lets: Let[] = []; - intro?: Transition = null; - outro?: Transition = null; - animation?: Animation = null; - children: INode[]; - scope: TemplateScope; - needs_manual_style_scoping: boolean; - - constructor(component: Component, parent, scope, info) { - super(component, parent, scope, info); - - this.name = info.name; - - if (typeof info.tag === 'string') { - this.tag = new Expression(component, this, scope, string_literal(info.tag) as Literal); - } else { - this.tag = new Expression(component, this, scope, info.tag); - } - - info.attributes.forEach((node) => { - switch (node.type) { - case 'Action': - this.actions.push(new Action(component, this, scope, node)); - break; - - case 'Attribute': - case 'Spread': - this.attributes.push(new Attribute(component, this, scope, node)); - break; - - case 'Binding': - this.bindings.push(new Binding(component, this, scope, node)); - break; - - case 'Class': - this.classes.push(new Class(component, this, scope, node)); - break; - - case 'EventHandler': - this.handlers.push(new EventHandler(component, this, scope, node)); - break; - - case 'Let': { - const l = new Let(component, this, scope, node); - this.lets.push(l); - const dependencies = new Set([l.name.name]); - - l.names.forEach((name) => { - scope.add(name, dependencies, this); - }); - break; - } - - case 'Transition': { - const transition = new Transition(component, this, scope, node); - if (node.intro) this.intro = transition; - if (node.outro) this.outro = transition; - break; - } - - case 'Animation': - this.animation = new Animation(component, this, scope, node); - break; - - default: - throw new Error(`Not implemented: ${node.type}`); - } - }); - - this.scope = scope; - - this.children = map_children(component, this, this.scope, info.children); - - this.validate(); - - // TODO create BaseElement class or an interface which both DynamicElement and Element use - // to resolve the hacky cast - component.apply_stylesheet(this as any); - } - - validate() { - this.bindings.forEach(binding => { - if (binding.name !== 'this') { - this.component.error(binding, { - code: 'invalid-binding', - message: `'${binding.name}' is not a valid binding. svelte:element only supports bind:this` - }); - } - }); - } - - add_css_class() { - if (this.attributes.some(attr => attr.is_spread)) { - this.needs_manual_style_scoping = true; - return; - } - - const { id } = this.component.stylesheet; - - const class_attribute = this.attributes.find(a => a.name === 'class'); - - if (class_attribute && !class_attribute.is_true) { - if (class_attribute.chunks.length === 1 && class_attribute.chunks[0].type === 'Text') { - (class_attribute.chunks[0] as Text).data += ` ${id}`; - } else { - (class_attribute.chunks as Node[]).push( - new Text(this.component, this, this.scope, { - type: 'Text', - data: ` ${id}`, - synthetic: true - } as any) - ); - } - } else { - this.attributes.push( - new Attribute(this.component, this, this.scope, { - type: 'Attribute', - name: 'class', - value: [{ type: 'Text', data: id, synthetic: true }] - } as any) - ); - } - } -} diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 74c70d2385..a495c4f58a 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -13,6 +13,8 @@ import map_children from './shared/map_children'; import { dimensions } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; +import { string_literal } from '../utils/stringify'; +import { Literal } from 'estree'; import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; @@ -132,12 +134,25 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; - dynamic_tag?: Expression; + // If tag is , it will be set. + dynamic_tag_expr?: Expression = null; + + get is_dynamic_tag(): boolean { + return this.name === 'svelte:element'; + } constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { super(component, parent, scope, info); this.name = info.name; + if (this.name === 'svelte:element') { + if (typeof info.tag === 'string') { + this.dynamic_tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal); + } else { + this.dynamic_tag_expr = new Expression(component, this, scope, info.tag); + } + } + this.namespace = get_namespace(parent as Element, this, component.namespace); if (this.namespace !== namespaces.foreign) { diff --git a/src/compiler/compile/nodes/Transition.ts b/src/compiler/compile/nodes/Transition.ts index 08edb9771c..78799ac7a9 100644 --- a/src/compiler/compile/nodes/Transition.ts +++ b/src/compiler/compile/nodes/Transition.ts @@ -4,7 +4,6 @@ import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; -import DynamicElement from './DynamicElement'; import compiler_errors from '../compiler_errors'; export default class Transition extends Node { @@ -14,7 +13,7 @@ export default class Transition extends Node { expression: Expression; is_local: boolean; - constructor(component: Component, parent: Element | DynamicElement, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); component.warn_if_undefined(info.name, info, scope); diff --git a/src/compiler/compile/nodes/interfaces.ts b/src/compiler/compile/nodes/interfaces.ts index e2d406b3cf..a98c21511f 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -32,7 +32,6 @@ import ThenBlock from './ThenBlock'; import Title from './Title'; import Transition from './Transition'; import Window from './Window'; -import DynamicElement from './DynamicElement'; // note: to write less types each of types in union below should have type defined as literal // https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions @@ -46,7 +45,6 @@ export type INode = Action | Class | Comment | DebugTag -| DynamicElement | EachBlock | Element | ElseBlock diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts index 42edbe1ef3..c6a9ac1747 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -1,7 +1,6 @@ import AwaitBlock from '../AwaitBlock'; import Body from '../Body'; import Comment from '../Comment'; -import DynamicElement from '../DynamicElement'; import EachBlock from '../EachBlock'; import Element from '../Element'; import Head from '../Head'; @@ -26,7 +25,7 @@ function get_constructor(type) { case 'AwaitBlock': return AwaitBlock; case 'Body': return Body; case 'Comment': return Comment; - case 'DynamicElement' : return DynamicElement; + case 'DynamicElement' : return Element; case 'EachBlock': return EachBlock; case 'Element': return Element; case 'Head': return Head; diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index 62967b2827..3edb11401d 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -3,13 +3,12 @@ import Renderer from '../Renderer'; import Block from '../Block'; import { b, x } from 'code-red'; import { Identifier } from 'estree'; -import DynamicElement from '../../nodes/DynamicElement'; import ElementWrapper from './Element/index'; import create_debugging_comment from './shared/create_debugging_comment'; import Element from '../../nodes/Element'; export default class DynamicElementWrapper extends Wrapper { - node: DynamicElement; + node: Element; elementWrapper: ElementWrapper; block: Block; dependencies: string[]; @@ -19,14 +18,14 @@ export default class DynamicElementWrapper extends Wrapper { renderer: Renderer, block: Block, parent: Wrapper, - node: DynamicElement, + node: Element, strip_whitespace: boolean, next_sibling: Wrapper ) { super(renderer, block, parent, node); this.not_static_content(); - this.dependencies = node.tag.dynamic_dependencies(); + this.dependencies = node.dynamic_tag_expr.dynamic_dependencies(); if (this.dependencies.length) { block = block.child({ @@ -37,8 +36,6 @@ export default class DynamicElementWrapper extends Wrapper { renderer.blocks.push(block); } - (node as unknown as Element).dynamic_tag = node.tag; - this.block = block; this.elementWrapper = new ElementWrapper( renderer, @@ -83,7 +80,7 @@ export default class DynamicElementWrapper extends Wrapper { const dynamic = this.block.has_update_method; const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.tag.manipulate(block); + const snippet = this.node.dynamic_tag_expr.manipulate(block); block.add_variable(previous_tag, snippet); const not_equal = this.renderer.component.component_options.immutable @@ -128,7 +125,7 @@ export default class DynamicElementWrapper extends Wrapper { if (dynamic) { block.chunks.update.push(b` if (${condition}) { - ${body} + ${body} } else { ${this.var}.p(#ctx, #dirty); } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 0d3570b050..e4039b4663 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -211,8 +211,8 @@ export default class ElementWrapper extends Wrapper { } }); - if (node.dynamic_tag) { - block.add_dependencies(node.dynamic_tag.dependencies); + if (node.is_dynamic_tag) { + block.add_dependencies(node.dynamic_tag_expr.dependencies); } if (this.parent) { @@ -248,11 +248,11 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (this.node.dynamic_tag && this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag.manipulate(block)});`); + if (this.node.is_dynamic_tag && this.renderer.options.dev) { + block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); if (renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag.manipulate(block)});`); + block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); } } @@ -354,8 +354,8 @@ export default class ElementWrapper extends Wrapper { this.add_classes(block); this.add_manual_style_scoping(block); - if (this.node.dynamic_tag) { - const dependencies = this.node.dynamic_tag.dynamic_dependencies(); + if (this.node.is_dynamic_tag) { + const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies(); if (dependencies.length) { const condition = block.renderer.dirty( dependencies @@ -405,7 +405,7 @@ export default class ElementWrapper extends Wrapper { return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; } - const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; + const reference = this.node.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; return x`@element(${reference})`; } @@ -417,7 +417,7 @@ export default class ElementWrapper extends Wrapper { const name = this.node.namespace ? this.node.name : this.node.name.toUpperCase(); - const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; + const reference = this.node.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; if (this.node.namespace === namespaces.svg) { return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; diff --git a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts index 9f701e29ab..6e1fd0ba2c 100644 --- a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -5,7 +5,6 @@ import { import { get_slot_scope } from './shared/get_slot_scope'; import { boolean_attributes } from './shared/boolean_attributes'; import Renderer, { RenderOptions } from '../Renderer'; -import DynamicElement from '../../nodes/DynamicElement'; import ElementHandler from './Element'; import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; @@ -14,17 +13,16 @@ import Element from '../../nodes/Element'; import { Expression as ESExpression } from 'estree'; export default function ( - node: DynamicElement, + node: Element, renderer: Renderer, options: RenderOptions & { slot_scopes: Map; } ) { - const dependencies = node.tag.dynamic_dependencies(); + const dependencies = node.dynamic_tag_expr.dynamic_dependencies(); if (dependencies.length === 0) { - ((node as unknown) as Element).dynamic_tag = node.tag; - ElementHandler((node as unknown) as Element, renderer, options); + ElementHandler(node, renderer, options); } else { const children = remove_whitespace_children(node.children, node.next); @@ -43,7 +41,7 @@ export default function ( } renderer.add_string('<'); - renderer.add_expression(node.tag.node as ESExpression); + renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); const class_expression_list = node.classes.map((class_directive) => { const { expression, name } = class_directive; @@ -184,13 +182,13 @@ export default function ( } renderer.add_string(''); } else if (slot && nearest_inline_component) { renderer.render(children, options); renderer.add_string(''); const lets = node.lets; @@ -208,7 +206,7 @@ export default function ( renderer.render(children, options); renderer.add_string(''); } } diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 10835eaa88..e1b14b760f 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -23,9 +23,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - if (node.dynamic_tag) { + if (node.is_dynamic_tag) { renderer.add_string('<'); - renderer.add_expression(node.dynamic_tag.node as ESExpression); + renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); } else { renderer.add_string(`<${node.name}`); } @@ -158,9 +158,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } if (!is_void(node.name)) { - if (node.dynamic_tag) { + if (node.is_dynamic_tag) { renderer.add_string(''); } else { renderer.add_string(``); @@ -170,9 +170,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.render(children, options); if (!is_void(node.name)) { - if (node.dynamic_tag) { + if (node.is_dynamic_tag) { renderer.add_string(''); } else { renderer.add_string(``); diff --git a/test/runtime/samples/dynamic-element-binding-invalid/_config.js b/test/runtime/samples/dynamic-element-binding-invalid/_config.js index 33a48a0e85..14c6d775dc 100644 --- a/test/runtime/samples/dynamic-element-binding-invalid/_config.js +++ b/test/runtime/samples/dynamic-element-binding-invalid/_config.js @@ -1,3 +1,3 @@ export default { - error: "'value' is not a valid binding. svelte:element only supports bind:this" + error: "'value' is not a valid binding on elements" };