From be84c96eba1a3ebeb4f595fc6b264db1de2d91a3 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 15 Sep 2018 18:23:50 -0400 Subject: [PATCH 01/37] put css/ under compile/ --- src/compile/Component.ts | 2 +- src/{ => compile}/css/Selector.ts | 0 src/{ => compile}/css/Stylesheet.ts | 0 src/{ => compile}/css/gatherPossibleValues.ts | 0 src/compile/dom/index.ts | 4 +--- 5 files changed, 2 insertions(+), 4 deletions(-) rename src/{ => compile}/css/Selector.ts (100%) rename src/{ => compile}/css/Stylesheet.ts (100%) rename src/{ => compile}/css/gatherPossibleValues.ts (100%) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 05bab19cc533..cd3d81082d78 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -13,7 +13,7 @@ import nodeToString from '../utils/nodeToString'; import wrapModule from './wrapModule'; import annotateWithScopes from '../utils/annotateWithScopes'; import getName from '../utils/getName'; -import Stylesheet from '../css/Stylesheet'; +import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import shared from './shared'; diff --git a/src/css/Selector.ts b/src/compile/css/Selector.ts similarity index 100% rename from src/css/Selector.ts rename to src/compile/css/Selector.ts diff --git a/src/css/Stylesheet.ts b/src/compile/css/Stylesheet.ts similarity index 100% rename from src/css/Stylesheet.ts rename to src/compile/css/Stylesheet.ts diff --git a/src/css/gatherPossibleValues.ts b/src/compile/css/gatherPossibleValues.ts similarity index 100% rename from src/css/gatherPossibleValues.ts rename to src/compile/css/gatherPossibleValues.ts diff --git a/src/compile/dom/index.ts b/src/compile/dom/index.ts index 5780aca1d8ce..11b30e83132f 100644 --- a/src/compile/dom/index.ts +++ b/src/compile/dom/index.ts @@ -3,10 +3,8 @@ import { stringify, escape } from '../../utils/stringify'; import CodeBuilder from '../../utils/CodeBuilder'; import globalWhitelist from '../../utils/globalWhitelist'; import Component from '../Component'; -import Stylesheet from '../../css/Stylesheet'; -import Stats from '../../Stats'; import Block from './Block'; -import { Ast, CompileOptions } from '../../interfaces'; +import { CompileOptions } from '../../interfaces'; export class DomTarget { blocks: (Block|string)[]; From 7ef5f4f54fff5f824721026fbc601f33ae37339c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Sat, 15 Sep 2018 21:47:13 -0400 Subject: [PATCH 02/37] move render logic to render-ssr --- src/compile/Component.ts | 8 +- src/compile/css/Stylesheet.ts | 12 +- src/compile/index.ts | 12 +- src/compile/nodes/Attribute.ts | 2 +- src/compile/nodes/AwaitBlock.ts | 2 +- src/compile/nodes/Binding.ts | 2 +- src/compile/nodes/CatchBlock.ts | 2 +- src/compile/nodes/DebugTag.ts | 2 +- src/compile/nodes/EachBlock.ts | 2 +- src/compile/nodes/Element.ts | 2 +- src/compile/nodes/ElseBlock.ts | 2 +- src/compile/nodes/Fragment.ts | 2 +- src/compile/nodes/Head.ts | 2 +- src/compile/nodes/IfBlock.ts | 2 +- src/compile/nodes/InlineComponent.ts | 2 +- src/compile/nodes/MustacheTag.ts | 2 +- src/compile/nodes/PendingBlock.ts | 2 +- src/compile/nodes/RawMustacheTag.ts | 2 +- src/compile/nodes/Slot.ts | 2 +- src/compile/nodes/Text.ts | 2 +- src/compile/nodes/ThenBlock.ts | 2 +- src/compile/nodes/Title.ts | 2 +- src/compile/nodes/Window.ts | 2 +- src/compile/nodes/shared/Node.ts | 2 +- src/compile/nodes/shared/Tag.ts | 2 +- src/compile/{dom => render-dom}/Block.ts | 0 src/compile/{dom => render-dom}/index.ts | 0 src/compile/render-ssr/Renderer.ts | 70 ++++++++++ src/compile/render-ssr/handlers/AwaitBlock.ts | 16 +++ src/compile/render-ssr/handlers/Comment.ts | 8 ++ src/compile/render-ssr/handlers/DebugTag.ts | 19 +++ src/compile/render-ssr/handlers/EachBlock.ts | 25 ++++ src/compile/render-ssr/handlers/Element.ts | 132 ++++++++++++++++++ src/compile/render-ssr/handlers/Head.ts | 7 + src/compile/render-ssr/handlers/HtmlTag.ts | 3 + src/compile/render-ssr/handlers/IfBlock.ts | 15 ++ .../render-ssr/handlers/InlineComponent.ts | 130 +++++++++++++++++ src/compile/render-ssr/handlers/Slot.ts | 14 ++ src/compile/render-ssr/handlers/Tag.ts | 9 ++ src/compile/render-ssr/handlers/Text.ts | 14 ++ src/compile/render-ssr/handlers/Title.ts | 7 + src/compile/{ssr => render-ssr}/index.ts | 40 ++---- src/interfaces.ts | 7 - src/shared/ssr.js | 2 +- 44 files changed, 521 insertions(+), 73 deletions(-) rename src/compile/{dom => render-dom}/Block.ts (100%) rename src/compile/{dom => render-dom}/index.ts (100%) create mode 100644 src/compile/render-ssr/Renderer.ts create mode 100644 src/compile/render-ssr/handlers/AwaitBlock.ts create mode 100644 src/compile/render-ssr/handlers/Comment.ts create mode 100644 src/compile/render-ssr/handlers/DebugTag.ts create mode 100644 src/compile/render-ssr/handlers/EachBlock.ts create mode 100644 src/compile/render-ssr/handlers/Element.ts create mode 100644 src/compile/render-ssr/handlers/Head.ts create mode 100644 src/compile/render-ssr/handlers/HtmlTag.ts create mode 100644 src/compile/render-ssr/handlers/IfBlock.ts create mode 100644 src/compile/render-ssr/handlers/InlineComponent.ts create mode 100644 src/compile/render-ssr/handlers/Slot.ts create mode 100644 src/compile/render-ssr/handlers/Tag.ts create mode 100644 src/compile/render-ssr/handlers/Text.ts create mode 100644 src/compile/render-ssr/handlers/Title.ts rename src/compile/{ssr => render-ssr}/index.ts (82%) diff --git a/src/compile/Component.ts b/src/compile/Component.ts index cd3d81082d78..560881730224 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -19,7 +19,7 @@ import Fragment from './nodes/Fragment'; import shared from './shared'; import { DomTarget } from './dom'; import { SsrTarget } from './ssr'; -import { Node, GenerateOptions, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces'; +import { Node, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces'; import error from '../utils/error'; import getCodeFrame from '../utils/getCodeFrame'; import checkForComputedKeys from './validate/js/utils/checkForComputedKeys'; @@ -319,7 +319,11 @@ export default class Component { return this.aliases.get(name); } - generate(result: string, options: CompileOptions, { banner = '', name, format }: GenerateOptions ) { + generate(result: string, options: CompileOptions, { + banner = '', + name, + format + }) { const pattern = /\[✂(\d+)-(\d+)$/; const helpers = new Set(); diff --git a/src/compile/css/Stylesheet.ts b/src/compile/css/Stylesheet.ts index 3edb6fd856e5..cf7fcf061edf 100644 --- a/src/compile/css/Stylesheet.ts +++ b/src/compile/css/Stylesheet.ts @@ -2,12 +2,12 @@ import MagicString from 'magic-string'; import { walk } from 'estree-walker'; import { getLocator } from 'locate-character'; import Selector from './Selector'; -import getCodeFrame from '../utils/getCodeFrame'; -import hash from '../utils/hash'; -import removeCSSPrefix from '../utils/removeCSSPrefix'; -import Element from '../compile/nodes/Element'; -import { Node, Ast, Warning } from '../interfaces'; -import Component from '../compile/Component'; +import getCodeFrame from '../../utils/getCodeFrame'; +import hash from '../../utils/hash'; +import removeCSSPrefix from '../../utils/removeCSSPrefix'; +import Element from '../nodes/Element'; +import { Node, Ast, Warning } from '../../interfaces'; +import Component from '../Component'; const isKeyframesNode = (node: Node) => removeCSSPrefix(node.name) === 'keyframes' diff --git a/src/compile/index.ts b/src/compile/index.ts index ccf917772b92..3a513ee73189 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -1,8 +1,8 @@ import { assign } from '../shared'; import Stats from '../Stats'; import parse from '../parse/index'; -import generate, { DomTarget } from './dom/index'; -import generateSSR, { SsrTarget } from './ssr/index'; +import renderDOM, { DomTarget } from './render-dom/index'; +import renderSSR from './render-ssr/index'; import { CompileOptions, Warning, Ast } from '../interfaces'; import Component from './Component'; @@ -77,7 +77,7 @@ export default function compile(source: string, options: CompileOptions) { stats, // TODO make component generator-agnostic, to allow e.g. WebGL generator - options.generate === 'ssr' ? new SsrTarget() : new DomTarget() + options.generate === 'ssr' ? null : new DomTarget() ); stats.stop('create component'); @@ -85,9 +85,11 @@ export default function compile(source: string, options: CompileOptions) { return { ast, stats: stats.render(null), js: null, css: null }; } - const compiler = options.generate === 'ssr' ? generateSSR : generate; + if (options.generate === 'ssr') { + return renderSSR(component, options); + } - return compiler(component, options); + return renderDOM(component, options); } catch (err) { options.onerror(err); return; diff --git a/src/compile/nodes/Attribute.ts b/src/compile/nodes/Attribute.ts index caf5f7ed2d6e..2ebeb1f79ded 100644 --- a/src/compile/nodes/Attribute.ts +++ b/src/compile/nodes/Attribute.ts @@ -6,7 +6,7 @@ import Component from '../Component'; import Node from './shared/Node'; import Element from './Element'; import Text from './Text'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Expression from './shared/Expression'; export interface StyleProp { diff --git a/src/compile/nodes/AwaitBlock.ts b/src/compile/nodes/AwaitBlock.ts index df30f828a355..7420594a54f6 100644 --- a/src/compile/nodes/AwaitBlock.ts +++ b/src/compile/nodes/AwaitBlock.ts @@ -1,6 +1,6 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import PendingBlock from './PendingBlock'; import ThenBlock from './ThenBlock'; import CatchBlock from './CatchBlock'; diff --git a/src/compile/nodes/Binding.ts b/src/compile/nodes/Binding.ts index 40ad3bce5f0c..9f3dab38e762 100644 --- a/src/compile/nodes/Binding.ts +++ b/src/compile/nodes/Binding.ts @@ -4,7 +4,7 @@ import getObject from '../../utils/getObject'; import getTailSnippet from '../../utils/getTailSnippet'; import flattenReference from '../../utils/flattenReference'; import Component from '../Component'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Expression from './shared/Expression'; import { dimensions } from '../../utils/patterns'; diff --git a/src/compile/nodes/CatchBlock.ts b/src/compile/nodes/CatchBlock.ts index 4fb9ae43f185..a8c55860f5d8 100644 --- a/src/compile/nodes/CatchBlock.ts +++ b/src/compile/nodes/CatchBlock.ts @@ -1,5 +1,5 @@ import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class CatchBlock extends Node { diff --git a/src/compile/nodes/DebugTag.ts b/src/compile/nodes/DebugTag.ts index 9768b5c121c9..26a326162b97 100644 --- a/src/compile/nodes/DebugTag.ts +++ b/src/compile/nodes/DebugTag.ts @@ -1,6 +1,6 @@ import Node from './shared/Node'; import Tag from './shared/Tag'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Expression from './shared/Expression'; import deindent from '../../utils/deindent'; import addToSet from '../../utils/addToSet'; diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index 60e814afa4d4..5ba1c5f2b3a2 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -1,7 +1,7 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; import ElseBlock from './ElseBlock'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import createDebuggingComment from '../../utils/createDebuggingComment'; import Expression from './shared/Expression'; import mapChildren from './shared/mapChildren'; diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index 32f0b04e3772..6a739dc9d10e 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -8,7 +8,7 @@ import fixAttributeCasing from '../../utils/fixAttributeCasing'; import { quoteNameIfNecessary, quotePropIfNecessary } from '../../utils/quoteIfNecessary'; import Component from '../Component'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Attribute from './Attribute'; import Binding from './Binding'; import EventHandler from './EventHandler'; diff --git a/src/compile/nodes/ElseBlock.ts b/src/compile/nodes/ElseBlock.ts index a8cb2b4c30d6..8bba03856fab 100644 --- a/src/compile/nodes/ElseBlock.ts +++ b/src/compile/nodes/ElseBlock.ts @@ -1,5 +1,5 @@ import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class ElseBlock extends Node { diff --git a/src/compile/nodes/Fragment.ts b/src/compile/nodes/Fragment.ts index f3d59adc2085..78f0481862ec 100644 --- a/src/compile/nodes/Fragment.ts +++ b/src/compile/nodes/Fragment.ts @@ -1,7 +1,7 @@ import Node from './shared/Node'; import Component from '../Component'; import mapChildren from './shared/mapChildren'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import TemplateScope from './shared/TemplateScope'; export default class Fragment extends Node { diff --git a/src/compile/nodes/Head.ts b/src/compile/nodes/Head.ts index 186939823854..f0cb3d23659f 100644 --- a/src/compile/nodes/Head.ts +++ b/src/compile/nodes/Head.ts @@ -1,5 +1,5 @@ import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class Head extends Node { diff --git a/src/compile/nodes/IfBlock.ts b/src/compile/nodes/IfBlock.ts index d25294d1084a..16e6a2115dd9 100644 --- a/src/compile/nodes/IfBlock.ts +++ b/src/compile/nodes/IfBlock.ts @@ -2,7 +2,7 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; import ElseBlock from './ElseBlock'; import Component from '../Component'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import createDebuggingComment from '../../utils/createDebuggingComment'; import Expression from './shared/Expression'; import mapChildren from './shared/mapChildren'; diff --git a/src/compile/nodes/InlineComponent.ts b/src/compile/nodes/InlineComponent.ts index 93d5661b0741..a544617e7f78 100644 --- a/src/compile/nodes/InlineComponent.ts +++ b/src/compile/nodes/InlineComponent.ts @@ -6,7 +6,7 @@ import getObject from '../../utils/getObject'; import { quoteNameIfNecessary, quotePropIfNecessary } from '../../utils/quoteIfNecessary'; import { escape, escapeTemplate, stringify } from '../../utils/stringify'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Attribute from './Attribute'; import mapChildren from './shared/mapChildren'; import Binding from './Binding'; diff --git a/src/compile/nodes/MustacheTag.ts b/src/compile/nodes/MustacheTag.ts index f543ee9df2db..227301b1646d 100644 --- a/src/compile/nodes/MustacheTag.ts +++ b/src/compile/nodes/MustacheTag.ts @@ -1,6 +1,6 @@ import Node from './shared/Node'; import Tag from './shared/Tag'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; export default class MustacheTag extends Tag { build( diff --git a/src/compile/nodes/PendingBlock.ts b/src/compile/nodes/PendingBlock.ts index 071f0652c262..e7956f066480 100644 --- a/src/compile/nodes/PendingBlock.ts +++ b/src/compile/nodes/PendingBlock.ts @@ -1,5 +1,5 @@ import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class PendingBlock extends Node { diff --git a/src/compile/nodes/RawMustacheTag.ts b/src/compile/nodes/RawMustacheTag.ts index 70f47a6332eb..a5f8a46dc3a9 100644 --- a/src/compile/nodes/RawMustacheTag.ts +++ b/src/compile/nodes/RawMustacheTag.ts @@ -1,7 +1,7 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; import Tag from './shared/Tag'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; export default class RawMustacheTag extends Tag { build( diff --git a/src/compile/nodes/Slot.ts b/src/compile/nodes/Slot.ts index fb3bc41c5f2e..4b2273ef8fb2 100644 --- a/src/compile/nodes/Slot.ts +++ b/src/compile/nodes/Slot.ts @@ -4,7 +4,7 @@ import reservedNames from '../../utils/reservedNames'; import Node from './shared/Node'; import Element from './Element'; import Attribute from './Attribute'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import { quotePropIfNecessary } from '../../utils/quoteIfNecessary'; function sanitize(name) { diff --git a/src/compile/nodes/Text.ts b/src/compile/nodes/Text.ts index 6bd5a958d8d4..b4f891662e75 100644 --- a/src/compile/nodes/Text.ts +++ b/src/compile/nodes/Text.ts @@ -1,6 +1,6 @@ import { escape, escapeHTML, escapeTemplate, stringify } from '../../utils/stringify'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; // Whitespace inside one of these elements will not result in // a whitespace node being created in any circumstances. (This diff --git a/src/compile/nodes/ThenBlock.ts b/src/compile/nodes/ThenBlock.ts index 8feb7a1ceef8..98b6ffd31cf4 100644 --- a/src/compile/nodes/ThenBlock.ts +++ b/src/compile/nodes/ThenBlock.ts @@ -1,5 +1,5 @@ import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class ThenBlock extends Node { diff --git a/src/compile/nodes/Title.ts b/src/compile/nodes/Title.ts index ee6bad05fc5c..4683cecf2181 100644 --- a/src/compile/nodes/Title.ts +++ b/src/compile/nodes/Title.ts @@ -1,6 +1,6 @@ import { stringify } from '../../utils/stringify'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import mapChildren from './shared/mapChildren'; export default class Title extends Node { diff --git a/src/compile/nodes/Window.ts b/src/compile/nodes/Window.ts index fc0f02cdd7c0..f8f90828ea20 100644 --- a/src/compile/nodes/Window.ts +++ b/src/compile/nodes/Window.ts @@ -1,6 +1,6 @@ import deindent from '../../utils/deindent'; import Node from './shared/Node'; -import Block from '../dom/Block'; +import Block from '../render-dom/Block'; import Binding from './Binding'; import EventHandler from './EventHandler'; import flattenReference from '../../utils/flattenReference'; diff --git a/src/compile/nodes/shared/Node.ts b/src/compile/nodes/shared/Node.ts index d338c378cd1a..99d8f39b7650 100644 --- a/src/compile/nodes/shared/Node.ts +++ b/src/compile/nodes/shared/Node.ts @@ -1,5 +1,5 @@ import Component from './../../Component'; -import Block from '../../dom/Block'; +import Block from '../../render-dom/Block'; import { trimStart, trimEnd } from '../../../utils/trim'; export default class Node { diff --git a/src/compile/nodes/shared/Tag.ts b/src/compile/nodes/shared/Tag.ts index b8adfbd7cfe1..f244aa08fe29 100644 --- a/src/compile/nodes/shared/Tag.ts +++ b/src/compile/nodes/shared/Tag.ts @@ -1,6 +1,6 @@ import Node from './Node'; import Expression from './Expression'; -import Block from '../../dom/Block'; +import Block from '../../render-dom/Block'; export default class Tag extends Node { expression: Expression; diff --git a/src/compile/dom/Block.ts b/src/compile/render-dom/Block.ts similarity index 100% rename from src/compile/dom/Block.ts rename to src/compile/render-dom/Block.ts diff --git a/src/compile/dom/index.ts b/src/compile/render-dom/index.ts similarity index 100% rename from src/compile/dom/index.ts rename to src/compile/render-dom/index.ts diff --git a/src/compile/render-ssr/Renderer.ts b/src/compile/render-ssr/Renderer.ts new file mode 100644 index 000000000000..754aa044ad0d --- /dev/null +++ b/src/compile/render-ssr/Renderer.ts @@ -0,0 +1,70 @@ +import AwaitBlock from './handlers/AwaitBlock'; +import Comment from './handlers/Comment'; +import DebugTag from './handlers/DebugTag'; +import EachBlock from './handlers/EachBlock'; +import Element from './handlers/Element'; +import Head from './handlers/Head'; +import HtmlTag from './handlers/HtmlTag'; +import IfBlock from './handlers/IfBlock'; +import InlineComponent from './handlers/InlineComponent'; +import Slot from './handlers/Slot'; +import Tag from './handlers/Tag'; +import Text from './handlers/Text'; +import Title from './handlers/Title'; + +type Handler = (node: any, target: any, options: any) => void; + +function noop(){} + +const handlers: Record = { + AwaitBlock, + Comment, + DebugTag, + EachBlock, + Element, + Head, + IfBlock, + InlineComponent, + MustacheTag: Tag, // TODO MustacheTag is an anachronism + RawMustacheTag: HtmlTag, + Slot, + Text, + Title, + Window: noop +}; + +type AppendTarget = any; // TODO + +export default class Renderer { + bindings: string[]; + code: string; + targets: AppendTarget[]; + + constructor() { + this.bindings = []; + this.code = ''; + this.targets = []; + } + + append(code: string) { + if (this.targets.length) { + const target = this.targets[this.targets.length - 1]; + const slotName = target.slotStack[target.slotStack.length - 1]; + target.slots[slotName] += code; + } else { + this.code += code; + } + } + + render(nodes, options) { + nodes.forEach(node => { + const handler = handlers[node.type]; + + if (!handler) { + throw new Error(`No handler for '${node.type}' nodes`); + } + + handler(node, this, options); + }); + } +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/AwaitBlock.ts b/src/compile/render-ssr/handlers/AwaitBlock.ts new file mode 100644 index 000000000000..42daa79619ed --- /dev/null +++ b/src/compile/render-ssr/handlers/AwaitBlock.ts @@ -0,0 +1,16 @@ +import Renderer from '../Renderer'; +import { CompileOptions } from '../../../interfaces'; + +export default function(node, renderer: Renderer, options: CompileOptions) { + const { snippet } = node.expression; + + renderer.append('${(function(__value) { if(@isPromise(__value)) return `'); + + renderer.render(node.pending.children, options); + + renderer.append('`; return function(ctx) { return `'); + + renderer.render(node.then.children, options); + + renderer.append(`\`;}(Object.assign({}, ctx, { ${node.value}: __value }));}(${snippet})) }`); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Comment.ts b/src/compile/render-ssr/handlers/Comment.ts new file mode 100644 index 000000000000..16006f1323ca --- /dev/null +++ b/src/compile/render-ssr/handlers/Comment.ts @@ -0,0 +1,8 @@ +import Renderer from '../Renderer'; +import { CompileOptions } from '../../../interfaces'; + +export default function(node, target: Renderer, options: CompileOptions) { + if (options.preserveComments) { + target.append(``); + } +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/DebugTag.ts b/src/compile/render-ssr/handlers/DebugTag.ts new file mode 100644 index 000000000000..6e72eb80bcbe --- /dev/null +++ b/src/compile/render-ssr/handlers/DebugTag.ts @@ -0,0 +1,19 @@ +import { stringify } from '../../../utils/stringify'; + +export default function(node, target, options) { + if (!options.dev) return; + + const filename = options.file || null; + const { line, column } = options.locate(node.start + 1); + + const obj = node.expressions.length === 0 + ? `ctx` + : `{ ${node.expressions + .map(e => e.node.name) + .map(name => `${name}: ctx.${name}`) + .join(', ')} }`; + + const str = '${@debug(' + `${filename && stringify(filename)}, ${line}, ${column}, ${obj})}`; + + target.append(str); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/EachBlock.ts b/src/compile/render-ssr/handlers/EachBlock.ts new file mode 100644 index 000000000000..10b2e1b85a8b --- /dev/null +++ b/src/compile/render-ssr/handlers/EachBlock.ts @@ -0,0 +1,25 @@ +export default function(node, renderer, options) { + const { snippet } = node.expression; + + const props = node.contexts.map(prop => `${prop.key.name}: item${prop.tail}`); + + const getContext = node.index + ? `(item, i) => Object.assign({}, ctx, { ${props.join(', ')}, ${node.index}: i })` + : `item => Object.assign({}, ctx, { ${props.join(', ')} })`; + + const open = `\${ ${node.else ? `${snippet}.length ? ` : ''}@each(${snippet}, ${getContext}, ctx => \``; + renderer.append(open); + + renderer.render(node.children, options); + + const close = `\`)`; + renderer.append(close); + + if (node.else) { + renderer.append(` : \``); + renderer.render(node.else.children, options); + renderer.append(`\``); + } + + renderer.append('}'); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Element.ts b/src/compile/render-ssr/handlers/Element.ts new file mode 100644 index 000000000000..00ff1ba98066 --- /dev/null +++ b/src/compile/render-ssr/handlers/Element.ts @@ -0,0 +1,132 @@ +import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../utils/quoteIfNecessary'; +import isVoidElementName from '../../../utils/isVoidElementName'; + +// source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 +const boolean_attributes = new Set([ + 'async', + 'autocomplete', + 'autofocus', + 'autoplay', + 'border', + 'challenge', + 'checked', + 'compact', + 'contenteditable', + 'controls', + 'default', + 'defer', + 'disabled', + 'formnovalidate', + 'frameborder', + 'hidden', + 'indeterminate', + 'ismap', + 'loop', + 'multiple', + 'muted', + 'nohref', + 'noresize', + 'noshade', + 'novalidate', + 'nowrap', + 'open', + 'readonly', + 'required', + 'reversed', + 'scoped', + 'scrolling', + 'seamless', + 'selected', + 'sortable', + 'spellcheck', + 'translate' +]); + +export default function(node, renderer, options) { + let openingTag = `<${node.name}`; + let textareaContents; // awkward special case + + const slot = node.getStaticAttributeValue('slot'); + if (slot && node.hasAncestor('InlineComponent')) { + const slot = node.attributes.find((attribute: Node) => attribute.name === 'slot'); + const slotName = slot.chunks[0].data; + const target = renderer.targets[renderer.targets.length - 1]; + target.slotStack.push(slotName); + target.slots[slotName] = ''; + } + + const classExpr = node.classes.map((classDir: Class) => { + const { expression, name } = classDir; + const snippet = expression ? expression.snippet : `ctx${quotePropIfNecessary(name)}`; + return `${snippet} ? "${name}" : ""`; + }).join(', '); + + let addClassAttribute = classExpr ? true : false; + + if (node.attributes.find(attr => attr.isSpread)) { + // TODO dry this out + const args = []; + node.attributes.forEach(attribute => { + if (attribute.isSpread) { + args.push(attribute.expression.snippet); + } else { + if (attribute.name === 'value' && node.name === 'textarea') { + textareaContents = attribute.stringifyForSsr(); + } else if (attribute.isTrue) { + args.push(`{ ${quoteNameIfNecessary(attribute.name)}: true }`); + } else if ( + boolean_attributes.has(attribute.name) && + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' + ) { + // a boolean attribute with one non-Text chunk + args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${attribute.chunks[0].snippet} }`); + } else { + args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${attribute.stringifyForSsr()}\` }`); + } + } + }); + + openingTag += "${@spread([" + args.join(', ') + "])}"; + } else { + node.attributes.forEach((attribute: Node) => { + if (attribute.type !== 'Attribute') return; + + if (attribute.name === 'value' && node.name === 'textarea') { + textareaContents = attribute.stringifyForSsr(); + } else if (attribute.isTrue) { + openingTag += ` ${attribute.name}`; + } else if ( + boolean_attributes.has(attribute.name) && + attribute.chunks.length === 1 && + attribute.chunks[0].type !== 'Text' + ) { + // a boolean attribute with one non-Text chunk + openingTag += '${' + attribute.chunks[0].snippet + ' ? " ' + attribute.name + '" : "" }'; + } else if (attribute.name === 'class' && classExpr) { + addClassAttribute = false; + openingTag += ` class="\${ [\`${attribute.stringifyForSsr()}\`, ${classExpr} ].join(' ').trim() }"`; + } else { + openingTag += ` ${attribute.name}="${attribute.stringifyForSsr()}"`; + } + }); + } + + if (addClassAttribute) { + openingTag += ` class="\${ [${classExpr}].join(' ').trim() }"`; + } + + openingTag += '>'; + + renderer.append(openingTag); + + if (node.name === 'textarea' && textareaContents !== undefined) { + renderer.append(textareaContents); + } else { + renderer.render(node.children, options); + } + + if (!isVoidElementName(node.name)) { + renderer.append(``); + } +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Head.ts b/src/compile/render-ssr/handlers/Head.ts new file mode 100644 index 000000000000..dfd7c3fd6678 --- /dev/null +++ b/src/compile/render-ssr/handlers/Head.ts @@ -0,0 +1,7 @@ +export default function(node, renderer, options) { + renderer.append('${(__result.head += `'); + + renderer.render(node.children, options); + + renderer.append('`, "")}'); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/HtmlTag.ts b/src/compile/render-ssr/handlers/HtmlTag.ts new file mode 100644 index 000000000000..9d1c88ec8004 --- /dev/null +++ b/src/compile/render-ssr/handlers/HtmlTag.ts @@ -0,0 +1,3 @@ +export default function(node, target, options) { + target.append('${' + node.expression.snippet + '}'); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/IfBlock.ts b/src/compile/render-ssr/handlers/IfBlock.ts new file mode 100644 index 000000000000..58f11faaa87d --- /dev/null +++ b/src/compile/render-ssr/handlers/IfBlock.ts @@ -0,0 +1,15 @@ +export default function(node, renderer, options) { + const { snippet } = node.expression; + + renderer.append('${ ' + snippet + ' ? `'); + + renderer.render(node.children, options); + + renderer.append('` : `'); + + if (node.else) { + renderer.render(node.else.children, options); + } + + renderer.append('` }'); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/InlineComponent.ts b/src/compile/render-ssr/handlers/InlineComponent.ts new file mode 100644 index 000000000000..a315d7a392c6 --- /dev/null +++ b/src/compile/render-ssr/handlers/InlineComponent.ts @@ -0,0 +1,130 @@ +import { escape, escapeTemplate, stringify } from '../../../utils/stringify'; +import getObject from '../../../utils/getObject'; +import getTailSnippet from '../../../utils/getTailSnippet'; +import { quoteNameIfNecessary, quotePropIfNecessary } from '../../../utils/quoteIfNecessary'; +import deindent from '../../../utils/deindent'; + +type AppendTarget = any; // TODO + +export default function(node, renderer, options) { + function stringifyAttribute(chunk: Node) { + if (chunk.type === 'Text') { + return escapeTemplate(escape(chunk.data)); + } + + return '${@escape( ' + chunk.snippet + ')}'; + } + + const bindingProps = node.bindings.map(binding => { + const { name } = getObject(binding.value.node); + const tail = binding.value.node.type === 'MemberExpression' + ? getTailSnippet(binding.value.node) + : ''; + + return `${quoteNameIfNecessary(binding.name)}: ctx${quotePropIfNecessary(name)}${tail}`; + }); + + function getAttributeValue(attribute) { + if (attribute.isTrue) return `true`; + if (attribute.chunks.length === 0) return `''`; + + if (attribute.chunks.length === 1) { + const chunk = attribute.chunks[0]; + if (chunk.type === 'Text') { + return stringify(chunk.data); + } + + return chunk.snippet; + } + + return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`'; + } + + const usesSpread = node.attributes.find(attr => attr.isSpread); + + const props = usesSpread + ? `Object.assign(${ + node.attributes + .map(attribute => { + if (attribute.isSpread) { + return attribute.expression.snippet; + } else { + return `{ ${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)} }`; + } + }) + .concat(bindingProps.map(p => `{ ${p} }`)) + .join(', ') + })` + : `{ ${node.attributes + .map(attribute => `${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)}`) + .concat(bindingProps) + .join(', ')} }`; + + const expression = ( + node.name === 'svelte:self' + ? node.component.name + : node.name === 'svelte:component' + ? `((${node.expression.snippet}) || @missingComponent)` + : `%components-${node.name}` + ); + + node.bindings.forEach(binding => { + const conditions = []; + + let parent = node; + while (parent = parent.parent) { + if (parent.type === 'IfBlock') { + // TODO handle contextual bindings... + conditions.push(`(${parent.expression.snippet})`); + } + } + + conditions.push( + `!('${binding.name}' in ctx)`, + `${expression}.data` + ); + + const { name } = getObject(binding.value.node); + + renderer.bindings.push(deindent` + if (${conditions.reverse().join('&&')}) { + tmp = ${expression}.data(); + if ('${name}' in tmp) { + ctx${quotePropIfNecessary(binding.name)} = tmp.${name}; + settled = false; + } + } + `); + }); + + let open = `\${@validateSsrComponent(${expression}, '${node.name}')._render(__result, ${props}`; + + const component_options = []; + component_options.push(`store: options.store`); + + if (node.children.length) { + const target: AppendTarget = { + slots: { default: '' }, + slotStack: ['default'] + }; + + renderer.targets.push(target); + + renderer.render(node.children, options); + + const slotted = Object.keys(target.slots) + .map(name => `${quoteNameIfNecessary(name)}: () => \`${target.slots[name]}\``) + .join(', '); + + component_options.push(`slotted: { ${slotted} }`); + + renderer.targets.pop(); + } + + if (component_options.length) { + open += `, { ${component_options.join(', ')} }`; + } + + renderer.append(open); + renderer.append(')}'); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Slot.ts b/src/compile/render-ssr/handlers/Slot.ts new file mode 100644 index 000000000000..91a7844d14b2 --- /dev/null +++ b/src/compile/render-ssr/handlers/Slot.ts @@ -0,0 +1,14 @@ +import { quotePropIfNecessary } from '../../../utils/quoteIfNecessary'; + +export default function(node, renderer, options) { + const name = node.attributes.find(attribute => attribute.name === 'name'); + + const slotName = name && name.chunks[0].data || 'default'; + const prop = quotePropIfNecessary(slotName); + + renderer.append(`\${options && options.slotted && options.slotted${prop} ? options.slotted${prop}() : \``); + + renderer.render(node.children, options); + + renderer.append(`\`}`); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Tag.ts b/src/compile/render-ssr/handlers/Tag.ts new file mode 100644 index 000000000000..d3c5f892c05f --- /dev/null +++ b/src/compile/render-ssr/handlers/Tag.ts @@ -0,0 +1,9 @@ +export default function(node, target, options) { + target.append( + node.parent && + node.parent.type === 'Element' && + node.parent.name === 'style' + ? '${' + node.expression.snippet + '}' + : '${@escape(' + node.expression.snippet + ')}' + ); +} \ No newline at end of file diff --git a/src/compile/render-ssr/handlers/Text.ts b/src/compile/render-ssr/handlers/Text.ts new file mode 100644 index 000000000000..7f57db6e3cb1 --- /dev/null +++ b/src/compile/render-ssr/handlers/Text.ts @@ -0,0 +1,14 @@ +import { escapeHTML, escapeTemplate, escape } from '../../../utils/stringify'; + +export default function(node, target, options) { + let text = node.data; + if ( + !node.parent || + node.parent.type !== 'Element' || + (node.parent.name !== 'script' && node.parent.name !== 'style') + ) { + // unless this Text node is inside a