Skip to content

Commit df2f656

Browse files
tanhauhaudummdidummbenmccann
authored
feat: improve hydration, claim static html elements using innerHTML instead of deopt to claiming every nodes (#7426)
Related: #7341, #7226 For purely static HTML, instead of walking the node tree and claiming every node/text etc, hydration now uses the same innerHTML optimization technique for hydration compared to normal create. It uses a new data-svelte-h attribute which is added upon server side rendering containing a hash (computed at build time), and then comparing that hash in the client to ensure it's the same node. If the hash is the same, the whole child content is expected to be the same. If the hash is different, the whole child content is replaced with innerHTML. --------- Co-authored-by: Simon H <[email protected]> Co-authored-by: Ben McCann <[email protected]> Co-authored-by: Simon Holthausen <[email protected]>
1 parent 6f8cdf3 commit df2f656

File tree

104 files changed

+384
-638
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+384
-638
lines changed

src/compiler/compile/Component.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import compiler_warnings from './compiler_warnings';
3838
import compiler_errors from './compiler_errors';
3939
import { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
4040
import check_enable_sourcemap from './utils/check_enable_sourcemap';
41+
import Tag from './nodes/shared/Tag';
4142

4243
interface ComponentOptions {
4344
namespace?: string;
@@ -110,6 +111,8 @@ export default class Component {
110111
slots: Map<string, Slot> = new Map();
111112
slot_outlets: Set<string> = new Set();
112113

114+
tags: Tag[] = [];
115+
113116
constructor(
114117
ast: Ast,
115118
source: string,
@@ -761,6 +764,7 @@ export default class Component {
761764

762765
this.hoist_instance_declarations();
763766
this.extract_reactive_declarations();
767+
this.check_if_tags_content_dynamic();
764768
}
765769

766770
post_template_walk() {
@@ -1479,6 +1483,12 @@ export default class Component {
14791483
unsorted_reactive_declarations.forEach(add_declaration);
14801484
}
14811485

1486+
check_if_tags_content_dynamic() {
1487+
this.tags.forEach(tag => {
1488+
tag.check_if_content_dynamic();
1489+
});
1490+
}
1491+
14821492
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
14831493
if (name[0] === '$') {
14841494
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {

src/compiler/compile/nodes/Attribute.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ export default class Attribute extends Node {
5959
return expression;
6060
});
6161
}
62+
63+
if (this.dependencies.size > 0) {
64+
parent.cannot_use_innerhtml();
65+
parent.not_static_content();
66+
}
6267
}
6368

6469
get_dependencies() {

src/compiler/compile/nodes/AwaitBlock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
2727

2828
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
2929
super(component, parent, scope, info);
30+
this.cannot_use_innerhtml();
31+
this.not_static_content();
3032

3133
this.expression = new Expression(component, this, scope, info.expression);
3234

src/compiler/compile/nodes/EachBlock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock {
3333

3434
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
3535
super(component, parent, scope, info);
36+
this.cannot_use_innerhtml();
37+
this.not_static_content();
3638

3739
this.expression = new Expression(component, this, scope, info.expression);
3840
this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring

src/compiler/compile/nodes/Element.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { is_name_contenteditable, get_contenteditable_attr, has_contenteditable_
1515
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns';
1616
import fuzzymatch from '../../utils/fuzzymatch';
1717
import list from '../../utils/list';
18+
import hash from '../utils/hash';
1819
import Let from './Let';
1920
import TemplateScope from './shared/TemplateScope';
2021
import { INode } from './interfaces';
@@ -503,6 +504,25 @@ export default class Element extends Node {
503504
this.optimise();
504505

505506
component.apply_stylesheet(this);
507+
508+
if (this.parent) {
509+
if (this.actions.length > 0 ||
510+
this.animation ||
511+
this.bindings.length > 0 ||
512+
this.classes.length > 0 ||
513+
this.intro || this.outro ||
514+
this.handlers.length > 0 ||
515+
this.styles.length > 0 ||
516+
this.name === 'option' ||
517+
this.is_dynamic_element ||
518+
this.tag_expr.dynamic_dependencies().length ||
519+
this.is_dynamic_element ||
520+
component.compile_options.dev
521+
) {
522+
this.parent.cannot_use_innerhtml(); // need to use add_location
523+
this.parent.not_static_content();
524+
}
525+
}
506526
}
507527

508528
validate() {
@@ -1262,6 +1282,20 @@ export default class Element extends Node {
12621282
}
12631283
});
12641284
}
1285+
1286+
get can_use_textcontent() {
1287+
return this.is_static_content && this.children.every(node => node.type === 'Text' || node.type === 'MustacheTag');
1288+
}
1289+
1290+
get can_optimise_to_html_string() {
1291+
const can_use_textcontent = this.can_use_textcontent;
1292+
const is_template_with_text_content = this.name === 'template' && can_use_textcontent;
1293+
return !is_template_with_text_content && !this.namespace && (this.can_use_innerhtml || can_use_textcontent) && this.children.length > 0;
1294+
}
1295+
1296+
hash() {
1297+
return `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
1298+
}
12651299
}
12661300

12671301
const regex_starts_with_vowel = /^[aeiou]/;

src/compiler/compile/nodes/Head.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export default class Head extends Node {
1515
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
1616
super(component, parent, scope, info);
1717

18+
this.cannot_use_innerhtml();
19+
1820
if (info.attributes.length) {
1921
component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
2022
return;

src/compiler/compile/nodes/IfBlock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export default class IfBlock extends AbstractBlock {
1818
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
1919
super(component, parent, scope, info);
2020
this.scope = scope.child();
21+
this.cannot_use_innerhtml();
22+
this.not_static_content();
2123

2224
this.expression = new Expression(component, this, this.scope, info.expression);
2325
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));

src/compiler/compile/nodes/InlineComponent.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export default class InlineComponent extends Node {
2828
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
2929
super(component, parent, scope, info);
3030

31+
this.cannot_use_innerhtml();
32+
this.not_static_content();
33+
3134
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
3235
const name = info.name.split('.')[0]; // accommodate namespaces
3336
component.warn_if_undefined(name, info, scope);

src/compiler/compile/nodes/KeyBlock.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock {
1313

1414
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
1515
super(component, parent, scope, info);
16+
this.cannot_use_innerhtml();
17+
this.not_static_content();
1618

1719
this.expression = new Expression(component, this, scope, info.expression);
1820

src/compiler/compile/nodes/RawMustacheTag.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,9 @@ import Tag from './shared/Tag';
22

33
export default class RawMustacheTag extends Tag {
44
type: 'RawMustacheTag';
5+
constructor(component, parent, scope, info) {
6+
super(component, parent, scope, info);
7+
this.cannot_use_innerhtml();
8+
this.not_static_content();
9+
}
510
}

0 commit comments

Comments
 (0)