diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index eea74c3173..2b921f812e 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -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; diff --git a/src/generators/dom/Block.ts b/src/generators/dom/Block.ts index adf410f9ef..ad452b930e 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -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);`); diff --git a/src/generators/nodes/AwaitBlock.ts b/src/generators/nodes/AwaitBlock.ts index f47f5de61b..8dccd62e83 100644 --- a/src/generators/nodes/AwaitBlock.ts +++ b/src/generators/nodes/AwaitBlock.ts @@ -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'; diff --git a/src/generators/nodes/Component.ts b/src/generators/nodes/Component.ts index 95b29fcbd3..e52c41e9f8 100644 --- a/src/generators/nodes/Component.ts +++ b/src/generators/nodes/Component.ts @@ -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'});` diff --git a/src/generators/nodes/EachBlock.ts b/src/generators/nodes/EachBlock.ts index 809eaf9397..688c964fac 100644 --- a/src/generators/nodes/EachBlock.ts +++ b/src/generators/nodes/EachBlock.ts @@ -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) { diff --git a/src/generators/nodes/Element.ts b/src/generators/nodes/Element.ts index 9a11cbde25..fdd09d676f 100644 --- a/src/generators/nodes/Element.ts +++ b/src/generators/nodes/Element.ts @@ -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. , 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; diff --git a/src/generators/nodes/Document.ts b/src/generators/nodes/Head.ts similarity index 53% rename from src/generators/nodes/Document.ts rename to src/generators/nodes/Head.ts index c481b15299..be72087aca 100644 --- a/src/generators/nodes/Document.ts +++ b/src/generators/nodes/Head.ts @@ -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); }); } } diff --git a/src/generators/nodes/IfBlock.ts b/src/generators/nodes/IfBlock.ts index b3978e5356..3cec2245d9 100644 --- a/src/generators/nodes/IfBlock.ts +++ b/src/generators/nodes/IfBlock.ts @@ -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 ); } diff --git a/src/generators/nodes/MustacheTag.ts b/src/generators/nodes/MustacheTag.ts index 35baccdc2b..21b3b30df2 100644 --- a/src/generators/nodes/MustacheTag.ts +++ b/src/generators/nodes/MustacheTag.ts @@ -22,7 +22,7 @@ export default class MustacheTag extends Tag { block.addElement( this.var, `@createText(${init})`, - `@claimText(${parentNodes}, ${init})`, + parentNodes && `@claimText(${parentNodes}, ${init})`, parentNode ); } diff --git a/src/generators/nodes/RawMustacheTag.ts b/src/generators/nodes/RawMustacheTag.ts index 8b81453033..6e9e5e664e 100644 --- a/src/generators/nodes/RawMustacheTag.ts +++ b/src/generators/nodes/RawMustacheTag.ts @@ -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 ); } diff --git a/src/generators/nodes/Text.ts b/src/generators/nodes/Text.ts index 28d56f14c4..c00b00f4fe 100644 --- a/src/generators/nodes/Text.ts +++ b/src/generators/nodes/Text.ts @@ -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 ); } diff --git a/src/generators/nodes/index.ts b/src/generators/nodes/index.ts index b41c676967..274735043d 100644 --- a/src/generators/nodes/index.ts +++ b/src/generators/nodes/index.ts @@ -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 = { CatchBlock, Comment, Component, - Document, EachBlock, Element, ElseBlock, EventHandler, Fragment, + Head, IfBlock, MustacheTag, PendingBlock, diff --git a/src/generators/nodes/shared/Node.ts b/src/generators/nodes/shared/Node.ts index 0658524bf5..3461ddd6f2 100644 --- a/src/generators/nodes/shared/Node.ts +++ b/src/generators/nodes/shared/Node.ts @@ -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 ); } diff --git a/src/generators/server-side-rendering/visitors/Document.ts b/src/generators/server-side-rendering/visitors/Head.ts similarity index 51% rename from src/generators/server-side-rendering/visitors/Document.ts rename to src/generators/server-side-rendering/visitors/Head.ts index de2c35fbf7..f43f989ccc 100644 --- a/src/generators/server-side-rendering/visitors/Document.ts +++ b/src/generators/server-side-rendering/visitors/Head.ts @@ -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) + '`, "")}'); + // } } \ No newline at end of file diff --git a/src/generators/server-side-rendering/visitors/index.ts b/src/generators/server-side-rendering/visitors/index.ts index 59a63b9dc8..c6b43b5d0f 100644 --- a/src/generators/server-side-rendering/visitors/index.ts +++ b/src/generators/server-side-rendering/visitors/index.ts @@ -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, diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index dc0fd37783..1e39a51059 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -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; } } diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index fc8c47cd4a..bf94b63c52 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -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) { diff --git a/src/validate/html/validateDocument.ts b/src/validate/html/validateDocument.ts deleted file mode 100644 index 10cc38b609..0000000000 --- a/src/validate/html/validateDocument.ts +++ /dev/null @@ -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, 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 - ); - } - } - }); -} diff --git a/src/validate/html/validateHead.ts b/src/validate/html/validateHead.ts new file mode 100644 index 0000000000..7883e452f9 --- /dev/null +++ b/src/validate/html/validateHead.ts @@ -0,0 +1,8 @@ +import { Validator } from '../index'; +import { Node } from '../../interfaces'; + +export default function validateHead(validator: Validator, node: Node, refs: Map, refCallees: Node[]) { + if (node.attributes.length) { + validator.error(`<:Head> should not have any attributes or directives`, node.start); + } +} diff --git a/test/runtime/samples/document-title-dynamic/main.html b/test/runtime/samples/document-title-dynamic/main.html deleted file mode 100644 index 88045474df..0000000000 --- a/test/runtime/samples/document-title-dynamic/main.html +++ /dev/null @@ -1 +0,0 @@ -<:Document title='a {{adjective}} title'/> \ No newline at end of file diff --git a/test/runtime/samples/document-title-static/main.html b/test/runtime/samples/document-title-static/main.html deleted file mode 100644 index d1022b95f7..0000000000 --- a/test/runtime/samples/document-title-static/main.html +++ /dev/null @@ -1 +0,0 @@ -<:Document title='changed'/> \ No newline at end of file diff --git a/test/runtime/samples/document-title-dynamic/_config.js b/test/runtime/samples/head-title-dynamic/_config.js similarity index 94% rename from test/runtime/samples/document-title-dynamic/_config.js rename to test/runtime/samples/head-title-dynamic/_config.js index 5155554a0e..8d8ed8b8c8 100644 --- a/test/runtime/samples/document-title-dynamic/_config.js +++ b/test/runtime/samples/head-title-dynamic/_config.js @@ -1,4 +1,6 @@ export default { + solo: true, + data: { adjective: 'custom' }, diff --git a/test/runtime/samples/head-title-dynamic/main.html b/test/runtime/samples/head-title-dynamic/main.html new file mode 100644 index 0000000000..ae2b8c1caa --- /dev/null +++ b/test/runtime/samples/head-title-dynamic/main.html @@ -0,0 +1,4 @@ +<:Head> + a {{adjective}} title + + \ No newline at end of file diff --git a/test/runtime/samples/document-title-static/_config.js b/test/runtime/samples/head-title-static/_config.js similarity index 100% rename from test/runtime/samples/document-title-static/_config.js rename to test/runtime/samples/head-title-static/_config.js diff --git a/test/runtime/samples/head-title-static/main.html b/test/runtime/samples/head-title-static/main.html new file mode 100644 index 0000000000..9ab533140f --- /dev/null +++ b/test/runtime/samples/head-title-static/main.html @@ -0,0 +1,4 @@ +<:Head> + changed + + \ No newline at end of file diff --git a/test/server-side-rendering/samples/document-title/main.html b/test/server-side-rendering/samples/document-title/main.html deleted file mode 100644 index e049742dbc..0000000000 --- a/test/server-side-rendering/samples/document-title/main.html +++ /dev/null @@ -1 +0,0 @@ -<:Document title='a {{adjective}} title'/> diff --git a/test/server-side-rendering/samples/document-title/_expected.html b/test/server-side-rendering/samples/head-title/_expected.html similarity index 100% rename from test/server-side-rendering/samples/document-title/_expected.html rename to test/server-side-rendering/samples/head-title/_expected.html diff --git a/test/server-side-rendering/samples/document-title/data.json b/test/server-side-rendering/samples/head-title/data.json similarity index 100% rename from test/server-side-rendering/samples/document-title/data.json rename to test/server-side-rendering/samples/head-title/data.json diff --git a/test/server-side-rendering/samples/head-title/main.html b/test/server-side-rendering/samples/head-title/main.html new file mode 100644 index 0000000000..18c0bbbabd --- /dev/null +++ b/test/server-side-rendering/samples/head-title/main.html @@ -0,0 +1,3 @@ +<:Head> + a {{adjective}} title + diff --git a/test/server-side-rendering/samples/document-title/title.txt b/test/server-side-rendering/samples/head-title/title.txt similarity index 100% rename from test/server-side-rendering/samples/document-title/title.txt rename to test/server-side-rendering/samples/head-title/title.txt