remove <:Document>, implement <:Head> on client-side

pull/1024/head
Rich Harris 8 years ago
parent a02c3dba28
commit 50654fab88

@ -727,9 +727,9 @@ export default class Generator {
} else if (node.name === ':Window') { // TODO do this in parse?
node.type = 'Window';
node.__proto__ = nodes.Window.prototype;
} else if (node.name === ':Document') { // TODO do this in parse?
node.type = 'Document';
node.__proto__ = nodes.Document.prototype;
} else if (node.name === ':Head') { // TODO do this in parse?
node.type = 'Head';
node.__proto__ = nodes.Head.prototype;
} else if (node.type === 'Element' && node.name === 'slot' && !generator.customElement) {
node.type = 'Slot';
node.__proto__ = nodes.Slot.prototype;

@ -132,9 +132,9 @@ export default class Block {
) {
this.addVariable(name);
this.builders.create.addLine(`${name} = ${renderStatement};`);
this.builders.claim.addLine(`${name} = ${claimStatement};`);
this.builders.claim.addLine(`${name} = ${claimStatement || renderStatement};`);
if (parentNode) {
if (parentNode && parentNode !== 'document.head') {
this.builders.mount.addLine(`@appendNode(${name}, ${parentNode});`);
} else {
this.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`);

@ -68,7 +68,7 @@ export default class AwaitBlock extends Node {
) {
const name = this.var;
const anchor = this.getOrCreateAnchor(block, parentNode);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor);
const params = block.params.join(', ');
@ -143,9 +143,11 @@ export default class AwaitBlock extends Node {
${await_block}.c();
`);
block.builders.claim.addBlock(deindent`
${await_block}.l(${parentNodes});
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${await_block}.l(${parentNodes});
`);
}
const initialMountNode = parentNode || '#target';
const anchorNode = parentNode ? 'null' : 'anchor';

@ -248,7 +248,7 @@ export default class Component extends Node {
block.contextualise(this.expression);
const { dependencies, snippet } = this.metadata;
const anchor = this.getOrCreateAnchor(block, parentNode);
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const params = block.params.join(', ');
@ -281,9 +281,11 @@ export default class Component extends Node {
`if (${name}) ${name}._fragment.c();`
);
block.builders.claim.addLine(
`if (${name}) ${name}._fragment.l(${parentNodes});`
);
if (parentNodes) {
block.builders.claim.addLine(
`if (${name}) ${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addLine(
`if (${name}) ${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});`
@ -350,9 +352,11 @@ export default class Component extends Node {
block.builders.create.addLine(`${name}._fragment.c();`);
block.builders.claim.addLine(
`${name}._fragment.l(${parentNodes});`
);
if (parentNodes) {
block.builders.claim.addLine(
`${name}._fragment.l(${parentNodes});`
);
}
block.builders.mount.addLine(
`${name}._mount(${parentNode || '#target'}, ${parentNode ? 'null' : 'anchor'});`

@ -154,7 +154,7 @@ export default class EachBlock extends Node {
block.addElement(
anchor,
`@createComment()`,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}
@ -263,7 +263,7 @@ export default class EachBlock extends Node {
this.block.addElement(
this.block.first,
`@createComment()`,
`@createComment()`,
parentNodes && `@createComment()`,
null
);
}
@ -293,13 +293,15 @@ export default class EachBlock extends Node {
}
`);
block.builders.claim.addBlock(deindent`
var ${iteration} = ${head};
while (${iteration}) {
${iteration}.l(${parentNodes});
${iteration} = ${iteration}.next;
}
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
var ${iteration} = ${head};
while (${iteration}) {
${iteration}.l(${parentNodes});
${iteration} = ${iteration}.next;
}
`);
}
block.builders.mount.addBlock(deindent`
var ${iteration} = ${head};
@ -481,11 +483,13 @@ export default class EachBlock extends Node {
}
`);
block.builders.claim.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].l(${parentNodes});
}
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {
${iterations}[#i].l(${parentNodes});
}
`);
}
block.builders.mount.addBlock(deindent`
for (var #i = 0; #i < ${iterations}.length; #i += 1) {

@ -163,7 +163,7 @@ export default class Element extends Node {
const childState = {
parentNode: this.var,
parentNodes: block.getUniqueName(`${this.var}_nodes`)
parentNodes: parentNodes && block.getUniqueName(`${this.var}_nodes`) // if we're in unclaimable territory, i.e. <head>, parentNodes is null
};
const name = this.var;
@ -175,22 +175,25 @@ export default class Element extends Node {
parentNode;
block.addVariable(name);
const renderStatement = getRenderStatement(this.generator, this.namespace, this.name);
block.builders.create.addLine(
`${name} = ${getRenderStatement(
this.generator,
this.namespace,
this.name
)};`
`${name} = ${renderStatement};`
);
if (this.generator.hydratable) {
block.builders.claim.addBlock(deindent`
${name} = ${getClaimStatement(generator, this.namespace, parentNodes, this)};
var ${childState.parentNodes} = @children(${name});
`);
if (parentNodes) {
block.builders.claim.addBlock(deindent`
${name} = ${getClaimStatement(generator, this.namespace, parentNodes, this)};
var ${childState.parentNodes} = @children(${name});
`);
} else {
block.builders.claim.addLine(
`${name} = ${renderStatement};`
);
}
}
if (initialMountNode) {
if (initialMountNode && initialMountNode !== 'document.head') {
block.builders.mount.addLine(
`@appendNode(${name}, ${initialMountNode});`
);
@ -394,9 +397,11 @@ export default class Element extends Node {
block.builders.mount.addBlock(this.initialUpdate);
}
block.builders.claim.addLine(
`${childState.parentNodes}.forEach(@detachNode);`
);
if (childState.parentNodes) {
block.builders.claim.addLine(
`${childState.parentNodes}.forEach(@detachNode);`
);
}
function toHTML(node: Element | Text) {
if (node.type === 'Text') return node.data;

@ -4,10 +4,18 @@ import Node from './shared/Node';
import Block from '../dom/Block';
import Attribute from './Attribute';
export default class Document extends Node {
type: 'Document';
export default class Head extends Node {
type: 'Head';
attributes: Attribute[];
init(
block: Block,
stripWhitespace: boolean,
nextSibling: Node
) {
this.initChildren(block, true, null);
}
build(
block: Block,
parentNode: string,
@ -15,12 +23,10 @@ export default class Document extends Node {
) {
const { generator } = this;
this.var = 'document';
this.var = 'document.head';
this.attributes.forEach((attribute: Attribute) => {
if (attribute.name === 'title') {
attribute.render(block);
}
this.children.forEach((child: Node) => {
child.build(block, 'document.head', null);
});
}
}

@ -133,15 +133,17 @@ export default class IfBlock extends Node {
block.builders.create.addLine(`${if_name}${name}.c();`);
block.builders.claim.addLine(
`${if_name}${name}.l(${parentNodes});`
);
if (parentNodes) {
block.builders.claim.addLine(
`${if_name}${name}.l(${parentNodes});`
);
}
if (needsAnchor) {
block.addElement(
anchor,
`@createComment()`,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}

@ -22,7 +22,7 @@ export default class MustacheTag extends Tag {
block.addElement(
this.var,
`@createText(${init})`,
`@claimText(${parentNodes}, ${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
);
}

@ -61,7 +61,7 @@ export default class RawMustacheTag extends Tag {
block.addElement(
anchorBefore,
`@createElement('noscript')`,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}
@ -70,7 +70,7 @@ export default class RawMustacheTag extends Tag {
block.addElement(
anchorAfter,
`@createElement('noscript')`,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}

@ -53,7 +53,7 @@ export default class Text extends Node {
block.addElement(
this.var,
`@createText(${stringify(this.data)})`,
`@claimText(${parentNodes}, ${stringify(this.data)})`,
parentNodes && `@claimText(${parentNodes}, ${stringify(this.data)})`,
parentNode
);
}

@ -5,12 +5,12 @@ import Binding from './Binding';
import CatchBlock from './CatchBlock';
import Comment from './Comment';
import Component from './Component';
import Document from './Document';
import EachBlock from './EachBlock';
import Element from './Element';
import ElseBlock from './ElseBlock';
import EventHandler from './EventHandler';
import Fragment from './Fragment';
import Head from './Head';
import IfBlock from './IfBlock';
import MustacheTag from './MustacheTag';
import PendingBlock from './PendingBlock';
@ -29,12 +29,12 @@ const nodes: Record<string, any> = {
CatchBlock,
Comment,
Component,
Document,
EachBlock,
Element,
ElseBlock,
EventHandler,
Fragment,
Head,
IfBlock,
MustacheTag,
PendingBlock,

@ -138,7 +138,7 @@ export default class Node {
if (this.parent) return this.parent.findNearest(selector);
}
getOrCreateAnchor(block: Block, parentNode: string) {
getOrCreateAnchor(block: Block, parentNode: string, parentNodes: string) {
// TODO use this in EachBlock and IfBlock — tricky because
// children need to be created first
const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode();
@ -150,7 +150,7 @@ export default class Node {
block.addElement(
anchor,
`@createComment()`,
`@createComment()`,
parentNodes && `@createComment()`,
parentNode
);
}

@ -8,9 +8,11 @@ export default function visitDocument(
block: Block,
node: Node
) {
const title = node.attributes.find(attribute => attribute.type === 'Attribute' && attribute.name === 'title');
throw new Error('TODO');
if (title) {
generator.append('${(__result.title = `' + stringifyAttributeValue(block, title.value) + '`, "")}');
}
// const title = node.attributes.find(attribute => attribute.type === 'Attribute' && attribute.name === 'title');
// if (title) {
// generator.append('${(__result.title = `' + stringifyAttributeValue(block, title.value) + '`, "")}');
// }
}

@ -1,9 +1,9 @@
import AwaitBlock from './AwaitBlock';
import Comment from './Comment';
import Component from './Component';
import Document from './Document';
import EachBlock from './EachBlock';
import Element from './Element';
import Head from './Head';
import IfBlock from './IfBlock';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
@ -15,9 +15,9 @@ export default {
AwaitBlock,
Comment,
Component,
Document,
EachBlock,
Element,
Head,
IfBlock,
MustacheTag,
RawMustacheTag,

@ -19,7 +19,7 @@ const COMPONENT = ':Component';
const metaTags = new Set([
':Window',
':Document'
':Head'
]);
const specials = new Map([
@ -88,21 +88,24 @@ export default function tag(parser: Parser) {
const name = readTagName(parser);
if (metaTags.has(name)) {
if (name in parser.metaTags) {
if (isClosingTag && parser.current().children.length) {
if (isClosingTag) {
if (name === ':Window' && parser.current().children.length) {
parser.error(
`<${name}> cannot have children`,
`<:Window> cannot have children`,
parser.current().children[0].start
);
}
} else {
if (name in parser.metaTags) {
parser.error(`A component can only have one <${name}> tag`, start);
}
parser.error(`A component can only have one <${name}> tag`, start);
}
parser.metaTags[name] = true;
if (parser.stack.length > 1) {
console.log(parser.stack);
parser.error(`<${name}> tags cannot be inside elements or blocks`, start);
}
if (parser.stack.length > 1) {
parser.error(`<${name}> tags cannot be inside elements or blocks`, start);
parser.metaTags[name] = true;
}
}

@ -1,6 +1,6 @@
import validateElement from './validateElement';
import validateWindow from './validateWindow';
import validateDocument from './validateDocument';
import validateHead from './validateHead';
import a11y from './a11y';
import fuzzymatch from '../utils/fuzzymatch'
import flattenReference from '../../utils/flattenReference';
@ -9,7 +9,7 @@ import { Node } from '../../interfaces';
const meta = new Map([
[':Window', validateWindow],
[':Document', validateDocument]
[':Head', validateHead]
]);
export default function validateHtml(validator: Validator, html: Node) {

@ -1,42 +0,0 @@
import flattenReference from '../../utils/flattenReference';
import fuzzymatch from '../utils/fuzzymatch';
import list from '../../utils/list';
import validateEventHandler from './validateEventHandler';
import { Validator } from '../index';
import { Node } from '../../interfaces';
const descriptions = {
Bindings: 'two-way bindings',
EventHandler: 'event handlers',
Transition: 'transitions',
Ref: 'refs'
};
export default function validateWindow(validator: Validator, node: Node, refs: Map<string, Node[]>, refCallees: Node[]) {
node.attributes.forEach((attribute: Node) => {
if (attribute.type === 'Attribute') {
if (attribute.name !== 'title') {
validator.error(
`<:Document> can only have a 'title' attribute`,
attribute.start
);
}
}
else {
const description = descriptions[attribute.type];
if (description) {
validator.error(
`<:Document> does not support ${description}`,
attribute.start
);
} else {
// future-proofing
validator.error(
`<:Document> can only have a 'title' attribute`,
attribute.start
);
}
}
});
}

@ -0,0 +1,8 @@
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);
}
}

@ -1 +0,0 @@
<:Document title='a {{adjective}} title'/>

@ -0,0 +1,4 @@
<:Head>
<title>a {{adjective}} title</title>
<meta name='twitter:creator' content='@sveltejs'>
</:Head>

@ -0,0 +1,4 @@
<:Head>
<title>changed</title>
<meta name='twitter:creator' content='@sveltejs'>
</:Head>

@ -1 +0,0 @@
<:Document title='a {{adjective}} title'/>

@ -0,0 +1,3 @@
<:Head>
<title>a {{adjective}} title</title>
</:Head>
Loading…
Cancel
Save