From f001107711d072892133a3c4964b7eced900d9ca Mon Sep 17 00:00:00 2001 From: Alfred Ringstad Date: Fri, 25 Sep 2020 16:30:42 +0200 Subject: [PATCH 01/68] Implement svelte:element for dynamically setting HTML DOM type --- src/compiler/compile/nodes/Animation.ts | 3 +- src/compiler/compile/nodes/Binding.ts | 3 +- src/compiler/compile/nodes/DynamicElement.ts | 99 ++++++++ src/compiler/compile/nodes/Element.ts | 2 + src/compiler/compile/nodes/Transition.ts | 3 +- src/compiler/compile/nodes/interfaces.ts | 2 + .../compile/nodes/shared/map_children.ts | 2 + .../render_dom/wrappers/DynamicElement.ts | 154 +++++++++++++ .../render_dom/wrappers/Element/index.ts | 10 +- .../compile/render_dom/wrappers/Fragment.ts | 2 + src/compiler/compile/render_ssr/Renderer.ts | 2 + .../render_ssr/handlers/DynamicElement.ts | 215 ++++++++++++++++++ .../compile/render_ssr/handlers/Element.ts | 24 +- src/compiler/parse/state/tag.ts | 29 ++- .../dynamic-element-string/input.svelte | 1 + .../dynamic-element-string/output.json | 18 ++ .../dynamic-element-variable/input.svelte | 1 + .../dynamic-element-variable/output.json | 33 +++ .../error-svelte-selfdestructive/error.json | 2 +- .../_config.js | 22 ++ .../main.svelte | 9 + .../dynamic-element-change-tag/_config.js | 17 ++ .../dynamic-element-change-tag/main.svelte | 5 + .../dynamic-element-expression/_config.js | 3 + .../dynamic-element-expression/main.svelte | 1 + .../dynamic-element-pass-props/_config.js | 16 ++ .../dynamic-element-pass-props/main.svelte | 6 + .../samples/dynamic-element-string/_config.js | 3 + .../dynamic-element-string/main.svelte | 1 + .../dynamic-element-variable/_config.js | 20 ++ .../dynamic-element-variable/main.svelte | 6 + .../dynamic-element-string/_expected.html | 1 + .../dynamic-element-string/main.svelte | 1 + .../dynamic-element-variable/_expected.html | 2 + .../dynamic-element-variable/main.svelte | 7 + test/sourcemaps/samples/two-scripts/output.js | 47 ++++ 36 files changed, 758 insertions(+), 14 deletions(-) create mode 100644 src/compiler/compile/nodes/DynamicElement.ts create mode 100644 src/compiler/compile/render_dom/wrappers/DynamicElement.ts create mode 100644 src/compiler/compile/render_ssr/handlers/DynamicElement.ts create mode 100644 test/parser/samples/dynamic-element-string/input.svelte create mode 100644 test/parser/samples/dynamic-element-string/output.json create mode 100644 test/parser/samples/dynamic-element-variable/input.svelte create mode 100644 test/parser/samples/dynamic-element-variable/output.json create mode 100644 test/runtime/samples/dynamic-element-change-tag-reuse-children/_config.js create mode 100644 test/runtime/samples/dynamic-element-change-tag-reuse-children/main.svelte create mode 100644 test/runtime/samples/dynamic-element-change-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-change-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-expression/_config.js create mode 100644 test/runtime/samples/dynamic-element-expression/main.svelte create mode 100644 test/runtime/samples/dynamic-element-pass-props/_config.js create mode 100644 test/runtime/samples/dynamic-element-pass-props/main.svelte create mode 100644 test/runtime/samples/dynamic-element-string/_config.js create mode 100644 test/runtime/samples/dynamic-element-string/main.svelte create mode 100644 test/runtime/samples/dynamic-element-variable/_config.js create mode 100644 test/runtime/samples/dynamic-element-variable/main.svelte create mode 100644 test/server-side-rendering/samples/dynamic-element-string/_expected.html create mode 100644 test/server-side-rendering/samples/dynamic-element-string/main.svelte create mode 100644 test/server-side-rendering/samples/dynamic-element-variable/_expected.html create mode 100644 test/server-side-rendering/samples/dynamic-element-variable/main.svelte create mode 100644 test/sourcemaps/samples/two-scripts/output.js diff --git a/src/compiler/compile/nodes/Animation.ts b/src/compiler/compile/nodes/Animation.ts index 0758a6c1374e..6ac8b0cf14c6 100644 --- a/src/compiler/compile/nodes/Animation.ts +++ b/src/compiler/compile/nodes/Animation.ts @@ -5,13 +5,14 @@ import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; import EachBlock from './EachBlock'; +import DynamicElement from './DynamicElement'; export default class Animation extends Node { type: 'Animation'; name: string; expression: Expression; - constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element | DynamicElement, 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 8fcc70ded9a0..ed74f276f270 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -10,6 +10,7 @@ import Element from './Element'; import InlineComponent from './InlineComponent'; import Window from './Window'; import { clone } from '../../utils/clone'; +import DynamicElement from './DynamicElement'; // TODO this should live in a specific binding const read_only_media_attributes = new Set([ @@ -31,7 +32,7 @@ export default class Binding extends Node { is_contextual: boolean; is_readonly: boolean; - constructor(component: Component, parent: Element | InlineComponent | Window, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element | InlineComponent | Window | DynamicElement, 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 new file mode 100644 index 000000000000..1b5348e03036 --- /dev/null +++ b/src/compiler/compile/nodes/DynamicElement.ts @@ -0,0 +1,99 @@ +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'; + +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; + + 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); + } +} diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 77cb2e062d73..caca40e12045 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -17,6 +17,7 @@ import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; import Component from '../Component'; +import Expression from './shared/Expression'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|svg|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/; @@ -129,6 +130,7 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; + dynamic_tag?: Expression; constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { super(component, parent, scope, info); diff --git a/src/compiler/compile/nodes/Transition.ts b/src/compiler/compile/nodes/Transition.ts index 27ee5d9df276..beb4661ff100 100644 --- a/src/compiler/compile/nodes/Transition.ts +++ b/src/compiler/compile/nodes/Transition.ts @@ -4,6 +4,7 @@ import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; +import DynamicElement from './DynamicElement'; export default class Transition extends Node { type: 'Transition'; @@ -12,7 +13,7 @@ export default class Transition extends Node { expression: Expression; is_local: boolean; - constructor(component: Component, parent: Element, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Element | DynamicElement, 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 a98c21511fb7..e2d406b3cf6d 100644 --- a/src/compiler/compile/nodes/interfaces.ts +++ b/src/compiler/compile/nodes/interfaces.ts @@ -32,6 +32,7 @@ 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 @@ -45,6 +46,7 @@ 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 b1d0816aacbe..42edbe1ef3cf 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -1,6 +1,7 @@ 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'; @@ -25,6 +26,7 @@ function get_constructor(type) { case 'AwaitBlock': return AwaitBlock; case 'Body': return Body; case 'Comment': return Comment; + case 'DynamicElement' : return DynamicElement; 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 new file mode 100644 index 000000000000..f71ff8f0fa93 --- /dev/null +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -0,0 +1,154 @@ +import Wrapper from './shared/Wrapper'; +import Renderer from '../Renderer'; +import Block from '../Block'; +import FragmentWrapper from './Fragment'; +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 { + fragment: FragmentWrapper; + node: DynamicElement; + elementWrapper: ElementWrapper; + block: Block; + dependencies: string[]; + var: Identifier = { type: 'Identifier', name: 'dynamic_element' }; + + constructor( + renderer: Renderer, + block: Block, + parent: Wrapper, + node: DynamicElement, + strip_whitespace: boolean, + next_sibling: Wrapper + ) { + super(renderer, block, parent, node); + + this.not_static_content(); + this.dependencies = node.tag.dynamic_dependencies(); + + if (this.dependencies.length) { + block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('dynamic_element_block'), + type: 'dynamic_element' + }); + renderer.blocks.push(block); + } + + (node as unknown as Element).dynamic_tag = node.tag; + + this.block = block; + this.elementWrapper = new ElementWrapper( + renderer, + this.block, + parent, + (node as unknown) as Element, + strip_whitespace, + next_sibling + ); + } + + render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + if (this.dependencies.length === 0) { + this.render_static_tag(block, parent_node, parent_nodes); + } else { + this.render_dynamic_tag(block, parent_node, parent_nodes); + } + } + + render_static_tag( + _block: Block, + parent_node: Identifier, + parent_nodes: Identifier + ) { + this.elementWrapper.render(this.block, parent_node, parent_nodes); + } + + render_dynamic_tag( + block: Block, + parent_node: Identifier, + parent_nodes: Identifier + ) { + this.elementWrapper.render( + this.block, + null, + (x`#nodes` as unknown) as Identifier + ); + + const has_transitions = !!( + this.block.has_intro_method || this.block.has_outro_method + ); + const dynamic = this.block.has_update_method; + + const previous_tag = block.get_unique_name('previous_tag'); + const snippet = this.node.tag.manipulate(block); + block.add_variable(previous_tag, snippet); + + const not_equal = this.renderer.component.component_options.immutable + ? x`@not_equal` + : x`@safe_not_equal`; + const condition = x`${this.renderer.dirty( + this.dependencies + )} && ${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; + + block.chunks.init.push(b` + let ${this.var} = ${this.block.name}(#ctx); + `); + + block.chunks.create.push(b`${this.var}.c();`); + + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); + } + + block.chunks.mount.push( + b`${this.var}.m(${parent_node || '#target'}, ${ + parent_node ? 'null' : '#anchor' + });` + ); + + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const body = b` + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ` + : b`${this.var}.d(1);` + } + ${this.var} = ${this.block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; + + if (dynamic) { + block.chunks.update.push(b` + if (${condition}) { + ${body} + } else { + ${this.var}.p(#ctx, #dirty); + } + `); + } else { + block.chunks.update.push(b` + if (${condition}) { + ${body} + } + `); + } + + if (has_transitions) { + block.chunks.intro.push(b`@transition_in(${this.var})`); + block.chunks.outro.push(b`@transition_out(${this.var})`); + } + + block.chunks.destroy.push(b`${this.var}.d(detaching)`); + } +} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index b21a1aa24b59..362c766221da 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -247,7 +247,7 @@ export default class ElementWrapper extends Wrapper { if (renderer.options.hydratable) { if (parent_nodes) { block.chunks.claim.push(b` - ${node} = ${this.get_claim_statement(parent_nodes)}; + ${node} = ${this.get_claim_statement(block, parent_nodes)}; `); if (!this.void && this.node.children.length > 0) { @@ -368,10 +368,11 @@ export default class ElementWrapper extends Wrapper { return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; } - return x`@element("${name}")`; + const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; + return x`@element(${reference})`; } - get_claim_statement(nodes: Identifier) { + get_claim_statement(block: Block, nodes: Identifier) { const attributes = this.node.attributes .filter((attr) => attr.type === 'Attribute') .map((attr) => p`${attr.name}: true`); @@ -382,7 +383,8 @@ export default class ElementWrapper extends Wrapper { const svg = this.node.namespace === namespaces.svg ? 1 : null; - return x`@claim_element(${nodes}, "${name}", { ${attributes} }, ${svg})`; + const reference = this.node.dynamic_tag ? this.node.dynamic_tag.manipulate(block) : `"${name}"`; + return x`@claim_element(${nodes}, ${reference}, { ${attributes} }, ${svg})`; } add_directives_in_order (block: Block) { diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 98805b9639b4..519ed2adc843 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -2,6 +2,7 @@ import Wrapper from './shared/Wrapper'; import AwaitBlock from './AwaitBlock'; import Body from './Body'; import DebugTag from './DebugTag'; +import DynamicElement from './DynamicElement'; import EachBlock from './EachBlock'; import Element from './Element/index'; import Head from './Head'; @@ -27,6 +28,7 @@ const wrappers = { Body, Comment: null, DebugTag, + DynamicElement, EachBlock, Element, Head, diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 64e9ee1f4e81..b013fdd98dd5 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -1,6 +1,7 @@ import AwaitBlock from './handlers/AwaitBlock'; import Comment from './handlers/Comment'; import DebugTag from './handlers/DebugTag'; +import DynamicElement from './handlers/DynamicElement'; import EachBlock from './handlers/EachBlock'; import Element from './handlers/Element'; import Head from './handlers/Head'; @@ -27,6 +28,7 @@ const handlers: Record = { Body: noop, Comment, DebugTag, + DynamicElement, EachBlock, Element, Head, diff --git a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts new file mode 100644 index 000000000000..631ced09d427 --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -0,0 +1,215 @@ +import { + get_attribute_value, + get_class_attribute_value +} from './shared/get_attribute_value'; +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'; +import remove_whitespace_children from './utils/remove_whitespace_children'; +import Element from '../../nodes/Element'; +import { Expression as ESExpression } from 'estree'; + +export default function ( + node: DynamicElement, + renderer: Renderer, + options: RenderOptions & { + slot_scopes: Map; + } +) { + const dependencies = node.tag.dynamic_dependencies(); + + if (dependencies.length === 0) { + ((node as unknown) as Element).dynamic_tag = node.tag; + ElementHandler((node as unknown) as Element, renderer, options); + } else { + const children = remove_whitespace_children(node.children, node.next); + + // awkward special case + let node_contents; + + const contenteditable = node.attributes.some( + (attribute) => attribute.name === 'contenteditable' + ); + + const slot = node.get_static_attribute_value('slot'); + const nearest_inline_component = node.find_nearest(/InlineComponent/); + + if (slot && nearest_inline_component) { + renderer.push(); + } + + renderer.add_string('<'); + renderer.add_expression(node.tag.node as ESExpression); + + const class_expression_list = node.classes.map((class_directive) => { + const { expression, name } = class_directive; + const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? + return x`${snippet} ? "${name}" : ""`; + }); + // if (node.needs_manual_style_scoping) { + // class_expression_list.push(x`"${node.component.stylesheet.id}"`); + // } + const class_expression = + class_expression_list.length > 0 && + class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); + + if (node.attributes.some((attr) => attr.is_spread)) { + // TODO dry this out + const args = []; + node.attributes.forEach((attribute) => { + if (attribute.is_spread) { + args.push(attribute.expression.node); + } else { + const name = attribute.name.toLowerCase(); + if (attribute.is_true) { + args.push(x`{ ${attribute.name}: true }`); + } else if ( + boolean_attributes.has(name) && + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' + ) { + // a boolean attribute with one non-Text chunk + args.push( + x`{ ${attribute.name}: ${ + (attribute.chunks[0] as Expression).node + } || null }` + ); + } else { + args.push( + x`{ ${attribute.name}: ${get_attribute_value(attribute)} }` + ); + } + } + }); + + renderer.add_expression(x`@spread([${args}], ${class_expression})`); + } else { + let add_class_attribute = !!class_expression; + node.attributes.forEach((attribute) => { + const name = attribute.name.toLowerCase(); + if (attribute.is_true) { + renderer.add_string(` ${attribute.name}`); + } else if ( + boolean_attributes.has(name) && + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' + ) { + // a boolean attribute with one non-Text chunk + renderer.add_string(' '); + renderer.add_expression( + x`${(attribute.chunks[0] as Expression).node} ? "${ + attribute.name + }" : ""` + ); + } else if (name === 'class' && class_expression) { + add_class_attribute = false; + renderer.add_string(` ${attribute.name}="`); + renderer.add_expression( + x`[${get_class_attribute_value( + attribute + )}, ${class_expression}].join(' ').trim()` + ); + renderer.add_string('"'); + } else if ( + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' + ) { + const snippet = (attribute.chunks[0] as Expression).node; + renderer.add_expression( + x`@add_attribute("${attribute.name}", ${snippet}, ${ + boolean_attributes.has(name) ? 1 : 0 + })` + ); + } else { + renderer.add_string(` ${attribute.name}="`); + renderer.add_expression( + (name === 'class' + ? get_class_attribute_value + : get_attribute_value)(attribute) + ); + renderer.add_string('"'); + } + }); + if (add_class_attribute) { + renderer.add_expression( + x`@add_classes([${class_expression}].join(' ').trim())` + ); + } + } + + node.bindings.forEach((binding) => { + const { name, expression } = binding; + + if (binding.is_readonly) { + return; + } + + if (name === 'group') { + // TODO server-render group bindings + } else if ( + contenteditable && + (name === 'textContent' || name === 'innerHTML') + ) { + node_contents = expression.node; + + // TODO where was this used? + // value = name === 'textContent' ? x`@escape($$value)` : x`$$value`; + } else { + const snippet = expression.node; + renderer.add_expression(x`@add_attribute("${name}", ${snippet}, 1)`); + } + }); + + if (options.hydratable && options.head_id) { + renderer.add_string(` data-svelte="${options.head_id}"`); + } + + renderer.add_string('>'); + + if (node_contents !== undefined) { + if (contenteditable) { + renderer.push(); + renderer.render(children, options); + const result = renderer.pop(); + + renderer.add_expression( + x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})` + ); + } else { + renderer.add_expression(node_contents); + } + + renderer.add_string(''); + } else if (slot && nearest_inline_component) { + renderer.render(children, options); + + renderer.add_string(''); + + const lets = node.lets; + const seen = new Set(lets.map((l) => l.name.name)); + + nearest_inline_component.lets.forEach((l) => { + if (!seen.has(l.name.name)) lets.push(l); + }); + + options.slot_scopes.set(slot, { + input: get_slot_scope(node.lets), + output: renderer.pop() + }); + } else { + 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 d10c16519876..4a84e52adca2 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -6,6 +6,7 @@ import Element from '../../nodes/Element'; import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; +import { Expression as ESExpression } from 'estree'; export default function(node: Element, renderer: Renderer, options: RenderOptions) { @@ -20,7 +21,12 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - renderer.add_string(`<${node.name}`); + if (node.dynamic_tag) { + renderer.add_string('<'); + renderer.add_expression(node.dynamic_tag.node as ESExpression); + } else { + renderer.add_string(`<${node.name}`); + } const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -136,13 +142,25 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } if (!is_void(node.name)) { - renderer.add_string(``); + if (node.dynamic_tag) { + renderer.add_string(''); + } else { + renderer.add_string(``); + } } } else { renderer.render(children, options); if (!is_void(node.name)) { - renderer.add_string(``); + if (node.dynamic_tag) { + renderer.add_string(''); + } else { + renderer.add_string(``); + } } } } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 799124b2bdf7..a6934269353f 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -18,7 +18,7 @@ const meta_tags = new Map([ ['svelte:body', 'Body'] ]); -const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment'); +const valid_meta_tags = Array.from(meta_tags.keys()).concat('svelte:self', 'svelte:component', 'svelte:fragment', 'svelte:element'); const specials = new Map([ [ @@ -40,6 +40,7 @@ const specials = new Map([ const SELF = /^svelte:self(?=[\s/>])/; const COMPONENT = /^svelte:component(?=[\s/>])/; const SLOT = /^svelte:fragment(?=[\s/>])/; +const ELEMENT = /^svelte:element(?=[\s/>])/; function parent_is_head(stack) { let i = stack.length; @@ -109,8 +110,9 @@ export default function tag(parser: Parser) { ? meta_tags.get(name) : (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' : name === 'svelte:fragment' ? 'SlotTemplate' - : name === 'title' && parent_is_head(parser.stack) ? 'Title' - : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; + : (name === 'svelte:element') ? 'DynamicElement' + : name === 'title' && parent_is_head(parser.stack) ? 'Title' + : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; const element: TemplateNode = { start, @@ -196,6 +198,26 @@ export default function tag(parser: Parser) { element.expression = definition.value[0].expression; } + + if (name === 'svelte:element') { + const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'tag'); + if (!~index) { + parser.error({ + code: 'missing-element-definition', + message: ' must have a \'tag\' attribute' + }, start); + } + + const definition = element.attributes.splice(index, 1)[0]; + if (definition.value === true || definition.value.length !== 1 || (definition.value[0].type !== 'Text' && definition.value[0].type !== 'MustacheTag' && definition.value[0].type !== 'AttributeShorthand')) { + parser.error({ + code: 'invalid-element-definition', + message: 'invalid element definition' + }, definition.start); + } + + element.tag = definition.value[0].data || definition.value[0].expression; + } // special cases – top-level + + +
+ {text} +
+
\ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-change-tag/_config.js b/test/runtime/samples/dynamic-element-change-tag/_config.js new file mode 100644 index 000000000000..9e4bf6fd32ad --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-change-tag/main.svelte b/test/runtime/samples/dynamic-element-change-tag/main.svelte new file mode 100644 index 000000000000..8e30ac99c53b --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-expression/_config.js b/test/runtime/samples/dynamic-element-expression/_config.js new file mode 100644 index 000000000000..acad91c9015a --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-expression/main.svelte b/test/runtime/samples/dynamic-element-expression/main.svelte new file mode 100644 index 000000000000..f12ee38b9786 --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-pass-props/_config.js b/test/runtime/samples/dynamic-element-pass-props/_config.js new file mode 100644 index 000000000000..35f8b7abdf49 --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/_config.js @@ -0,0 +1,16 @@ +let clicked = false; + +export default { + props: { + tag: 'div', + onClick: () => clicked = true + }, + html: '
Foo
', + + async test({ assert, target, window }) { + const div = target.querySelector('div'); + await div.dispatchEvent(new window.MouseEvent('click')); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-pass-props/main.svelte b/test/runtime/samples/dynamic-element-pass-props/main.svelte new file mode 100644 index 000000000000..dbd4e79abd74 --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-string/_config.js b/test/runtime/samples/dynamic-element-string/_config.js new file mode 100644 index 000000000000..acad91c9015a --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-string/main.svelte b/test/runtime/samples/dynamic-element-string/main.svelte new file mode 100644 index 000000000000..2cf3b2b1895b --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-variable/_config.js b/test/runtime/samples/dynamic-element-variable/_config.js new file mode 100644 index 000000000000..76dd0e606e6a --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/_config.js @@ -0,0 +1,20 @@ +export default { + props: { + tag: 'di', + text: 'Foo' + }, + html: '
Foo
', + + test({ assert, component, target }) { + const div = target.firstChild; + component.tag = 'na'; + component.text = 'Bar'; + + assert.htmlEqual(target.innerHTML, ` + + `); + + const h1 = target.firstChild; + assert.notEqual(div, h1); + } +}; diff --git a/test/runtime/samples/dynamic-element-variable/main.svelte b/test/runtime/samples/dynamic-element-variable/main.svelte new file mode 100644 index 000000000000..8e94f12e11d7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,6 @@ + + +{text} \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-string/_expected.html b/test/server-side-rendering/samples/dynamic-element-string/_expected.html new file mode 100644 index 000000000000..cb98432e1415 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/_expected.html @@ -0,0 +1 @@ +
Foo
diff --git a/test/server-side-rendering/samples/dynamic-element-string/main.svelte b/test/server-side-rendering/samples/dynamic-element-string/main.svelte new file mode 100644 index 000000000000..2cf3b2b1895b --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/_expected.html b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html new file mode 100644 index 000000000000..3ae445f54c71 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html @@ -0,0 +1,2 @@ +

Foo

+
Bar
\ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/main.svelte b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte new file mode 100644 index 000000000000..45addc257614 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,7 @@ + + +Foo +Bar \ No newline at end of file diff --git a/test/sourcemaps/samples/two-scripts/output.js b/test/sourcemaps/samples/two-scripts/output.js new file mode 100644 index 000000000000..3d6fd51b85d5 --- /dev/null +++ b/test/sourcemaps/samples/two-scripts/output.js @@ -0,0 +1,47 @@ +/* test/sourcemaps/samples/two-scripts/input.svelte generated by Svelte vx.xx.x */ +import { + SvelteComponent, + detach, + init, + insert, + noop, + safe_not_equal, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let t_value = foo.bar.baz + ""; + let t; + + return { + c() { + t = text(t_value); + }, + m(target, anchor) { + insert(target, t, anchor); + }, + p: noop, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(t); + } + }; +} + +let first; + +function assertThisLine() { + +} + +class Input extends SvelteComponent { + constructor(options) { + super(); + init(this, options, null, create_fragment, safe_not_equal, {}); + } +} + +export default Input; +export { first }; +//# sourceMappingURL=output.js.map \ No newline at end of file From 4fcc68188d5c4d9098a195511d2cfc86da66090d Mon Sep 17 00:00:00 2001 From: Alfred Ringstad Date: Wed, 7 Oct 2020 09:26:46 +0200 Subject: [PATCH 02/68] Add add_css_class method to DynamicElement --- src/compiler/compile/nodes/DynamicElement.ts | 35 +++++++++++++++++++ .../render_ssr/handlers/DynamicElement.ts | 6 ++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/nodes/DynamicElement.ts b/src/compiler/compile/nodes/DynamicElement.ts index 1b5348e03036..8ec96f6eafcf 100644 --- a/src/compiler/compile/nodes/DynamicElement.ts +++ b/src/compiler/compile/nodes/DynamicElement.ts @@ -14,6 +14,7 @@ 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'; @@ -30,6 +31,7 @@ export default class DynamicElement extends Node { animation?: Animation = null; children: INode[]; scope: TemplateScope; + needs_manual_style_scoping: boolean; constructor(component: Component, parent, scope, info) { super(component, parent, scope, info); @@ -96,4 +98,37 @@ export default class DynamicElement extends Node { this.children = map_children(component, this, this.scope, info.children); } + + 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/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts index 631ced09d427..9f701e29ab0d 100644 --- a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -50,9 +50,9 @@ export default function ( const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? return x`${snippet} ? "${name}" : ""`; }); - // if (node.needs_manual_style_scoping) { - // class_expression_list.push(x`"${node.component.stylesheet.id}"`); - // } + if (node.needs_manual_style_scoping) { + class_expression_list.push(x`"${node.component.stylesheet.id}"`); + } const class_expression = class_expression_list.length > 0 && class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); From 54e1c23b329bd67cb3b43e491dbf07ef35d85fd4 Mon Sep 17 00:00:00 2001 From: Alfred Ringstad Date: Wed, 31 Mar 2021 19:08:43 +0200 Subject: [PATCH 03/68] Reuse DOM elements if possible. Add dev warnings when passing nullish values as tag. Throw an error when trying to bind other than this. Add more tests. --- src/compiler/compile/nodes/DynamicElement.ts | 13 ++++ .../render_dom/wrappers/DynamicElement.ts | 60 +++++++++---------- .../render_dom/wrappers/Element/index.ts | 41 +++++++++++-- src/compiler/parse/state/tag.ts | 4 +- src/runtime/internal/dev.ts | 6 ++ .../_config.js | 9 +++ .../main.svelte | 5 ++ .../_config.js | 3 + .../main.svelte | 6 ++ .../dynamic-element-binding-this/_config.js | 8 +++ .../dynamic-element-binding-this/main.svelte | 6 ++ .../dynamic-element-event-handler/_config.js | 21 +++++++ .../dynamic-element-event-handler/main.svelte | 6 ++ .../_config.js | 0 .../main.svelte | 0 .../samples/dynamic-element-store/_config.js | 3 + .../samples/dynamic-element-store/main.svelte | 6 ++ .../dynamic-element-variable/_config.js | 4 +- .../dynamic-element-variable/main.svelte | 4 +- test/sourcemaps/samples/two-scripts/output.js | 47 --------------- 20 files changed, 163 insertions(+), 89 deletions(-) create mode 100644 test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js create mode 100644 test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-binding-invalid/_config.js create mode 100644 test/runtime/samples/dynamic-element-binding-invalid/main.svelte create mode 100644 test/runtime/samples/dynamic-element-binding-this/_config.js create mode 100644 test/runtime/samples/dynamic-element-binding-this/main.svelte create mode 100644 test/runtime/samples/dynamic-element-event-handler/_config.js create mode 100644 test/runtime/samples/dynamic-element-event-handler/main.svelte rename test/runtime/samples/{dynamic-element-change-tag-reuse-children => dynamic-element-reuse-children}/_config.js (100%) rename test/runtime/samples/{dynamic-element-change-tag-reuse-children => dynamic-element-reuse-children}/main.svelte (100%) create mode 100644 test/runtime/samples/dynamic-element-store/_config.js create mode 100644 test/runtime/samples/dynamic-element-store/main.svelte delete mode 100644 test/sourcemaps/samples/two-scripts/output.js diff --git a/src/compiler/compile/nodes/DynamicElement.ts b/src/compiler/compile/nodes/DynamicElement.ts index 8ec96f6eafcf..bb59ad44c9e4 100644 --- a/src/compiler/compile/nodes/DynamicElement.ts +++ b/src/compiler/compile/nodes/DynamicElement.ts @@ -97,6 +97,19 @@ export default class DynamicElement extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); + + this.validate(); + } + + 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() { diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index f71ff8f0fa93..62967b282792 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -1,7 +1,6 @@ import Wrapper from './shared/Wrapper'; import Renderer from '../Renderer'; import Block from '../Block'; -import FragmentWrapper from './Fragment'; import { b, x } from 'code-red'; import { Identifier } from 'estree'; import DynamicElement from '../../nodes/DynamicElement'; @@ -10,7 +9,6 @@ import create_debugging_comment from './shared/create_debugging_comment'; import Element from '../../nodes/Element'; export default class DynamicElementWrapper extends Wrapper { - fragment: FragmentWrapper; node: DynamicElement; elementWrapper: ElementWrapper; block: Block; @@ -112,43 +110,45 @@ export default class DynamicElementWrapper extends Wrapper { ); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const body = b` - ${ - has_transitions - ? b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); - ` - : b`${this.var}.d(1);` - } - ${this.var} = ${this.block.name}(#ctx); - ${this.var}.c(); - ${has_transitions && b`@transition_in(${this.var})`} - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - `; - if (dynamic) { - block.chunks.update.push(b` - if (${condition}) { - ${body} + if (has_transitions) { + block.chunks.intro.push(b`@transition_in(${this.var})`); + block.chunks.outro.push(b`@transition_out(${this.var})`); + + const body = b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ${this.var} = ${this.block.name}(#ctx); + ${this.var}.c(); + @transition_in(${this.var}); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; + + if (dynamic) { + block.chunks.update.push(b` + if (${condition}) { + ${body} + } else { + ${this.var}.p(#ctx, #dirty); + } + `); } else { - ${this.var}.p(#ctx, #dirty); + block.chunks.update.push(b` + if (${condition}) { + ${body} + } + `); } - `); - } else { + } else if (dynamic) { block.chunks.update.push(b` + ${this.var}.p(#ctx, #dirty); if (${condition}) { - ${body} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); } `); } - if (has_transitions) { - block.chunks.intro.push(b`@transition_in(${this.var})`); - block.chunks.outro.push(b`@transition_out(${this.var})`); - } - block.chunks.destroy.push(b`${this.var}.d(detaching)`); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 362c766221da..c9ae8b9150d1 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -211,6 +211,10 @@ export default class ElementWrapper extends Wrapper { } }); + if (node.dynamic_tag) { + block.add_dependencies(node.dynamic_tag.dependencies); + } + if (this.parent) { if (node.actions.length > 0 || node.animation || @@ -244,6 +248,14 @@ 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 (renderer.options.hydratable) { + block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag.manipulate(block)});`); + } + } + if (renderer.options.hydratable) { if (parent_nodes) { block.chunks.claim.push(b` @@ -278,13 +290,14 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (detaching) @detach(${node});`); } + let staticChildren = null; + // insert static children with textContent or innerHTML const can_use_textcontent = this.can_use_textcontent(); if (!this.node.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.fragment.nodes.length > 0) { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { - block.chunks.create.push( - b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};` - ); + staticChildren = b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};`; + block.chunks.create.push(staticChildren); } else { const state = { quasi: { @@ -303,9 +316,8 @@ export default class ElementWrapper extends Wrapper { to_html((this.fragment.nodes as unknown as Array), block, literal, state, can_use_raw_text); literal.quasis.push(state.quasi); - block.chunks.create.push( - b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};` - ); + staticChildren = b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};`; + block.chunks.create.push(staticChildren); } } else { this.fragment.nodes.forEach((child: Wrapper) => { @@ -334,6 +346,23 @@ 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 (dependencies.length) { + const condition = block.renderer.dirty( + dependencies + ); + + block.chunks.update.push(b` + if (${condition}) { + @detach(${node}); + ${node} = ${render_statement}; + ${staticChildren} + } + `); + } + } + if (nodes && this.renderer.options.hydratable && !this.void) { block.chunks.claim.push( b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);` diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index a6934269353f..dada08b686ea 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -181,7 +181,7 @@ export default function tag(parser: Parser) { if (name === 'svelte:component') { const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this'); - if (!~index) { + if (index === -1) { parser.error({ code: 'missing-component-definition', message: " must have a 'this' attribute" @@ -201,7 +201,7 @@ export default function tag(parser: Parser) { if (name === 'svelte:element') { const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'tag'); - if (!~index) { + if (index === -1) { parser.error({ code: 'missing-element-definition', message: ' must have a \'tag\' attribute' diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 99ff067474dd..bfb3d795f2ad 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -97,6 +97,12 @@ export function validate_slots(name, slot, keys) { } } +export function validate_dynamic_element(tag) { + if (tag == null) { + console.warn(' expects a non-nullish value in attribute "tag"'); + } +} + type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; diff --git a/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js new file mode 100644 index 000000000000..026a93cf7422 --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + + warnings: [ + ' expects a non-nullish value in attribute "tag"' + ] +}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte new file mode 100644 index 000000000000..8ab231d49d00 --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/dynamic-element-binding-invalid/_config.js b/test/runtime/samples/dynamic-element-binding-invalid/_config.js new file mode 100644 index 000000000000..33a48a0e853e --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/_config.js @@ -0,0 +1,3 @@ +export default { + error: "'value' is not a valid binding. svelte:element only supports bind:this" +}; diff --git a/test/runtime/samples/dynamic-element-binding-invalid/main.svelte b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte new file mode 100644 index 000000000000..4639427fdf82 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-binding-this/_config.js b/test/runtime/samples/dynamic-element-binding-this/_config.js new file mode 100644 index 000000000000..e0722c937573 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/_config.js @@ -0,0 +1,8 @@ +export default { + html: '
', + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div, component.foo); + } +}; diff --git a/test/runtime/samples/dynamic-element-binding-this/main.svelte b/test/runtime/samples/dynamic-element-binding-this/main.svelte new file mode 100644 index 000000000000..0c1e3a3f15cb --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-event-handler/_config.js b/test/runtime/samples/dynamic-element-event-handler/_config.js new file mode 100644 index 000000000000..03b8f7879d44 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler/_config.js @@ -0,0 +1,21 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + handler + }, + html: '', + + test({ assert, target }) { + assert.equal(clicked, false); + + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler/main.svelte b/test/runtime/samples/dynamic-element-event-handler/main.svelte new file mode 100644 index 000000000000..1fd8c41dcf0b --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-change-tag-reuse-children/_config.js b/test/runtime/samples/dynamic-element-reuse-children/_config.js similarity index 100% rename from test/runtime/samples/dynamic-element-change-tag-reuse-children/_config.js rename to test/runtime/samples/dynamic-element-reuse-children/_config.js diff --git a/test/runtime/samples/dynamic-element-change-tag-reuse-children/main.svelte b/test/runtime/samples/dynamic-element-reuse-children/main.svelte similarity index 100% rename from test/runtime/samples/dynamic-element-change-tag-reuse-children/main.svelte rename to test/runtime/samples/dynamic-element-reuse-children/main.svelte diff --git a/test/runtime/samples/dynamic-element-store/_config.js b/test/runtime/samples/dynamic-element-store/_config.js new file mode 100644 index 000000000000..ded19eef7954 --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
' +}; diff --git a/test/runtime/samples/dynamic-element-store/main.svelte b/test/runtime/samples/dynamic-element-store/main.svelte new file mode 100644 index 000000000000..5b8098d24405 --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/main.svelte @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-variable/_config.js b/test/runtime/samples/dynamic-element-variable/_config.js index 76dd0e606e6a..20e0fa94184d 100644 --- a/test/runtime/samples/dynamic-element-variable/_config.js +++ b/test/runtime/samples/dynamic-element-variable/_config.js @@ -1,13 +1,13 @@ export default { props: { - tag: 'di', + tag: 'div', text: 'Foo' }, html: '
Foo
', test({ assert, component, target }) { const div = target.firstChild; - component.tag = 'na'; + component.tag = 'nav'; component.text = 'Bar'; assert.htmlEqual(target.innerHTML, ` diff --git a/test/runtime/samples/dynamic-element-variable/main.svelte b/test/runtime/samples/dynamic-element-variable/main.svelte index 8e94f12e11d7..4cac42263573 100644 --- a/test/runtime/samples/dynamic-element-variable/main.svelte +++ b/test/runtime/samples/dynamic-element-variable/main.svelte @@ -1,6 +1,6 @@ -{text} \ No newline at end of file +{text} \ No newline at end of file diff --git a/test/sourcemaps/samples/two-scripts/output.js b/test/sourcemaps/samples/two-scripts/output.js deleted file mode 100644 index 3d6fd51b85d5..000000000000 --- a/test/sourcemaps/samples/two-scripts/output.js +++ /dev/null @@ -1,47 +0,0 @@ -/* test/sourcemaps/samples/two-scripts/input.svelte generated by Svelte vx.xx.x */ -import { - SvelteComponent, - detach, - init, - insert, - noop, - safe_not_equal, - text -} from "svelte/internal"; - -function create_fragment(ctx) { - let t_value = foo.bar.baz + ""; - let t; - - return { - c() { - t = text(t_value); - }, - m(target, anchor) { - insert(target, t, anchor); - }, - p: noop, - i: noop, - o: noop, - d(detaching) { - if (detaching) detach(t); - } - }; -} - -let first; - -function assertThisLine() { - -} - -class Input extends SvelteComponent { - constructor(options) { - super(); - init(this, options, null, create_fragment, safe_not_equal, {}); - } -} - -export default Input; -export { first }; -//# sourceMappingURL=output.js.map \ No newline at end of file From 392bdc6b109727eb0f80d2eaf3770f947b27089c Mon Sep 17 00:00:00 2001 From: Alfred Ringstad Date: Wed, 31 Mar 2021 19:37:14 +0200 Subject: [PATCH 04/68] Add documentation --- site/content/docs/02-template-syntax.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 0e0e8e400822..6221dfe12482 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1461,6 +1461,28 @@ If `this` is falsy, no component is rendered. ``` +### `` + +```sv + +``` + +--- + +The `` element lets you render an element of a dynamically specified type. This is useful for example when rich text content from a CMS. If the tag is changed, the children will be preserved unless there's a transition attached to the element. Any properties and event listeners present will be applied to the element. + +The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type. + +If `tag` has a nullish value, a warning will be logged in development mode. + +```sv + + +Foo +``` ### `` From bd41b85d31e792dca8c26d69a019e784d10803c0 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Sun, 26 Sep 2021 18:38:51 +0200 Subject: [PATCH 05/68] tag -> this --- site/content/docs/02-template-syntax.md | 6 +- src/compiler/parse/errors.ts | 8 +++ src/compiler/parse/state/tag.ts | 15 ++--- src/runtime/internal/dev.ts | 2 +- .../dynamic-element-string/input.svelte | 3 +- .../dynamic-element-string/output.json | 38 +++++++++++- .../dynamic-element-variable/input.svelte | 3 +- .../dynamic-element-variable/output.json | 59 +++++++++++++++++-- .../_config.js | 2 +- .../main.svelte | 2 +- .../main.svelte | 2 +- .../dynamic-element-binding-this/main.svelte | 2 +- .../dynamic-element-change-tag/main.svelte | 2 +- .../dynamic-element-event-handler/main.svelte | 2 +- .../dynamic-element-expression/main.svelte | 2 +- .../dynamic-element-pass-props/main.svelte | 2 +- .../main.svelte | 2 +- .../samples/dynamic-element-store/main.svelte | 2 +- .../dynamic-element-string/main.svelte | 2 +- .../dynamic-element-variable/main.svelte | 2 +- .../dynamic-element-string/main.svelte | 2 +- .../dynamic-element-variable/main.svelte | 4 +- 22 files changed, 124 insertions(+), 40 deletions(-) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index e985234025bb..75ac845938d4 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1551,7 +1551,7 @@ If `this` is falsy, no component is rendered. ### `` ```sv - + ``` --- @@ -1560,7 +1560,7 @@ The `` element lets you render an element of a dynamically speci The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type. -If `tag` has a nullish value, a warning will be logged in development mode. +If `this` has a nullish value, a warning will be logged in development mode. ```sv -Foo +Foo ``` ### `` diff --git a/src/compiler/parse/errors.ts b/src/compiler/parse/errors.ts index ef1f72a8be84..63bd5b09199f 100644 --- a/src/compiler/parse/errors.ts +++ b/src/compiler/parse/errors.ts @@ -99,6 +99,10 @@ export default { code: `invalid-${slug}-content`, message: `<${name}> cannot have children` }), + invalid_element_definition: { + code: 'invalid-element-definition', + message: 'Invalid element definition' + }, invalid_element_placement: (slug: string, name: string) => ({ code: `invalid-${slug}-placement`, message: `<${name}> tags cannot be inside elements or blocks` @@ -161,6 +165,10 @@ export default { code: 'missing-attribute-value', message: 'Expected value for the attribute' }, + missing_element_definition: { + code: 'missing-element-definition', + message: ' must have a \'this\' attribute' + }, unclosed_script: { code: 'unclosed-script', message: ' - + diff --git a/test/runtime/samples/dynamic-element-binding-invalid/main.svelte b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte index 4639427fdf82..45f8f9606141 100644 --- a/test/runtime/samples/dynamic-element-binding-invalid/main.svelte +++ b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte @@ -3,4 +3,4 @@ let value; - + diff --git a/test/runtime/samples/dynamic-element-binding-this/main.svelte b/test/runtime/samples/dynamic-element-binding-this/main.svelte index 0c1e3a3f15cb..75e8b02ce161 100644 --- a/test/runtime/samples/dynamic-element-binding-this/main.svelte +++ b/test/runtime/samples/dynamic-element-binding-this/main.svelte @@ -3,4 +3,4 @@ export let foo; - + diff --git a/test/runtime/samples/dynamic-element-change-tag/main.svelte b/test/runtime/samples/dynamic-element-change-tag/main.svelte index 8e30ac99c53b..a9c4d5c00c74 100644 --- a/test/runtime/samples/dynamic-element-change-tag/main.svelte +++ b/test/runtime/samples/dynamic-element-change-tag/main.svelte @@ -2,4 +2,4 @@ export let tag = 'div'; -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-event-handler/main.svelte b/test/runtime/samples/dynamic-element-event-handler/main.svelte index 1fd8c41dcf0b..7a7fef9c22cf 100644 --- a/test/runtime/samples/dynamic-element-event-handler/main.svelte +++ b/test/runtime/samples/dynamic-element-event-handler/main.svelte @@ -3,4 +3,4 @@ export let handler; -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-expression/main.svelte b/test/runtime/samples/dynamic-element-expression/main.svelte index f12ee38b9786..7ec11e4ef6d8 100644 --- a/test/runtime/samples/dynamic-element-expression/main.svelte +++ b/test/runtime/samples/dynamic-element-expression/main.svelte @@ -1 +1 @@ -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-pass-props/main.svelte b/test/runtime/samples/dynamic-element-pass-props/main.svelte index dbd4e79abd74..6a54a93f2718 100644 --- a/test/runtime/samples/dynamic-element-pass-props/main.svelte +++ b/test/runtime/samples/dynamic-element-pass-props/main.svelte @@ -3,4 +3,4 @@ export let onClick; -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-reuse-children/main.svelte b/test/runtime/samples/dynamic-element-reuse-children/main.svelte index a46765658f66..4be99aab09a6 100644 --- a/test/runtime/samples/dynamic-element-reuse-children/main.svelte +++ b/test/runtime/samples/dynamic-element-reuse-children/main.svelte @@ -2,7 +2,7 @@ export let tag = 'div', text = 'Foo'; - +
{text}
diff --git a/test/runtime/samples/dynamic-element-store/main.svelte b/test/runtime/samples/dynamic-element-store/main.svelte index 5b8098d24405..84a577ecee38 100644 --- a/test/runtime/samples/dynamic-element-store/main.svelte +++ b/test/runtime/samples/dynamic-element-store/main.svelte @@ -3,4 +3,4 @@ const foo = writable('div'); - \ No newline at end of file + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-string/main.svelte b/test/runtime/samples/dynamic-element-string/main.svelte index 2cf3b2b1895b..62d65d5f203c 100644 --- a/test/runtime/samples/dynamic-element-string/main.svelte +++ b/test/runtime/samples/dynamic-element-string/main.svelte @@ -1 +1 @@ -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-variable/main.svelte b/test/runtime/samples/dynamic-element-variable/main.svelte index 4cac42263573..d60953bba5de 100644 --- a/test/runtime/samples/dynamic-element-variable/main.svelte +++ b/test/runtime/samples/dynamic-element-variable/main.svelte @@ -3,4 +3,4 @@ export let text = "Foo"; -{text} \ No newline at end of file +{text} \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-string/main.svelte b/test/server-side-rendering/samples/dynamic-element-string/main.svelte index 2cf3b2b1895b..62d65d5f203c 100644 --- a/test/server-side-rendering/samples/dynamic-element-string/main.svelte +++ b/test/server-side-rendering/samples/dynamic-element-string/main.svelte @@ -1 +1 @@ -Foo \ No newline at end of file +Foo \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/main.svelte b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte index 45addc257614..9aaba18bf12f 100644 --- a/test/server-side-rendering/samples/dynamic-element-variable/main.svelte +++ b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte @@ -3,5 +3,5 @@ let tag = 'div'; -Foo -Bar \ No newline at end of file +Foo +Bar \ No newline at end of file From 45d3ab099f36464f22e13fdffa0e63c617bbe401 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Thu, 21 Oct 2021 18:15:46 +0200 Subject: [PATCH 06/68] fix: css classes for dynamic elements --- src/compiler/compile/nodes/DynamicElement.ts | 4 ++++ test/css/samples/dynamic-element/_config.js | 20 +++++++++++++++++++ test/css/samples/dynamic-element/expected.css | 1 + .../css/samples/dynamic-element/expected.html | 1 + test/css/samples/dynamic-element/input.svelte | 10 ++++++++++ 5 files changed, 36 insertions(+) create mode 100644 test/css/samples/dynamic-element/_config.js create mode 100644 test/css/samples/dynamic-element/expected.css create mode 100644 test/css/samples/dynamic-element/expected.html create mode 100644 test/css/samples/dynamic-element/input.svelte diff --git a/src/compiler/compile/nodes/DynamicElement.ts b/src/compiler/compile/nodes/DynamicElement.ts index bb59ad44c9e4..eae94f47e4ef 100644 --- a/src/compiler/compile/nodes/DynamicElement.ts +++ b/src/compiler/compile/nodes/DynamicElement.ts @@ -99,6 +99,10 @@ export default class DynamicElement extends Node { 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() { diff --git a/test/css/samples/dynamic-element/_config.js b/test/css/samples/dynamic-element/_config.js new file mode 100644 index 000000000000..8d8811c57f1d --- /dev/null +++ b/test/css/samples/dynamic-element/_config.js @@ -0,0 +1,20 @@ +export default { + warnings: [ + { + code: "css-unused-selector", + end: { + character: 86, + column: 8, + line: 7, + }, + frame: " 5: color: red;\n 6: }\n 7: .unused {\n ^\n 8: font-style: italic;\n 9: }", + message: 'Unused CSS selector ".unused"', + pos: 79, + start: { + character: 79, + column: 1, + line: 7, + } + } + ] +}; diff --git a/test/css/samples/dynamic-element/expected.css b/test/css/samples/dynamic-element/expected.css new file mode 100644 index 000000000000..561f56a975c2 --- /dev/null +++ b/test/css/samples/dynamic-element/expected.css @@ -0,0 +1 @@ +.used.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/dynamic-element/expected.html b/test/css/samples/dynamic-element/expected.html new file mode 100644 index 000000000000..7b11bf31f41e --- /dev/null +++ b/test/css/samples/dynamic-element/expected.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/css/samples/dynamic-element/input.svelte b/test/css/samples/dynamic-element/input.svelte new file mode 100644 index 000000000000..c00410a96d8f --- /dev/null +++ b/test/css/samples/dynamic-element/input.svelte @@ -0,0 +1,10 @@ + + + From 80e93bcc2aa560a5d534c31072ed9e8f2d2af8d8 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 23 Oct 2021 14:47:46 +0900 Subject: [PATCH 07/68] replace double quote to single quote --- site/content/docs/02-template-syntax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 10842aeb2df1..c0df3ac86ad4 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1564,7 +1564,7 @@ If `this` has a nullish value, a warning will be logged in development mode. ```sv From 7b5247d7a4a52b38f6ce1aab31ae94ace541489e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 23 Oct 2021 14:57:25 +0900 Subject: [PATCH 08/68] fix lint --- test/css/samples/dynamic-element/_config.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/css/samples/dynamic-element/_config.js b/test/css/samples/dynamic-element/_config.js index 8d8811c57f1d..344c2c7baa57 100644 --- a/test/css/samples/dynamic-element/_config.js +++ b/test/css/samples/dynamic-element/_config.js @@ -1,19 +1,20 @@ export default { warnings: [ { - code: "css-unused-selector", + code: 'css-unused-selector', end: { character: 86, column: 8, - line: 7, + line: 7 }, - frame: " 5: color: red;\n 6: }\n 7: .unused {\n ^\n 8: font-style: italic;\n 9: }", + frame: + ' 5: color: red;\n 6: }\n 7: .unused {\n ^\n 8: font-style: italic;\n 9: }', message: 'Unused CSS selector ".unused"', pos: 79, start: { character: 79, column: 1, - line: 7, + line: 7 } } ] From 185332e409edeaeedc56a85a85cb79575356f588 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Tue, 26 Oct 2021 22:56:54 +0900 Subject: [PATCH 09/68] clean up nodes --- src/compiler/compile/compiler_errors.ts | 2 +- src/compiler/compile/nodes/Animation.ts | 3 +- src/compiler/compile/nodes/Binding.ts | 3 +- src/compiler/compile/nodes/DynamicElement.ts | 151 ------------------ src/compiler/compile/nodes/Element.ts | 17 +- src/compiler/compile/nodes/Transition.ts | 3 +- src/compiler/compile/nodes/interfaces.ts | 2 - .../compile/nodes/shared/map_children.ts | 3 +- .../render_dom/wrappers/DynamicElement.ts | 13 +- .../render_dom/wrappers/Element/index.ts | 18 +-- .../render_ssr/handlers/DynamicElement.ts | 16 +- .../compile/render_ssr/handlers/Element.ts | 12 +- .../_config.js | 2 +- 13 files changed, 49 insertions(+), 196 deletions(-) delete mode 100644 src/compiler/compile/nodes/DynamicElement.ts diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 54263c3eb911..b12bcd88cc38 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 132688f40223..ac9dddd275ad 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 4861ebe60593..1efc1a3038c4 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 eae94f47e4ef..000000000000 --- 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 74c70d2385d6..a495c4f58a72 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 08edb9771c8f..78799ac7a99f 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 e2d406b3cf6d..a98c21511fb7 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 42edbe1ef3cf..c6a9ac17479e 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 62967b282792..3edb11401d5a 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 0d3570b05022..e4039b466322 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 9f701e29ab0d..6e1fd0ba2c0d 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 10835eaa88e3..e1b14b760f29 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 33a48a0e853e..14c6d775dc92 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" }; From 714433b8312099ab0faebfa423966ba24291b266 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 27 Oct 2021 22:11:07 +0900 Subject: [PATCH 10/68] little clean up render_dom --- .../render_dom/wrappers/DynamicElement.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index 3edb11401d5a..1e9a704f480e 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -10,8 +10,7 @@ import Element from '../../nodes/Element'; export default class DynamicElementWrapper extends Wrapper { node: Element; elementWrapper: ElementWrapper; - block: Block; - dependencies: string[]; + dynamic_element_block: Block; var: Identifier = { type: 'Identifier', name: 'dynamic_element' }; constructor( @@ -25,9 +24,8 @@ export default class DynamicElementWrapper extends Wrapper { super(renderer, block, parent, node); this.not_static_content(); - this.dependencies = node.dynamic_tag_expr.dynamic_dependencies(); - if (this.dependencies.length) { + if (this.node.dynamic_tag_expr.dynamic_dependencies().length) { block = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name('dynamic_element_block'), @@ -36,19 +34,19 @@ export default class DynamicElementWrapper extends Wrapper { renderer.blocks.push(block); } - this.block = block; + this.dynamic_element_block = block; this.elementWrapper = new ElementWrapper( renderer, - this.block, + this.dynamic_element_block, parent, - (node as unknown) as Element, + node, strip_whitespace, next_sibling ); } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - if (this.dependencies.length === 0) { + if (this.node.dynamic_tag_expr.dynamic_dependencies().length === 0) { this.render_static_tag(block, parent_node, parent_nodes); } else { this.render_dynamic_tag(block, parent_node, parent_nodes); @@ -60,7 +58,7 @@ export default class DynamicElementWrapper extends Wrapper { parent_node: Identifier, parent_nodes: Identifier ) { - this.elementWrapper.render(this.block, parent_node, parent_nodes); + this.elementWrapper.render(this.dynamic_element_block, parent_node, parent_nodes); } render_dynamic_tag( @@ -69,15 +67,15 @@ export default class DynamicElementWrapper extends Wrapper { parent_nodes: Identifier ) { this.elementWrapper.render( - this.block, + this.dynamic_element_block, null, (x`#nodes` as unknown) as Identifier ); const has_transitions = !!( - this.block.has_intro_method || this.block.has_outro_method + this.dynamic_element_block.has_intro_method || this.dynamic_element_block.has_outro_method ); - const dynamic = this.block.has_update_method; + const dynamic = this.dynamic_element_block.has_update_method; const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.dynamic_tag_expr.manipulate(block); @@ -87,11 +85,11 @@ export default class DynamicElementWrapper extends Wrapper { ? x`@not_equal` : x`@safe_not_equal`; const condition = x`${this.renderer.dirty( - this.dependencies + this.node.dynamic_tag_expr.dynamic_dependencies() )} && ${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; block.chunks.init.push(b` - let ${this.var} = ${this.block.name}(#ctx); + let ${this.var} = ${this.dynamic_element_block.name}(#ctx); `); block.chunks.create.push(b`${this.var}.c();`); @@ -116,7 +114,7 @@ export default class DynamicElementWrapper extends Wrapper { @group_outros(); @transition_out(${this.var}, 1, 1, @noop); @check_outros(); - ${this.var} = ${this.block.name}(#ctx); + ${this.var} = ${this.dynamic_element_block.name}(#ctx); ${this.var}.c(); @transition_in(${this.var}); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); From 89d273323ed9ce79fee6c73de519d70819286d33 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 28 Oct 2021 10:23:44 +0900 Subject: [PATCH 11/68] refactor tag.ts --- src/compiler/parse/state/tag.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 31bd977b7473..0703405126b8 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -85,7 +85,7 @@ export default function tag(parser: Parser) { parser.current().children.length ) { parser.error( - parser_errors.invalid_element_content(slug, name), + parser_errors.invalid_element_content(slug, name), parser.current().children[0].start ); } @@ -102,13 +102,15 @@ export default function tag(parser: Parser) { } } - const type = meta_tags.has(name) - ? meta_tags.get(name) - : (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' - : name === 'svelte:fragment' ? 'SlotTemplate' - : (name === 'svelte:element') ? 'DynamicElement' - : name === 'title' && parent_is_head(parser.stack) ? 'Title' - : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; + const type = (function () { + if (meta_tags.has(name)) return meta_tags.get(name); + if ((/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component')) return 'InlineComponent'; + if (name === 'svelte:fragment') return 'SlotTemplate'; + if (name === 'svelte:element') return 'DynamicElement'; + if (name === 'title' && parent_is_head(parser.stack)) return 'Title'; + if (name === 'slot' && !parser.customElement) return 'Slot'; + return 'Element'; + })(); const element: TemplateNode = { start, @@ -182,7 +184,7 @@ export default function tag(parser: Parser) { element.expression = definition.value[0].expression; } - + if (name === 'svelte:element') { const index = element.attributes.findIndex(attr => attr.type === 'Attribute' && attr.name === 'this'); if (index === -1) { @@ -276,7 +278,7 @@ function read_tag_name(parser: Parser) { const match = fuzzymatch(name.slice(7), valid_meta_tags); parser.error( - parser_errors.invalid_tag_name_svelte_element(valid_meta_tags, match), + parser_errors.invalid_tag_name_svelte_element(valid_meta_tags, match), start ); } From 14ca99006f46da3671147ac9a087a5c103dbd0cc Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 30 Oct 2021 18:51:47 +0900 Subject: [PATCH 12/68] refactor nodes --- src/compiler/compile/nodes/Element.ts | 13 ++++--------- .../compile/render_dom/wrappers/Element/index.ts | 10 +++++----- src/compiler/compile/render_ssr/handlers/Element.ts | 6 +++--- src/compiler/interfaces.ts | 2 +- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a495c4f58a72..a4a22a39f6a0 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -13,8 +13,6 @@ 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'; @@ -119,7 +117,7 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st } export default class Element extends Node { - type: 'Element'; + type: 'Element' | 'DynamicElement'; name: string; scope: TemplateScope; attributes: Attribute[] = []; @@ -134,21 +132,18 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; - // 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); + this.type = 'Element'; + this.name = info.tag; } else { + this.type = 'DynamicElement'; this.dynamic_tag_expr = new Expression(component, this, scope, info.tag); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e4039b466322..62fab592d076 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -211,7 +211,7 @@ export default class ElementWrapper extends Wrapper { } }); - if (node.is_dynamic_tag) { + if (node.type === 'DynamicElement') { block.add_dependencies(node.dynamic_tag_expr.dependencies); } @@ -248,7 +248,7 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (this.node.is_dynamic_tag && this.renderer.options.dev) { + if (this.node.type === 'DynamicElement' && this.renderer.options.dev) { block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); if (renderer.options.hydratable) { @@ -354,7 +354,7 @@ export default class ElementWrapper extends Wrapper { this.add_classes(block); this.add_manual_style_scoping(block); - if (this.node.is_dynamic_tag) { + if (this.node.type === 'DynamicElement') { const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies(); if (dependencies.length) { const condition = block.renderer.dirty( @@ -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.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.type === 'DynamicElement' ? 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.is_dynamic_tag ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.type === 'DynamicElement' ? 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/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index e1b14b760f29..8d84ea0bc045 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -23,7 +23,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - if (node.is_dynamic_tag) { + if (node.type === 'DynamicElement') { renderer.add_string('<'); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); } else { @@ -158,7 +158,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } if (!is_void(node.name)) { - if (node.is_dynamic_tag) { + if (node.type === 'DynamicElement') { renderer.add_string(''); @@ -170,7 +170,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.render(children, options); if (!is_void(node.name)) { - if (node.is_dynamic_tag) { + if (node.type === 'DynamicElement') { renderer.add_string(''); diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index b999fbd803dd..85feb72ce05b 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -47,7 +47,7 @@ interface BaseDirective extends BaseNode { } export interface Element extends BaseNode { - type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'Head' | 'Options' | 'Window' | 'Body'; + type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'DynamicElement' | 'Head' | 'Options' | 'Window' | 'Body'; attributes: Array; name: string; } From ed2eed5f97abdbb5f00290c101b5a11476f970d7 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 00:20:34 +0900 Subject: [PATCH 13/68] refactor render_dom --- .../render_dom/wrappers/DynamicElement.ts | 33 ++++--------------- .../render_dom/wrappers/Element/index.ts | 2 +- .../compile/render_dom/wrappers/Fragment.ts | 8 ++++- 3 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index 1e9a704f480e..e462f6fc0f6d 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -25,16 +25,13 @@ export default class DynamicElementWrapper extends Wrapper { this.not_static_content(); - if (this.node.dynamic_tag_expr.dynamic_dependencies().length) { - block = block.child({ - comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('dynamic_element_block'), - type: 'dynamic_element' - }); - renderer.blocks.push(block); - } + this.dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('dynamic_element_block'), + type: 'dynamic_element' + }); + renderer.blocks.push(this.dynamic_element_block); - this.dynamic_element_block = block; this.elementWrapper = new ElementWrapper( renderer, this.dynamic_element_block, @@ -45,23 +42,7 @@ export default class DynamicElementWrapper extends Wrapper { ); } - render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - if (this.node.dynamic_tag_expr.dynamic_dependencies().length === 0) { - this.render_static_tag(block, parent_node, parent_nodes); - } else { - this.render_dynamic_tag(block, parent_node, parent_nodes); - } - } - - render_static_tag( - _block: Block, - parent_node: Identifier, - parent_nodes: Identifier - ) { - this.elementWrapper.render(this.dynamic_element_block, parent_node, parent_nodes); - } - - render_dynamic_tag( + render( block: Block, parent_node: Identifier, parent_nodes: Identifier diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 62fab592d076..c2f32633c8fd 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -211,7 +211,7 @@ export default class ElementWrapper extends Wrapper { } }); - if (node.type === 'DynamicElement') { + if (node.dynamic_tag_expr) { block.add_dependencies(node.dynamic_tag_expr.dependencies); } diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 519ed2adc843..134f51b18c10 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -116,7 +116,13 @@ export default class FragmentWrapper { link(last_child, last_child = wrapper); } else { - const Wrapper = wrappers[child.type]; + const Wrapper = (function () { + if (child.type === 'DynamicElement' && child.dynamic_tag_expr.dynamic_dependencies().length === 0) { + return wrappers['Element']; + } else { + return wrappers[child.type]; + } + }()); if (!Wrapper) continue; const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); From 5d1bc8b2e0bcfe1aa31753f0058c568e14dc1d2a Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 13:06:50 +0900 Subject: [PATCH 14/68] refactor render_dom --- .../render_dom/wrappers/DynamicElement.ts | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index e462f6fc0f6d..feb23ac5b0e3 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -42,11 +42,7 @@ export default class DynamicElementWrapper extends Wrapper { ); } - render( - block: Block, - parent_node: Identifier, - parent_nodes: Identifier - ) { + render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { this.elementWrapper.render( this.dynamic_element_block, null, @@ -56,7 +52,6 @@ export default class DynamicElementWrapper extends Wrapper { const has_transitions = !!( this.dynamic_element_block.has_intro_method || this.dynamic_element_block.has_outro_method ); - const dynamic = this.dynamic_element_block.has_update_method; const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.dynamic_tag_expr.manipulate(block); @@ -101,22 +96,14 @@ export default class DynamicElementWrapper extends Wrapper { ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); `; - if (dynamic) { - block.chunks.update.push(b` - if (${condition}) { - ${body} - } else { - ${this.var}.p(#ctx, #dirty); - } - `); - } else { - block.chunks.update.push(b` - if (${condition}) { - ${body} - } - `); - } - } else if (dynamic) { + block.chunks.update.push(b` + if (${condition}) { + ${body} + } else { + ${this.var}.p(#ctx, #dirty); + } + `); + } else { block.chunks.update.push(b` ${this.var}.p(#ctx, #dirty); if (${condition}) { From 84d9f836981ce396a6a23b5701feb775408f8a4c Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 15:19:31 +0900 Subject: [PATCH 15/68] refactor render_dom --- .../render_dom/wrappers/DynamicElement.ts | 70 ++++++++----------- 1 file changed, 31 insertions(+), 39 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts index feb23ac5b0e3..cbb91edfef8e 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/DynamicElement.ts @@ -27,7 +27,7 @@ export default class DynamicElementWrapper extends Wrapper { this.dynamic_element_block = block.child({ comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('dynamic_element_block'), + name: renderer.component.get_unique_name('create_dynamic_element'), type: 'dynamic_element' }); renderer.blocks.push(this.dynamic_element_block); @@ -49,21 +49,10 @@ export default class DynamicElementWrapper extends Wrapper { (x`#nodes` as unknown) as Identifier ); - const has_transitions = !!( - this.dynamic_element_block.has_intro_method || this.dynamic_element_block.has_outro_method - ); - const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.dynamic_tag_expr.manipulate(block); block.add_variable(previous_tag, snippet); - const not_equal = this.renderer.component.component_options.immutable - ? x`@not_equal` - : x`@safe_not_equal`; - const condition = x`${this.renderer.dirty( - this.node.dynamic_tag_expr.dynamic_dependencies() - )} && ${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - block.chunks.init.push(b` let ${this.var} = ${this.dynamic_element_block.name}(#ctx); `); @@ -75,41 +64,44 @@ export default class DynamicElementWrapper extends Wrapper { } block.chunks.mount.push( - b`${this.var}.m(${parent_node || '#target'}, ${ - parent_node ? 'null' : '#anchor' - });` + b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor' + });` ); + const has_transitions = !!( + this.dynamic_element_block.has_intro_method || this.dynamic_element_block.has_outro_method + ); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - if (has_transitions) { - block.chunks.intro.push(b`@transition_in(${this.var})`); - block.chunks.outro.push(b`@transition_out(${this.var})`); + const body = b` + ${has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ` + : b`${this.var}.d(1);` + } + ${this.var} = ${this.dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; + + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - const body = b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); - ${this.var} = ${this.dynamic_element_block.name}(#ctx); - ${this.var}.c(); - @transition_in(${this.var}); - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - `; - - block.chunks.update.push(b` - if (${condition}) { - ${body} - } else { - ${this.var}.p(#ctx, #dirty); - } - `); - } else { - block.chunks.update.push(b` - ${this.var}.p(#ctx, #dirty); + block.chunks.update.push(b` if (${condition}) { - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${body} + } else { + ${this.var}.p(#ctx, #dirty); } `); + + if (has_transitions) { + block.chunks.intro.push(b`@transition_in(${this.var})`); + block.chunks.outro.push(b`@transition_out(${this.var})`); } block.chunks.destroy.push(b`${this.var}.d(detaching)`); From d65da8d17442cddf380229a9fc4066ad6c7528d8 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 17:27:51 +0900 Subject: [PATCH 16/68] add tests --- .../dynamic-element-attribute/_config.js | 17 ++++++++++++++ .../dynamic-element-attribute/main.svelte | 5 ++++ .../_config.js | 0 .../main.svelte | 0 .../dynamic-element-event-handler2/_config.js | 23 +++++++++++++++++++ .../main.svelte | 6 +++++ 6 files changed, 51 insertions(+) create mode 100644 test/runtime/samples/dynamic-element-attribute/_config.js create mode 100644 test/runtime/samples/dynamic-element-attribute/main.svelte rename test/runtime/samples/{dynamic-element-event-handler => dynamic-element-event-handler1}/_config.js (100%) rename test/runtime/samples/{dynamic-element-event-handler => dynamic-element-event-handler1}/main.svelte (100%) create mode 100644 test/runtime/samples/dynamic-element-event-handler2/_config.js create mode 100644 test/runtime/samples/dynamic-element-event-handler2/main.svelte diff --git a/test/runtime/samples/dynamic-element-attribute/_config.js b/test/runtime/samples/dynamic-element-attribute/_config.js new file mode 100644 index 000000000000..6e7c340437d7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-attribute/main.svelte b/test/runtime/samples/dynamic-element-attribute/main.svelte new file mode 100644 index 000000000000..2498e06de9e6 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-event-handler/_config.js b/test/runtime/samples/dynamic-element-event-handler1/_config.js similarity index 100% rename from test/runtime/samples/dynamic-element-event-handler/_config.js rename to test/runtime/samples/dynamic-element-event-handler1/_config.js diff --git a/test/runtime/samples/dynamic-element-event-handler/main.svelte b/test/runtime/samples/dynamic-element-event-handler1/main.svelte similarity index 100% rename from test/runtime/samples/dynamic-element-event-handler/main.svelte rename to test/runtime/samples/dynamic-element-event-handler1/main.svelte diff --git a/test/runtime/samples/dynamic-element-event-handler2/_config.js b/test/runtime/samples/dynamic-element-event-handler2/_config.js new file mode 100644 index 000000000000..22cbe735a70f --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/_config.js @@ -0,0 +1,23 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + tag: 'div', + handler + }, + html: '
Foo
', + + test({ assert, component, target }) { + assert.equal(clicked, false); + + component.tag = 'button'; + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler2/main.svelte b/test/runtime/samples/dynamic-element-event-handler2/main.svelte new file mode 100644 index 000000000000..f2534c1f62a8 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/main.svelte @@ -0,0 +1,6 @@ + + +Foo From 7ced0cef4cf8fabee675137f855173626c614827 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 18:44:17 +0900 Subject: [PATCH 17/68] refactor render_ssr --- .../render_ssr/handlers/DynamicElement.ts | 284 ++++++++---------- 1 file changed, 122 insertions(+), 162 deletions(-) diff --git a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts index 6e1fd0ba2c0d..b3d7df518ab4 100644 --- a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -5,209 +5,169 @@ import { import { get_slot_scope } from './shared/get_slot_scope'; import { boolean_attributes } from './shared/boolean_attributes'; import Renderer, { RenderOptions } from '../Renderer'; -import ElementHandler from './Element'; import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; import Element from '../../nodes/Element'; import { Expression as ESExpression } from 'estree'; +import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; +import { namespaces } from '../../../utils/namespaces'; -export default function ( - node: Element, - renderer: Renderer, - options: RenderOptions & { - slot_scopes: Map; - } -) { - const dependencies = node.dynamic_tag_expr.dynamic_dependencies(); +export default function (node: Element, renderer: Renderer, options: RenderOptions & { slot_scopes: Map}) { - if (dependencies.length === 0) { - ElementHandler(node, renderer, options); - } else { - const children = remove_whitespace_children(node.children, node.next); + const children = remove_whitespace_children(node.children, node.next); - // awkward special case - let node_contents; + // awkward special case + let node_contents; - const contenteditable = node.attributes.some( - (attribute) => attribute.name === 'contenteditable' - ); + const contenteditable = node.attributes.some( + (attribute) => attribute.name === 'contenteditable' + ); - const slot = node.get_static_attribute_value('slot'); - const nearest_inline_component = node.find_nearest(/InlineComponent/); + const slot = node.get_static_attribute_value('slot'); + const nearest_inline_component = node.find_nearest(/InlineComponent/); - if (slot && nearest_inline_component) { - renderer.push(); - } + if (slot && nearest_inline_component) { + renderer.push(); + } - renderer.add_string('<'); - renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); + renderer.add_string('<'); + renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); - const class_expression_list = node.classes.map((class_directive) => { - const { expression, name } = class_directive; - const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? - return x`${snippet} ? "${name}" : ""`; - }); - if (node.needs_manual_style_scoping) { - class_expression_list.push(x`"${node.component.stylesheet.id}"`); - } - const class_expression = - class_expression_list.length > 0 && - class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); - - if (node.attributes.some((attr) => attr.is_spread)) { - // TODO dry this out - const args = []; - node.attributes.forEach((attribute) => { - if (attribute.is_spread) { - args.push(attribute.expression.node); - } else { - const name = attribute.name.toLowerCase(); - if (attribute.is_true) { - args.push(x`{ ${attribute.name}: true }`); - } else if ( - boolean_attributes.has(name) && - attribute.chunks.length === 1 && - attribute.chunks[0].type !== 'Text' - ) { - // a boolean attribute with one non-Text chunk - args.push( - x`{ ${attribute.name}: ${ - (attribute.chunks[0] as Expression).node - } || null }` - ); - } else { - args.push( - x`{ ${attribute.name}: ${get_attribute_value(attribute)} }` - ); - } - } - }); - - renderer.add_expression(x`@spread([${args}], ${class_expression})`); - } else { - let add_class_attribute = !!class_expression; - node.attributes.forEach((attribute) => { + const class_expression_list = node.classes.map(class_directive => { + const { expression, name } = class_directive; + const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right? + return x`${snippet} ? "${name}" : ""`; + }); + if (node.needs_manual_style_scoping) { + class_expression_list.push(x`"${node.component.stylesheet.id}"`); + } + const class_expression = + class_expression_list.length > 0 && + class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`); + + if (node.attributes.some(attr => attr.is_spread)) { + // TODO dry this out + const args = []; + node.attributes.forEach(attribute => { + if (attribute.is_spread) { + args.push(x`@escape_object(${attribute.expression.node})`); + } else { + const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); const name = attribute.name.toLowerCase(); if (attribute.is_true) { - renderer.add_string(` ${attribute.name}`); + args.push(x`{ ${attr_name}: true }`); } else if ( boolean_attributes.has(name) && attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text' ) { // a boolean attribute with one non-Text chunk - renderer.add_string(' '); - renderer.add_expression( - x`${(attribute.chunks[0] as Expression).node} ? "${ - attribute.name - }" : ""` - ); - } else if (name === 'class' && class_expression) { - add_class_attribute = false; - renderer.add_string(` ${attribute.name}="`); - renderer.add_expression( - x`[${get_class_attribute_value( - attribute - )}, ${class_expression}].join(' ').trim()` - ); - renderer.add_string('"'); - } else if ( - attribute.chunks.length === 1 && - attribute.chunks[0].type !== 'Text' - ) { - const snippet = (attribute.chunks[0] as Expression).node; - renderer.add_expression( - x`@add_attribute("${attribute.name}", ${snippet}, ${ - boolean_attributes.has(name) ? 1 : 0 - })` - ); + args.push(x`{ ${attr_name}: ${(attribute.chunks[0] as Expression).node} || null }`); } else { - renderer.add_string(` ${attribute.name}="`); - renderer.add_expression( - (name === 'class' - ? get_class_attribute_value - : get_attribute_value)(attribute) - ); - renderer.add_string('"'); + args.push(x`{ ${attr_name}: ${get_attribute_value(attribute)} }`); } - }); - if (add_class_attribute) { - renderer.add_expression( - x`@add_classes([${class_expression}].join(' ').trim())` - ); - } - } - - node.bindings.forEach((binding) => { - const { name, expression } = binding; - - if (binding.is_readonly) { - return; } + }); - if (name === 'group') { - // TODO server-render group bindings + renderer.add_expression(x`@spread([${args}], ${class_expression})`); + } else { + let add_class_attribute = !!class_expression; + node.attributes.forEach(attribute => { + const name = attribute.name.toLowerCase(); + const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); + if (attribute.is_true) { + renderer.add_string(` ${attr_name}`); } else if ( - contenteditable && - (name === 'textContent' || name === 'innerHTML') + boolean_attributes.has(name) && + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' ) { - node_contents = expression.node; - - // TODO where was this used? - // value = name === 'textContent' ? x`@escape($$value)` : x`$$value`; + // a boolean attribute with one non-Text chunk + renderer.add_string(' '); + renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`); + } else if (name === 'class' && class_expression) { + add_class_attribute = false; + renderer.add_string(` ${attr_name}="`); + renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`); + renderer.add_string('"'); + } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { + const snippet = (attribute.chunks[0] as Expression).node; + renderer.add_expression(x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); } else { - const snippet = expression.node; - renderer.add_expression(x`@add_attribute("${name}", ${snippet}, 1)`); + renderer.add_string(` ${attr_name}="`); + renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)); + renderer.add_string('"'); } }); + if (add_class_attribute) { + renderer.add_expression(x`@add_classes([${class_expression}].join(' ').trim())`); + } + } + + node.bindings.forEach(binding => { + const { name, expression } = binding; - if (options.hydratable && options.head_id) { - renderer.add_string(` data-svelte="${options.head_id}"`); + if (binding.is_readonly) { + return; } - renderer.add_string('>'); + if (name === 'group') { + // TODO server-render group bindings + } else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) { + node_contents = expression.node; - if (node_contents !== undefined) { - if (contenteditable) { - renderer.push(); - renderer.render(children, options); - const result = renderer.pop(); + // TODO where was this used? + // value = name === 'textContent' ? x`@escape($$value)` : x`$$value`; + } else { + const snippet = expression.node; + renderer.add_expression(x`@add_attribute("${name}", ${snippet}, 1)`); + } + }); - renderer.add_expression( - x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})` - ); - } else { - renderer.add_expression(node_contents); - } + if (options.hydratable && options.head_id) { + renderer.add_string(` data-svelte="${options.head_id}"`); + } + + renderer.add_string('>'); - renderer.add_string(''); - } else if (slot && nearest_inline_component) { + if (node_contents !== undefined) { + if (contenteditable) { + renderer.push(); renderer.render(children, options); + const result = renderer.pop(); + + renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); + } else { + renderer.add_expression(node_contents); + } - renderer.add_string(''); + renderer.add_string(''); + } else if (slot && nearest_inline_component) { + renderer.render(children, options); - const lets = node.lets; - const seen = new Set(lets.map((l) => l.name.name)); + renderer.add_string(''); - nearest_inline_component.lets.forEach((l) => { - if (!seen.has(l.name.name)) lets.push(l); - }); + const lets = node.lets; + const seen = new Set(lets.map((l) => l.name.name)); - options.slot_scopes.set(slot, { - input: get_slot_scope(node.lets), - output: renderer.pop() - }); - } else { - renderer.render(children, options); + nearest_inline_component.lets.forEach(l => { + if (!seen.has(l.name.name)) lets.push(l); + }); - renderer.add_string(''); - } + options.slot_scopes.set(slot, { + input: get_slot_scope(node.lets), + output: renderer.pop() + }); + } else { + renderer.render(children, options); + + renderer.add_string(''); } } From 24a338a4e8c643350ac68269def95e3413d13244 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 18:54:07 +0900 Subject: [PATCH 18/68] refactor render_ssr --- .../render_ssr/handlers/DynamicElement.ts | 6 +++-- .../compile/render_ssr/handlers/Element.ts | 24 +++---------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts index b3d7df518ab4..96cc23432dd8 100644 --- a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -20,8 +20,10 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio // awkward special case let node_contents; - const contenteditable = node.attributes.some( - (attribute) => attribute.name === 'contenteditable' + const contenteditable = ( + node.name !== 'textarea' && + node.name !== 'input' && + node.attributes.some((attribute) => attribute.name === 'contenteditable') ); const slot = node.get_static_attribute_value('slot'); diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 8d84ea0bc045..c0bf826a8ecc 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -6,7 +6,6 @@ import Element from '../../nodes/Element'; import { x } from 'code-red'; import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; -import { Expression as ESExpression } from 'estree'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; @@ -23,12 +22,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - if (node.type === 'DynamicElement') { - renderer.add_string('<'); - renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); - } else { - renderer.add_string(`<${node.name}`); - } + renderer.add_string(`<${node.name}`); const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -158,25 +152,13 @@ export default function(node: Element, renderer: Renderer, options: RenderOption } if (!is_void(node.name)) { - if (node.type === 'DynamicElement') { - renderer.add_string(''); - } else { - renderer.add_string(``); - } + renderer.add_string(``); } } else { renderer.render(children, options); if (!is_void(node.name)) { - if (node.type === 'DynamicElement') { - renderer.add_string(''); - } else { - renderer.add_string(``); - } + renderer.add_string(``); } } } From ca63111498f4ceeba9d09d0571703b74abf3ebf9 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 20:58:58 +0900 Subject: [PATCH 19/68] fix named slot bug --- src/compiler/compile/nodes/InlineComponent.ts | 8 ++--- .../samples/dynamic-element-slot/Foo.svelte | 7 +++++ .../samples/dynamic-element-slot/_config.js | 29 +++++++++++++++++++ .../samples/dynamic-element-slot/main.svelte | 10 +++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 test/runtime/samples/dynamic-element-slot/Foo.svelte create mode 100644 test/runtime/samples/dynamic-element-slot/_config.js create mode 100644 test/runtime/samples/dynamic-element-slot/main.svelte diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index a7bc986e9b95..3d9a8ab0dbcb 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -106,7 +106,7 @@ export default class InlineComponent extends Node { if (child.type === 'SlotTemplate') { children.push(child); info.children.splice(i, 1); - } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { + } else if ((child.type === 'Element' || child.type === 'DynamicElement' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { const slot_template = { start: child.start, end: child.end, @@ -126,17 +126,17 @@ export default class InlineComponent extends Node { slot_template.attributes.push(attribute); } } - + children.push(slot_template); info.children.splice(i, 1); } } if (info.children.some(node => not_whitespace_text(node))) { - children.push({ + children.push({ start: info.start, end: info.end, - type: 'SlotTemplate', + type: 'SlotTemplate', name: 'svelte:fragment', attributes: [], children: info.children diff --git a/test/runtime/samples/dynamic-element-slot/Foo.svelte b/test/runtime/samples/dynamic-element-slot/Foo.svelte new file mode 100644 index 000000000000..51dda4b7e38d --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/Foo.svelte @@ -0,0 +1,7 @@ +

Foo

+
+ +
+
+ +
diff --git a/test/runtime/samples/dynamic-element-slot/_config.js b/test/runtime/samples/dynamic-element-slot/_config.js new file mode 100644 index 000000000000..aa9da522a34d --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/_config.js @@ -0,0 +1,29 @@ +export default { + props: { + x: true + }, + + html: ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `, + + test({ assert, component, target }) { + component.tag = 'h2'; + + assert.htmlEqual(target.innerHTML, ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `); + } +}; diff --git a/test/runtime/samples/dynamic-element-slot/main.svelte b/test/runtime/samples/dynamic-element-slot/main.svelte new file mode 100644 index 000000000000..4b1cd81969e3 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/main.svelte @@ -0,0 +1,10 @@ + + + + This is default slot + This is other slot + + From 85eab20b0149e00f93bd0f56adc23c72ac1eeac5 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 22:12:29 +0900 Subject: [PATCH 20/68] rafactor parse step --- src/compiler/compile/nodes/Element.ts | 4 +--- src/compiler/compile/nodes/InlineComponent.ts | 2 +- src/compiler/compile/nodes/shared/map_children.ts | 1 - src/compiler/compile/render_dom/wrappers/Element/index.ts | 8 ++++---- src/compiler/compile/render_dom/wrappers/Fragment.ts | 4 ++-- src/compiler/compile/render_ssr/Renderer.ts | 8 +++++++- src/compiler/interfaces.ts | 2 +- src/compiler/parse/state/tag.ts | 2 +- test/parser/samples/dynamic-element-string/output.json | 6 +++--- test/parser/samples/dynamic-element-variable/output.json | 4 ++-- 10 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a4a22a39f6a0..d26cad8bb719 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -117,7 +117,7 @@ function get_namespace(parent: Element, element: Element, explicit_namespace: st } export default class Element extends Node { - type: 'Element' | 'DynamicElement'; + type: 'Element'; name: string; scope: TemplateScope; attributes: Attribute[] = []; @@ -140,10 +140,8 @@ export default class Element extends Node { if (this.name === 'svelte:element') { if (typeof info.tag === 'string') { - this.type = 'Element'; this.name = info.tag; } else { - this.type = 'DynamicElement'; this.dynamic_tag_expr = new Expression(component, this, scope, info.tag); } } diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index 3d9a8ab0dbcb..bf7c58a327b4 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -106,7 +106,7 @@ export default class InlineComponent extends Node { if (child.type === 'SlotTemplate') { children.push(child); info.children.splice(i, 1); - } else if ((child.type === 'Element' || child.type === 'DynamicElement' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { + } else if ((child.type === 'Element' || child.type === 'InlineComponent' || child.type === 'Slot') && child.attributes.find(attribute => attribute.name === 'slot')) { const slot_template = { start: child.start, end: child.end, diff --git a/src/compiler/compile/nodes/shared/map_children.ts b/src/compiler/compile/nodes/shared/map_children.ts index c6a9ac17479e..b1d0816aacbe 100644 --- a/src/compiler/compile/nodes/shared/map_children.ts +++ b/src/compiler/compile/nodes/shared/map_children.ts @@ -25,7 +25,6 @@ function get_constructor(type) { case 'AwaitBlock': return AwaitBlock; case 'Body': return Body; case 'Comment': return Comment; - 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/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index c2f32633c8fd..9b76078b5b2b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -248,7 +248,7 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (this.node.type === 'DynamicElement' && this.renderer.options.dev) { + if (this.node.dynamic_tag_expr && this.renderer.options.dev) { block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); if (renderer.options.hydratable) { @@ -354,7 +354,7 @@ export default class ElementWrapper extends Wrapper { this.add_classes(block); this.add_manual_style_scoping(block); - if (this.node.type === 'DynamicElement') { + if (this.node.dynamic_tag_expr) { const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies(); if (dependencies.length) { const condition = block.renderer.dirty( @@ -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.type === 'DynamicElement' ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.dynamic_tag_expr ? 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.type === 'DynamicElement' ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.dynamic_tag_expr ? 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_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 134f51b18c10..26a4e32bce07 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -117,8 +117,8 @@ export default class FragmentWrapper { link(last_child, last_child = wrapper); } else { const Wrapper = (function () { - if (child.type === 'DynamicElement' && child.dynamic_tag_expr.dynamic_dependencies().length === 0) { - return wrappers['Element']; + if (child.type === 'Element' && child.dynamic_tag_expr) { + return wrappers['DynamicElement']; } else { return wrappers[child.type]; } diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index b013fdd98dd5..997392572e57 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -113,7 +113,13 @@ export default class Renderer { render(nodes: INode[], options: RenderOptions) { nodes.forEach(node => { - const handler = handlers[node.type]; + const handler = (function () { + if (node.type === 'Element' && node.dynamic_tag_expr) { + return handlers['DynamicElement']; + } else { + return handlers[node.type]; + } + }()); if (!handler) { throw new Error(`No handler for '${node.type}' nodes`); diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index 85feb72ce05b..b999fbd803dd 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -47,7 +47,7 @@ interface BaseDirective extends BaseNode { } export interface Element extends BaseNode { - type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'DynamicElement' | 'Head' | 'Options' | 'Window' | 'Body'; + type: 'InlineComponent' | 'SlotTemplate' | 'Title' | 'Slot' | 'Element' | 'Head' | 'Options' | 'Window' | 'Body'; attributes: Array; name: string; } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 0703405126b8..b9fb296b58ae 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -106,9 +106,9 @@ export default function tag(parser: Parser) { if (meta_tags.has(name)) return meta_tags.get(name); if ((/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component')) return 'InlineComponent'; if (name === 'svelte:fragment') return 'SlotTemplate'; - if (name === 'svelte:element') return 'DynamicElement'; if (name === 'title' && parent_is_head(parser.stack)) return 'Title'; if (name === 'slot' && !parser.customElement) return 'Slot'; + if (name === 'svelte:element') return 'Element'; return 'Element'; })(); diff --git a/test/parser/samples/dynamic-element-string/output.json b/test/parser/samples/dynamic-element-string/output.json index b88e796bb9b2..9e9245b1aed7 100644 --- a/test/parser/samples/dynamic-element-string/output.json +++ b/test/parser/samples/dynamic-element-string/output.json @@ -7,7 +7,7 @@ { "start": 0, "end": 44, - "type": "DynamicElement", + "type": "Element", "name": "svelte:element", "attributes": [], "children": [], @@ -23,7 +23,7 @@ { "start": 45, "end": 101, - "type": "DynamicElement", + "type": "Element", "name": "svelte:element", "attributes": [ { @@ -47,4 +47,4 @@ } ] } -} \ No newline at end of file +} diff --git a/test/parser/samples/dynamic-element-variable/output.json b/test/parser/samples/dynamic-element-variable/output.json index 7c5fe713d029..53769e94e3b0 100644 --- a/test/parser/samples/dynamic-element-variable/output.json +++ b/test/parser/samples/dynamic-element-variable/output.json @@ -7,7 +7,7 @@ { "start": 0, "end": 44, - "type": "DynamicElement", + "type": "Element", "name": "svelte:element", "attributes": [], "children": [], @@ -38,7 +38,7 @@ { "start": 45, "end": 101, - "type": "DynamicElement", + "type": "Element", "name": "svelte:element", "children": [], "attributes": [ From e3c0091e0c9fc6ec9ab54b63e6bbabe9f5226361 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 31 Oct 2021 23:21:05 +0900 Subject: [PATCH 21/68] refactor render_ssr --- .../render_ssr/handlers/DynamicElement.ts | 60 ++++++++----------- .../compile/render_ssr/handlers/Element.ts | 2 +- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts index 96cc23432dd8..356d545b27a8 100644 --- a/src/compiler/compile/render_ssr/handlers/DynamicElement.ts +++ b/src/compiler/compile/render_ssr/handlers/DynamicElement.ts @@ -1,8 +1,4 @@ -import { - get_attribute_value, - get_class_attribute_value -} from './shared/get_attribute_value'; -import { get_slot_scope } from './shared/get_slot_scope'; +import { get_attribute_expression, get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import { boolean_attributes } from './shared/boolean_attributes'; import Renderer, { RenderOptions } from '../Renderer'; import { x } from 'code-red'; @@ -13,7 +9,7 @@ import { Expression as ESExpression } from 'estree'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; -export default function (node: Element, renderer: Renderer, options: RenderOptions & { slot_scopes: Map}) { +export default function (node: Element, renderer: Renderer, options: RenderOptions) { const children = remove_whitespace_children(node.children, node.next); @@ -26,13 +22,6 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - const slot = node.get_static_attribute_value('slot'); - const nearest_inline_component = node.find_nearest(/InlineComponent/); - - if (slot && nearest_inline_component) { - renderer.push(); - } - renderer.add_string('<'); renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); @@ -57,7 +46,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio } else { const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); const name = attribute.name.toLowerCase(); - if (attribute.is_true) { + if (name === 'value' && node.name.toLowerCase() === 'textarea') { + node_contents = get_attribute_value(attribute); + } else if (attribute.is_true) { args.push(x`{ ${attr_name}: true }`); } else if ( boolean_attributes.has(name) && @@ -66,6 +57,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio ) { // a boolean attribute with one non-Text chunk args.push(x`{ ${attr_name}: ${(attribute.chunks[0] as Expression).node} || null }`); + } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { + const snippet = (attribute.chunks[0] as Expression).node; + args.push(x`{ ${attr_name}: @escape_attribute_value(${snippet}) }`); } else { args.push(x`{ ${attr_name}: ${get_attribute_value(attribute)} }`); } @@ -78,7 +72,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio node.attributes.forEach(attribute => { const name = attribute.name.toLowerCase(); const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name); - if (attribute.is_true) { + if (name === 'value' && node.name.toLowerCase() === 'textarea') { + node_contents = get_attribute_value(attribute); + } else if (attribute.is_true) { renderer.add_string(` ${attr_name}`); } else if ( boolean_attributes.has(name) && @@ -115,15 +111,27 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio } if (name === 'group') { - // TODO server-render group bindings + const value_attribute = node.attributes.find(({ name }) => name === 'value'); + if (value_attribute) { + const value = get_attribute_expression(value_attribute); + const type = node.get_static_attribute_value('type'); + const bound = expression.node; + const condition = type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`; + renderer.add_expression(x`${condition} ? @add_attribute("checked", true, 1) : ""`); + } } else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) { node_contents = expression.node; // TODO where was this used? // value = name === 'textContent' ? x`@escape($$value)` : x`$$value`; + } else if (binding.name === 'value' && node.name === 'textarea') { + const snippet = expression.node; + node_contents = x`${snippet} || ""`; + } else if (binding.name === 'value' && node.name === 'select') { + // NOTE: do not add "value" attribute on - } else { - const snippet = expression.node; - renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); - } - }); - - if (options.hydratable && options.head_id) { - renderer.add_string(` data-svelte="${options.head_id}"`); - } - - renderer.add_string('>'); - - if (node_contents !== undefined) { - if (contenteditable) { - renderer.push(); - renderer.render(children, options); - const result = renderer.pop(); - - renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`); - } else { - renderer.add_expression(node_contents); - } - - renderer.add_string(''); - } else { - 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 496091a0f1a7..91ff5bda5a3b 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -8,6 +8,7 @@ import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; +import { Expression as ESExpression } from 'estree'; export default function (node: Element, renderer: Renderer, options: RenderOptions) { @@ -22,7 +23,12 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - renderer.add_string(`<${node.name}`); + if (node.dynamic_tag_expr) { + renderer.add_string('<'); + renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); + } else { + renderer.add_string(`<${node.name}`); + } const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -151,14 +157,21 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio renderer.add_expression(node_contents); } - if (!is_void(node.name)) { - renderer.add_string(``); - } + add_close_tag(); } else { renderer.render(children, options); + add_close_tag(); + } + function add_close_tag() { if (!is_void(node.name)) { - renderer.add_string(``); + if (node.dynamic_tag_expr) { + renderer.add_string(''); + } else { + renderer.add_string(``); + } } } } From 8c9e93bd623d7c29fd0db5fee25112f2c2e28bdb Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 3 Nov 2021 17:47:50 +0900 Subject: [PATCH 23/68] refactor parse step --- src/compiler/parse/state/tag.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index b9fb296b58ae..298086ede540 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -192,8 +192,9 @@ export default function tag(parser: Parser) { } const definition = element.attributes.splice(index, 1)[0]; - if (definition.value === true || definition.value.length !== 1 || - (definition.value[0].type !== 'Text' && definition.value[0].type !== 'MustacheTag' && definition.value[0].type !== 'AttributeShorthand')) { + if (definition.value === true + || definition.value.length !== 1 + || !['Text', 'MustacheTag', 'AttributeShorthand'].includes(definition.value[0].type)) { parser.error(parser_errors.invalid_element_definition, definition.start); } From a86ec67d4142bd8d2c076284452b32f120e0eedf Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 3 Nov 2021 19:21:01 +0900 Subject: [PATCH 24/68] throw error if uses animation --- src/compiler/compile/compiler_errors.ts | 4 ++++ src/compiler/compile/nodes/Element.ts | 14 ++++++++++++- .../_config.js | 3 +++ .../main.svelte | 20 +++++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/dynamic-element-animation-invalid/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation-invalid/main.svelte diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index b12bcd88cc38..463f6b27cf65 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -238,6 +238,10 @@ export default { code: 'invalid-animation', message: 'An element that uses the animate directive must be the sole child of a keyed each block' }, + invalid_animation_dynamic_element: { + code: 'invalid-animation', + message: ' cannot have a animate directive' + }, invalid_directive_value: { code: 'invalid-directive-value', message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index d26cad8bb719..7632a204db4a 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -16,6 +16,7 @@ import list from '../../utils/list'; import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; +import { TemplateNode } from '../../interfaces'; import Component from '../Component'; import Expression from './shared/Expression'; import compiler_warnings from '../compiler_warnings'; @@ -134,7 +135,7 @@ export default class Element extends Node { needs_manual_style_scoping: boolean; dynamic_tag_expr?: Expression = null; - constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { + constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.name = info.name; @@ -251,6 +252,9 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); + if (this.dynamic_tag_expr) { + this.validate_dynamic_element(info); + } this.validate(); this.optimise(); @@ -258,6 +262,14 @@ export default class Element extends Node { component.apply_stylesheet(this); } + validate_dynamic_element(info: TemplateNode) { + info.attributes.forEach(node => { + if (node.type === 'Animation') { + this.component.error(node, compiler_errors.invalid_animation_dynamic_element); + } + }); + } + validate() { if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported) { this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); diff --git a/test/runtime/samples/dynamic-element-animation-invalid/_config.js b/test/runtime/samples/dynamic-element-animation-invalid/_config.js new file mode 100644 index 000000000000..a3b4e1f9d398 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-invalid/_config.js @@ -0,0 +1,3 @@ +export default { + error: ' cannot have a animate directive' +}; diff --git a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte b/test/runtime/samples/dynamic-element-animation-invalid/main.svelte new file mode 100644 index 000000000000..870d1b3cffae --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-invalid/main.svelte @@ -0,0 +1,20 @@ + + +{#each things as thing (thing.id)} + +{/each} From 89dd933c8bdc2ff9839790f5b8b08600348f81c9 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 3 Nov 2021 20:45:50 +0900 Subject: [PATCH 25/68] move DynamicElementWrapper folder --- .../wrappers/{ => Element}/DynamicElement.ts | 12 ++++++------ src/compiler/compile/render_dom/wrappers/Fragment.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/compiler/compile/render_dom/wrappers/{ => Element}/DynamicElement.ts (91%) diff --git a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts similarity index 91% rename from src/compiler/compile/render_dom/wrappers/DynamicElement.ts rename to src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts index cbb91edfef8e..c81e4f01a3d5 100644 --- a/src/compiler/compile/render_dom/wrappers/DynamicElement.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts @@ -1,11 +1,11 @@ -import Wrapper from './shared/Wrapper'; -import Renderer from '../Renderer'; -import Block from '../Block'; +import Wrapper from '../shared/Wrapper'; +import Renderer from '../../Renderer'; +import Block from '../../Block'; import { b, x } from 'code-red'; import { Identifier } from 'estree'; -import ElementWrapper from './Element/index'; -import create_debugging_comment from './shared/create_debugging_comment'; -import Element from '../../nodes/Element'; +import ElementWrapper from './index'; +import create_debugging_comment from '../shared/create_debugging_comment'; +import Element from '../../../nodes/Element'; export default class DynamicElementWrapper extends Wrapper { node: Element; diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index 26a4e32bce07..d2336103955a 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -2,7 +2,7 @@ import Wrapper from './shared/Wrapper'; import AwaitBlock from './AwaitBlock'; import Body from './Body'; import DebugTag from './DebugTag'; -import DynamicElement from './DynamicElement'; +import DynamicElement from './Element/DynamicElement'; import EachBlock from './EachBlock'; import Element from './Element/index'; import Head from './Head'; From f8cc52a0c37b9883dd412ce64ad89e272f09c8d0 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 3 Nov 2021 21:52:47 +0900 Subject: [PATCH 26/68] merge DynamicElement to Element --- .../wrappers/Element/DynamicElement.ts | 109 ------------------ .../render_dom/wrappers/Element/index.ts | 100 ++++++++++++++++ .../compile/render_dom/wrappers/Fragment.ts | 10 +- 3 files changed, 101 insertions(+), 118 deletions(-) delete mode 100644 src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts diff --git a/src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts b/src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts deleted file mode 100644 index c81e4f01a3d5..000000000000 --- a/src/compiler/compile/render_dom/wrappers/Element/DynamicElement.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Wrapper from '../shared/Wrapper'; -import Renderer from '../../Renderer'; -import Block from '../../Block'; -import { b, x } from 'code-red'; -import { Identifier } from 'estree'; -import ElementWrapper from './index'; -import create_debugging_comment from '../shared/create_debugging_comment'; -import Element from '../../../nodes/Element'; - -export default class DynamicElementWrapper extends Wrapper { - node: Element; - elementWrapper: ElementWrapper; - dynamic_element_block: Block; - var: Identifier = { type: 'Identifier', name: 'dynamic_element' }; - - constructor( - renderer: Renderer, - block: Block, - parent: Wrapper, - node: Element, - strip_whitespace: boolean, - next_sibling: Wrapper - ) { - super(renderer, block, parent, node); - - this.not_static_content(); - - this.dynamic_element_block = block.child({ - comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('create_dynamic_element'), - type: 'dynamic_element' - }); - renderer.blocks.push(this.dynamic_element_block); - - this.elementWrapper = new ElementWrapper( - renderer, - this.dynamic_element_block, - parent, - node, - strip_whitespace, - next_sibling - ); - } - - render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - this.elementWrapper.render( - this.dynamic_element_block, - null, - (x`#nodes` as unknown) as Identifier - ); - - const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.dynamic_tag_expr.manipulate(block); - block.add_variable(previous_tag, snippet); - - block.chunks.init.push(b` - let ${this.var} = ${this.dynamic_element_block.name}(#ctx); - `); - - block.chunks.create.push(b`${this.var}.c();`); - - if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); - } - - block.chunks.mount.push( - b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor' - });` - ); - - const has_transitions = !!( - this.dynamic_element_block.has_intro_method || this.dynamic_element_block.has_outro_method - ); - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - - const body = b` - ${has_transitions - ? b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); - ` - : b`${this.var}.d(1);` - } - ${this.var} = ${this.dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${has_transitions && b`@transition_in(${this.var})`} - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - `; - - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - - block.chunks.update.push(b` - if (${condition}) { - ${body} - } else { - ${this.var}.p(#ctx, #dirty); - } - `); - - if (has_transitions) { - block.chunks.intro.push(b`@transition_in(${this.var})`); - block.chunks.outro.push(b`@transition_out(${this.var})`); - } - - block.chunks.destroy.push(b`${this.var}.d(detaching)`); - } -} diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 9b76078b5b2b..12422ae1631a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -19,6 +19,7 @@ import { add_event_handler } from '../shared/add_event_handlers'; import { add_action } from '../shared/add_actions'; import bind_this from '../shared/bind_this'; import { is_head } from '../shared/is_head'; +import create_debugging_comment from '../shared/create_debugging_comment'; import { Identifier, ExpressionStatement, CallExpression } from 'estree'; import EventHandler from './EventHandler'; import { extract_names } from 'periscopic'; @@ -146,6 +147,9 @@ export default class ElementWrapper extends Wrapper { var: any; void: boolean; + child_dynamic_element_block?: Block = null; + child_dynamic_element?: ElementWrapper = null; + constructor( renderer: Renderer, block: Block, @@ -155,11 +159,34 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); + this.var = { type: 'Identifier', name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') }; + if (node.dynamic_tag_expr) { + if (block.type !== 'child_dynamic_element') { + this.not_static_content(); + this.child_dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('create_dynamic_element'), + type: 'child_dynamic_element' + }); + renderer.blocks.push(this.child_dynamic_element_block); + this.child_dynamic_element = new ElementWrapper( + renderer, + this.child_dynamic_element_block, + parent, + node, + strip_whitespace, + next_sibling + ); + } else { + this.var = { type: 'Identifier', name: 'dynamic_element' }; + } + } + this.void = is_void(node.name); this.class_dependencies = []; @@ -234,6 +261,14 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + if (this.child_dynamic_element) { + this.render_dynamic_element(block, parent_node, parent_nodes); + } else { + this.render_element(block, parent_node, parent_nodes); + } + } + + render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { const { renderer } = this; if (this.node.name === 'noscript') return; @@ -385,6 +420,71 @@ export default class ElementWrapper extends Wrapper { } } + render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + this.child_dynamic_element.render( + this.child_dynamic_element_block, + null, + (x`#nodes` as unknown) as Identifier + ); + + const previous_tag = block.get_unique_name('previous_tag'); + const snippet = this.node.dynamic_tag_expr.manipulate(block); + block.add_variable(previous_tag, snippet); + + block.chunks.init.push(b` + let ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + `); + + block.chunks.create.push(b`${this.var}.c();`); + + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); + } + + block.chunks.mount.push( + b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor' + });` + ); + + const has_transitions = !!( + this.child_dynamic_element_block.has_intro_method || this.child_dynamic_element_block.has_outro_method + ); + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + + const body = b` + ${has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ` + : b`${this.var}.d(1);` + } + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; + + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; + + block.chunks.update.push(b` + if (${condition}) { + ${body} + } else { + ${this.var}.p(#ctx, #dirty); + } + `); + + if (has_transitions) { + block.chunks.intro.push(b`@transition_in(${this.var})`); + block.chunks.outro.push(b`@transition_out(${this.var})`); + } + + block.chunks.destroy.push(b`${this.var}.d(detaching)`); + } + can_use_textcontent() { return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag'); } diff --git a/src/compiler/compile/render_dom/wrappers/Fragment.ts b/src/compiler/compile/render_dom/wrappers/Fragment.ts index d2336103955a..98805b9639b4 100644 --- a/src/compiler/compile/render_dom/wrappers/Fragment.ts +++ b/src/compiler/compile/render_dom/wrappers/Fragment.ts @@ -2,7 +2,6 @@ import Wrapper from './shared/Wrapper'; import AwaitBlock from './AwaitBlock'; import Body from './Body'; import DebugTag from './DebugTag'; -import DynamicElement from './Element/DynamicElement'; import EachBlock from './EachBlock'; import Element from './Element/index'; import Head from './Head'; @@ -28,7 +27,6 @@ const wrappers = { Body, Comment: null, DebugTag, - DynamicElement, EachBlock, Element, Head, @@ -116,13 +114,7 @@ export default class FragmentWrapper { link(last_child, last_child = wrapper); } else { - const Wrapper = (function () { - if (child.type === 'Element' && child.dynamic_tag_expr) { - return wrappers['DynamicElement']; - } else { - return wrappers[child.type]; - } - }()); + const Wrapper = wrappers[child.type]; if (!Wrapper) continue; const wrapper = new Wrapper(renderer, block, parent, child, strip_whitespace, last_child || next_sibling); From e2a36a4a48470e6dbc91542562c72606a86c9fa7 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Wed, 3 Nov 2021 23:43:24 +0900 Subject: [PATCH 27/68] Reuse DOM elements if possible. --- .../render_dom/wrappers/Element/index.ts | 73 ++++++++++--------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 12422ae1631a..ae31be4cbe0a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -389,6 +389,19 @@ export default class ElementWrapper extends Wrapper { this.add_classes(block); this.add_manual_style_scoping(block); + if (nodes && this.renderer.options.hydratable && !this.void) { + block.chunks.claim.push( + b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);` + ); + } + + if (renderer.options.dev) { + const loc = renderer.locate(this.node.start); + block.chunks.hydrate.push( + b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` + ); + } + if (this.node.dynamic_tag_expr) { const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies(); if (dependencies.length) { @@ -396,28 +409,19 @@ export default class ElementWrapper extends Wrapper { dependencies ); + const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; block.chunks.update.push(b` if (${condition}) { @detach(${node}); ${node} = ${render_statement}; + @validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)}); + ${block.chunks.hydrate} + ${block.event_listeners.length && b`${mounted} = false`}; ${staticChildren} } `); } } - - if (nodes && this.renderer.options.hydratable && !this.void) { - block.chunks.claim.push( - b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);` - ); - } - - if (renderer.options.dev) { - const loc = renderer.locate(this.node.start); - block.chunks.hydrate.push( - b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` - ); - } } render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { @@ -450,32 +454,35 @@ export default class ElementWrapper extends Wrapper { this.child_dynamic_element_block.has_intro_method || this.child_dynamic_element_block.has_outro_method ); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - - const body = b` - ${has_transitions - ? b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); - ` - : b`${this.var}.d(1);` - } - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - ${has_transitions && b`@transition_in(${this.var})`} - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); - `; - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - block.chunks.update.push(b` + if (has_transitions) { + const body = b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + @transition_in(${this.var}); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; + + block.chunks.update.push(b` + if (${condition}) { + ${body} + } else { + ${this.var}.p(#ctx, #dirty); + } + `); + } else { + block.chunks.update.push(b` + ${this.var}.p(#ctx, #dirty); if (${condition}) { - ${body} - } else { - ${this.var}.p(#ctx, #dirty); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); } `); + } if (has_transitions) { block.chunks.intro.push(b`@transition_in(${this.var})`); From 75621dc88b7d19c56d0c44b3c161f0500a3649e9 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Thu, 4 Nov 2021 00:05:00 +0900 Subject: [PATCH 28/68] revert useless changes --- src/compiler/compile/nodes/InlineComponent.ts | 6 +++--- src/compiler/compile/render_ssr/Renderer.ts | 1 + src/compiler/parse/state/tag.ts | 15 ++++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index bf7c58a327b4..a7bc986e9b95 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -126,17 +126,17 @@ export default class InlineComponent extends Node { slot_template.attributes.push(attribute); } } - + children.push(slot_template); info.children.splice(i, 1); } } if (info.children.some(node => not_whitespace_text(node))) { - children.push({ + children.push({ start: info.start, end: info.end, - type: 'SlotTemplate', + type: 'SlotTemplate', name: 'svelte:fragment', attributes: [], children: info.children diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index cc89745ec4da..64e9ee1f4e81 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -112,6 +112,7 @@ export default class Renderer { render(nodes: INode[], options: RenderOptions) { nodes.forEach(node => { const handler = handlers[node.type]; + if (!handler) { throw new Error(`No handler for '${node.type}' nodes`); } diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 298086ede540..15b1f1571c30 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -102,15 +102,12 @@ export default function tag(parser: Parser) { } } - const type = (function () { - if (meta_tags.has(name)) return meta_tags.get(name); - if ((/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component')) return 'InlineComponent'; - if (name === 'svelte:fragment') return 'SlotTemplate'; - if (name === 'title' && parent_is_head(parser.stack)) return 'Title'; - if (name === 'slot' && !parser.customElement) return 'Slot'; - if (name === 'svelte:element') return 'Element'; - return 'Element'; - })(); + const type = meta_tags.has(name) + ? meta_tags.get(name) + : (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent' + : name === 'svelte:fragment' ? 'SlotTemplate' + : name === 'title' && parent_is_head(parser.stack) ? 'Title' + : name === 'slot' && !parser.customElement ? 'Slot' : 'Element'; const element: TemplateNode = { start, From 42e465635e604de457947c34f3ba1941d22e4b29 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 09:30:18 +0900 Subject: [PATCH 29/68] refactor --- src/compiler/compile/nodes/Element.ts | 17 ++++++++--- .../render_dom/wrappers/Element/index.ts | 28 +++++++++---------- .../compile/render_ssr/handlers/Element.ts | 18 ++++-------- test/js/samples/debug-ssr-foo/expected.js | 6 ++-- .../samples/ssr-preserve-comments/expected.js | 4 +-- 5 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 7632a204db4a..509ee5f4622b 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -19,6 +19,8 @@ import { INode } from './interfaces'; import { TemplateNode } from '../../interfaces'; import Component from '../Component'; import Expression from './shared/Expression'; +import { string_literal } from '../utils/stringify'; +import { Literal } from 'estree'; import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; @@ -133,18 +135,25 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; - dynamic_tag_expr?: Expression = null; + tag_expr: Expression; + + is_dynamic_element() { + return this.name === 'svelte:element'; + } constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.name = info.name; - if (this.name === 'svelte:element') { + if (info.name === 'svelte:element') { if (typeof info.tag === 'string') { this.name = info.tag; + this.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.tag_expr = new Expression(component, this, scope, info.tag); } + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(info.name) as Literal); } this.namespace = get_namespace(parent as Element, this, component.namespace); @@ -252,7 +261,7 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); - if (this.dynamic_tag_expr) { + if (this.is_dynamic_element()) { this.validate_dynamic_element(info); } this.validate(); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index ae31be4cbe0a..f6bf948d3b2e 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -165,7 +165,7 @@ export default class ElementWrapper extends Wrapper { name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') }; - if (node.dynamic_tag_expr) { + if (node.is_dynamic_element()) { if (block.type !== 'child_dynamic_element') { this.not_static_content(); this.child_dynamic_element_block = block.child({ @@ -225,6 +225,8 @@ export default class ElementWrapper extends Wrapper { block.add_animation(); } + block.add_dependencies(node.tag_expr.dependencies); + // add directive and handler dependencies [node.animation, node.outro, ...node.actions, ...node.classes].forEach(directive => { if (directive && directive.expression) { @@ -238,10 +240,6 @@ export default class ElementWrapper extends Wrapper { } }); - if (node.dynamic_tag_expr) { - block.add_dependencies(node.dynamic_tag_expr.dependencies); - } - if (this.parent) { if (node.actions.length > 0 || node.animation || @@ -283,11 +281,11 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (this.node.dynamic_tag_expr && this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); + if (this.node.is_dynamic_element() && this.renderer.options.dev) { + block.chunks.create.push(b`@validate_dynamic_element(${this.node.tag_expr.manipulate(block)});`); if (renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)});`); + block.chunks.claim.push(b`@validate_dynamic_element(${this.node.tag_expr.manipulate(block)});`); } } @@ -402,8 +400,8 @@ export default class ElementWrapper extends Wrapper { ); } - if (this.node.dynamic_tag_expr) { - const dependencies = this.node.dynamic_tag_expr.dynamic_dependencies(); + if (this.node.is_dynamic_element()) { + const dependencies = this.node.tag_expr.dynamic_dependencies(); if (dependencies.length) { const condition = block.renderer.dirty( dependencies @@ -414,7 +412,7 @@ export default class ElementWrapper extends Wrapper { if (${condition}) { @detach(${node}); ${node} = ${render_statement}; - @validate_dynamic_element(${this.node.dynamic_tag_expr.manipulate(block)}); + @validate_dynamic_element(${this.node.tag_expr.manipulate(block)}); ${block.chunks.hydrate} ${block.event_listeners.length && b`${mounted} = false`}; ${staticChildren} @@ -432,7 +430,7 @@ export default class ElementWrapper extends Wrapper { ); const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.dynamic_tag_expr.manipulate(block); + const snippet = this.node.tag_expr.manipulate(block); block.add_variable(previous_tag, snippet); block.chunks.init.push(b` @@ -497,7 +495,7 @@ export default class ElementWrapper extends Wrapper { } get_render_statement(block: Block) { - const { name, namespace } = this.node; + const { name, namespace, tag_expr } = this.node; if (namespace === namespaces.svg) { return x`@svg_element("${name}")`; @@ -512,7 +510,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_expr ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = tag_expr.manipulate(block); return x`@element(${reference})`; } @@ -524,7 +522,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_expr ? this.node.dynamic_tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.is_dynamic_element() ? this.node.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/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 91ff5bda5a3b..c69d3e88a4cb 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -23,12 +23,8 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - if (node.dynamic_tag_expr) { - renderer.add_string('<'); - renderer.add_expression(node.dynamic_tag_expr.node as ESExpression); - } else { - renderer.add_string(`<${node.name}`); - } + renderer.add_string('<'); + renderer.add_expression(node.tag_expr.node as ESExpression); const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -165,13 +161,9 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio function add_close_tag() { if (!is_void(node.name)) { - if (node.dynamic_tag_expr) { - renderer.add_string(''); - } else { - renderer.add_string(``); - } + renderer.add_string(''); } } } diff --git a/test/js/samples/debug-ssr-foo/expected.js b/test/js/samples/debug-ssr-foo/expected.js index 69da37b2d921..bbd3ea8dac8c 100644 --- a/test/js/samples/debug-ssr-foo/expected.js +++ b/test/js/samples/debug-ssr-foo/expected.js @@ -7,10 +7,10 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => if ($$props.things === void 0 && $$bindings.things && things !== void 0) $$bindings.things(things); if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo); - return `${each(things, thing => `${escape(thing.name)} + return `${each(things, thing => `<${"span"}>${escape(thing.name)} ${debug(null, 7, 2, { foo })}`)} -

foo: ${escape(foo)}

`; +<${"p"}>foo: ${escape(foo)}`; }); -export default Component; \ No newline at end of file +export default Component; diff --git a/test/js/samples/ssr-preserve-comments/expected.js b/test/js/samples/ssr-preserve-comments/expected.js index 4f3326cec569..c787906b0406 100644 --- a/test/js/samples/ssr-preserve-comments/expected.js +++ b/test/js/samples/ssr-preserve-comments/expected.js @@ -2,9 +2,9 @@ import { create_ssr_component } from "svelte/internal"; const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { - return `
content
+ return `<${"div"}>content -
more content
`; +<${"div"}>more content`; }); export default Component; From d223df6c23c23089856f63a6830207895294cf6b Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 10:26:33 +0900 Subject: [PATCH 30/68] stop to use dynamic_element_block --- .../render_dom/wrappers/Element/index.ts | 75 +++---------------- 1 file changed, 11 insertions(+), 64 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index f6bf948d3b2e..f7cbe3108dd3 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -19,7 +19,7 @@ import { add_event_handler } from '../shared/add_event_handlers'; import { add_action } from '../shared/add_actions'; import bind_this from '../shared/bind_this'; import { is_head } from '../shared/is_head'; -import create_debugging_comment from '../shared/create_debugging_comment'; +// import create_debugging_comment from '../shared/create_debugging_comment'; import { Identifier, ExpressionStatement, CallExpression } from 'estree'; import EventHandler from './EventHandler'; import { extract_names } from 'periscopic'; @@ -147,9 +147,6 @@ export default class ElementWrapper extends Wrapper { var: any; void: boolean; - child_dynamic_element_block?: Block = null; - child_dynamic_element?: ElementWrapper = null; - constructor( renderer: Renderer, block: Block, @@ -165,28 +162,6 @@ export default class ElementWrapper extends Wrapper { name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') }; - if (node.is_dynamic_element()) { - if (block.type !== 'child_dynamic_element') { - this.not_static_content(); - this.child_dynamic_element_block = block.child({ - comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('create_dynamic_element'), - type: 'child_dynamic_element' - }); - renderer.blocks.push(this.child_dynamic_element_block); - this.child_dynamic_element = new ElementWrapper( - renderer, - this.child_dynamic_element_block, - parent, - node, - strip_whitespace, - next_sibling - ); - } else { - this.var = { type: 'Identifier', name: 'dynamic_element' }; - } - } - this.void = is_void(node.name); this.class_dependencies = []; @@ -248,6 +223,7 @@ export default class ElementWrapper extends Wrapper { node.intro || node.outro || node.handlers.length > 0 || this.node.name === 'option' || + node.is_dynamic_element() || renderer.options.dev ) { this.parent.cannot_use_innerhtml(); // need to use add_location @@ -259,10 +235,10 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - if (this.child_dynamic_element) { + + this.render_element(block, parent_node, parent_nodes); + if (this.node.is_dynamic_element()) { this.render_dynamic_element(block, parent_node, parent_nodes); - } else { - this.render_element(block, parent_node, parent_nodes); } } @@ -423,71 +399,42 @@ export default class ElementWrapper extends Wrapper { } render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - this.child_dynamic_element.render( - this.child_dynamic_element_block, - null, - (x`#nodes` as unknown) as Identifier - ); const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.tag_expr.manipulate(block); block.add_variable(previous_tag, snippet); - block.chunks.init.push(b` - let ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - `); - - block.chunks.create.push(b`${this.var}.c();`); - - if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); - } - - block.chunks.mount.push( - b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor' - });` - ); - const has_transitions = !!( - this.child_dynamic_element_block.has_intro_method || this.child_dynamic_element_block.has_outro_method + block.has_intro_method || block.has_outro_method ); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; + const render_statement = this.get_render_statement(block); + if (has_transitions) { const body = b` @group_outros(); @transition_out(${this.var}, 1, 1, @noop); @check_outros(); - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); + ${this.var} = ${render_statement}; @transition_in(${this.var}); - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); `; block.chunks.update.push(b` if (${condition}) { ${body} - } else { - ${this.var}.p(#ctx, #dirty); } `); } else { block.chunks.update.push(b` - ${this.var}.p(#ctx, #dirty); if (${condition}) { - ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); } `); } - - if (has_transitions) { - block.chunks.intro.push(b`@transition_in(${this.var})`); - block.chunks.outro.push(b`@transition_out(${this.var})`); - } - - block.chunks.destroy.push(b`${this.var}.d(detaching)`); } can_use_textcontent() { From cdd7180306a8e4ce47de3801fd54667088cd0cfd Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 10:38:40 +0900 Subject: [PATCH 31/68] refactor render_dom --- .../render_dom/wrappers/Element/index.ts | 99 ++++++++----------- 1 file changed, 41 insertions(+), 58 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index f7cbe3108dd3..1e464f5452ce 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -235,14 +235,6 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - - this.render_element(block, parent_node, parent_nodes); - if (this.node.is_dynamic_element()) { - this.render_dynamic_element(block, parent_node, parent_nodes); - } - } - - render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { const { renderer } = this; if (this.node.name === 'noscript') return; @@ -377,63 +369,54 @@ export default class ElementWrapper extends Wrapper { } if (this.node.is_dynamic_element()) { - const dependencies = this.node.tag_expr.dynamic_dependencies(); - if (dependencies.length) { - const condition = block.renderer.dirty( - dependencies - ); + const condition = block.renderer.dirty( + this.node.tag_expr.dynamic_dependencies() + ); + + const render_statement = this.get_render_statement(block); + const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; + block.chunks.update.push(b` + if (${condition}) { + @detach(${node}); + ${node} = ${render_statement}; + @validate_dynamic_element(${this.node.tag_expr.manipulate(block)}); + ${block.chunks.hydrate} + ${block.event_listeners.length && b`${mounted} = false`}; + ${staticChildren} + } + `); + + const previous_tag = block.get_unique_name('previous_tag'); + const snippet = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, snippet); + + const has_transitions = !!(block.has_intro_method || block.has_outro_method); + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; + + if (has_transitions) { + const body = b` + @group_outros(); + @transition_out(${this.var}, 1, 1, @noop); + @check_outros(); + ${this.var} = ${render_statement}; + @transition_in(${this.var}); + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + `; - const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; block.chunks.update.push(b` - if (${condition}) { - @detach(${node}); - ${node} = ${render_statement}; - @validate_dynamic_element(${this.node.tag_expr.manipulate(block)}); - ${block.chunks.hydrate} - ${block.event_listeners.length && b`${mounted} = false`}; - ${staticChildren} + if (${if_statement}) { + ${body} } `); - } - } - } - - render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { - - const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.tag_expr.manipulate(block); - block.add_variable(previous_tag, snippet); - - const has_transitions = !!( - block.has_intro_method || block.has_outro_method - ); - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - const condition = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - - const render_statement = this.get_render_statement(block); - - if (has_transitions) { - const body = b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); - ${this.var} = ${render_statement}; - @transition_in(${this.var}); - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); - `; - - block.chunks.update.push(b` - if (${condition}) { - ${body} + } else { + block.chunks.update.push(b` + if (${if_statement}) { + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); } `); - } else { - block.chunks.update.push(b` - if (${condition}) { - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); } - `); } } From 9068c900c4c049a2e7b82697f451d504495b46c1 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 11:22:42 +0900 Subject: [PATCH 32/68] refactor render_dom --- .../render_dom/wrappers/Element/index.ts | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 1e464f5452ce..e5a15fbe084b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -249,14 +249,6 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (this.node.is_dynamic_element() && this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${this.node.tag_expr.manipulate(block)});`); - - if (renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${this.node.tag_expr.manipulate(block)});`); - } - } - if (renderer.options.hydratable) { if (parent_nodes) { block.chunks.claim.push(b` @@ -369,27 +361,25 @@ export default class ElementWrapper extends Wrapper { } if (this.node.is_dynamic_element()) { - const condition = block.renderer.dirty( + block.renderer.dirty( this.node.tag_expr.dynamic_dependencies() ); - const render_statement = this.get_render_statement(block); - const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; - block.chunks.update.push(b` - if (${condition}) { - @detach(${node}); - ${node} = ${render_statement}; - @validate_dynamic_element(${this.node.tag_expr.manipulate(block)}); - ${block.chunks.hydrate} - ${block.event_listeners.length && b`${mounted} = false`}; - ${staticChildren} - } - `); - const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.tag_expr.manipulate(block); block.add_variable(previous_tag, snippet); + const render_statement = this.get_render_statement(block); + const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; + const statement = (b` + @detach(${node}); + ${node} = ${render_statement}; + @validate_dynamic_element(${snippet}); + ${block.chunks.hydrate} + ${block.event_listeners.length && b`${mounted} = false`}; + ${staticChildren} + `); + const has_transitions = !!(block.has_intro_method || block.has_outro_method); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; @@ -400,7 +390,7 @@ export default class ElementWrapper extends Wrapper { @group_outros(); @transition_out(${this.var}, 1, 1, @noop); @check_outros(); - ${this.var} = ${render_statement}; + ${statement} @transition_in(${this.var}); this.m(${this.get_update_mount_node(anchor)}, ${anchor}); `; @@ -413,10 +403,18 @@ export default class ElementWrapper extends Wrapper { } else { block.chunks.update.push(b` if (${if_statement}) { + ${statement} this.m(${this.get_update_mount_node(anchor)}, ${anchor}); } `); } + + if (this.renderer.options.dev) { + block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); + if (renderer.options.hydratable) { + block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); + } + } } } From 9cd3fc6fd52b5730f317e9f7ed6f44224575dcf3 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 11:29:33 +0900 Subject: [PATCH 33/68] refactor render_dom --- src/compiler/compile/render_dom/wrappers/Element/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e5a15fbe084b..e040540d46dc 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -374,7 +374,6 @@ export default class ElementWrapper extends Wrapper { const statement = (b` @detach(${node}); ${node} = ${render_statement}; - @validate_dynamic_element(${snippet}); ${block.chunks.hydrate} ${block.event_listeners.length && b`${mounted} = false`}; ${staticChildren} @@ -414,6 +413,11 @@ export default class ElementWrapper extends Wrapper { if (renderer.options.hydratable) { block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); } + block.chunks.update.push(b` + if (${if_statement}) { + @validate_dynamic_element(${snippet}); + } + `); } } } From c77b73c001c7c89b41f1bf78d5db38e6feaba247 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 12:13:51 +0900 Subject: [PATCH 34/68] improve transition behavior --- .../render_dom/wrappers/Element/index.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e040540d46dc..3396ad421e7b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -342,7 +342,7 @@ export default class ElementWrapper extends Wrapper { this.add_attributes(block); this.add_directives_in_order(block); - this.add_transitions(block); + const transition_names = this.add_transitions(block); this.add_animation(block); this.add_classes(block); this.add_manual_style_scoping(block); @@ -386,12 +386,9 @@ export default class ElementWrapper extends Wrapper { if (has_transitions) { const body = b` - @group_outros(); - @transition_out(${this.var}, 1, 1, @noop); - @check_outros(); ${statement} - @transition_in(${this.var}); this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${transition_names.outro_name && b`${transition_names.outro_name} = null;`} `; block.chunks.update.push(b` @@ -767,13 +764,16 @@ export default class ElementWrapper extends Wrapper { add_transitions( block: Block - ) { + ): { intro_name: Identifier | null, outro_name: Identifier | null } { + const transition_names = { intro_name: null, outro_name: null }; const { intro, outro } = this.node; - if (!intro && !outro) return; + if (!intro && !outro) return transition_names; if (intro === outro) { // bidirectional transition const name = block.get_unique_name(`${this.var.name}_transition`); + transition_names.intro_name = name; + transition_names.outro_name = name; const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`; @@ -815,6 +815,8 @@ export default class ElementWrapper extends Wrapper { } else { const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`); const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`); + transition_names.intro_name = intro_name; + transition_names.outro_name = outro_name; if (intro) { block.add_variable(intro_name); @@ -895,6 +897,8 @@ export default class ElementWrapper extends Wrapper { if ((intro && intro.expression && intro.expression.dependencies.size) || (outro && outro.expression && outro.expression.dependencies.size)) { block.maintain_context = true; } + + return transition_names; } add_animation(block: Block) { From f51d0f5b331d2e251c4b54a41e324e7777e73537 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 12:22:50 +0900 Subject: [PATCH 35/68] refactor render_dom --- .../render_dom/wrappers/Element/index.ts | 38 ++++--------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 3396ad421e7b..5f0e4705899a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -371,50 +371,28 @@ export default class ElementWrapper extends Wrapper { const render_statement = this.get_render_statement(block); const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; - const statement = (b` - @detach(${node}); - ${node} = ${render_statement}; - ${block.chunks.hydrate} - ${block.event_listeners.length && b`${mounted} = false`}; - ${staticChildren} - `); - - const has_transitions = !!(block.has_intro_method || block.has_outro_method); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - if (has_transitions) { - const body = b` - ${statement} - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); - ${transition_names.outro_name && b`${transition_names.outro_name} = null;`} - `; - - block.chunks.update.push(b` - if (${if_statement}) { - ${body} - } - `); - } else { - block.chunks.update.push(b` + block.chunks.update.push(b` if (${if_statement}) { - ${statement} + @detach(${node}); + ${node} = ${render_statement}; + ${block.chunks.hydrate} + ${block.event_listeners.length && b`${mounted} = false`}; + ${staticChildren} this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${transition_names.outro_name && b`${transition_names.outro_name} = null;`} + ${this.renderer.options.dev && b`@validate_dynamic_element(${snippet});`} } `); - } if (this.renderer.options.dev) { block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); if (renderer.options.hydratable) { block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); } - block.chunks.update.push(b` - if (${if_statement}) { - @validate_dynamic_element(${snippet}); - } - `); } } } From 5160b2bc15afdfe5f8681cb76142b2682cf515ff Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 12:49:42 +0900 Subject: [PATCH 36/68] support animation --- src/compiler/compile/compiler_errors.ts | 4 -- src/compiler/compile/nodes/Element.ts | 11 ---- .../animation-dynamic-element/_config.js | 57 +++++++++++++++++++ .../main.svelte | 8 +-- .../_config.js | 3 - 5 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 test/runtime/samples/animation-dynamic-element/_config.js rename test/runtime/samples/{dynamic-element-animation-invalid => animation-dynamic-element}/main.svelte (72%) delete mode 100644 test/runtime/samples/dynamic-element-animation-invalid/_config.js diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index 463f6b27cf65..b12bcd88cc38 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -238,10 +238,6 @@ export default { code: 'invalid-animation', message: 'An element that uses the animate directive must be the sole child of a keyed each block' }, - invalid_animation_dynamic_element: { - code: 'invalid-animation', - message: ' cannot have a animate directive' - }, invalid_directive_value: { code: 'invalid-directive-value', message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 509ee5f4622b..b29382d20fcc 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -261,9 +261,6 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); - if (this.is_dynamic_element()) { - this.validate_dynamic_element(info); - } this.validate(); this.optimise(); @@ -271,14 +268,6 @@ export default class Element extends Node { component.apply_stylesheet(this); } - validate_dynamic_element(info: TemplateNode) { - info.attributes.forEach(node => { - if (node.type === 'Animation') { - this.component.error(node, compiler_errors.invalid_animation_dynamic_element); - } - }); - } - validate() { if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported) { this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); diff --git a/test/runtime/samples/animation-dynamic-element/_config.js b/test/runtime/samples/animation-dynamic-element/_config.js new file mode 100644 index 000000000000..b80545de3ab1 --- /dev/null +++ b/test/runtime/samples/animation-dynamic-element/_config.js @@ -0,0 +1,57 @@ +export default { + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ] + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test({ assert, component, target, raf }) { + let divs = target.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + component.things = [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]; + + divs = target.querySelectorAll('div'); + assert.ok(~divs[0].style.animation.indexOf('__svelte')); + assert.equal(divs[1].style.animation, ''); + assert.equal(divs[2].style.animation, ''); + assert.equal(divs[3].style.animation, ''); + assert.ok(~divs[4].style.animation.indexOf('__svelte')); + + raf.tick(100); + assert.deepEqual([ + divs[0].style.animation, + divs[4].style.animation + ], ['', '']); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte b/test/runtime/samples/animation-dynamic-element/main.svelte similarity index 72% rename from test/runtime/samples/dynamic-element-animation-invalid/main.svelte rename to test/runtime/samples/animation-dynamic-element/main.svelte index 870d1b3cffae..2b1b37b5b7f7 100644 --- a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte +++ b/test/runtime/samples/animation-dynamic-element/main.svelte @@ -1,8 +1,6 @@ {#each things as thing (thing.id)} - + {thing.name} {/each} diff --git a/test/runtime/samples/dynamic-element-animation-invalid/_config.js b/test/runtime/samples/dynamic-element-animation-invalid/_config.js deleted file mode 100644 index a3b4e1f9d398..000000000000 --- a/test/runtime/samples/dynamic-element-animation-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: ' cannot have a animate directive' -}; From da22d6306f52c7357b5c79b7c6a8f1b20e1be7f8 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 12:56:47 +0900 Subject: [PATCH 37/68] revert useless changes --- src/compiler/compile/render_dom/wrappers/Element/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 5f0e4705899a..d5d5e91e47f6 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -19,7 +19,6 @@ import { add_event_handler } from '../shared/add_event_handlers'; import { add_action } from '../shared/add_actions'; import bind_this from '../shared/bind_this'; import { is_head } from '../shared/is_head'; -// import create_debugging_comment from '../shared/create_debugging_comment'; import { Identifier, ExpressionStatement, CallExpression } from 'estree'; import EventHandler from './EventHandler'; import { extract_names } from 'periscopic'; @@ -156,7 +155,6 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); - this.var = { type: 'Identifier', name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') From 11d12ab56a9477ccfe247d02fe70e702174ab573 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 13:21:17 +0900 Subject: [PATCH 38/68] use getter --- src/compiler/compile/nodes/Element.ts | 2 +- src/compiler/compile/render_dom/wrappers/Element/index.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index b29382d20fcc..a7fdd12e1f7f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -137,7 +137,7 @@ export default class Element extends Node { needs_manual_style_scoping: boolean; tag_expr: Expression; - is_dynamic_element() { + get is_dynamic_element() { return this.name === 'svelte:element'; } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index d5d5e91e47f6..5a86180a552e 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -221,7 +221,7 @@ export default class ElementWrapper extends Wrapper { node.intro || node.outro || node.handlers.length > 0 || this.node.name === 'option' || - node.is_dynamic_element() || + node.is_dynamic_element || renderer.options.dev ) { this.parent.cannot_use_innerhtml(); // need to use add_location @@ -358,7 +358,7 @@ export default class ElementWrapper extends Wrapper { ); } - if (this.node.is_dynamic_element()) { + if (this.node.is_dynamic_element) { block.renderer.dirty( this.node.tag_expr.dynamic_dependencies() ); @@ -427,7 +427,7 @@ export default class ElementWrapper extends Wrapper { const name = this.node.namespace ? this.node.name : this.node.name.toUpperCase(); - const reference = this.node.is_dynamic_element() ? this.node.tag_expr.manipulate(block) : `"${name}"`; + const reference = this.node.is_dynamic_element ? this.node.tag_expr.manipulate(block) : `"${name}"`; if (this.node.namespace === namespaces.svg) { return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; From 25a12a76efa1e08de69f7869728534d6ee5dcc9e Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 13:53:00 +0900 Subject: [PATCH 39/68] refactor --- src/compiler/compile/nodes/Element.ts | 17 ++++++++--------- .../render_dom/wrappers/Element/index.ts | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index a7fdd12e1f7f..71e4dc17925a 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -143,17 +143,16 @@ export default class Element extends Node { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); - this.name = info.name; + if (info.name === 'svelte:element' && typeof info.tag === 'string') { + this.name = info.tag; + } else { + this.name = info.name; + } - if (info.name === 'svelte:element') { - if (typeof info.tag === 'string') { - this.name = info.tag; - this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal); - } else { - this.tag_expr = new Expression(component, this, scope, info.tag); - } + if (info.name === 'svelte:element' && typeof info.tag !== 'string') { + this.tag_expr = new Expression(component, this, scope, info.tag); } else { - this.tag_expr = new Expression(component, this, scope, string_literal(info.name) as Literal); + this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal); } this.namespace = get_namespace(parent as Element, this, component.namespace); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 5a86180a552e..a5bb7790a19b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -221,7 +221,7 @@ export default class ElementWrapper extends Wrapper { node.intro || node.outro || node.handlers.length > 0 || this.node.name === 'option' || - node.is_dynamic_element || + node.tag_expr.dynamic_dependencies().length || renderer.options.dev ) { this.parent.cannot_use_innerhtml(); // need to use add_location From 9d76a92e5f1587e2b635848c4285c431adddfbfa Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 14:28:40 +0900 Subject: [PATCH 40/68] make it more correct of claim_element --- .../compile/render_dom/wrappers/Element/index.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index a5bb7790a19b..e03c0da6d379 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -424,10 +424,15 @@ export default class ElementWrapper extends Wrapper { .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); - const name = this.node.namespace - ? this.node.name - : this.node.name.toUpperCase(); - const reference = this.node.is_dynamic_element ? this.node.tag_expr.manipulate(block) : `"${name}"`; + const reference = (function (node) { + if (node.tag_expr.node.type === 'Literal') { + if (node.namespace) return `"${node.tag_expr.node.value}"`; + return `"${(node.tag_expr.node.value as String || '').toUpperCase()}"`; + } else { + if (node.namespace) return x`${node.tag_expr.manipulate(block)}`; + return x`(${node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; + } + }(this.node)); if (this.node.namespace === namespaces.svg) { return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; From 9863c5e19c2b828ef7576a3870c3f61c75d76c53 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 6 Nov 2021 14:42:36 +0900 Subject: [PATCH 41/68] refactor --- .../render_dom/wrappers/Element/index.ts | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e03c0da6d379..66d2a314dc12 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -381,7 +381,7 @@ export default class ElementWrapper extends Wrapper { ${block.event_listeners.length && b`${mounted} = false`}; ${staticChildren} this.m(${this.get_update_mount_node(anchor)}, ${anchor}); - ${transition_names.outro_name && b`${transition_names.outro_name} = null;`} + ${transition_names.outro && b`${transition_names.outro} = null;`} ${this.renderer.options.dev && b`@validate_dynamic_element(${snippet});`} } `); @@ -745,21 +745,20 @@ export default class ElementWrapper extends Wrapper { add_transitions( block: Block - ): { intro_name: Identifier | null, outro_name: Identifier | null } { - const transition_names = { intro_name: null, outro_name: null }; + ): { intro: Identifier | null, outro: Identifier | null } { + const names = { intro: null, outro: null }; const { intro, outro } = this.node; - if (!intro && !outro) return transition_names; + if (!intro && !outro) return names; if (intro === outro) { // bidirectional transition const name = block.get_unique_name(`${this.var.name}_transition`); - transition_names.intro_name = name; - transition_names.outro_name = name; + names.intro = names.outro = name; const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`; - block.add_variable(name); + block.add_variable(names.intro); const fn = this.renderer.reference(intro.name); @@ -794,13 +793,11 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`); } else { - const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`); - const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`); - transition_names.intro_name = intro_name; - transition_names.outro_name = outro_name; + names.intro = intro && block.get_unique_name(`${this.var.name}_intro`); + names.outro = outro && block.get_unique_name(`${this.var.name}_outro`); if (intro) { - block.add_variable(intro_name); + block.add_variable(names.intro); const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`; @@ -812,19 +809,19 @@ export default class ElementWrapper extends Wrapper { if (outro) { intro_block = b` @add_render_callback(() => { - if (${outro_name}) ${outro_name}.end(1); - ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${intro_name}.start(); + if (${names.outro}) ${names.outro}.end(1); + ${names.intro} = @create_in_transition(${this.var}, ${fn}, ${snippet}); + ${names.intro}.start(); }); `; - block.chunks.outro.push(b`if (${intro_name}) ${intro_name}.invalidate();`); + block.chunks.outro.push(b`if (${names.intro}) ${names.intro}.invalidate();`); } else { intro_block = b` - if (!${intro_name}) { + if (!${names.intro}) { @add_render_callback(() => { - ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${intro_name}.start(); + ${names.intro} = @create_in_transition(${this.var}, ${fn}, ${snippet}); + ${names.intro}.start(); }); } `; @@ -842,7 +839,7 @@ export default class ElementWrapper extends Wrapper { } if (outro) { - block.add_variable(outro_name); + block.add_variable(names.outro); const snippet = outro.expression ? outro.expression.manipulate(block) : x`{}`; @@ -851,14 +848,14 @@ export default class ElementWrapper extends Wrapper { if (!intro) { block.chunks.intro.push(b` - if (${outro_name}) ${outro_name}.end(1); + if (${names.outro}) ${names.outro}.end(1); `); } // TODO hide elements that have outro'd (unless they belong to a still-outroing // group) prior to their removal from the DOM let outro_block = b` - ${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet}); + ${names.outro} = @create_out_transition(${this.var}, ${fn}, ${snippet}); `; if (outro.is_local) { @@ -871,7 +868,7 @@ export default class ElementWrapper extends Wrapper { block.chunks.outro.push(outro_block); - block.chunks.destroy.push(b`if (detaching && ${outro_name}) ${outro_name}.end();`); + block.chunks.destroy.push(b`if (detaching && ${names.outro}) ${names.outro}.end();`); } } @@ -879,7 +876,7 @@ export default class ElementWrapper extends Wrapper { block.maintain_context = true; } - return transition_names; + return names; } add_animation(block: Block) { From b34b0ca6a6a15075f839dd6c5f6e02f1bce556f3 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 7 Nov 2021 22:17:24 +0900 Subject: [PATCH 42/68] stop to use anonymous function --- .../render_dom/wrappers/Element/index.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 66d2a314dc12..e6f54c04b407 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -424,15 +424,18 @@ export default class ElementWrapper extends Wrapper { .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); - const reference = (function (node) { - if (node.tag_expr.node.type === 'Literal') { - if (node.namespace) return `"${node.tag_expr.node.value}"`; - return `"${(node.tag_expr.node.value as String || '').toUpperCase()}"`; + let reference; + if (this.node.tag_expr.node.type === 'Literal') { + if (this.node.namespace) { + reference = `"${this.node.tag_expr.node.value}"`; } else { - if (node.namespace) return x`${node.tag_expr.manipulate(block)}`; - return x`(${node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; + reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`; } - }(this.node)); + } else if (this.node.namespace) { + reference = x`${this.node.tag_expr.manipulate(block)}`; + } else { + reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; + } if (this.node.namespace === namespaces.svg) { return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; From 49bef3afc35b775d676cab2f5232518970c9b025 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sun, 7 Nov 2021 22:27:25 +0900 Subject: [PATCH 43/68] improve performance of render_ssr --- src/compiler/compile/render_ssr/handlers/Element.ts | 12 ++++++++++-- test/js/samples/debug-ssr-foo/expected.js | 4 ++-- test/js/samples/ssr-preserve-comments/expected.js | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index c69d3e88a4cb..819e04ed2912 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -24,7 +24,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio ); renderer.add_string('<'); - renderer.add_expression(node.tag_expr.node as ESExpression); + add_tag_name(); const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -162,9 +162,17 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio function add_close_tag() { if (!is_void(node.name)) { renderer.add_string(''); } } + + function add_tag_name() { + if (node.tag_expr.node.type === 'Literal') { + renderer.add_string(node.tag_expr.node.value as string); + } else { + renderer.add_expression(node.tag_expr.node as ESExpression); + } + } } diff --git a/test/js/samples/debug-ssr-foo/expected.js b/test/js/samples/debug-ssr-foo/expected.js index bbd3ea8dac8c..40531f18c08c 100644 --- a/test/js/samples/debug-ssr-foo/expected.js +++ b/test/js/samples/debug-ssr-foo/expected.js @@ -7,10 +7,10 @@ const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => if ($$props.things === void 0 && $$bindings.things && things !== void 0) $$bindings.things(things); if ($$props.foo === void 0 && $$bindings.foo && foo !== void 0) $$bindings.foo(foo); - return `${each(things, thing => `<${"span"}>${escape(thing.name)} + return `${each(things, thing => `${escape(thing.name)} ${debug(null, 7, 2, { foo })}`)} -<${"p"}>foo: ${escape(foo)}`; +

foo: ${escape(foo)}

`; }); export default Component; diff --git a/test/js/samples/ssr-preserve-comments/expected.js b/test/js/samples/ssr-preserve-comments/expected.js index c787906b0406..4f3326cec569 100644 --- a/test/js/samples/ssr-preserve-comments/expected.js +++ b/test/js/samples/ssr-preserve-comments/expected.js @@ -2,9 +2,9 @@ import { create_ssr_component } from "svelte/internal"; const Component = create_ssr_component(($$result, $$props, $$bindings, slots) => { - return `<${"div"}>content + return `
content
-<${"div"}>more content`; +
more content
`; }); export default Component; From 6788b47eaaebddd89eaeb2b618632b15d5cce896 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 14:14:43 +0900 Subject: [PATCH 44/68] use child_dynamic_element --- .../render_dom/wrappers/Element/index.ts | 87 +++++++++++++++++-- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e6f54c04b407..ea8ac45c1422 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -26,6 +26,7 @@ import Action from '../../../nodes/Action'; import MustacheTagWrapper from '../MustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag'; import is_dynamic from '../shared/is_dynamic'; +import create_debugging_comment from '../shared/create_debugging_comment'; interface BindingGroup { events: string[]; @@ -146,6 +147,9 @@ export default class ElementWrapper extends Wrapper { var: any; void: boolean; + child_dynamic_element_block?: Block = null; + child_dynamic_element?: ElementWrapper = null; + constructor( renderer: Renderer, block: Block, @@ -155,10 +159,34 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); - this.var = { - type: 'Identifier', - name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') - }; + + if (node.is_dynamic_element) { + if (block.type !== 'child_dynamic_element') { + this.child_dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('create_dynamic_element'), + type: 'child_dynamic_element' + }); + renderer.blocks.push(this.child_dynamic_element_block); + this.child_dynamic_element = new ElementWrapper( + renderer, + this.child_dynamic_element_block, + parent, + node, + strip_whitespace, + next_sibling + ); + } + } + + if (block.type === 'child_dynamic_element') { + this.var = { type: 'Identifier', name: 'dynamic_element' }; + } else { + this.var = { + type: 'Identifier', + name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') + }; + } this.void = is_void(node.name); @@ -233,6 +261,55 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + if (this.child_dynamic_element) { + this.render_dynamic_element(block, parent_node, parent_nodes); + } else { + this.render_element(block, parent_node, parent_nodes); + } + } + + render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + this.child_dynamic_element.render( + this.child_dynamic_element_block, + null, + (x`#nodes` as unknown) as Identifier + ); + + block.chunks.init.push(b` + let ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + `); + + block.chunks.create.push(b`${this.var}.c();`); + + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); + } + + block.chunks.mount.push( + b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` + ); + + block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`); + + if (this.child_dynamic_element_block.has_animation) { + block.chunks.measure.push(b`${this.var}.r();`); + block.chunks.fix.push(b`${this.var}.f();`); + block.chunks.animate.push(b`${this.var}.a();`); + } + + if (this.child_dynamic_element_block.has_intros) { + block.chunks.intro.push(b`@transition_in(${this.var});`); + } + + if (this.child_dynamic_element_block.has_outros) { + block.chunks.outro.push(b`@transition_out(${this.var});`); + } + + block.chunks.destroy.push(b`${this.var}.d(detaching)`); + } + + render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + const { renderer } = this; if (this.node.name === 'noscript') return; @@ -358,7 +435,7 @@ export default class ElementWrapper extends Wrapper { ); } - if (this.node.is_dynamic_element) { + if (block.type === 'child_dynamic_element') { block.renderer.dirty( this.node.tag_expr.dynamic_dependencies() ); From 28b266511759a0ef9c94486e31cf79e1ce2b1162 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 15:10:32 +0900 Subject: [PATCH 45/68] Revert "support animation" This reverts commit 5160b2bc15afdfe5f8681cb76142b2682cf515ff. --- src/compiler/compile/compiler_errors.ts | 4 ++ src/compiler/compile/nodes/Element.ts | 11 ++++ .../animation-dynamic-element/_config.js | 57 ------------------- .../_config.js | 3 + .../main.svelte | 8 ++- 5 files changed, 23 insertions(+), 60 deletions(-) delete mode 100644 test/runtime/samples/animation-dynamic-element/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation-invalid/_config.js rename test/runtime/samples/{animation-dynamic-element => dynamic-element-animation-invalid}/main.svelte (72%) diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index b12bcd88cc38..463f6b27cf65 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -238,6 +238,10 @@ export default { code: 'invalid-animation', message: 'An element that uses the animate directive must be the sole child of a keyed each block' }, + invalid_animation_dynamic_element: { + code: 'invalid-animation', + message: ' cannot have a animate directive' + }, invalid_directive_value: { code: 'invalid-directive-value', message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 71e4dc17925a..5595ad4762c1 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -260,6 +260,9 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); + if (this.is_dynamic_element()) { + this.validate_dynamic_element(info); + } this.validate(); this.optimise(); @@ -267,6 +270,14 @@ export default class Element extends Node { component.apply_stylesheet(this); } + validate_dynamic_element(info: TemplateNode) { + info.attributes.forEach(node => { + if (node.type === 'Animation') { + this.component.error(node, compiler_errors.invalid_animation_dynamic_element); + } + }); + } + validate() { if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported) { this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); diff --git a/test/runtime/samples/animation-dynamic-element/_config.js b/test/runtime/samples/animation-dynamic-element/_config.js deleted file mode 100644 index b80545de3ab1..000000000000 --- a/test/runtime/samples/animation-dynamic-element/_config.js +++ /dev/null @@ -1,57 +0,0 @@ -export default { - props: { - things: [ - { id: 1, name: 'a' }, - { id: 2, name: 'b' }, - { id: 3, name: 'c' }, - { id: 4, name: 'd' }, - { id: 5, name: 'e' } - ] - }, - - html: ` -
a
-
b
-
c
-
d
-
e
- `, - - test({ assert, component, target, raf }) { - let divs = target.querySelectorAll('div'); - divs.forEach(div => { - div.getBoundingClientRect = function() { - const index = [...this.parentNode.children].indexOf(this); - const top = index * 30; - - return { - left: 0, - right: 100, - top, - bottom: top + 20 - }; - }; - }); - - component.things = [ - { id: 5, name: 'e' }, - { id: 2, name: 'b' }, - { id: 3, name: 'c' }, - { id: 4, name: 'd' }, - { id: 1, name: 'a' } - ]; - - divs = target.querySelectorAll('div'); - assert.ok(~divs[0].style.animation.indexOf('__svelte')); - assert.equal(divs[1].style.animation, ''); - assert.equal(divs[2].style.animation, ''); - assert.equal(divs[3].style.animation, ''); - assert.ok(~divs[4].style.animation.indexOf('__svelte')); - - raf.tick(100); - assert.deepEqual([ - divs[0].style.animation, - divs[4].style.animation - ], ['', '']); - } -}; diff --git a/test/runtime/samples/dynamic-element-animation-invalid/_config.js b/test/runtime/samples/dynamic-element-animation-invalid/_config.js new file mode 100644 index 000000000000..a3b4e1f9d398 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-invalid/_config.js @@ -0,0 +1,3 @@ +export default { + error: ' cannot have a animate directive' +}; diff --git a/test/runtime/samples/animation-dynamic-element/main.svelte b/test/runtime/samples/dynamic-element-animation-invalid/main.svelte similarity index 72% rename from test/runtime/samples/animation-dynamic-element/main.svelte rename to test/runtime/samples/dynamic-element-animation-invalid/main.svelte index 2b1b37b5b7f7..870d1b3cffae 100644 --- a/test/runtime/samples/animation-dynamic-element/main.svelte +++ b/test/runtime/samples/dynamic-element-animation-invalid/main.svelte @@ -1,6 +1,8 @@ {#each things as thing (thing.id)} - {thing.name} + {/each} From 3a96f18beb57f9ad7acc523dbfb97e6b739d8ebb Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 15:12:26 +0900 Subject: [PATCH 46/68] fix compile error --- src/compiler/compile/nodes/Element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 5595ad4762c1..afe8a512ae38 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -260,7 +260,7 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); - if (this.is_dynamic_element()) { + if (this.is_dynamic_element) { this.validate_dynamic_element(info); } this.validate(); From 2e22241e3cf667c21be70ec173cbe63d7bcc9cb8 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 16:06:04 +0900 Subject: [PATCH 47/68] refactor --- .../render_dom/wrappers/Element/index.ts | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index ea8ac45c1422..9a92a6956c36 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -179,14 +179,10 @@ export default class ElementWrapper extends Wrapper { } } - if (block.type === 'child_dynamic_element') { - this.var = { type: 'Identifier', name: 'dynamic_element' }; - } else { - this.var = { - type: 'Identifier', - name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') - }; - } + this.var = { + type: 'Identifier', + name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') + }; this.void = is_void(node.name); @@ -291,12 +287,6 @@ export default class ElementWrapper extends Wrapper { block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`); - if (this.child_dynamic_element_block.has_animation) { - block.chunks.measure.push(b`${this.var}.r();`); - block.chunks.fix.push(b`${this.var}.f();`); - block.chunks.animate.push(b`${this.var}.a();`); - } - if (this.child_dynamic_element_block.has_intros) { block.chunks.intro.push(b`@transition_in(${this.var});`); } @@ -435,10 +425,9 @@ export default class ElementWrapper extends Wrapper { ); } - if (block.type === 'child_dynamic_element') { - block.renderer.dirty( - this.node.tag_expr.dynamic_dependencies() - ); + const dynamic_dependencies = this.node.tag_expr.dynamic_dependencies(); + if (block.type === 'child_dynamic_element' && dynamic_dependencies.length > 0) { + block.renderer.dirty(dynamic_dependencies); const previous_tag = block.get_unique_name('previous_tag'); const snippet = this.node.tag_expr.manipulate(block); @@ -450,7 +439,7 @@ export default class ElementWrapper extends Wrapper { const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - block.chunks.update.push(b` + block.chunks.update.unshift(b` if (${if_statement}) { @detach(${node}); ${node} = ${render_statement}; From 7c0337a697d2356cae0ee23370a73c9e8737d362 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 16:59:19 +0900 Subject: [PATCH 48/68] stop reuse children of --- .../render_dom/wrappers/Element/index.ts | 49 ++++++++----------- .../dynamic-element-reuse-children/_config.js | 22 --------- .../main.svelte | 9 ---- 3 files changed, 21 insertions(+), 59 deletions(-) delete mode 100644 test/runtime/samples/dynamic-element-reuse-children/_config.js delete mode 100644 test/runtime/samples/dynamic-element-reuse-children/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 9a92a6956c36..2e6ea0cc6465 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -271,6 +271,10 @@ export default class ElementWrapper extends Wrapper { (x`#nodes` as unknown) as Identifier ); + const previous_tag = block.get_unique_name('previous_tag'); + const snippet = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, snippet); + block.chunks.init.push(b` let ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); `); @@ -285,7 +289,20 @@ export default class ElementWrapper extends Wrapper { b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` ); - block.chunks.update.push(b`${this.var}.p(#ctx, #dirty);`); + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; + + block.chunks.update.unshift(b` + if (${if_statement}) { + ${this.var}.d(1); + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + `); if (this.child_dynamic_element_block.has_intros) { block.chunks.intro.push(b`@transition_in(${this.var});`); @@ -407,7 +424,7 @@ export default class ElementWrapper extends Wrapper { this.add_attributes(block); this.add_directives_in_order(block); - const transition_names = this.add_transitions(block); + this.add_transitions(block); this.add_animation(block); this.add_classes(block); this.add_manual_style_scoping(block); @@ -425,33 +442,9 @@ export default class ElementWrapper extends Wrapper { ); } - const dynamic_dependencies = this.node.tag_expr.dynamic_dependencies(); - if (block.type === 'child_dynamic_element' && dynamic_dependencies.length > 0) { - block.renderer.dirty(dynamic_dependencies); - - const previous_tag = block.get_unique_name('previous_tag'); + block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); + if (block.type === 'child_dynamic_element' && this.node.tag_expr.dependencies.size > 0) { const snippet = this.node.tag_expr.manipulate(block); - block.add_variable(previous_tag, snippet); - - const render_statement = this.get_render_statement(block); - const mounted: Identifier = { type: 'Identifier', name: '#mounted' }; - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); - const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - - block.chunks.update.unshift(b` - if (${if_statement}) { - @detach(${node}); - ${node} = ${render_statement}; - ${block.chunks.hydrate} - ${block.event_listeners.length && b`${mounted} = false`}; - ${staticChildren} - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); - ${transition_names.outro && b`${transition_names.outro} = null;`} - ${this.renderer.options.dev && b`@validate_dynamic_element(${snippet});`} - } - `); - if (this.renderer.options.dev) { block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); if (renderer.options.hydratable) { diff --git a/test/runtime/samples/dynamic-element-reuse-children/_config.js b/test/runtime/samples/dynamic-element-reuse-children/_config.js deleted file mode 100644 index 3974ee5876f9..000000000000 --- a/test/runtime/samples/dynamic-element-reuse-children/_config.js +++ /dev/null @@ -1,22 +0,0 @@ -export default { - props: { - tag: 'div', - text: 'Foo' - }, - html: '
Foo
', - - test({ assert, component, target }) { - const innerDiv = target.querySelector('div > div'); - component.tag = 'h1'; - component.text = 'Bar'; - - assert.htmlEqual( - target.innerHTML, - ` -

Bar

- ` - ); - - assert.equal(innerDiv, target.querySelector('h1 > div')); - } -}; diff --git a/test/runtime/samples/dynamic-element-reuse-children/main.svelte b/test/runtime/samples/dynamic-element-reuse-children/main.svelte deleted file mode 100644 index 4be99aab09a6..000000000000 --- a/test/runtime/samples/dynamic-element-reuse-children/main.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - - -
- {text} -
-
\ No newline at end of file From 7b6dbcd228fd28cb753b67573951f1511c211f67 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 17:02:08 +0900 Subject: [PATCH 49/68] revert add_transitions --- .../render_dom/wrappers/Element/index.ts | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 2e6ea0cc6465..23b86f0d34dd 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -807,20 +807,18 @@ export default class ElementWrapper extends Wrapper { add_transitions( block: Block - ): { intro: Identifier | null, outro: Identifier | null } { - const names = { intro: null, outro: null }; + ) { const { intro, outro } = this.node; - if (!intro && !outro) return names; + if (!intro && !outro) return; if (intro === outro) { // bidirectional transition const name = block.get_unique_name(`${this.var.name}_transition`); - names.intro = names.outro = name; const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`; - block.add_variable(names.intro); + block.add_variable(name); const fn = this.renderer.reference(intro.name); @@ -855,11 +853,11 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (detaching && ${name}) ${name}.end();`); } else { - names.intro = intro && block.get_unique_name(`${this.var.name}_intro`); - names.outro = outro && block.get_unique_name(`${this.var.name}_outro`); + const intro_name = intro && block.get_unique_name(`${this.var.name}_intro`); + const outro_name = outro && block.get_unique_name(`${this.var.name}_outro`); if (intro) { - block.add_variable(names.intro); + block.add_variable(intro_name); const snippet = intro.expression ? intro.expression.manipulate(block) : x`{}`; @@ -871,19 +869,19 @@ export default class ElementWrapper extends Wrapper { if (outro) { intro_block = b` @add_render_callback(() => { - if (${names.outro}) ${names.outro}.end(1); - ${names.intro} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${names.intro}.start(); + if (${outro_name}) ${outro_name}.end(1); + ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); + ${intro_name}.start(); }); `; - block.chunks.outro.push(b`if (${names.intro}) ${names.intro}.invalidate();`); + block.chunks.outro.push(b`if (${intro_name}) ${intro_name}.invalidate();`); } else { intro_block = b` - if (!${names.intro}) { + if (!${intro_name}) { @add_render_callback(() => { - ${names.intro} = @create_in_transition(${this.var}, ${fn}, ${snippet}); - ${names.intro}.start(); + ${intro_name} = @create_in_transition(${this.var}, ${fn}, ${snippet}); + ${intro_name}.start(); }); } `; @@ -901,7 +899,7 @@ export default class ElementWrapper extends Wrapper { } if (outro) { - block.add_variable(names.outro); + block.add_variable(outro_name); const snippet = outro.expression ? outro.expression.manipulate(block) : x`{}`; @@ -910,14 +908,14 @@ export default class ElementWrapper extends Wrapper { if (!intro) { block.chunks.intro.push(b` - if (${names.outro}) ${names.outro}.end(1); + if (${outro_name}) ${outro_name}.end(1); `); } // TODO hide elements that have outro'd (unless they belong to a still-outroing // group) prior to their removal from the DOM let outro_block = b` - ${names.outro} = @create_out_transition(${this.var}, ${fn}, ${snippet}); + ${outro_name} = @create_out_transition(${this.var}, ${fn}, ${snippet}); `; if (outro.is_local) { @@ -930,15 +928,13 @@ export default class ElementWrapper extends Wrapper { block.chunks.outro.push(outro_block); - block.chunks.destroy.push(b`if (detaching && ${names.outro}) ${names.outro}.end();`); + block.chunks.destroy.push(b`if (detaching && ${outro_name}) ${outro_name}.end();`); } } if ((intro && intro.expression && intro.expression.dependencies.size) || (outro && outro.expression && outro.expression.dependencies.size)) { block.maintain_context = true; } - - return names; } add_animation(block: Block) { From 8560d7ca2b8459d81b986b873c791e1df35abfc4 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 13 Nov 2021 17:06:55 +0900 Subject: [PATCH 50/68] revert unnecessary changes --- .../compile/render_dom/wrappers/Element/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 23b86f0d34dd..ef5aa8a18e77 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -373,14 +373,13 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (detaching) @detach(${node});`); } - let staticChildren = null; - // insert static children with textContent or innerHTML const can_use_textcontent = this.can_use_textcontent(); if (!this.node.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.fragment.nodes.length > 0) { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { - staticChildren = b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};`; - block.chunks.create.push(staticChildren); + block.chunks.create.push( + b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};` + ); } else { const state = { quasi: { @@ -399,8 +398,9 @@ export default class ElementWrapper extends Wrapper { to_html((this.fragment.nodes as unknown as Array), block, literal, state, can_use_raw_text); literal.quasis.push(state.quasi); - staticChildren = b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};`; - block.chunks.create.push(staticChildren); + block.chunks.create.push( + b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};` + ); } } else { this.fragment.nodes.forEach((child: Wrapper) => { From 2171c672250bcea4e7e0fa013c4dbb9fd2595454 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 28 Nov 2021 15:13:33 +0900 Subject: [PATCH 51/68] fix in transition when tag of changed --- .../render_dom/wrappers/Element/index.ts | 1 + .../_config.js | 14 ++++++++++++++ .../main.svelte | 17 +++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index ef5aa8a18e77..e9d9fc8f4b00 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -298,6 +298,7 @@ export default class ElementWrapper extends Wrapper { ${this.var}.d(1); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); + @transition_in(${this.var}); this.m(${this.get_update_mount_node(anchor)}, ${anchor}); } else { ${this.var}.p(#ctx, #dirty); diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js b/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js new file mode 100644 index 000000000000..256d83af4e16 --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js @@ -0,0 +1,14 @@ +export default { + test({ assert, component, target, raf }) { + component.visible = true; + const h1 = target.querySelector('h1'); + + assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + + raf.tick(50); + component.tag = 'h2'; + const h2 = target.querySelector('h2'); + assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + assert.equal(h2.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + } +}; diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte b/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte new file mode 100644 index 000000000000..b0f24c8dec36 --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte @@ -0,0 +1,17 @@ + + +{#if visible} + +{/if} From d146c297acec257ef35a4587311012a89a45ea25 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 28 Nov 2021 17:11:14 +0900 Subject: [PATCH 52/68] call of action when tag changed --- .../render_dom/wrappers/Element/index.ts | 3 +-- .../dynamic-element-action-update/_config.js | 21 +++++++++++++++++++ .../dynamic-element-action-update/main.svelte | 13 ++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/dynamic-element-action-update/_config.js create mode 100644 test/runtime/samples/dynamic-element-action-update/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e9d9fc8f4b00..504923b247cf 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -294,14 +294,13 @@ export default class ElementWrapper extends Wrapper { const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; block.chunks.update.unshift(b` + ${this.var}.p(#ctx, #dirty); if (${if_statement}) { ${this.var}.d(1); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); @transition_in(${this.var}); this.m(${this.get_update_mount_node(anchor)}, ${anchor}); - } else { - ${this.var}.p(#ctx, #dirty); } `); diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js new file mode 100644 index 000000000000..c2c6c16451ab --- /dev/null +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -0,0 +1,21 @@ +export default { + html: ` +

tag is h1.

+ `, + + async test({ assert, component, target }) { + + assert.equal(component.tag, 'h1'); + assert.equal(component.updateText, ''); + assert.equal(component.destroyText, ''); + + component.tag = 'h2'; + + assert.equal(component.tag, 'h2'); + assert.equal(component.updateText, 'update: h2'); + assert.equal(component.destroyText, 'destroy'); + assert.htmlEqual(target.innerHTML, ` +

tag is h2.

+ `); + } +}; diff --git a/test/runtime/samples/dynamic-element-action-update/main.svelte b/test/runtime/samples/dynamic-element-action-update/main.svelte new file mode 100644 index 000000000000..1abec7a64988 --- /dev/null +++ b/test/runtime/samples/dynamic-element-action-update/main.svelte @@ -0,0 +1,13 @@ + + +tag is {tag}. From 7a832d69fd42af6c811bfe1548f234a11bb06da1 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 28 Nov 2021 18:03:29 +0900 Subject: [PATCH 53/68] stop to transform to normal element even if svelte:element's value of this is literal --- src/compiler/compile/nodes/Element.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index afe8a512ae38..7490f9e7d678 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -143,14 +143,14 @@ export default class Element extends Node { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); - if (info.name === 'svelte:element' && typeof info.tag === 'string') { - this.name = info.tag; - } else { - this.name = info.name; - } + this.name = info.name; - if (info.name === 'svelte:element' && typeof info.tag !== 'string') { - this.tag_expr = new Expression(component, this, scope, info.tag); + if (info.name === 'svelte:element') { + if (typeof info.tag !== 'string') { + this.tag_expr = new Expression(component, this, scope, info.tag); + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal); + } } else { this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal); } From b437985e0dadfc6eb635289c26ba44d495091ee4 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 9 Jan 2022 08:15:58 +0900 Subject: [PATCH 54/68] refactor redundant if statement --- .../render_dom/wrappers/Element/index.ts | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 8bf6d78f6b66..7bfe95ac3e4a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -160,23 +160,21 @@ export default class ElementWrapper extends Wrapper { ) { super(renderer, block, parent, node); - if (node.is_dynamic_element) { - if (block.type !== 'child_dynamic_element') { - this.child_dynamic_element_block = block.child({ - comment: create_debugging_comment(node, renderer.component), - name: renderer.component.get_unique_name('create_dynamic_element'), - type: 'child_dynamic_element' - }); - renderer.blocks.push(this.child_dynamic_element_block); - this.child_dynamic_element = new ElementWrapper( - renderer, - this.child_dynamic_element_block, - parent, - node, - strip_whitespace, - next_sibling - ); - } + if (node.is_dynamic_element && block.type !== 'child_dynamic_element') { + this.child_dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('create_dynamic_element'), + type: 'child_dynamic_element' + }); + renderer.blocks.push(this.child_dynamic_element_block); + this.child_dynamic_element = new ElementWrapper( + renderer, + this.child_dynamic_element_block, + parent, + node, + strip_whitespace, + next_sibling + ); } this.var = { @@ -1046,7 +1044,7 @@ export default class ElementWrapper extends Wrapper { if (should_cache) { block.chunks.update.push(b` if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) { - ${updater} + ${updater} } `); } else { From 58501553ab11eac0043a9e579d2f6506daa938a5 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sun, 9 Jan 2022 09:46:34 +0900 Subject: [PATCH 55/68] prevent to create element if value of `this` attribute is null / undefined / empty string. --- .../render_dom/wrappers/Element/index.ts | 44 ++++++++++--------- src/runtime/internal/dev.ts | 2 +- .../_config.js | 0 .../main.svelte | 0 .../_config.js | 9 ++++ .../main.svelte | 5 +++ .../_config.js | 9 ++++ .../main.svelte | 5 +++ .../dynamic-element-empty-tag/_config.js | 3 ++ .../dynamic-element-empty-tag/main.svelte | 5 +++ .../dynamic-element-null-tag/_config.js | 3 ++ .../dynamic-element-null-tag/main.svelte | 5 +++ .../dynamic-element-undefined-tag/_config.js | 3 ++ .../dynamic-element-undefined-tag/main.svelte | 5 +++ 14 files changed, 77 insertions(+), 21 deletions(-) rename test/runtime/samples/{dev-warning-dynamic-element-nullish-tag => dev-warning-dynamic-element-empty-tag}/_config.js (100%) rename test/runtime/samples/{dev-warning-dynamic-element-nullish-tag => dev-warning-dynamic-element-empty-tag}/main.svelte (100%) create mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js create mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte create mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js create mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-empty-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-empty-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-null-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-null-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-undefined-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-undefined-tag/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 7bfe95ac3e4a..ee8c95423e54 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -274,17 +274,17 @@ export default class ElementWrapper extends Wrapper { block.add_variable(previous_tag, snippet); block.chunks.init.push(b` - let ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + let ${this.var} = ${snippet} && ${this.child_dynamic_element_block.name}(#ctx); `); - block.chunks.create.push(b`${this.var}.c();`); + block.chunks.create.push(b`if (${this.var}) ${this.var}.c();`); if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`${this.var}.l(${parent_nodes});`); + block.chunks.claim.push(b`if (${this.var}) ${this.var}.l(${parent_nodes});`); } block.chunks.mount.push( - b`${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` + b`if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` ); const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); @@ -292,13 +292,18 @@ export default class ElementWrapper extends Wrapper { const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; block.chunks.update.unshift(b` - ${this.var}.p(#ctx, #dirty); - if (${if_statement}) { + if (${snippet}) { + if (${this.var}) ${this.var}.p(#ctx, #dirty); + if (${if_statement}) { + if (${this.var}) ${this.var}.d(1); + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + @transition_in(${this.var}); + this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } + } else if (${this.var}) { ${this.var}.d(1); - ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); - ${this.var}.c(); - @transition_in(${this.var}); - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${this.var} = null; } `); @@ -310,7 +315,15 @@ export default class ElementWrapper extends Wrapper { block.chunks.outro.push(b`@transition_out(${this.var});`); } - block.chunks.destroy.push(b`${this.var}.d(detaching)`); + block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); + + if (this.renderer.options.dev) { + block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); + } + block.chunks.update.push(b`@validate_dynamic_element(${snippet});`); + } } render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { @@ -442,15 +455,6 @@ export default class ElementWrapper extends Wrapper { } block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); - if (block.type === 'child_dynamic_element' && this.node.tag_expr.dependencies.size > 0) { - const snippet = this.node.tag_expr.manipulate(block); - if (this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); - if (renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); - } - } - } } can_use_textcontent() { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 5a459aefd781..ee40fb045648 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -108,7 +108,7 @@ export function validate_slots(name, slot, keys) { } export function validate_dynamic_element(tag) { - if (tag == null) { + if (!tag) { console.warn(' expects a non-nullish value in attribute "this"'); } } diff --git a/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js similarity index 100% rename from test/runtime/samples/dev-warning-dynamic-element-nullish-tag/_config.js rename to test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js diff --git a/test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte similarity index 100% rename from test/runtime/samples/dev-warning-dynamic-element-nullish-tag/main.svelte rename to test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js new file mode 100644 index 000000000000..a010e9365a77 --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + + warnings: [ + ' expects a non-nullish value in attribute "this"' + ] +}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte new file mode 100644 index 000000000000..e094a54fac68 --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js new file mode 100644 index 000000000000..a010e9365a77 --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + + warnings: [ + ' expects a non-nullish value in attribute "this"' + ] +}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte new file mode 100644 index 000000000000..6e5934d581de --- /dev/null +++ b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/dynamic-element-empty-tag/_config.js b/test/runtime/samples/dynamic-element-empty-tag/_config.js new file mode 100644 index 000000000000..22dc25b41e1f --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-empty-tag/main.svelte b/test/runtime/samples/dynamic-element-empty-tag/main.svelte new file mode 100644 index 000000000000..e3889ce0f5c7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-null-tag/_config.js b/test/runtime/samples/dynamic-element-null-tag/_config.js new file mode 100644 index 000000000000..22dc25b41e1f --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-null-tag/main.svelte b/test/runtime/samples/dynamic-element-null-tag/main.svelte new file mode 100644 index 000000000000..58dc96ff2a17 --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dynamic-element-undefined-tag/_config.js new file mode 100644 index 000000000000..22dc25b41e1f --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte new file mode 100644 index 000000000000..785ab58073f4 --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo From 02cc14fbd39d7a0254a9f4f48526ac097dd218cc Mon Sep 17 00:00:00 2001 From: baseballyama Date: Sat, 12 Feb 2022 14:30:06 +0900 Subject: [PATCH 56/68] workaround ts error I faced this error and I avoid it by workaround. 'error TS2590: Expression produces a union type that is too complex to represent.' --- src/compiler/compile/nodes/Element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index e0edc2682081..163aeac07df5 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -200,7 +200,7 @@ export default class Element extends Node { return this.name === 'svelte:element'; } - constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { + constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { super(component, parent, scope, info); this.name = info.name; From a8d7905ea4e9a9e27ea47bbb3533ceec02bed30c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 1 Apr 2022 17:56:56 +0200 Subject: [PATCH 57/68] refactor update block, support animations, support falsy values, adjust tests --- src/compiler/compile/nodes/Element.ts | 12 --- .../render_dom/wrappers/Element/index.ts | 84 +++++++++++++------ src/runtime/internal/dev.ts | 6 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../_config.js | 9 -- .../main.svelte | 5 -- .../dynamic-element-action-update/_config.js | 8 +- .../dynamic-element-action-update/main.svelte | 7 +- .../_config.js | 3 - .../dynamic-element-animation/_config.js | 62 ++++++++++++++ .../main.svelte | 10 +-- .../_config.js | 11 ++- .../main.svelte | 2 +- 16 files changed, 143 insertions(+), 104 deletions(-) delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js delete mode 100644 test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte delete mode 100644 test/runtime/samples/dynamic-element-animation-invalid/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation/_config.js rename test/runtime/samples/{dynamic-element-animation-invalid => dynamic-element-animation}/main.svelte (70%) rename test/runtime/samples/{dynamic-element-transition-css-change-tag => dynamic-element-transition}/_config.js (63%) rename test/runtime/samples/{dynamic-element-transition-css-change-tag => dynamic-element-transition}/main.svelte (75%) diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index d27df6ef401a..00a9b6b22fce 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -17,7 +17,6 @@ import list from '../../utils/list'; import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; -import { TemplateNode } from '../../interfaces'; import Component from '../Component'; import Expression from './shared/Expression'; import { string_literal } from '../utils/stringify'; @@ -323,9 +322,6 @@ export default class Element extends Node { this.scope = scope; this.children = map_children(component, this, this.scope, info.children); - if (this.is_dynamic_element) { - this.validate_dynamic_element(info); - } this.validate(); this.optimise(); @@ -333,14 +329,6 @@ export default class Element extends Node { component.apply_stylesheet(this); } - validate_dynamic_element(info: TemplateNode) { - info.attributes.forEach(node => { - if (node.type === 'Animation') { - this.component.error(node, compiler_errors.invalid_animation_dynamic_element); - } - }); - } - validate() { if (this.component.var_lookup.has(this.name) && this.component.var_lookup.get(this.name).imported) { this.component.warn(this, compiler_warnings.component_name_lowercase(this.name)); diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 8a9257de6695..e0d942b8641b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -272,41 +272,63 @@ export default class ElementWrapper extends Wrapper { ); const previous_tag = block.get_unique_name('previous_tag'); - const snippet = this.node.tag_expr.manipulate(block); - block.add_variable(previous_tag, snippet); + const tag = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, tag); block.chunks.init.push(b` - let ${this.var} = ${snippet} && ${this.child_dynamic_element_block.name}(#ctx); + let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); `); - block.chunks.create.push(b`if (${this.var}) ${this.var}.c();`); + block.chunks.create.push(b` + if (${this.var}) ${this.var}.c(); + `); if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`if (${this.var}) ${this.var}.l(${parent_nodes});`); + block.chunks.claim.push(b` + if (${this.var}) ${this.var}.l(${parent_nodes}); + `); } - block.chunks.mount.push( - b`if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'});` - ); + block.chunks.mount.push(b` + if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); + `); - const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const anchor = block.get_unique_name(`${this.child_dynamic_element_block.name.name}_anchor`); + const has_transitions = !!(this.node.intro || this.node.outro); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; - const if_statement = x`${not_equal}(${previous_tag}, ${previous_tag} = ${snippet})`; - block.chunks.update.unshift(b` - if (${snippet}) { - if (${this.var}) ${this.var}.p(#ctx, #dirty); - if (${if_statement}) { - if (${this.var}) ${this.var}.d(1); + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); - @transition_in(${this.var}); - this.m(${this.get_update_mount_node(anchor)}, ${anchor}); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); + ${this.var} = null; + ` } - } else if (${this.var}) { - ${this.var}.d(1); - ${this.var} = null; } + ${previous_tag} = ${tag}; `); if (this.child_dynamic_element_block.has_intros) { @@ -319,13 +341,23 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); - if (this.renderer.options.dev) { - block.chunks.create.push(b`@validate_dynamic_element(${snippet});`); - if (this.renderer.options.hydratable) { - block.chunks.claim.push(b`@validate_dynamic_element(${snippet});`); - } - block.chunks.update.push(b`@validate_dynamic_element(${snippet});`); + if (this.node.animation) { + block.chunks.measure.push(b`${this.var}.r()`); + block.chunks.fix.push(b`${this.var}.f()`); + block.chunks.animate.push(b`${this.var}.a()`); } + + // Needs to come last due to statement ordering + block.add_element( + anchor as Identifier, + x`@empty()`, + parent_nodes && x`@empty()`, + parent_node + ); + } + + is_dom_node() { + return super.is_dom_node() && !this.child_dynamic_element; } render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index ee40fb045648..76d68086c841 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -107,12 +107,6 @@ export function validate_slots(name, slot, keys) { } } -export function validate_dynamic_element(tag) { - if (!tag) { - console.warn(' expects a non-nullish value in attribute "this"'); - } -} - type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; diff --git a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js deleted file mode 100644 index a010e9365a77..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte deleted file mode 100644 index 978f3d25bee8..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-empty-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js deleted file mode 100644 index a010e9365a77..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-null-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte deleted file mode 100644 index e094a54fac68..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-null-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js deleted file mode 100644 index a010e9365a77..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/_config.js +++ /dev/null @@ -1,9 +0,0 @@ -export default { - compileOptions: { - dev: true - }, - - warnings: [ - ' expects a non-nullish value in attribute "this"' - ] -}; diff --git a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte deleted file mode 100644 index 6e5934d581de..000000000000 --- a/test/runtime/samples/dev-warning-dynamic-element-undefined-tag/main.svelte +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js index c2c6c16451ab..3756313dab47 100644 --- a/test/runtime/samples/dynamic-element-action-update/_config.js +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -9,10 +9,16 @@ export default { assert.equal(component.updateText, ''); assert.equal(component.destroyText, ''); + component.opt = 'opt2'; + + assert.equal(component.tag, 'h1'); + assert.equal(component.updateText, 'update: h1,opt2'); + assert.equal(component.destroyText, ''); + component.tag = 'h2'; assert.equal(component.tag, 'h2'); - assert.equal(component.updateText, 'update: h2'); + assert.equal(component.updateText, 'update: h1,opt2'); assert.equal(component.destroyText, 'destroy'); assert.htmlEqual(target.innerHTML, `

tag is h2.

diff --git a/test/runtime/samples/dynamic-element-action-update/main.svelte b/test/runtime/samples/dynamic-element-action-update/main.svelte index 1abec7a64988..beccf0071b77 100644 --- a/test/runtime/samples/dynamic-element-action-update/main.svelte +++ b/test/runtime/samples/dynamic-element-action-update/main.svelte @@ -2,12 +2,13 @@ export let updateText = ""; export let destroyText = ""; export let tag = "h1"; - function foo(node, tag) { + export let opt = "opt1"; + function foo(node, {tag, opt}) { return { - update: (tag) => updateText = `update: ${tag}`, + update: ({tag, opt}) => updateText = `update: ${tag},${opt}`, destroy: () => destroyText = 'destroy' }; } -tag is {tag}. +tag is {tag}. diff --git a/test/runtime/samples/dynamic-element-animation-invalid/_config.js b/test/runtime/samples/dynamic-element-animation-invalid/_config.js deleted file mode 100644 index a3b4e1f9d398..000000000000 --- a/test/runtime/samples/dynamic-element-animation-invalid/_config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - error: ' cannot have a animate directive' -}; diff --git a/test/runtime/samples/dynamic-element-animation/_config.js b/test/runtime/samples/dynamic-element-animation/_config.js new file mode 100644 index 000000000000..e3c57a868e1a --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/_config.js @@ -0,0 +1,62 @@ +export default { + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test({ assert, component, target, raf }) { + component.tag = 'p'; + assert.equal(target.querySelectorAll('p').length, 5); + + component.tag = 'div'; + let divs = target.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + component.things = [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]; + + divs = target.querySelectorAll('div'); + assert.ok(~divs[0].style.animation.indexOf('__svelte')); + assert.equal(divs[1].style.animation, ''); + assert.equal(divs[2].style.animation, ''); + assert.equal(divs[3].style.animation, ''); + assert.ok(~divs[4].style.animation.indexOf('__svelte')); + + raf.tick(100); + assert.deepEqual([ + divs[0].style.animation, + divs[4].style.animation + ], ['', '']); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte b/test/runtime/samples/dynamic-element-animation/main.svelte similarity index 70% rename from test/runtime/samples/dynamic-element-animation-invalid/main.svelte rename to test/runtime/samples/dynamic-element-animation/main.svelte index 870d1b3cffae..596d12c77ac3 100644 --- a/test/runtime/samples/dynamic-element-animation-invalid/main.svelte +++ b/test/runtime/samples/dynamic-element-animation/main.svelte @@ -1,8 +1,6 @@ {#each things as thing (thing.id)} - -{/each} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js b/test/runtime/samples/dynamic-element-transition/_config.js similarity index 63% rename from test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js rename to test/runtime/samples/dynamic-element-transition/_config.js index 256d83af4e16..cb8474afc191 100644 --- a/test/runtime/samples/dynamic-element-transition-css-change-tag/_config.js +++ b/test/runtime/samples/dynamic-element-transition/_config.js @@ -2,13 +2,16 @@ export default { test({ assert, component, target, raf }) { component.visible = true; const h1 = target.querySelector('h1'); - assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); - raf.tick(50); + raf.tick(150); component.tag = 'h2'; const h2 = target.querySelector('h2'); - assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); - assert.equal(h2.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + assert.equal(h1.style.animation, ''); + assert.equal(h2.style.animation, ''); + + raf.tick(50); + component.visible = false; + assert.equal(h2.style.animation, '__svelte_3750847757_0 100ms linear 0ms 1 both'); } }; diff --git a/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte b/test/runtime/samples/dynamic-element-transition/main.svelte similarity index 75% rename from test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte rename to test/runtime/samples/dynamic-element-transition/main.svelte index b0f24c8dec36..b8c0eff0bd6f 100644 --- a/test/runtime/samples/dynamic-element-transition-css-change-tag/main.svelte +++ b/test/runtime/samples/dynamic-element-transition/main.svelte @@ -13,5 +13,5 @@ {#if visible} - + {/if} From 024a7487ea00473fd2882ca4cd97c5b018a10298 Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Wed, 6 Apr 2022 09:08:45 +0800 Subject: [PATCH 58/68] does not always need to create a new anchor --- .../compile/render_dom/wrappers/Element/index.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index e0d942b8641b..7c8d373e11ba 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -293,7 +293,7 @@ export default class ElementWrapper extends Wrapper { if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); `); - const anchor = block.get_unique_name(`${this.child_dynamic_element_block.name.name}_anchor`); + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); const has_transitions = !!(this.node.intro || this.node.outro); const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; @@ -346,14 +346,6 @@ export default class ElementWrapper extends Wrapper { block.chunks.fix.push(b`${this.var}.f()`); block.chunks.animate.push(b`${this.var}.a()`); } - - // Needs to come last due to statement ordering - block.add_element( - anchor as Identifier, - x`@empty()`, - parent_nodes && x`@empty()`, - parent_node - ); } is_dom_node() { From ef8a3f9c6be6f0554ea7056c5ae2074f544773d6 Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Wed, 6 Apr 2022 09:36:59 +0800 Subject: [PATCH 59/68] validate tag variable in dev mode --- .../compile/render_dom/wrappers/Element/index.ts | 2 ++ src/runtime/internal/dev.ts | 6 ++++++ .../samples/dynamic-element-invalid-this/_config.js | 9 +++++++++ .../samples/dynamic-element-invalid-this/main.svelte | 5 +++++ 4 files changed, 22 insertions(+) create mode 100644 test/runtime/samples/dynamic-element-invalid-this/_config.js create mode 100644 test/runtime/samples/dynamic-element-invalid-this/main.svelte diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 7c8d373e11ba..266cc7314092 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -276,6 +276,7 @@ export default class ElementWrapper extends Wrapper { block.add_variable(previous_tag, tag); block.chunks.init.push(b` + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); `); @@ -306,6 +307,7 @@ export default class ElementWrapper extends Wrapper { ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); } else if (${not_equal}(${previous_tag}, ${tag})) { ${this.var}.d(1); + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); ${this.var}.c(); ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 76d68086c841..4501771c0acd 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -107,6 +107,12 @@ export function validate_slots(name, slot, keys) { } } +export function validate_dynamic_element(tag: unknown) { + if (tag && typeof tag !== 'string') { + throw new Error(' expects "this" attribute to be a string.'); + } +} + type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; diff --git a/test/runtime/samples/dynamic-element-invalid-this/_config.js b/test/runtime/samples/dynamic-element-invalid-this/_config.js new file mode 100644 index 000000000000..3aaa5549911b --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 123 + }, + error: ' expects "this" attribute to be a string.' +}; diff --git a/test/runtime/samples/dynamic-element-invalid-this/main.svelte b/test/runtime/samples/dynamic-element-invalid-this/main.svelte new file mode 100644 index 000000000000..bcc2c293bb24 --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/main.svelte @@ -0,0 +1,5 @@ + + + From 496501033cf4e4bd3319820110d61349457d1446 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Wed, 6 Apr 2022 10:42:41 +0900 Subject: [PATCH 60/68] support template literals --- src/compiler/parse/state/tag.ts | 4 +--- .../_config.js | 21 +++++++++++++++++++ .../main.svelte | 5 +++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 test/runtime/samples/dynamic-element-template-literals/_config.js create mode 100644 test/runtime/samples/dynamic-element-template-literals/main.svelte diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index b1e7282912f0..d3854a5c2a8f 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -189,9 +189,7 @@ export default function tag(parser: Parser) { } const definition = element.attributes.splice(index, 1)[0]; - if (definition.value === true - || definition.value.length !== 1 - || !['Text', 'MustacheTag', 'AttributeShorthand'].includes(definition.value[0].type)) { + if (definition.value === true || !['Text', 'MustacheTag', 'AttributeShorthand'].includes(definition.value[0].type)) { parser.error(parser_errors.invalid_element_definition, definition.start); } diff --git a/test/runtime/samples/dynamic-element-template-literals/_config.js b/test/runtime/samples/dynamic-element-template-literals/_config.js new file mode 100644 index 000000000000..d37164b4d8c2 --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/_config.js @@ -0,0 +1,21 @@ +export default { + props: { + size: 1 + }, + html: '

This is h1 tag

', + + test({ assert, component, target }) { + const h1 = target.firstChild; + component.size = 2; + + assert.htmlEqual( + target.innerHTML, + ` +

This is h2 tag

+ ` + ); + + const h2 = target.firstChild; + assert.notEqual(h1, h2); + } +}; diff --git a/test/runtime/samples/dynamic-element-template-literals/main.svelte b/test/runtime/samples/dynamic-element-template-literals/main.svelte new file mode 100644 index 000000000000..84b63dcca70d --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/main.svelte @@ -0,0 +1,5 @@ + + +This is h{size} tag From 31573fd1750bda98915aa91e3d5d95312dadd583 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Wed, 6 Apr 2022 12:05:20 +0900 Subject: [PATCH 61/68] add test for validating tag definition --- .../dinamic-element-invalid-tag/errors.json | 15 +++++++++++++++ .../dinamic-element-invalid-tag/input.svelte | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 test/validator/samples/dinamic-element-invalid-tag/errors.json create mode 100644 test/validator/samples/dinamic-element-invalid-tag/input.svelte diff --git a/test/validator/samples/dinamic-element-invalid-tag/errors.json b/test/validator/samples/dinamic-element-invalid-tag/errors.json new file mode 100644 index 000000000000..8243deeb8094 --- /dev/null +++ b/test/validator/samples/dinamic-element-invalid-tag/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "missing-element-definition", + "message": " must have a 'this' attribute", + "start": { + "line": 2, + "column": 1, + "character": 7 + }, + "end": { + "line": 2, + "column": 1, + "character": 7 + }, + "pos": 7 +}] diff --git a/test/validator/samples/dinamic-element-invalid-tag/input.svelte b/test/validator/samples/dinamic-element-invalid-tag/input.svelte new file mode 100644 index 000000000000..4b645d25a14a --- /dev/null +++ b/test/validator/samples/dinamic-element-invalid-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
From c5c0dee90e12519cd3b07f753e8fc7c744032a20 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Wed, 6 Apr 2022 12:20:09 +0900 Subject: [PATCH 62/68] remove useless validation --- src/compiler/parse/state/tag.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index d3854a5c2a8f..0ab500edb841 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -189,10 +189,6 @@ export default function tag(parser: Parser) { } const definition = element.attributes.splice(index, 1)[0]; - if (definition.value === true || !['Text', 'MustacheTag', 'AttributeShorthand'].includes(definition.value[0].type)) { - parser.error(parser_errors.invalid_element_definition, definition.start); - } - element.tag = definition.value[0].data || definition.value[0].expression; } From 2858cf582ee25ab216148c3cfb74d34ed20ab6e3 Mon Sep 17 00:00:00 2001 From: baseballyama Date: Wed, 6 Apr 2022 14:20:20 +0900 Subject: [PATCH 63/68] add test for shorthand this --- .../samples/dinamic-element-this/errors.json | 15 +++++++++++++++ .../samples/dinamic-element-this/input.svelte | 3 +++ 2 files changed, 18 insertions(+) create mode 100644 test/validator/samples/dinamic-element-this/errors.json create mode 100644 test/validator/samples/dinamic-element-this/input.svelte diff --git a/test/validator/samples/dinamic-element-this/errors.json b/test/validator/samples/dinamic-element-this/errors.json new file mode 100644 index 000000000000..7d61c20ee2dd --- /dev/null +++ b/test/validator/samples/dinamic-element-this/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "unexpected-reserved-word", + "message": "'this' is a reserved word in JavaScript and cannot be used here", + "start": { + "line": 2, + "column": 18, + "character": 24 + }, + "end": { + "line": 2, + "column": 18, + "character": 24 + }, + "pos": 24 + }] diff --git a/test/validator/samples/dinamic-element-this/input.svelte b/test/validator/samples/dinamic-element-this/input.svelte new file mode 100644 index 000000000000..1a5079b92426 --- /dev/null +++ b/test/validator/samples/dinamic-element-this/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
From 1e86df520a9bb69f911aed75f7f81c6d5faad1bd Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Thu, 7 Apr 2022 22:48:04 +0800 Subject: [PATCH 64/68] add more parser validation test --- .../dynamic-element-invalid-tag/errors.json | 17 +++++++++++++++++ .../dynamic-element-invalid-tag/input.svelte | 3 +++ .../errors.json | 0 .../input.svelte | 0 .../errors.json | 0 .../input.svelte | 0 6 files changed, 20 insertions(+) create mode 100644 test/validator/samples/dynamic-element-invalid-tag/errors.json create mode 100644 test/validator/samples/dynamic-element-invalid-tag/input.svelte rename test/validator/samples/{dinamic-element-invalid-tag => dynamic-element-missing-tag}/errors.json (100%) rename test/validator/samples/{dinamic-element-invalid-tag => dynamic-element-missing-tag}/input.svelte (100%) rename test/validator/samples/{dinamic-element-this => dynamic-element-this}/errors.json (100%) rename test/validator/samples/{dinamic-element-this => dynamic-element-this}/input.svelte (100%) diff --git a/test/validator/samples/dynamic-element-invalid-tag/errors.json b/test/validator/samples/dynamic-element-invalid-tag/errors.json new file mode 100644 index 000000000000..c8d3c52490f4 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Invalid element definition", + "code": "invalid-element-definition", + "start": { + "line": 2, + "column": 17, + "character": 23 + }, + "end": { + "line": 2, + "column": 17, + "character": 23 + }, + "pos": 23 + } +] diff --git a/test/validator/samples/dynamic-element-invalid-tag/input.svelte b/test/validator/samples/dynamic-element-invalid-tag/input.svelte new file mode 100644 index 000000000000..bc6b8be82213 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
diff --git a/test/validator/samples/dinamic-element-invalid-tag/errors.json b/test/validator/samples/dynamic-element-missing-tag/errors.json similarity index 100% rename from test/validator/samples/dinamic-element-invalid-tag/errors.json rename to test/validator/samples/dynamic-element-missing-tag/errors.json diff --git a/test/validator/samples/dinamic-element-invalid-tag/input.svelte b/test/validator/samples/dynamic-element-missing-tag/input.svelte similarity index 100% rename from test/validator/samples/dinamic-element-invalid-tag/input.svelte rename to test/validator/samples/dynamic-element-missing-tag/input.svelte diff --git a/test/validator/samples/dinamic-element-this/errors.json b/test/validator/samples/dynamic-element-this/errors.json similarity index 100% rename from test/validator/samples/dinamic-element-this/errors.json rename to test/validator/samples/dynamic-element-this/errors.json diff --git a/test/validator/samples/dinamic-element-this/input.svelte b/test/validator/samples/dynamic-element-this/input.svelte similarity index 100% rename from test/validator/samples/dinamic-element-this/input.svelte rename to test/validator/samples/dynamic-element-this/input.svelte From f4a8e799ff5a9334fdbbc2fc301f9ff8600a94f4 Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Thu, 7 Apr 2022 23:14:20 +0800 Subject: [PATCH 65/68] improve testcases --- .../dynamic-element-action-update/_config.js | 38 ++++++++++++++----- .../dynamic-element-action-update/main.svelte | 10 ++--- .../dynamic-element-undefined-tag/_config.js | 19 +++++++++- .../dynamic-element-undefined-tag/main.svelte | 2 +- 4 files changed, 53 insertions(+), 16 deletions(-) diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js index 3756313dab47..c9d53088e912 100644 --- a/test/runtime/samples/dynamic-element-action-update/_config.js +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -1,27 +1,47 @@ +let logs = []; + export default { html: `

tag is h1.

`, + props: { + logs + }, + before_test() { + logs = []; + }, async test({ assert, component, target }) { - assert.equal(component.tag, 'h1'); assert.equal(component.updateText, ''); assert.equal(component.destroyText, ''); + assert.deepEqual(logs, ['create: h1,opt2']); component.opt = 'opt2'; - + assert.equal(component.tag, 'h1'); - assert.equal(component.updateText, 'update: h1,opt2'); - assert.equal(component.destroyText, ''); + assert.deepEqual(logs, ['create: h1,opt2', 'update: h1,opt2']); component.tag = 'h2'; assert.equal(component.tag, 'h2'); - assert.equal(component.updateText, 'update: h1,opt2'); - assert.equal(component.destroyText, 'destroy'); - assert.htmlEqual(target.innerHTML, ` -

tag is h2.

- `); + assert.deepEqual(logs, [ + 'create: h1,opt2', + 'update: h1,opt2', + 'destroy', + 'create: h2,opt2' + ]); + assert.htmlEqual(target.innerHTML, '

tag is h2.

'); + + component.tag = false; + assert.deepEqual(logs, [ + 'create: h1,opt2', + 'update: h1,opt2', + 'destroy', + 'create: h2,opt2', + 'destroy' + ]); + + assert.htmlEqual(target.innerHTML, ''); } }; diff --git a/test/runtime/samples/dynamic-element-action-update/main.svelte b/test/runtime/samples/dynamic-element-action-update/main.svelte index beccf0071b77..da269e115ed7 100644 --- a/test/runtime/samples/dynamic-element-action-update/main.svelte +++ b/test/runtime/samples/dynamic-element-action-update/main.svelte @@ -1,12 +1,12 @@ diff --git a/test/runtime/samples/dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dynamic-element-undefined-tag/_config.js index 22dc25b41e1f..53b4e6e5630d 100644 --- a/test/runtime/samples/dynamic-element-undefined-tag/_config.js +++ b/test/runtime/samples/dynamic-element-undefined-tag/_config.js @@ -1,3 +1,20 @@ export default { - html: '' + solo: true, + html: '', + test({ component, target, assert }) { + component.tag = 'h1'; + assert.htmlEqual(target.innerHTML, '

Foo

'); + + component.tag = null; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'div'; + assert.htmlEqual(target.innerHTML, '
Foo
'); + + component.tag = false; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'span'; + assert.htmlEqual(target.innerHTML, 'Foo'); + } }; diff --git a/test/runtime/samples/dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte index 785ab58073f4..6aca93ca1319 100644 --- a/test/runtime/samples/dynamic-element-undefined-tag/main.svelte +++ b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte @@ -1,5 +1,5 @@ Foo From 0ef1dfca7aaeafbbde875c1a5ea8ae7e45ab8672 Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Fri, 8 Apr 2022 01:15:17 +0800 Subject: [PATCH 66/68] animate while switching dynamic element --- src/compiler/compile/render_dom/Block.ts | 9 ++ .../render_dom/wrappers/Element/index.ts | 22 +++- .../dynamic-element-animation-2/_config.js | 105 ++++++++++++++++++ .../dynamic-element-animation-2/main.svelte | 26 +++++ .../dynamic-element-undefined-tag/_config.js | 1 - 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 test/runtime/samples/dynamic-element-animation-2/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation-2/main.svelte diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index cad7e221700d..34c477480463 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -48,6 +48,7 @@ export default class Block { hydrate: Array; mount: Array; measure: Array; + restore_measurements: Array; fix: Array; animate: Array; intro: Array; @@ -96,6 +97,7 @@ export default class Block { hydrate: [], mount: [], measure: [], + restore_measurements: [], fix: [], animate: [], intro: [], @@ -326,6 +328,12 @@ export default class Block { ${this.chunks.measure} }`; + if (this.chunks.restore_measurements.length) { + properties.restore_measurements = x`function #restore_measurements(#measurement) { + ${this.chunks.restore_measurements} + }`; + } + properties.fix = x`function #fix() { ${this.chunks.fix} }`; @@ -379,6 +387,7 @@ export default class Block { m: ${properties.mount}, p: ${properties.update}, r: ${properties.measure}, + s: ${properties.restore_measurements}, f: ${properties.fix}, a: ${properties.animate}, i: ${properties.intro}, diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 266cc7314092..b66c8938fcd4 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -135,6 +135,8 @@ const events = [ } ]; +const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; + export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; @@ -161,11 +163,11 @@ export default class ElementWrapper extends Wrapper { ) { super(renderer, block, parent, node); - if (node.is_dynamic_element && block.type !== 'child_dynamic_element') { + if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { this.child_dynamic_element_block = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name('create_dynamic_element'), - type: 'child_dynamic_element' + type: CHILD_DYNAMIC_ELEMENT_BLOCK }); renderer.blocks.push(this.child_dynamic_element_block); this.child_dynamic_element = new ElementWrapper( @@ -344,9 +346,14 @@ export default class ElementWrapper extends Wrapper { block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); if (this.node.animation) { - block.chunks.measure.push(b`${this.var}.r()`); - block.chunks.fix.push(b`${this.var}.f()`); - block.chunks.animate.push(b`${this.var}.a()`); + const measurements = block.get_unique_name('measurements'); + block.add_variable(measurements); + block.chunks.measure.push(b`${measurements} = ${this.var}.r()`); + block.chunks.fix.push(b`${this.var}.f();`); + block.chunks.animate.push(b` + ${this.var}.s(${measurements}); + ${this.var}.a() + `); } } @@ -983,6 +990,11 @@ export default class ElementWrapper extends Wrapper { ${rect} = ${this.var}.getBoundingClientRect(); `); + if (block.type === CHILD_DYNAMIC_ELEMENT_BLOCK) { + block.chunks.measure.push(b`return ${rect}`); + block.chunks.restore_measurements.push(b`${rect} = #measurement;`); + } + block.chunks.fix.push(b` @fix_position(${this.var}); ${stop_animation}(); diff --git a/test/runtime/samples/dynamic-element-animation-2/_config.js b/test/runtime/samples/dynamic-element-animation-2/_config.js new file mode 100644 index 000000000000..27b28bb34920 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/_config.js @@ -0,0 +1,105 @@ +let originalDivGetBoundingClientRect; +let originalSpanGetBoundingClientRect; +let originalParagraphGetBoundingClientRect; + +export default { + skip_if_ssr: true, + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + before_test() { + originalDivGetBoundingClientRect = + window.HTMLDivElement.prototype.getBoundingClientRect; + originalSpanGetBoundingClientRect = + window.HTMLSpanElement.prototype.getBoundingClientRect; + originalParagraphGetBoundingClientRect = + window.HTMLParagraphElement.prototype.getBoundingClientRect; + + window.HTMLDivElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + + function fakeGetBoundingClientRect() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + } + }, + after_test() { + window.HTMLDivElement.prototype.getBoundingClientRect = + originalDivGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + originalSpanGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + originalParagraphGetBoundingClientRect; + }, + + async test({ assert, component, target, raf }) { + // switch tag and things at the same time + await component.update('p', [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]); + + const ps = document.querySelectorAll('p'); + assert.equal(ps[0].dy, 120); + assert.equal(ps[4].dy, -120); + + raf.tick(50); + assert.equal(ps[0].dy, 60); + assert.equal(ps[4].dy, -60); + + raf.tick(100); + assert.equal(ps[0].dy, 0); + assert.equal(ps[4].dy, 0); + + await component.update('span', [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ]); + + const spans = document.querySelectorAll('span'); + + assert.equal(spans[0].dy, 120); + assert.equal(spans[4].dy, -120); + + raf.tick(150); + assert.equal(spans[0].dy, 60); + assert.equal(spans[4].dy, -60); + + raf.tick(200); + assert.equal(spans[0].dy, 0); + assert.equal(spans[4].dy, 0); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-2/main.svelte b/test/runtime/samples/dynamic-element-animation-2/main.svelte new file mode 100644 index 000000000000..f655a40af9bd --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/main.svelte @@ -0,0 +1,26 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dynamic-element-undefined-tag/_config.js index 53b4e6e5630d..d0bd665d3d9b 100644 --- a/test/runtime/samples/dynamic-element-undefined-tag/_config.js +++ b/test/runtime/samples/dynamic-element-undefined-tag/_config.js @@ -1,5 +1,4 @@ export default { - solo: true, html: '', test({ component, target, assert }) { component.tag = 'h1'; From 13c6c12ab781b189855594e5c1ee38fca07e5b3d Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Fri, 8 Apr 2022 01:34:50 +0800 Subject: [PATCH 67/68] fix test --- src/compiler/parse/state/tag.ts | 3 +++ .../samples/dynamic-element-action-update/_config.js | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts index 0ab500edb841..46e6e2f83aa1 100644 --- a/src/compiler/parse/state/tag.ts +++ b/src/compiler/parse/state/tag.ts @@ -189,6 +189,9 @@ export default function tag(parser: Parser) { } const definition = element.attributes.splice(index, 1)[0]; + if (definition.value === true) { + parser.error(parser_errors.invalid_element_definition, definition.start); + } element.tag = definition.value[0].data || definition.value[0].expression; } diff --git a/test/runtime/samples/dynamic-element-action-update/_config.js b/test/runtime/samples/dynamic-element-action-update/_config.js index c9d53088e912..ebbe6f05a939 100644 --- a/test/runtime/samples/dynamic-element-action-update/_config.js +++ b/test/runtime/samples/dynamic-element-action-update/_config.js @@ -7,26 +7,24 @@ export default { props: { logs }, - before_test() { + after_test() { logs = []; }, async test({ assert, component, target }) { assert.equal(component.tag, 'h1'); - assert.equal(component.updateText, ''); - assert.equal(component.destroyText, ''); - assert.deepEqual(logs, ['create: h1,opt2']); + assert.deepEqual(logs, ['create: h1,opt1']); component.opt = 'opt2'; assert.equal(component.tag, 'h1'); - assert.deepEqual(logs, ['create: h1,opt2', 'update: h1,opt2']); + assert.deepEqual(logs, ['create: h1,opt1', 'update: h1,opt2']); component.tag = 'h2'; assert.equal(component.tag, 'h2'); assert.deepEqual(logs, [ - 'create: h1,opt2', + 'create: h1,opt1', 'update: h1,opt2', 'destroy', 'create: h2,opt2' @@ -35,7 +33,7 @@ export default { component.tag = false; assert.deepEqual(logs, [ - 'create: h1,opt2', + 'create: h1,opt1', 'update: h1,opt2', 'destroy', 'create: h2,opt2', From 8057a142c0f7b442aefac37c194871e83ba3a071 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 8 Apr 2022 15:30:46 +0200 Subject: [PATCH 68/68] add tutorial section --- .../03-svelte-element/app-a/App.svelte | 18 +++++++++++++++ .../03-svelte-element/app-b/App.svelte | 12 ++++++++++ .../03-svelte-element/text.md | 23 +++++++++++++++++++ .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-a/Todo.svelte | 0 .../app-a/flash.js | 0 .../app-b/App.svelte | 0 .../app-b/Todo.svelte | 0 .../app-b/flash.js | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-a/Box.svelte | 0 .../app-b/App.svelte | 0 .../app-b/Box.svelte | 0 .../text.md | 0 27 files changed, 53 insertions(+) create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/text.md rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/text.md (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/text.md (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/text.md (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/text.md (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/Todo.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/flash.js (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/Todo.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/flash.js (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/text.md (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-a/Box.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-b/Box.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/text.md (100%) diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte b/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte new file mode 100644 index 000000000000..0718efd94943 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte @@ -0,0 +1,18 @@ + + + + +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte new file mode 100644 index 000000000000..068f7b3c4f56 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte @@ -0,0 +1,12 @@ + + + + +I'm a {selected} tag diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/text.md b/site/content/tutorial/16-special-elements/03-svelte-element/text.md new file mode 100644 index 000000000000..ace344db42d2 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/text.md @@ -0,0 +1,23 @@ +--- +title: +--- + +Sometimes we don't know in advance what kind of DOM element to render. `` comes in handy here. Instead of a sequence of `if` blocks... + +```html +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} +``` + +...we can have a single dynamic component: + +```html +I'm a {selected} tag +``` + +The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered. \ No newline at end of file diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/text.md b/site/content/tutorial/16-special-elements/04-svelte-window/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/text.md rename to site/content/tutorial/16-special-elements/04-svelte-window/text.md diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/text.md b/site/content/tutorial/16-special-elements/06-svelte-body/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/text.md rename to site/content/tutorial/16-special-elements/06-svelte-body/text.md diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/text.md b/site/content/tutorial/16-special-elements/07-svelte-head/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/text.md rename to site/content/tutorial/16-special-elements/07-svelte-head/text.md diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/text.md b/site/content/tutorial/16-special-elements/08-svelte-options/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/text.md rename to site/content/tutorial/16-special-elements/08-svelte-options/text.md diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/text.md b/site/content/tutorial/16-special-elements/09-svelte-fragment/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/text.md rename to site/content/tutorial/16-special-elements/09-svelte-fragment/text.md