From 0363be1a6d5d966a8f047d31725f3609f2ee3c7a Mon Sep 17 00:00:00 2001 From: tanhauhau Date: Thu, 7 Apr 2022 21:18:01 +0800 Subject: [PATCH 01/16] claim static html elements using innerHTML instead of deopt to creating the nodes revert code update test case --- src/compiler/compile/Component.ts | 10 +++ src/compiler/compile/nodes/Attribute.ts | 5 ++ src/compiler/compile/nodes/AwaitBlock.ts | 2 + src/compiler/compile/nodes/EachBlock.ts | 2 + src/compiler/compile/nodes/Element.ts | 33 ++++++++ src/compiler/compile/nodes/Head.ts | 2 + src/compiler/compile/nodes/IfBlock.ts | 2 + src/compiler/compile/nodes/InlineComponent.ts | 3 + src/compiler/compile/nodes/KeyBlock.ts | 2 + src/compiler/compile/nodes/RawMustacheTag.ts | 4 + src/compiler/compile/nodes/Slot.ts | 3 + src/compiler/compile/nodes/shared/Node.ts | 9 +++ src/compiler/compile/nodes/shared/Tag.ts | 11 +++ .../compile/render_dom/wrappers/AwaitBlock.ts | 3 - .../compile/render_dom/wrappers/EachBlock.ts | 2 - .../render_dom/wrappers/Element/Attribute.ts | 3 - .../render_dom/wrappers/Element/index.ts | 81 +++++++++---------- .../compile/render_dom/wrappers/Head.ts | 2 - .../compile/render_dom/wrappers/IfBlock.ts | 3 - .../wrappers/InlineComponent/index.ts | 3 - .../compile/render_dom/wrappers/KeyBlock.ts | 3 - .../render_dom/wrappers/RawMustacheTag.ts | 2 - .../compile/render_dom/wrappers/Slot.ts | 2 - .../compile/render_dom/wrappers/shared/Tag.ts | 9 --- .../render_dom/wrappers/shared/Wrapper.ts | 15 ---- src/compiler/compile/render_ssr/Renderer.ts | 1 + .../compile/render_ssr/handlers/Element.ts | 11 +++ .../utils/remove_whitespace_children.ts | 1 + src/compiler/compile/utils/stringify.ts | 3 +- src/runtime/internal/dom.ts | 4 + test/helpers.ts | 13 +-- test/hydration/samples/basic/_after.html | 2 +- test/hydration/samples/basic/_before.html | 2 +- .../claim-static-incorrect-hash/_after.html | 8 ++ .../claim-static-incorrect-hash/_before.html | 8 ++ .../claim-static-incorrect-hash/main.svelte | 8 ++ .../samples/claim-static-no-hash/_after.html | 8 ++ .../samples/claim-static-no-hash/_before.html | 8 ++ .../samples/claim-static-no-hash/main.svelte | 8 ++ .../samples/component-in-element/_after.html | 2 +- .../samples/component-in-element/_before.html | 2 +- test/hydration/samples/component/_after.html | 2 +- test/hydration/samples/component/_before.html | 2 +- .../samples/element-nested/_after.html | 2 +- .../samples/element-nested/_before.html | 2 +- test/runtime/index.ts | 4 +- .../dynamic-element-action-update/_config.js | 4 +- .../dynamic-element-action-update/main.svelte | 8 +- .../_expected.html | 2 +- .../samples/pre-tag/_expected.html | 6 +- 50 files changed, 222 insertions(+), 115 deletions(-) create mode 100644 test/hydration/samples/claim-static-incorrect-hash/_after.html create mode 100644 test/hydration/samples/claim-static-incorrect-hash/_before.html create mode 100644 test/hydration/samples/claim-static-incorrect-hash/main.svelte create mode 100644 test/hydration/samples/claim-static-no-hash/_after.html create mode 100644 test/hydration/samples/claim-static-no-hash/_before.html create mode 100644 test/hydration/samples/claim-static-no-hash/main.svelte diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index f7d8e8f5682..7dfd6c086c9 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -39,6 +39,7 @@ import compiler_errors from './compiler_errors'; import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore'; import check_enable_sourcemap from './utils/check_enable_sourcemap'; import is_dynamic from './render_dom/wrappers/shared/is_dynamic'; +import Tag from './nodes/shared/Tag'; interface ComponentOptions { namespace?: string; @@ -111,6 +112,8 @@ export default class Component { slots: Map = new Map(); slot_outlets: Set = new Set(); + tags: Tag[] = [] + constructor( ast: Ast, source: string, @@ -762,6 +765,7 @@ export default class Component { this.hoist_instance_declarations(); this.extract_reactive_declarations(); + this.check_if_tags_content_dynamic(); } post_template_walk() { @@ -1492,6 +1496,12 @@ export default class Component { unsorted_reactive_declarations.forEach(add_declaration); } + check_if_tags_content_dynamic() { + this.tags.forEach(tag => { + tag.check_if_content_dynamic(); + }); + } + warn_if_undefined(name: string, node, template_scope: TemplateScope) { if (name[0] === '$') { if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { diff --git a/src/compiler/compile/nodes/Attribute.ts b/src/compiler/compile/nodes/Attribute.ts index ebed483288a..9e14a3233eb 100644 --- a/src/compiler/compile/nodes/Attribute.ts +++ b/src/compiler/compile/nodes/Attribute.ts @@ -59,6 +59,11 @@ export default class Attribute extends Node { return expression; }); } + + if (this.dependencies.size > 0) { + parent.cannot_use_innerhtml(); + parent.not_static_content(); + } } get_dependencies() { diff --git a/src/compiler/compile/nodes/AwaitBlock.ts b/src/compiler/compile/nodes/AwaitBlock.ts index 4a669b6365b..b61beab478b 100644 --- a/src/compiler/compile/nodes/AwaitBlock.ts +++ b/src/compiler/compile/nodes/AwaitBlock.ts @@ -27,6 +27,8 @@ export default class AwaitBlock extends Node { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.cannot_use_innerhtml(); + this.not_static_content(); this.expression = new Expression(component, this, scope, info.expression); diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index 4a5ea19e377..69b21111722 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.cannot_use_innerhtml(); + this.not_static_content(); this.expression = new Expression(component, this, scope, info.expression); this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 416f1d7b316..a09bf881c86 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -14,6 +14,7 @@ import map_children from './shared/map_children'; import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; +import hash from '../utils/hash'; import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; @@ -438,6 +439,24 @@ export default class Element extends Node { this.optimise(); component.apply_stylesheet(this); + + if (this.parent) { + if (this.actions.length > 0 || + this.animation || + this.bindings.length > 0 || + this.classes.length > 0 || + this.intro || this.outro || + this.handlers.length > 0 || + this.styles.length > 0 || + this.name === 'option' || + this.tag_expr.dynamic_dependencies().length || + this.is_dynamic_element || + component.compile_options.dev + ) { + this.parent.cannot_use_innerhtml(); // need to use add_location + this.parent.not_static_content(); + } + } } validate() { @@ -1140,6 +1159,20 @@ export default class Element extends Node { } }); } + + get can_use_textcontent() { + return this.is_static_content && this.children.every(node => node.type === 'Text' || node.type === 'MustacheTag'); + } + + get can_optimise_to_html_string() { + const can_use_textcontent = this.can_use_textcontent; + const is_template_with_text_content = this.name === 'template' && can_use_textcontent; + return !is_template_with_text_content && !this.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.children.length > 0; + } + + hash() { + return `svelte-${hash(this.component.source.slice(this.start, this.end))}`; + } } const regex_starts_with_vowel = /^[aeiou]/; diff --git a/src/compiler/compile/nodes/Head.ts b/src/compiler/compile/nodes/Head.ts index 1ebe0c053aa..c6307222a57 100644 --- a/src/compiler/compile/nodes/Head.ts +++ b/src/compiler/compile/nodes/Head.ts @@ -15,6 +15,8 @@ export default class Head extends Node { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.can_use_innerhtml = false; + if (info.attributes.length) { component.error(info.attributes[0], compiler_errors.invalid_attribute_head); return; diff --git a/src/compiler/compile/nodes/IfBlock.ts b/src/compiler/compile/nodes/IfBlock.ts index 8351f1e0e27..4c337ac0cee 100644 --- a/src/compiler/compile/nodes/IfBlock.ts +++ b/src/compiler/compile/nodes/IfBlock.ts @@ -18,6 +18,8 @@ export default class IfBlock extends AbstractBlock { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); this.scope = scope.child(); + this.cannot_use_innerhtml(); + this.not_static_content(); this.expression = new Expression(component, this, this.scope, info.expression); ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); diff --git a/src/compiler/compile/nodes/InlineComponent.ts b/src/compiler/compile/nodes/InlineComponent.ts index e9ac86a55cf..5fc7c89ae44 100644 --- a/src/compiler/compile/nodes/InlineComponent.ts +++ b/src/compiler/compile/nodes/InlineComponent.ts @@ -28,6 +28,9 @@ export default class InlineComponent extends Node { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.cannot_use_innerhtml(); + this.not_static_content(); + if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { const name = info.name.split('.')[0]; // accommodate namespaces component.warn_if_undefined(name, info, scope); diff --git a/src/compiler/compile/nodes/KeyBlock.ts b/src/compiler/compile/nodes/KeyBlock.ts index 5261e14e0a0..cd0ea56f8ea 100644 --- a/src/compiler/compile/nodes/KeyBlock.ts +++ b/src/compiler/compile/nodes/KeyBlock.ts @@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { super(component, parent, scope, info); + this.cannot_use_innerhtml(); + this.not_static_content(); this.expression = new Expression(component, this, scope, info.expression); diff --git a/src/compiler/compile/nodes/RawMustacheTag.ts b/src/compiler/compile/nodes/RawMustacheTag.ts index 816ef8d7c57..998fdce3ad7 100644 --- a/src/compiler/compile/nodes/RawMustacheTag.ts +++ b/src/compiler/compile/nodes/RawMustacheTag.ts @@ -2,4 +2,8 @@ import Tag from './shared/Tag'; export default class RawMustacheTag extends Tag { type: 'RawMustacheTag'; + constructor(component, parent, scope, info) { + super(component, parent, scope, info); + this.not_static_content(); + } } diff --git a/src/compiler/compile/nodes/Slot.ts b/src/compiler/compile/nodes/Slot.ts index c21a48f5d66..9a0b1ea815f 100644 --- a/src/compiler/compile/nodes/Slot.ts +++ b/src/compiler/compile/nodes/Slot.ts @@ -60,5 +60,8 @@ export default class Slot extends Element { } component.slots.set(this.slot_name, this); + + this.cannot_use_innerhtml(); + this.not_static_content(); } } diff --git a/src/compiler/compile/nodes/shared/Node.ts b/src/compiler/compile/nodes/shared/Node.ts index 3c39f6d9d34..9eeb110d201 100644 --- a/src/compiler/compile/nodes/shared/Node.ts +++ b/src/compiler/compile/nodes/shared/Node.ts @@ -15,6 +15,7 @@ export default class Node { next?: INode; can_use_innerhtml: boolean; + is_static_content: boolean; var: string; attributes: Attribute[]; @@ -33,6 +34,9 @@ export default class Node { value: parent } }); + + this.can_use_innerhtml = true; + this.is_static_content = true; } cannot_use_innerhtml() { @@ -42,6 +46,11 @@ export default class Node { } } + not_static_content() { + this.is_static_content = false; + if (this.parent) this.parent.not_static_content(); + } + find_nearest(selector: RegExp) { if (selector.test(this.type)) return this; if (this.parent) return this.parent.find_nearest(selector); diff --git a/src/compiler/compile/nodes/shared/Tag.ts b/src/compiler/compile/nodes/shared/Tag.ts index bc826458d9a..afb09d83ced 100644 --- a/src/compiler/compile/nodes/shared/Tag.ts +++ b/src/compiler/compile/nodes/shared/Tag.ts @@ -8,6 +8,9 @@ export default class Tag extends Node { constructor(component, parent, scope, info) { super(component, parent, scope, info); + component.tags.push(this); + this.cannot_use_innerhtml(); + this.expression = new Expression(component, this, scope, info.expression); this.should_cache = ( @@ -15,4 +18,12 @@ export default class Tag extends Node { (this.expression.dependencies.size && scope.names.has(info.expression.name)) ); } + is_dependencies_static() { + return this.expression.contextual_dependencies.size === 0 && this.expression.dynamic_dependencies().length === 0; + } + check_if_content_dynamic() { + if (!this.is_dependencies_static()) { + this.not_static_content(); + } + } } diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index 9c07267223f..e1965d9d928 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -133,9 +133,6 @@ export default class AwaitBlockWrapper extends Wrapper { ) { super(renderer, block, parent, node); - this.cannot_use_innerhtml(); - this.not_static_content(); - block.add_dependencies(this.node.expression.dependencies); let is_dynamic = false; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 1032f4c6e3f..1842cb9a7ae 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -79,8 +79,6 @@ export default class EachBlockWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); - this.cannot_use_innerhtml(); - this.not_static_content(); const { dependencies } = node.expression; block.add_dependencies(dependencies); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index ca35ea84db4..4e66b5f55db 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -36,9 +36,6 @@ export class BaseAttributeWrapper { this.parent = parent; if (node.dependencies.size > 0) { - parent.cannot_use_innerhtml(); - parent.not_static_content(); - block.add_dependencies(node.dependencies); } } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 3472b0612cd..e7d67de15c4 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -262,24 +262,6 @@ export default class ElementWrapper extends Wrapper { } }); - if (this.parent) { - if (node.actions.length > 0 || - node.animation || - node.bindings.length > 0 || - node.classes.length > 0 || - node.intro || node.outro || - node.handlers.length > 0 || - node.styles.length > 0 || - this.node.name === 'option' || - node.tag_expr.dynamic_dependencies().length || - node.is_dynamic_element || - renderer.options.dev - ) { - this.parent.cannot_use_innerhtml(); // need to use add_location - this.parent.not_static_content(); - } - } - this.fragment = new FragmentWrapper(renderer, block, node.children, this, strip_whitespace, next_sibling); } @@ -417,6 +399,7 @@ export default class ElementWrapper extends Wrapper { render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { const { renderer } = this; + const hydratable = renderer.options.hydratable; if (this.node.name === 'noscript') return; @@ -430,13 +413,15 @@ export default class ElementWrapper extends Wrapper { b`${node} = ${render_statement};` ); - if (renderer.options.hydratable) { + const { can_use_textcontent, can_optimise_to_html_string } = this.node; + + if (hydratable) { if (parent_nodes) { block.chunks.claim.push(b` - ${node} = ${this.get_claim_statement(block, parent_nodes)}; + ${node} = ${this.get_claim_statement(block, parent_nodes, can_optimise_to_html_string)}; `); - if (!this.void && this.node.children.length > 0) { + if (!can_optimise_to_html_string && !this.void && this.node.children.length > 0) { block.chunks.claim.push(b` var ${nodes} = ${children}; `); @@ -474,15 +459,19 @@ export default class ElementWrapper extends Wrapper { // insert static children with textContent or innerHTML // skip textcontent for