diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 363f5118ec..0971b7552a 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -161,9 +161,9 @@ export default class Generator { this.computations = []; this.templateProperties = {}; - this.name = this.alias(name); this.walkJs(dom); + this.name = this.alias(name); if (options.customElement === true) { this.customElement = { @@ -731,6 +731,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 === ':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 4d4413d8c8..036b4e9014 100644 --- a/src/generators/dom/Block.ts +++ b/src/generators/dom/Block.ts @@ -132,10 +132,11 @@ 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) { this.builders.mount.addLine(`@appendNode(${name}, ${parentNode});`); + if (parentNode === 'document.head') this.builders.unmount.addLine(`@detachNode(${name});`); } else { this.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`); this.builders.unmount.addLine(`@detachNode(${name});`); @@ -203,7 +204,7 @@ export default class Block { this.builders.hydrate.addLine(`this.first = ${this.first};`); } - if (this.builders.create.isEmpty()) { + if (this.builders.create.isEmpty() && this.builders.hydrate.isEmpty()) { properties.addBlock(`c: @noop,`); } else { properties.addBlock(deindent` @@ -215,7 +216,7 @@ export default class Block { } if (this.generator.hydratable) { - if (this.builders.claim.isEmpty()) { + if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) { properties.addBlock(`l: @noop,`); } else { properties.addBlock(deindent` diff --git a/src/generators/nodes/Attribute.ts b/src/generators/nodes/Attribute.ts index 78441eea8e..639870536c 100644 --- a/src/generators/nodes/Attribute.ts +++ b/src/generators/nodes/Attribute.ts @@ -77,10 +77,7 @@ export default class Attribute { ? '@setXlinkAttribute' : '@setAttribute'; - const isDynamic = - (this.value !== true && this.value.length > 1) || - (this.value.length === 1 && this.value[0].type !== 'Text'); - + const isDynamic = this.isDynamic(); const isLegacyInputType = this.generator.legacy && name === 'type' && this.parent.name === 'input'; const isDataSet = /^data-/.test(name) && !this.generator.legacy && !node.namespace; @@ -310,6 +307,12 @@ export default class Attribute { ); }); } + + isDynamic() { + if (this.value === true || this.value.length === 0) return false; + if (this.value.length > 1) return true; + return this.value[0].type !== 'Text'; + } } // source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes 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 8115ac808b..35100e16a1 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 33c21d63f0..e4c47cb52f 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,25 +175,32 @@ 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) { block.builders.mount.addLine( `@appendNode(${name}, ${initialMountNode});` ); + + if (initialMountNode === 'document.head') { + block.builders.unmount.addLine(`@detachNode(${name});`); + } } else { block.builders.mount.addLine(`@insertNode(${name}, #target, anchor);`); @@ -394,9 +401,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/Head.ts b/src/generators/nodes/Head.ts new file mode 100644 index 0000000000..be72087aca --- /dev/null +++ b/src/generators/nodes/Head.ts @@ -0,0 +1,32 @@ +import deindent from '../../utils/deindent'; +import { stringify } from '../../utils/stringify'; +import Node from './shared/Node'; +import Block from '../dom/Block'; +import Attribute from './Attribute'; + +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, + parentNodes: string + ) { + const { generator } = this; + + this.var = 'document.head'; + + 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 0c8b174698..274735043d 100644 --- a/src/generators/nodes/index.ts +++ b/src/generators/nodes/index.ts @@ -10,6 +10,7 @@ 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'; @@ -33,6 +34,7 @@ const nodes: Record = { 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/index.ts b/src/generators/server-side-rendering/index.ts index 22242999ed..0ccee8cdd5 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -91,6 +91,7 @@ export default function ssr( initialState.push('state'); + // TODO concatenate CSS maps const result = deindent` ${generator.javascript} @@ -103,6 +104,30 @@ export default function ssr( }; ${name}.render = function(state, options = {}) { + var components = new Set(); + + function addComponent(component) { + components.add(component); + } + + var result = { head: '', addComponent }; + var html = ${name}._render(result, state, options); + + var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\\n'); + + return { + html, + head: result.head, + css: { code: cssCode, map: null }, + toString() { + return result.html; + } + }; + } + + ${name}._render = function(__result, state, options) { + __result.addComponent(${name}); + state = Object.assign(${initialState.join(', ')}); ${computations.map( @@ -125,15 +150,26 @@ export default function ssr( return \`${generator.renderCode}\`; }; + ${name}.css = { + code: ${css ? stringify(css) : `''`}, + map: ${cssMap ? stringify(cssMap.toString()) : 'null'} + }; + + var warned = false; ${name}.renderCss = function() { + if (!warned) { + console.error('Component.renderCss(...) is deprecated and will be removed in v2 — use Component.render(...).css instead'); + warned = true; + } + var components = []; ${generator.stylesheet.hasStyles && deindent` components.push({ filename: ${name}.filename, - css: ${stringify(css)}, - map: ${stringify(cssMap.toString())} + css: ${name}.css && ${name}.css.code, + map: ${name}.css && ${name}.css.map }); `} diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index d6f44abb23..4843010fcb 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -84,7 +84,7 @@ export default function visitComponent( block.addBinding(binding, expression); }); - let open = `\${${expression}.render({${props}}`; + let open = `\${${expression}._render(__result, {${props}}`; const options = []; if (generator.options.store) { diff --git a/src/generators/server-side-rendering/visitors/Element.ts b/src/generators/server-side-rendering/visitors/Element.ts index 731f73b5fa..a1645d0860 100644 --- a/src/generators/server-side-rendering/visitors/Element.ts +++ b/src/generators/server-side-rendering/visitors/Element.ts @@ -5,22 +5,8 @@ import visit from '../visit'; import { SsrGenerator } from '../index'; import Element from '../../nodes/Element'; import Block from '../Block'; -import { escape } from '../../../utils/stringify'; import { Node } from '../../../interfaces'; - -function stringifyAttributeValue(block: Block, chunks: Node[]) { - return chunks - .map((chunk: Node) => { - if (chunk.type === 'Text') { - return escape(chunk.data).replace(/"/g, '"'); - } - - block.contextualise(chunk.expression); - const { snippet } = chunk.metadata; - return '${' + snippet + '}'; - }) - .join(''); -} +import stringifyAttributeValue from './shared/stringifyAttributeValue'; export default function visitElement( generator: SsrGenerator, diff --git a/src/generators/server-side-rendering/visitors/Head.ts b/src/generators/server-side-rendering/visitors/Head.ts new file mode 100644 index 0000000000..e7b4681f90 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/Head.ts @@ -0,0 +1,19 @@ +import { SsrGenerator } from '../index'; +import Block from '../Block'; +import { Node } from '../../../interfaces'; +import stringifyAttributeValue from './shared/stringifyAttributeValue'; +import visit from '../visit'; + +export default function visitDocument( + generator: SsrGenerator, + block: Block, + node: Node +) { + generator.append('${(__result.head += `'); + + node.children.forEach((child: Node) => { + visit(generator, block, child); + }); + + generator.append('`, "")}'); +} \ 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 73d51043b5..c6b43b5d0f 100644 --- a/src/generators/server-side-rendering/visitors/index.ts +++ b/src/generators/server-side-rendering/visitors/index.ts @@ -3,6 +3,7 @@ import Comment from './Comment'; import Component from './Component'; import EachBlock from './EachBlock'; import Element from './Element'; +import Head from './Head'; import IfBlock from './IfBlock'; import MustacheTag from './MustacheTag'; import RawMustacheTag from './RawMustacheTag'; @@ -16,6 +17,7 @@ export default { Component, EachBlock, Element, + Head, IfBlock, MustacheTag, RawMustacheTag, diff --git a/src/generators/server-side-rendering/visitors/shared/stringifyAttributeValue.ts b/src/generators/server-side-rendering/visitors/shared/stringifyAttributeValue.ts new file mode 100644 index 0000000000..22eb0eff76 --- /dev/null +++ b/src/generators/server-side-rendering/visitors/shared/stringifyAttributeValue.ts @@ -0,0 +1,17 @@ +import Block from '../../Block'; +import { escape } from '../../../../utils/stringify'; +import { Node } from '../../../../interfaces'; + +export default function stringifyAttributeValue(block: Block, chunks: Node[]) { + return chunks + .map((chunk: Node) => { + if (chunk.type === 'Text') { + return escape(chunk.data).replace(/"/g, '"'); + } + + block.contextualise(chunk.expression); + const { snippet } = chunk.metadata; + return '${' + snippet + '}'; + }) + .join(''); +} \ No newline at end of file diff --git a/src/parse/state/tag.ts b/src/parse/state/tag.ts index 90d9f5cd2c..1e39a51059 100644 --- a/src/parse/state/tag.ts +++ b/src/parse/state/tag.ts @@ -17,9 +17,10 @@ const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const SELF = ':Self'; const COMPONENT = ':Component'; -const metaTags = { - ':Window': true -}; +const metaTags = new Set([ + ':Window', + ':Head' +]); const specials = new Map([ [ @@ -86,22 +87,25 @@ export default function tag(parser: Parser) { const name = readTagName(parser); - if (name in metaTags) { - if (name in parser.metaTags) { - if (isClosingTag && parser.current().children.length) { + if (metaTags.has(name)) { + 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; } } @@ -252,7 +256,7 @@ function readTagName(parser: Parser) { const name = parser.readUntil(/(\s|\/|>)/); - if (name in metaTags) return name; + if (metaTags.has(name)) return name; if (!validTagName.test(name)) { parser.error(`Expected valid tag name`, start); diff --git a/src/shared/index.js b/src/shared/index.js index fc87e66eb2..e2efb5b4c3 100644 --- a/src/shared/index.js +++ b/src/shared/index.js @@ -185,7 +185,7 @@ export function _mount(target, anchor) { } export function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } export function isPromise(value) { diff --git a/src/validate/html/index.ts b/src/validate/html/index.ts index 9a3b573505..bf94b63c52 100644 --- a/src/validate/html/index.ts +++ b/src/validate/html/index.ts @@ -1,12 +1,16 @@ import validateElement from './validateElement'; import validateWindow from './validateWindow'; +import validateHead from './validateHead'; import a11y from './a11y'; import fuzzymatch from '../utils/fuzzymatch' import flattenReference from '../../utils/flattenReference'; import { Validator } from '../index'; import { Node } from '../../interfaces'; -const meta = new Map([[':Window', validateWindow]]); +const meta = new Map([ + [':Window', validateWindow], + [':Head', validateHead] +]); export default function validateHtml(validator: Validator, html: Node) { const refs = new Map(); 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/css/index.js b/test/css/index.js index 3310be8267..1e91e770c8 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -131,7 +131,7 @@ describe('css', () => { assert.equal( normalizeHtml( window, - component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz') + component.render(config.data).html.replace(/svelte-\d+/g, 'svelte-xyz') ), normalizeHtml(window, expected.html) ); diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index 6c2eadf148..c1aaf7b0f6 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -175,7 +175,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/component-static/expected-bundle.js b/test/js/samples/component-static/expected-bundle.js index 7045cd9ff8..5868dd94e5 100644 --- a/test/js/samples/component-static/expected-bundle.js +++ b/test/js/samples/component-static/expected-bundle.js @@ -151,7 +151,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 0a2f23d100..cc822269ce 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -151,7 +151,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/css-media-query/expected-bundle.js b/test/js/samples/css-media-query/expected-bundle.js index 57181b1064..123e8b07fa 100644 --- a/test/js/samples/css-media-query/expected-bundle.js +++ b/test/js/samples/css-media-query/expected-bundle.js @@ -171,7 +171,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js index ce3509fa69..d1a113a6ab 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js @@ -163,7 +163,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/do-use-dataset/expected-bundle.js b/test/js/samples/do-use-dataset/expected-bundle.js index e82c4bf620..9a48d8db96 100644 --- a/test/js/samples/do-use-dataset/expected-bundle.js +++ b/test/js/samples/do-use-dataset/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js b/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js index 9a982ad043..0602ad1ed4 100644 --- a/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js +++ b/test/js/samples/dont-use-dataset-in-legacy/expected-bundle.js @@ -171,7 +171,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js b/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js index 253506e4c9..b595d14633 100644 --- a/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js +++ b/test/js/samples/dont-use-dataset-in-svg/expected-bundle.js @@ -171,7 +171,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/each-block-changed-check/expected-bundle.js b/test/js/samples/each-block-changed-check/expected-bundle.js index 79439f1af0..685e073f38 100644 --- a/test/js/samples/each-block-changed-check/expected-bundle.js +++ b/test/js/samples/each-block-changed-check/expected-bundle.js @@ -183,7 +183,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index c061742725..6f51e1b8d2 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -163,7 +163,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/if-block-no-update/expected-bundle.js b/test/js/samples/if-block-no-update/expected-bundle.js index 673464486c..a4cf23cfed 100644 --- a/test/js/samples/if-block-no-update/expected-bundle.js +++ b/test/js/samples/if-block-no-update/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/if-block-simple/expected-bundle.js b/test/js/samples/if-block-simple/expected-bundle.js index fc54ee85a7..639f037528 100644 --- a/test/js/samples/if-block-simple/expected-bundle.js +++ b/test/js/samples/if-block-simple/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js index fa6efd1cec..8fa1f7bfd4 100644 --- a/test/js/samples/inline-style-optimized-multiple/expected-bundle.js +++ b/test/js/samples/inline-style-optimized-multiple/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/inline-style-optimized-url/expected-bundle.js b/test/js/samples/inline-style-optimized-url/expected-bundle.js index 2efac93cd5..fb61583a46 100644 --- a/test/js/samples/inline-style-optimized-url/expected-bundle.js +++ b/test/js/samples/inline-style-optimized-url/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/inline-style-optimized/expected-bundle.js b/test/js/samples/inline-style-optimized/expected-bundle.js index d69bc32b94..1cf474d1b0 100644 --- a/test/js/samples/inline-style-optimized/expected-bundle.js +++ b/test/js/samples/inline-style-optimized/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/inline-style-unoptimized/expected-bundle.js b/test/js/samples/inline-style-unoptimized/expected-bundle.js index 1765fc1728..c4152305da 100644 --- a/test/js/samples/inline-style-unoptimized/expected-bundle.js +++ b/test/js/samples/inline-style-unoptimized/expected-bundle.js @@ -167,7 +167,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/input-without-blowback-guard/expected-bundle.js b/test/js/samples/input-without-blowback-guard/expected-bundle.js index a4b7837682..bd62e84ac5 100644 --- a/test/js/samples/input-without-blowback-guard/expected-bundle.js +++ b/test/js/samples/input-without-blowback-guard/expected-bundle.js @@ -171,7 +171,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/legacy-input-type/expected-bundle.js b/test/js/samples/legacy-input-type/expected-bundle.js index b7cfc1320a..8eeda86d6f 100644 --- a/test/js/samples/legacy-input-type/expected-bundle.js +++ b/test/js/samples/legacy-input-type/expected-bundle.js @@ -169,7 +169,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/legacy-quote-class/expected-bundle.js b/test/js/samples/legacy-quote-class/expected-bundle.js index d71ad3e74b..9e454ba444 100644 --- a/test/js/samples/legacy-quote-class/expected-bundle.js +++ b/test/js/samples/legacy-quote-class/expected-bundle.js @@ -186,7 +186,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/media-bindings/expected-bundle.js b/test/js/samples/media-bindings/expected-bundle.js index 9ab9d72d9d..80729e9179 100644 --- a/test/js/samples/media-bindings/expected-bundle.js +++ b/test/js/samples/media-bindings/expected-bundle.js @@ -179,7 +179,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index a7dd66c128..3c6ff9f91f 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -165,7 +165,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 5e3648119d..4357b4a202 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -151,7 +151,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 1e35598d5a..9ff9831ae4 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -151,7 +151,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js b/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js index 514e40d54a..42f630e0e9 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js @@ -9,12 +9,47 @@ SvelteComponent.data = function() { }; SvelteComponent.render = function(state, options = {}) { + var components = new Set(); + + function addComponent(component) { + components.add(component); + } + + var result = { head: '', addComponent }; + var html = SvelteComponent._render(result, state, options); + + var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\n'); + + return { + html, + head: result.head, + css: { code: cssCode, map: null }, + toString() { + return result.html; + } + }; +}; + +SvelteComponent._render = function(__result, state, options) { + __result.addComponent(SvelteComponent); + state = Object.assign({}, state); return ``; }; +SvelteComponent.css = { + code: '', + map: null +}; + +var warned = false; SvelteComponent.renderCss = function() { + if (!warned) { + console.error('Component.renderCss(...) is deprecated and will be removed in v2 — use Component.render(...).css instead'); + warned = true; + } + var components = []; return { diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js index 735d359a1f..7168662072 100644 --- a/test/js/samples/ssr-no-oncreate-etc/expected.js +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -11,12 +11,47 @@ SvelteComponent.data = function() { }; SvelteComponent.render = function(state, options = {}) { + var components = new Set(); + + function addComponent(component) { + components.add(component); + } + + var result = { head: '', addComponent }; + var html = SvelteComponent._render(result, state, options); + + var cssCode = Array.from(components).map(c => c.css && c.css.code).filter(Boolean).join('\n'); + + return { + html, + head: result.head, + css: { code: cssCode, map: null }, + toString() { + return result.html; + } + }; +} + +SvelteComponent._render = function(__result, state, options) { + __result.addComponent(SvelteComponent); + state = Object.assign({}, state); return ``; }; +SvelteComponent.css = { + code: '', + map: null +}; + +var warned = false; SvelteComponent.renderCss = function() { + if (!warned) { + console.error('Component.renderCss(...) is deprecated and will be removed in v2 — use Component.render(...).css instead'); + warned = true; + } + var components = []; return { diff --git a/test/js/samples/use-elements-as-anchors/expected-bundle.js b/test/js/samples/use-elements-as-anchors/expected-bundle.js index 47d5a50224..c97c52ab12 100644 --- a/test/js/samples/use-elements-as-anchors/expected-bundle.js +++ b/test/js/samples/use-elements-as-anchors/expected-bundle.js @@ -175,7 +175,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/js/samples/window-binding-scroll/expected-bundle.js b/test/js/samples/window-binding-scroll/expected-bundle.js index aedda719ed..d44d545cbf 100644 --- a/test/js/samples/window-binding-scroll/expected-bundle.js +++ b/test/js/samples/window-binding-scroll/expected-bundle.js @@ -171,7 +171,7 @@ function _mount(target, anchor) { } function _unmount() { - this._fragment.u(); + if (this._fragment) this._fragment.u(); } var proto = { diff --git a/test/runtime/index.js b/test/runtime/index.js index 1ead0a11cb..fa3482337b 100644 --- a/test/runtime/index.js +++ b/test/runtime/index.js @@ -52,7 +52,7 @@ describe("runtime", () => { throw new Error("Forgot to remove `solo: true` from test"); } - (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers)`, () => { + (config.skip ? it.skip : config.solo ? it.only : it)(`${dir} (${shared ? 'shared' : 'inline'} helpers${hydrate ? ' , hydration' : ''})`, () => { if (failed.has(dir)) { // this makes debugging easier, by only printing compiled output once throw new Error('skipping test, already failed'); @@ -183,7 +183,9 @@ describe("runtime", () => { } if (config.test) { - return config.test(assert, component, target, window, raf); + return Promise.resolve(config.test(assert, component, target, window, raf)).then(() => { + component.destroy(); + }); } else { component.destroy(); assert.equal(target.innerHTML, ""); diff --git a/test/runtime/samples/head-title-dynamic/_config.js b/test/runtime/samples/head-title-dynamic/_config.js new file mode 100644 index 0000000000..5155554a0e --- /dev/null +++ b/test/runtime/samples/head-title-dynamic/_config.js @@ -0,0 +1,12 @@ +export default { + data: { + adjective: 'custom' + }, + + test(assert, component, target, window) { + assert.equal(window.document.title, 'a custom title'); + + component.set({ adjective: 'different' }); + assert.equal(window.document.title, 'a different title'); + } +}; \ No newline at end of file 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/head-title-static/_config.js b/test/runtime/samples/head-title-static/_config.js new file mode 100644 index 0000000000..3757effa54 --- /dev/null +++ b/test/runtime/samples/head-title-static/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, component, target, window) { + assert.equal(window.document.title, 'changed'); + } +}; \ No newline at end of file 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/index.js b/test/server-side-rendering/index.js index b6bd109c0e..385f781b6e 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -59,18 +59,25 @@ describe("ssr", () => { const data = tryToLoadJson(`${dir}/data.json`); - const html = component.render(data); - const css = component.renderCss().css; + const { html, css, head } = component.render(data); fs.writeFileSync(`${dir}/_actual.html`, html); - if (css) fs.writeFileSync(`${dir}/_actual.css`, css); + if (css.code) fs.writeFileSync(`${dir}/_actual.css`, css.code); assert.htmlEqual(html, expectedHtml); assert.equal( - css.replace(/^\s+/gm, ""), + css.code.replace(/^\s+/gm, ""), expectedCss.replace(/^\s+/gm, "") ); + if (fs.existsSync(`${dir}/_expected-head.html`)) { + fs.writeFileSync(`${dir}/_actual-head.html`, head); + assert.htmlEqual( + head, + fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8') + ); + } + if (show) showOutput(dir, { generate: 'ssr' }); } catch (err) { showOutput(dir, { generate: 'ssr' }); @@ -105,7 +112,7 @@ describe("ssr", () => { try { const component = require(`../runtime/samples/${dir}/main.html`); - const html = component.render(config.data, { + const { html } = component.render(config.data, { store: config.store }); diff --git a/test/server-side-rendering/samples/head-title/_actual-head.html b/test/server-side-rendering/samples/head-title/_actual-head.html new file mode 100644 index 0000000000..b0a06af0cc --- /dev/null +++ b/test/server-side-rendering/samples/head-title/_actual-head.html @@ -0,0 +1,2 @@ + + a custom title diff --git a/test/server-side-rendering/samples/head-title/_expected-head.html b/test/server-side-rendering/samples/head-title/_expected-head.html new file mode 100644 index 0000000000..7d696352f9 --- /dev/null +++ b/test/server-side-rendering/samples/head-title/_expected-head.html @@ -0,0 +1 @@ +a custom title \ No newline at end of file diff --git a/test/server-side-rendering/samples/head-title/_expected.html b/test/server-side-rendering/samples/head-title/_expected.html new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/server-side-rendering/samples/head-title/data.json b/test/server-side-rendering/samples/head-title/data.json new file mode 100644 index 0000000000..eab238e816 --- /dev/null +++ b/test/server-side-rendering/samples/head-title/data.json @@ -0,0 +1,3 @@ +{ + "adjective": "custom" +} \ No newline at end of file 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/styles-nested/One.html b/test/server-side-rendering/samples/styles-nested/One.html index 1b2c21edc8..742cc01f79 100644 --- a/test/server-side-rendering/samples/styles-nested/One.html +++ b/test/server-side-rendering/samples/styles-nested/One.html @@ -1,5 +1,7 @@
green: {{message}}
- + + +