Skip to content

Commit

Permalink
fix hydrating <head> (#4082)
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau authored and Conduitry committed Jan 13, 2020
1 parent d7d7ce1 commit b3582c7
Show file tree
Hide file tree
Showing 23 changed files with 109 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Unreleased

* Remove old `<head>` 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))
Expand Down
10 changes: 1 addition & 9 deletions src/compiler/compile/css/Stylesheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))-/, '');
Expand Down Expand Up @@ -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[];
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/compile/nodes/Head.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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))}`;
}
}
}
17 changes: 15 additions & 2 deletions src/compiler/compile/render_dom/wrappers/Head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);`
);
}
}
}
1 change: 1 addition & 0 deletions src/compiler/compile/render_ssr/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const handlers: Record<string, Handler> = {

export interface RenderOptions extends CompileOptions{
locate: (c: number) => { line: number; column: number };
head_id?: string;
}

export default class Renderer {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/compile/render_ssr/handlers/Element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion src/compiler/compile/render_ssr/handlers/Head.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}, "")`);
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/compile/render_ssr/handlers/Title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { x } from 'code-red';
export default function(node: Title, renderer: Renderer, options: RenderOptions) {
renderer.push();

renderer.add_string(`<title>`);
renderer.add_string(`<title data-svelte="${options.head_id}">`);

renderer.render(node.children, options);

Expand Down
8 changes: 8 additions & 0 deletions src/compiler/compile/utils/hash.ts
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 4 additions & 0 deletions src/runtime/internal/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ export function custom_event<T=any>(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[];
Expand Down
1 change: 1 addition & 0 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ window.scrollTo = function(pageXOffset, pageYOffset) {

export function env() {
window.document.title = '';
window.document.head.innerHTML = '';
window.document.body.innerHTML = '<main></main>';

return window;
Expand Down
21 changes: 21 additions & 0 deletions test/hydration/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Just a dummy page.</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<title>Some Title</title>
<link href="/" rel="canonical">
<meta content="some description" name="description">
<meta content="some keywords" name="keywords">
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div>Just a dummy page.</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<title data-svelte="svelte-1s8aodm">Some Title</title>
<link rel="canonical" href="/" data-svelte="svelte-1s8aodm">
<meta name="description" content="some description" data-svelte="svelte-1s8aodm">
<meta name="keywords" content="some keywords" data-svelte="svelte-1s8aodm">
5 changes: 5 additions & 0 deletions test/hydration/samples/head-meta-hydrate-duplicate/_config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
test(assert, target, snapshot, component, window) {
assert.equal(window.document.querySelectorAll('meta').length, 2);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<svelte:head>
<title>Some Title</title>
<link rel="canonical" href="/">
<meta name="description" content="some description">
<meta name="keywords" content="some keywords">
</svelte:head>

<div>Just a dummy page.</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<title data-svelte="svelte-1s8aodm">Some Title</title>
<link rel="canonical" href="/" data-svelte="svelte-1s8aodm">
<meta name="description" content="some description" data-svelte="svelte-1s8aodm">
<meta name="keywords" content="some keywords" data-svelte="svelte-1s8aodm">
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@


<div>Just a dummy page.</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<svelte:head>
<title>Some Title</title>
<link rel="canonical" href="/">
<meta name="description" content="some description">
<meta name="keywords" content="some keywords">
</svelte:head>

<div>Just a dummy page.</div>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<title>B</title>
<title data-svelte="svelte-1csszk6">B</title>
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<title>a custom title</title>
<title data-svelte="svelte-135agoq">a custom title</title>

0 comments on commit b3582c7

Please sign in to comment.