Skip to content

Commit

Permalink
Merge pull request #1053 from sveltejs/gh-1027
Browse files Browse the repository at this point in the history
optimise <title>
  • Loading branch information
Rich-Harris authored Dec 30, 2017
2 parents 3a9b3ea + a8f7d57 commit 0df6cfa
Show file tree
Hide file tree
Showing 18 changed files with 710 additions and 1 deletion.
11 changes: 11 additions & 0 deletions src/generators/Generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,14 @@ export default class Generator {
let indexes = new Set();
const indexesStack: Set<string>[] = [indexes];

function parentIsHead(node) {
if (!node) return false;
if (node.type === 'Component' || node.type === 'Element') return false;
if (node.type === 'Head') return true;

return parentIsHead(node.parent);
}

walk(html, {
enter(node: Node, parent: Node, key: string) {
// TODO this is hacky as hell
Expand All @@ -738,6 +746,9 @@ export default class Generator {
} else if (node.name === ':Head') { // TODO do this in parse?
node.type = 'Head';
Object.setPrototypeOf(node, nodes.Head.prototype);
} else if (node.type === 'Element' && node.name === 'title' && parentIsHead(parent)) { // TODO do this in parse?
node.type = 'Title';
Object.setPrototypeOf(node, nodes.Title.prototype);
} else if (node.type === 'Element' && node.name === 'slot' && !generator.customElement) {
node.type = 'Slot';
Object.setPrototypeOf(node, nodes.Slot.prototype);
Expand Down
98 changes: 98 additions & 0 deletions src/generators/nodes/Title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { stringify } from '../../utils/stringify';
import getExpressionPrecedence from '../../utils/getExpressionPrecedence';
import Node from './shared/Node';
import Block from '../dom/Block';

export default class Title extends Node {
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const isDynamic = !!this.children.find(node => node.type !== 'Text');

if (isDynamic) {
let value;

const allDependencies = new Set();
let shouldCache;

// TODO some of this code is repeated in Tag.ts — would be good to
// DRY it out if that's possible without introducing crazy indirection
if (this.children.length === 1) {
// single {{tag}} — may be a non-string
const { expression } = this.children[0];
const { indexes } = block.contextualise(expression);
const { dependencies, snippet } = this.children[0].metadata;

value = snippet;
dependencies.forEach(d => {
allDependencies.add(d);
});

shouldCache = (
expression.type !== 'Identifier' ||
block.contexts.has(expression.name)
);
} else {
// '{{foo}} {{bar}}' — treat as string concatenation
value =
(this.children[0].type === 'Text' ? '' : `"" + `) +
this.children
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const { indexes } = block.contextualise(chunk.expression);
const { dependencies, snippet } = chunk.metadata;

dependencies.forEach(d => {
allDependencies.add(d);
});

return getExpressionPrecedence(chunk.expression) <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');

shouldCache = true;
}

const last = shouldCache && block.getUniqueName(
`title_value`
);

if (shouldCache) block.addVariable(last);

let updater;
const init = shouldCache ? `${last} = ${value}` : value;

block.builders.init.addLine(
`document.title = ${init};`
);
updater = `document.title = ${shouldCache ? last : value};`;

if (allDependencies.size) {
const dependencies = Array.from(allDependencies);
const changedCheck = (
( block.hasOutroMethod ? `#outroing || ` : '' ) +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);

const updateCachedValue = `${last} !== (${last} = ${value})`;

const condition = shouldCache ?
( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) :
changedCheck;

block.builders.update.addConditional(
condition,
updater
);
}
} else {
const value = stringify(this.children[0].data);
block.builders.hydrate.addLine(`document.title = ${value};`);
}
}
}
2 changes: 2 additions & 0 deletions src/generators/nodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Ref from './Ref';
import Slot from './Slot';
import Text from './Text';
import ThenBlock from './ThenBlock';
import Title from './Title';
import Transition from './Transition';
import Window from './Window';

Expand All @@ -43,6 +44,7 @@ const nodes: Record<string, any> = {
Slot,
Text,
ThenBlock,
Title,
Transition,
Window
};
Expand Down
19 changes: 19 additions & 0 deletions src/generators/server-side-rendering/visitors/Title.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { SsrGenerator } from '../index';
import Block from '../Block';
import { escape } from '../../../utils/stringify';
import visit from '../visit';
import { Node } from '../../../interfaces';

export default function visitTitle(
generator: SsrGenerator,
block: Block,
node: Node
) {
generator.append(`<title>`);

node.children.forEach((child: Node) => {
visit(generator, block, child);
});

generator.append(`</title>`);
}
2 changes: 2 additions & 0 deletions src/generators/server-side-rendering/visitors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot';
import Text from './Text';
import Title from './Title';
import Window from './Window';

export default {
Expand All @@ -23,5 +24,6 @@ export default {
RawMustacheTag,
Slot,
Text,
Title,
Window
};
20 changes: 19 additions & 1 deletion src/validate/html/validateElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import validateEventHandler from './validateEventHandler';
import validate, { Validator } from '../index';
import { Node } from '../../interfaces';

const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/;
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|tref|tspan|unknown|use|view|vkern)$/;

export default function validateElement(
validator: Validator,
Expand Down Expand Up @@ -61,6 +61,24 @@ export default function validateElement(
}
}

if (node.name === 'title') {
if (node.attributes.length > 0) {
validator.error(
`<title> cannot have attributes`,
node.attributes[0].start
);
}

node.children.forEach(child => {
if (child.type !== 'Text' && child.type !== 'MustacheTag') {
validator.error(
`<title> can only contain text and {{tags}}`,
child.start
);
}
});
}

let hasIntro: boolean;
let hasOutro: boolean;
let hasTransition: boolean;
Expand Down
8 changes: 8 additions & 0 deletions src/validate/html/validateHead.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import validateElement from './validateElement';
import { Validator } from '../index';
import { Node } from '../../interfaces';

export default function validateHead(validator: Validator, node: Node, refs: Map<string, Node[]>, refCallees: Node[]) {
if (node.attributes.length) {
validator.error(`<:Head> should not have any attributes or directives`, node.start);
}

// TODO ensure only valid elements are included here

node.children.forEach(node => {
if (node.type !== 'Element') return; // TODO handle {{#if}} and friends?
validateElement(validator, node, refs, refCallees, [], []);
});
}
Loading

0 comments on commit 0df6cfa

Please sign in to comment.