claim static html elements using innerHTML instead of deopt to creating the nodes

revert code

update test case
pull/7426/head
tanhauhau 4 years ago
parent e2b9df1888
commit 0363be1a6d

@ -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 { extract_ignores_above_position, extract_svelte_ignore_from_comments } from '../utils/extract_svelte_ignore';
import check_enable_sourcemap from './utils/check_enable_sourcemap'; import check_enable_sourcemap from './utils/check_enable_sourcemap';
import is_dynamic from './render_dom/wrappers/shared/is_dynamic'; import is_dynamic from './render_dom/wrappers/shared/is_dynamic';
import Tag from './nodes/shared/Tag';
interface ComponentOptions { interface ComponentOptions {
namespace?: string; namespace?: string;
@ -111,6 +112,8 @@ export default class Component {
slots: Map<string, Slot> = new Map(); slots: Map<string, Slot> = new Map();
slot_outlets: Set<string> = new Set(); slot_outlets: Set<string> = new Set();
tags: Tag[] = []
constructor( constructor(
ast: Ast, ast: Ast,
source: string, source: string,
@ -762,6 +765,7 @@ export default class Component {
this.hoist_instance_declarations(); this.hoist_instance_declarations();
this.extract_reactive_declarations(); this.extract_reactive_declarations();
this.check_if_tags_content_dynamic();
} }
post_template_walk() { post_template_walk() {
@ -1492,6 +1496,12 @@ export default class Component {
unsorted_reactive_declarations.forEach(add_declaration); 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) { warn_if_undefined(name: string, node, template_scope: TemplateScope) {
if (name[0] === '$') { if (name[0] === '$') {
if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) { if (name === '$' || name[1] === '$' && !is_reserved_keyword(name)) {

@ -59,6 +59,11 @@ export default class Attribute extends Node {
return expression; return expression;
}); });
} }
if (this.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
}
} }
get_dependencies() { get_dependencies() {

@ -27,6 +27,8 @@ export default class AwaitBlock extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);

@ -33,6 +33,8 @@ export default class EachBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); 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 this.context = info.context.name || 'each'; // TODO this is used to facilitate binding; currently fails with destructuring

@ -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 { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch'; import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list'; import list from '../../utils/list';
import hash from '../utils/hash';
import Let from './Let'; import Let from './Let';
import TemplateScope from './shared/TemplateScope'; import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces'; import { INode } from './interfaces';
@ -438,6 +439,24 @@ export default class Element extends Node {
this.optimise(); this.optimise();
component.apply_stylesheet(this); 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() { 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]/; const regex_starts_with_vowel = /^[aeiou]/;

@ -15,6 +15,8 @@ export default class Head extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.can_use_innerhtml = false;
if (info.attributes.length) { if (info.attributes.length) {
component.error(info.attributes[0], compiler_errors.invalid_attribute_head); component.error(info.attributes[0], compiler_errors.invalid_attribute_head);
return; return;

@ -18,6 +18,8 @@ export default class IfBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.scope = scope.child(); this.scope = scope.child();
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, this.scope, info.expression); this.expression = new Expression(component, this, this.scope, info.expression);
([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this));

@ -28,6 +28,9 @@ export default class InlineComponent extends Node {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
if (info.name !== 'svelte:component' && info.name !== 'svelte:self') { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
const name = info.name.split('.')[0]; // accommodate namespaces const name = info.name.split('.')[0]; // accommodate namespaces
component.warn_if_undefined(name, info, scope); component.warn_if_undefined(name, info, scope);

@ -13,6 +13,8 @@ export default class KeyBlock extends AbstractBlock {
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) { constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
super(component, parent, scope, info); super(component, parent, scope, info);
this.cannot_use_innerhtml();
this.not_static_content();
this.expression = new Expression(component, this, scope, info.expression); this.expression = new Expression(component, this, scope, info.expression);

@ -2,4 +2,8 @@ import Tag from './shared/Tag';
export default class RawMustacheTag extends Tag { export default class RawMustacheTag extends Tag {
type: 'RawMustacheTag'; type: 'RawMustacheTag';
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.not_static_content();
}
} }

@ -60,5 +60,8 @@ export default class Slot extends Element {
} }
component.slots.set(this.slot_name, this); component.slots.set(this.slot_name, this);
this.cannot_use_innerhtml();
this.not_static_content();
} }
} }

@ -15,6 +15,7 @@ export default class Node {
next?: INode; next?: INode;
can_use_innerhtml: boolean; can_use_innerhtml: boolean;
is_static_content: boolean;
var: string; var: string;
attributes: Attribute[]; attributes: Attribute[];
@ -33,6 +34,9 @@ export default class Node {
value: parent value: parent
} }
}); });
this.can_use_innerhtml = true;
this.is_static_content = true;
} }
cannot_use_innerhtml() { 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) { find_nearest(selector: RegExp) {
if (selector.test(this.type)) return this; if (selector.test(this.type)) return this;
if (this.parent) return this.parent.find_nearest(selector); if (this.parent) return this.parent.find_nearest(selector);

@ -8,6 +8,9 @@ export default class Tag extends Node {
constructor(component, parent, scope, info) { constructor(component, parent, scope, info) {
super(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.expression = new Expression(component, this, scope, info.expression);
this.should_cache = ( this.should_cache = (
@ -15,4 +18,12 @@ export default class Tag extends Node {
(this.expression.dependencies.size && scope.names.has(info.expression.name)) (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();
}
}
} }

@ -133,9 +133,6 @@ export default class AwaitBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
let is_dynamic = false; let is_dynamic = false;

@ -79,8 +79,6 @@ export default class EachBlockWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
const { dependencies } = node.expression; const { dependencies } = node.expression;
block.add_dependencies(dependencies); block.add_dependencies(dependencies);

@ -36,9 +36,6 @@ export class BaseAttributeWrapper {
this.parent = parent; this.parent = parent;
if (node.dependencies.size > 0) { if (node.dependencies.size > 0) {
parent.cannot_use_innerhtml();
parent.not_static_content();
block.add_dependencies(node.dependencies); block.add_dependencies(node.dependencies);
} }
} }

@ -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); 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) { render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
const { renderer } = this; const { renderer } = this;
const hydratable = renderer.options.hydratable;
if (this.node.name === 'noscript') return; if (this.node.name === 'noscript') return;
@ -430,13 +413,15 @@ export default class ElementWrapper extends Wrapper {
b`${node} = ${render_statement};` b`${node} = ${render_statement};`
); );
if (renderer.options.hydratable) { const { can_use_textcontent, can_optimise_to_html_string } = this.node;
if (hydratable) {
if (parent_nodes) { if (parent_nodes) {
block.chunks.claim.push(b` 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` block.chunks.claim.push(b`
var ${nodes} = ${children}; var ${nodes} = ${children};
`); `);
@ -474,15 +459,19 @@ export default class ElementWrapper extends Wrapper {
// insert static children with textContent or innerHTML // insert static children with textContent or innerHTML
// skip textcontent for <template>. append nodes to TemplateElement.content instead // skip textcontent for <template>. append nodes to TemplateElement.content instead
const can_use_textcontent = this.can_use_textcontent(); if (can_optimise_to_html_string) {
const is_template = this.node.name === 'template';
const is_template_with_text_content = is_template && can_use_textcontent;
if (!is_template_with_text_content && !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') { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') {
block.chunks.create.push( let text: Node = string_literal((this.fragment.nodes[0] as TextWrapper).data);
b`${node}.textContent = ${string_literal((this.fragment.nodes[0] as TextWrapper).data)};` if (hydratable) {
); const variable = block.get_unique_name('textContent');
block.add_variable(variable, text);
text = variable;
}
block.chunks.create.push(b`${node}.textContent = ${text};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.textContent = ${text};`);
}
} else { } else {
const state = { const state = {
quasi: { quasi: {
@ -491,25 +480,33 @@ export default class ElementWrapper extends Wrapper {
} }
}; };
const literal = { let literal: Node = {
type: 'TemplateLiteral', type: 'TemplateLiteral',
expressions: [], expressions: [],
quasis: [] quasis: []
}; };
const can_use_raw_text = !this.can_use_innerhtml && can_use_textcontent; const can_use_raw_text = !this.node.can_use_innerhtml && can_use_textcontent;
to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text); to_html((this.fragment.nodes as unknown as Array<ElementWrapper | TextWrapper>), block, literal, state, can_use_raw_text);
literal.quasis.push(state.quasi); literal.quasis.push(state.quasi as any);
block.chunks.create.push( if (hydratable) {
b`${node}.${this.can_use_innerhtml ? 'innerHTML' : 'textContent'} = ${literal};` const variable = block.get_unique_name('textContent');
); block.add_variable(variable, literal);
literal = variable;
}
const property = this.node.can_use_innerhtml ? 'innerHTML' : 'textContent';
block.chunks.create.push(b`${node}.${property} = ${literal};`);
if (hydratable) {
block.chunks.claim.push(b`if (@get_svelte_dataset(${node}) !== "${this.node.hash()}") ${node}.${property} = ${literal};`);
}
} }
} else { } else {
this.fragment.nodes.forEach((child: Wrapper) => { this.fragment.nodes.forEach((child: Wrapper) => {
child.render( child.render(
block, block,
is_template ? x`${node}.content` : node, this.node.name === 'template' ? x`${node}.content` : node,
nodes nodes
); );
}); });
@ -537,7 +534,7 @@ export default class ElementWrapper extends Wrapper {
this.add_styles(block); this.add_styles(block);
this.add_manual_style_scoping(block); this.add_manual_style_scoping(block);
if (nodes && this.renderer.options.hydratable && !this.void) { if (nodes && hydratable && !this.void && !can_optimise_to_html_string) {
block.chunks.claim.push( block.chunks.claim.push(
b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);` b`${this.node.children.length > 0 ? nodes : children}.forEach(@detach);`
); );
@ -553,10 +550,6 @@ export default class ElementWrapper extends Wrapper {
block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); block.renderer.dirty(this.node.tag_expr.dynamic_dependencies());
} }
can_use_textcontent() {
return this.is_static_content && this.fragment.nodes.every(node => node.node.type === 'Text' || node.node.type === 'MustacheTag');
}
get_render_statement(block: Block) { get_render_statement(block: Block) {
const { name, namespace, tag_expr } = this.node; const { name, namespace, tag_expr } = this.node;
const reference = tag_expr.manipulate(block); const reference = tag_expr.manipulate(block);
@ -577,7 +570,7 @@ export default class ElementWrapper extends Wrapper {
return x`@element(${reference})`; return x`@element(${reference})`;
} }
get_claim_statement(block: Block, nodes: Identifier) { get_claim_statement(block: Block, nodes: Identifier, can_optimise_to_html_string: boolean) {
const attributes = this.attributes const attributes = this.attributes
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
@ -595,6 +588,10 @@ export default class ElementWrapper extends Wrapper {
reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`;
} }
if (can_optimise_to_html_string) {
attributes.push(p`["data-svelte"]: true`);
}
if (this.node.namespace === namespaces.svg) { if (this.node.namespace === namespaces.svg) {
return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`;
} else { } else {

@ -20,8 +20,6 @@ export default class HeadWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.can_use_innerhtml = false;
this.fragment = new FragmentWrapper( this.fragment = new FragmentWrapper(
renderer, renderer,
block, block,

@ -105,9 +105,6 @@ export default class IfBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.branches = []; this.branches = [];
const blocks: Block[] = []; const blocks: Block[] = [];

@ -44,9 +44,6 @@ export default class InlineComponentWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.expression) { if (this.node.expression) {
block.add_dependencies(this.node.expression.dependencies); block.add_dependencies(this.node.expression.dependencies);
} }

@ -24,9 +24,6 @@ export default class KeyBlockWrapper extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
this.dependencies = node.expression.dynamic_dependencies(); this.dependencies = node.expression.dynamic_dependencies();
if (this.dependencies.length) { if (this.dependencies.length) {

@ -20,8 +20,6 @@ export default class RawMustacheTagWrapper extends Tag {
node: MustacheTag | RawMustacheTag node: MustacheTag | RawMustacheTag
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
} }
render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) { render(block: Block, parent_node: Identifier, _parent_nodes: Identifier) {

@ -30,8 +30,6 @@ export default class SlotWrapper extends Wrapper {
next_sibling: Wrapper next_sibling: Wrapper
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
this.not_static_content();
if (this.node.children.length) { if (this.node.children.length) {
this.fallback = block.child({ this.fallback = block.child({

@ -12,18 +12,9 @@ export default class Tag extends Wrapper {
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.cannot_use_innerhtml();
if (!this.is_dependencies_static()) {
this.not_static_content();
}
block.add_dependencies(node.expression.dependencies); block.add_dependencies(node.expression.dependencies);
} }
is_dependencies_static() {
return this.node.expression.contextual_dependencies.size === 0 && this.node.expression.dynamic_dependencies().length === 0;
}
rename_this_method( rename_this_method(
block: Block, block: Block,
update: ((value: Node) => (Node | Node[])) update: ((value: Node) => (Node | Node[]))

@ -13,8 +13,6 @@ export default class Wrapper {
next: Wrapper | null; next: Wrapper | null;
var: Identifier; var: Identifier;
can_use_innerhtml: boolean;
is_static_content: boolean;
constructor( constructor(
renderer: Renderer, renderer: Renderer,
@ -35,22 +33,9 @@ export default class Wrapper {
} }
}); });
this.can_use_innerhtml = !renderer.options.hydratable;
this.is_static_content = !renderer.options.hydratable;
block.wrappers.push(this); block.wrappers.push(this);
} }
cannot_use_innerhtml() {
this.can_use_innerhtml = false;
if (this.parent) this.parent.cannot_use_innerhtml();
}
not_static_content() {
this.is_static_content = false;
if (this.parent) this.parent.not_static_content();
}
get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) { get_or_create_anchor(block: Block, parent_node: Identifier, parent_nodes: Identifier) {
// TODO use this in EachBlock and IfBlock — tricky because // TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first // children need to be created first

@ -47,6 +47,7 @@ const handlers: Record<string, Handler> = {
export interface RenderOptions extends CompileOptions{ export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number }; locate: (c: number) => { line: number; column: number };
head_id?: string; head_id?: string;
has_added_svelte_hash?: boolean;
} }
export default class Renderer { export default class Renderer {

@ -160,6 +160,17 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
} }
}); });
if (options.hydratable) {
if (options.head_id) {
renderer.add_string(` data-svelte="${options.head_id}"`);
}
if (node.can_optimise_to_html_string && !options.has_added_svelte_hash) {
renderer.add_string(` data-svelte="${node.hash()}"`);
options = { ...options, has_added_svelte_hash: true };
}
}
renderer.add_string('>'); renderer.add_string('>');
if (node_contents !== undefined) { if (node_contents !== undefined) {

@ -39,6 +39,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
continue; continue;
} }
child.data = data;
nodes.unshift(child); nodes.unshift(child);
link(last_child, last_child = child); link(last_child, last_child = child);
} else { } else {

@ -1,4 +1,5 @@
export function string_literal(data: string) { import { Literal } from 'estree';
export function string_literal(data: string): Literal {
return { return {
type: 'Literal', type: 'Literal',
value: data value: data

@ -348,6 +348,10 @@ export function xlink_attr(node, attribute, value) {
node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value); node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
} }
export function get_svelte_dataset(node: HTMLElement) {
return node.dataset.svelte;
}
export function get_binding_group_value(group, __value, checked) { export function get_binding_group_value(group, __value, checked) {
const value = new Set(); const value = new Set();
for (let i = 0; i < group.length; i += 1) { for (let i = 0; i < group.length; i += 1) {

@ -140,11 +140,12 @@ function cleanChildren(node) {
} }
} }
export function normalizeHtml(window, html, preserveComments = false) { export function normalizeHtml(window, html, { removeDataSvelte = false, preserveComments = false }: { removeDataSvelte?: boolean, preserveComments?: boolean} = {}) {
try { try {
const node = window.document.createElement('div'); const node = window.document.createElement('div');
node.innerHTML = html node.innerHTML = html
.replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '') .replace(/(<!--.*?-->)/g, preserveComments ? '$1' : '')
.replace(/(data-svelte="[^"]+")/g, removeDataSvelte ? '': '$1')
.replace(/>[\s\r\n]+</g, '><') .replace(/>[\s\r\n]+</g, '><')
.trim(); .trim();
cleanChildren(node); cleanChildren(node);
@ -154,22 +155,22 @@ export function normalizeHtml(window, html, preserveComments = false) {
} }
} }
export function setupHtmlEqual() { export function setupHtmlEqual(options?: { removeDataSvelte?: boolean }) {
const window = env(); const window = env();
// eslint-disable-next-line no-import-assign // eslint-disable-next-line no-import-assign
assert.htmlEqual = (actual, expected, message) => { assert.htmlEqual = (actual, expected, message) => {
assert.deepEqual( assert.deepEqual(
normalizeHtml(window, actual), normalizeHtml(window, actual, options),
normalizeHtml(window, expected), normalizeHtml(window, expected, options),
message message
); );
}; };
// eslint-disable-next-line no-import-assign // eslint-disable-next-line no-import-assign
assert.htmlEqualWithComments = (actual, expected, message) => { assert.htmlEqualWithComments = (actual, expected, message) => {
assert.deepEqual( assert.deepEqual(
normalizeHtml(window, actual, true), normalizeHtml(window, actual, { ...options, preserveComments: true }),
normalizeHtml(window, expected, true), normalizeHtml(window, expected, { ...options, preserveComments: true }),
message message
); );
}; };

@ -1 +1 @@
<h1>Hello world!</h1> <h1 data-svelte="svelte-1vv3a6r">Hello world!</h1>

@ -1 +1 @@
<h1>Hello world!</h1> <h1 data-svelte="svelte-1vv3a6r">Hello world!</h1>

@ -0,0 +1,8 @@
<div data-svelte="xxx">hello</div>
<div data-svelte="xxx"><div>bye</div></div>
<div data-svelte="xxx">
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div data-svelte="xxx">hello</div>
<div data-svelte="xxx"><div data-svelte="yyy">bye</div></div>
<div data-svelte="xxx">
<div data-svelte="yyy">aaa</div>
<div data-svelte="zzz">bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -0,0 +1,8 @@
<div>hello</div>
<div><div>bye</div></div>
<div>
<div>aaa</div>
<div>bbb</div>
</div>

@ -1,3 +1,3 @@
<div> <div>
<p>nested</p> <p data-svelte="svelte-1x3hbnh">nested</p>
</div> </div>

@ -1,3 +1,3 @@
<div> <div>
<p>nested</p> <p data-svelte="svelte-1x3hbnh">nested</p>
</div> </div>

@ -1 +1 @@
<p>nested</p> <p data-svelte="svelte-1x3hbnh">nested</p>

@ -1 +1 @@
<p>nested</p> <p data-svelte="svelte-1x3hbnh">nested</p>

@ -1,3 +1,3 @@
<div> <div data-svelte="svelte-1aqf5aj">
<p>nested</p> <p>nested</p>
</div> </div>

@ -1,3 +1,3 @@
<div> <div data-svelte="svelte-1aqf5aj">
<p>nested</p> <p>nested</p>
</div> </div>

@ -45,7 +45,7 @@ describe('runtime', () => {
return module._compile(code, filename); return module._compile(code, filename);
}; };
return setupHtmlEqual(); return setupHtmlEqual({ removeDataSvelte: true });
}); });
after(() => process.removeListener('unhandledRejection', unhandledRejection_handler)); after(() => process.removeListener('unhandledRejection', unhandledRejection_handler));
@ -159,10 +159,12 @@ describe('runtime', () => {
// ssr into target // ssr into target
compileOptions.generate = 'ssr'; compileOptions.generate = 'ssr';
cleanRequireCache(); cleanRequireCache();
if (config.before_test) config.before_test();
const SsrSvelteComponent = require(`./samples/${dir}/main.svelte`).default; const SsrSvelteComponent = require(`./samples/${dir}/main.svelte`).default;
const { html } = SsrSvelteComponent.render(config.props); const { html } = SsrSvelteComponent.render(config.props);
target.innerHTML = html; target.innerHTML = html;
delete compileOptions.generate; delete compileOptions.generate;
if (config.after_test) config.after_test();
} else { } else {
target.innerHTML = ''; target.innerHTML = '';
} }

@ -5,7 +5,9 @@ export default {
<h1>tag is h1.</h1> <h1>tag is h1.</h1>
`, `,
props: { props: {
logs pushLogs(log) {
logs.push(log);
}
}, },
after_test() { after_test() {
logs = []; logs = [];

@ -1,12 +1,12 @@
<script> <script>
export let logs = []; export let pushLogs;
export let tag = "h1"; export let tag = "h1";
export let opt = "opt1"; export let opt = "opt1";
function foo(node, {tag, opt}) { function foo(node, {tag, opt}) {
logs.push(`create: ${tag},${opt}`); pushLogs(`create: ${tag},${opt}`);
return { return {
update: ({tag, opt}) => logs.push(`update: ${tag},${opt}`), update: ({tag, opt}) => pushLogs(`update: ${tag},${opt}`),
destroy: () => logs.push('destroy'), destroy: () => pushLogs('destroy'),
}; };
} }
</script> </script>

@ -1,3 +1,3 @@
<div>Just a dummy page.</div> <div data-svelte="svelte-156ixv6">Just a dummy page.</div>

@ -11,11 +11,9 @@
<div>A <div>A
B B
<span>C <span>C
D D</span>
</span>
E E
F F</div>
</div>
<div><pre> A <div><pre> A
B B

Loading…
Cancel
Save