` elements during hydration so they aren't duplicated ([#1607](https://github.com/sveltejs/svelte/issues/1607))
* Prevent text input cursor jumping in Safari with one-way binding ([#3449](https://github.com/sveltejs/svelte/issues/3449))
* Expose compiler version in dev events ([#4047](https://github.com/sveltejs/svelte/issues/4047))
* Don't run actions before their element is in the document ([#4166](https://github.com/sveltejs/svelte/issues/4166))
diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts
index 998a879687..246dab0f12 100644
--- a/src/compiler/compile/css/Stylesheet.ts
+++ b/src/compiler/compile/css/Stylesheet.ts
@@ -5,6 +5,7 @@ import Element from '../nodes/Element';
import { Ast, TemplateNode } from '../../interfaces';
import Component from '../Component';
import { CssNode } from './interfaces';
+import hash from "../utils/hash";
function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
@@ -37,15 +38,6 @@ function minify_declarations(
return c;
}
-// https://github.com/darkskyapp/string-hash/blob/master/index.js
-function hash(str: string): string {
- let hash = 5381;
- let i = str.length;
-
- while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
- return (hash >>> 0).toString(36);
-}
-
class Rule {
selectors: Selector[];
declarations: Declaration[];
diff --git a/src/compiler/compile/nodes/Head.ts b/src/compiler/compile/nodes/Head.ts
index 2c08dcd595..53e76d7a4d 100644
--- a/src/compiler/compile/nodes/Head.ts
+++ b/src/compiler/compile/nodes/Head.ts
@@ -1,9 +1,11 @@
import Node from './shared/Node';
import map_children from './shared/map_children';
+import hash from '../utils/hash';
export default class Head extends Node {
type: 'Head';
children: any[]; // TODO
+ id: string;
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
@@ -18,5 +20,9 @@ export default class Head extends Node {
this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || /\S/.test(child.data));
}));
+
+ if (this.children.length > 0) {
+ this.id = `svelte-${hash(this.component.source.slice(this.start, this.end))}`;
+ }
}
}
diff --git a/src/compiler/compile/render_dom/wrappers/Head.ts b/src/compiler/compile/render_dom/wrappers/Head.ts
index 188c26931a..e0b723d6dd 100644
--- a/src/compiler/compile/render_dom/wrappers/Head.ts
+++ b/src/compiler/compile/render_dom/wrappers/Head.ts
@@ -3,11 +3,12 @@ import Renderer from '../Renderer';
import Block from '../Block';
import Head from '../../nodes/Head';
import FragmentWrapper from './Fragment';
-import { x } from 'code-red';
+import { x, b } from 'code-red';
import { Identifier } from 'estree';
export default class HeadWrapper extends Wrapper {
fragment: FragmentWrapper;
+ node: Head;
constructor(
renderer: Renderer,
@@ -32,6 +33,18 @@ export default class HeadWrapper extends Wrapper {
}
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
- this.fragment.render(block, x`@_document.head` as unknown as Identifier, x`#nodes` as unknown as Identifier);
+ let nodes;
+ if (this.renderer.options.hydratable && this.fragment.nodes.length) {
+ nodes = block.get_unique_name('head_nodes');
+ block.chunks.claim.push(b`const ${nodes} = @query_selector_all('[data-svelte="${this.node.id}"]', @_document.head);`);
+ }
+
+ this.fragment.render(block, x`@_document.head` as unknown as Identifier, nodes);
+
+ if (nodes && this.renderer.options.hydratable) {
+ block.chunks.claim.push(
+ b`${nodes}.forEach(@detach);`
+ );
+ }
}
}
diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts
index 00a7ee2fb5..fb9216327c 100644
--- a/src/compiler/compile/render_ssr/Renderer.ts
+++ b/src/compiler/compile/render_ssr/Renderer.ts
@@ -41,6 +41,7 @@ const handlers: Record = {
export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number };
+ head_id?: string;
}
export default class Renderer {
diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts
index 81b8801686..4c1eca8a9d 100644
--- a/src/compiler/compile/render_ssr/handlers/Element.ts
+++ b/src/compiler/compile/render_ssr/handlers/Element.ts
@@ -124,6 +124,10 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
}
});
+ if (options.head_id) {
+ renderer.add_string(` data-svelte="${options.head_id}"`);
+ }
+
renderer.add_string('>');
if (node_contents !== undefined) {
diff --git a/src/compiler/compile/render_ssr/handlers/Head.ts b/src/compiler/compile/render_ssr/handlers/Head.ts
index d457942922..456e5c279b 100644
--- a/src/compiler/compile/render_ssr/handlers/Head.ts
+++ b/src/compiler/compile/render_ssr/handlers/Head.ts
@@ -3,8 +3,13 @@ import Head from '../../nodes/Head';
import { x } from 'code-red';
export default function(node: Head, renderer: Renderer, options: RenderOptions) {
+ const head_options = {
+ ...options,
+ head_id: node.id
+ };
+
renderer.push();
- renderer.render(node.children, options);
+ renderer.render(node.children, head_options);
const result = renderer.pop();
renderer.add_expression(x`($$result.head += ${result}, "")`);
diff --git a/src/compiler/compile/render_ssr/handlers/Title.ts b/src/compiler/compile/render_ssr/handlers/Title.ts
index f1f458ed5b..e93ae13d66 100644
--- a/src/compiler/compile/render_ssr/handlers/Title.ts
+++ b/src/compiler/compile/render_ssr/handlers/Title.ts
@@ -5,7 +5,7 @@ import { x } from 'code-red';
export default function(node: Title, renderer: Renderer, options: RenderOptions) {
renderer.push();
- renderer.add_string(``);
+ renderer.add_string(``);
renderer.render(node.children, options);
diff --git a/src/compiler/compile/utils/hash.ts b/src/compiler/compile/utils/hash.ts
new file mode 100644
index 0000000000..7ac892611b
--- /dev/null
+++ b/src/compiler/compile/utils/hash.ts
@@ -0,0 +1,8 @@
+// https://github.com/darkskyapp/string-hash/blob/master/index.js
+export default function hash(str: string): string {
+ let hash = 5381;
+ let i = str.length;
+
+ while (i--) hash = ((hash << 5) - hash) ^ str.charCodeAt(i);
+ return (hash >>> 0).toString(36);
+}
\ No newline at end of file
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
index c641315bc3..f9e89f41b9 100644
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -273,6 +273,10 @@ export function custom_event(type: string, detail?: T) {
return e;
}
+export function query_selector_all(selector: string, parent: HTMLElement = document.body) {
+ return Array.from(parent.querySelectorAll(selector));
+}
+
export class HtmlTag {
e: HTMLElement;
n: ChildNode[];
diff --git a/test/helpers.js b/test/helpers.js
index 2a03e0f436..5e9428243b 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -68,6 +68,7 @@ window.scrollTo = function(pageXOffset, pageYOffset) {
export function env() {
window.document.title = '';
+ window.document.head.innerHTML = '';
window.document.body.innerHTML = '';
return window;
diff --git a/test/hydration/index.js b/test/hydration/index.js
index a0bfd6de4b..f57a0cdc1a 100644
--- a/test/hydration/index.js
+++ b/test/hydration/index.js
@@ -67,8 +67,16 @@ describe('hydration', () => {
}
const target = window.document.body;
+ const head = window.document.head;
+
target.innerHTML = fs.readFileSync(`${cwd}/_before.html`, 'utf-8');
+ let before_head;
+ try {
+ before_head = fs.readFileSync(`${cwd}/_before_head.html`, 'utf-8');
+ head.innerHTML = before_head;
+ } catch (err) {}
+
const snapshot = config.snapshot ? config.snapshot(target) : {};
const component = new SvelteComponent({
@@ -88,6 +96,19 @@ describe('hydration', () => {
}
}
+ if (before_head) {
+ try {
+ assert.htmlEqual(head.innerHTML, fs.readFileSync(`${cwd}/_after_head.html`, 'utf-8'));
+ } catch (error) {
+ if (shouldUpdateExpected()) {
+ fs.writeFileSync(`${cwd}/_after_head.html`, head.innerHTML);
+ console.log(`Updated ${cwd}/_after_head.html.`);
+ } else {
+ throw error;
+ }
+ }
+ }
+
if (config.test) {
config.test(assert, target, snapshot, component, window);
} else {
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html
new file mode 100644
index 0000000000..3e5b375f0a
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after.html
@@ -0,0 +1 @@
+
Just a dummy page.
\ No newline at end of file
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html
new file mode 100644
index 0000000000..10cf2c8b9a
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/_after_head.html
@@ -0,0 +1,4 @@
+Some Title
+
+
+
\ No newline at end of file
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html
new file mode 100644
index 0000000000..3e5b375f0a
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before.html
@@ -0,0 +1 @@
+
Just a dummy page.
\ No newline at end of file
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html
new file mode 100644
index 0000000000..d2f218fb8d
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/_before_head.html
@@ -0,0 +1,4 @@
+Some Title
+
+
+
\ No newline at end of file
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/_config.js b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js
new file mode 100644
index 0000000000..482efd564d
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/_config.js
@@ -0,0 +1,5 @@
+export default {
+ test(assert, target, snapshot, component, window) {
+ assert.equal(window.document.querySelectorAll('meta').length, 2);
+ }
+};
diff --git a/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte
new file mode 100644
index 0000000000..1a8b125dd2
--- /dev/null
+++ b/test/hydration/samples/head-meta-hydrate-duplicate/main.svelte
@@ -0,0 +1,8 @@
+
+ Some Title
+
+
+
+
+
+
Just a dummy page.
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html
new file mode 100644
index 0000000000..d2f218fb8d
--- /dev/null
+++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected-head.html
@@ -0,0 +1,4 @@
+Some Title
+
+
+
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html
new file mode 100644
index 0000000000..a469e618fa
--- /dev/null
+++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/_expected.html
@@ -0,0 +1,3 @@
+
+
+
Just a dummy page.
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte
new file mode 100644
index 0000000000..1a8b125dd2
--- /dev/null
+++ b/test/server-side-rendering/samples/head-meta-hydrate-duplicate/main.svelte
@@ -0,0 +1,8 @@
+
+ Some Title
+
+
+
+
+
+
Just a dummy page.
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html
index af5c5feba4..7147550839 100644
--- a/test/server-side-rendering/samples/head-multiple-title/_expected-head.html
+++ b/test/server-side-rendering/samples/head-multiple-title/_expected-head.html
@@ -1 +1 @@
-B
\ No newline at end of file
+B
\ No newline at end of file
diff --git a/test/server-side-rendering/samples/head-title/_expected-head.html b/test/server-side-rendering/samples/head-title/_expected-head.html
index 7d696352f9..6e73e671e6 100644
--- a/test/server-side-rendering/samples/head-title/_expected-head.html
+++ b/test/server-side-rendering/samples/head-title/_expected-head.html
@@ -1 +1 @@
-a custom title
\ No newline at end of file
+a custom title
\ No newline at end of file