diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 4de3da6687..b477dcafbe 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -837,7 +837,7 @@ export default class Component { } rewrite_props(get_insert: (variable: Var) => Node[]) { - // TODO + if (!this.ast.instance) return; const component = this; const { instance_scope, instance_scope_map: map } = this; diff --git a/src/compiler/compile/render_ssr/Renderer.ts b/src/compiler/compile/render_ssr/Renderer.ts index 9680866349..37d6f3f5b2 100644 --- a/src/compiler/compile/render_ssr/Renderer.ts +++ b/src/compiler/compile/render_ssr/Renderer.ts @@ -13,6 +13,7 @@ import Text from './handlers/Text'; import Title from './handlers/Title'; import { AppendTarget, CompileOptions } from '../../interfaces'; import { INode } from '../nodes/interfaces'; +import { Expression } from 'estree'; type Handler = (node: any, renderer: Renderer, options: CompileOptions) => void; @@ -43,17 +44,45 @@ export interface RenderOptions extends CompileOptions{ export default class Renderer { has_bindings = false; - code = ''; + + state = { + quasi: { + type: 'TemplateElement', + value: { raw: '' } + } + }; + + literal = { + type: 'TemplateLiteral', + expressions: [], + quasis: [] + }; + targets: AppendTarget[] = []; append(code: string) { - if (this.targets.length) { - const target = this.targets[this.targets.length - 1]; - const slot_name = target.slot_stack[target.slot_stack.length - 1]; - target.slots[slot_name] += code; - } else { - this.code += code; - } + throw new Error('no more append'); + // if (this.targets.length) { + // const target = this.targets[this.targets.length - 1]; + // const slot_name = target.slot_stack[target.slot_stack.length - 1]; + // target.slots[slot_name] += code; + // } else { + // this.code += code; + // } + } + + add_string(str: string) { + this.state.quasi.value.raw += str; + } + + add_expression(node: Expression) { + this.literal.quasis.push(this.state.quasi); + this.literal.expressions.push(node); + + this.state.quasi = { + type: 'TemplateElement', + value: { raw: '' } + }; } render(nodes: INode[], options: RenderOptions) { diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index e9704331b1..38addbfe4a 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,12 +1,12 @@ import { is_void, quote_prop_if_necessary, quote_name_if_necessary } from '../../../utils/names'; import Attribute from '../../nodes/Attribute'; import Class from '../../nodes/Class'; -import { snip } from '../../utils/snip'; import { stringify_attribute, stringify_class_attribute } from '../../utils/stringify_attribute'; import { get_slot_scope } from './shared/get_slot_scope'; import Renderer, { RenderOptions } from '../Renderer'; import Element from '../../nodes/Element'; import Text from '../../nodes/Text'; +import { x } from 'code-red'; // source: https://gist.github.com/ArjanSchouten/0b8574a6ad7f5065a5e7 const boolean_attributes = new Set([ @@ -52,7 +52,7 @@ const boolean_attributes = new Set([ export default function(node: Element, renderer: Renderer, options: RenderOptions & { slot_scopes: Map; }) { - let opening_tag = `<${node.name}`; + renderer.add_string(`<${node.name}`); // awkward special case let node_contents; @@ -96,29 +96,29 @@ export default function(node: Element, renderer: Renderer, options: RenderOption const args = []; node.attributes.forEach(attribute => { if (attribute.is_spread) { - args.push(snip(attribute.expression)); + args.push(attribute.expression.node); } else { if (attribute.name === 'value' && node.name === 'textarea') { node_contents = stringify_attribute(attribute, true); } else if (attribute.is_true) { - args.push(`{ ${quote_name_if_necessary(attribute.name)}: true }`); + args.push(x`{ ${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(`{ ${quote_name_if_necessary(attribute.name)}: ${snip(attribute.chunks[0])} }`); + args.push(x`{ ${attribute.name}: ${attribute.chunks[0].node} }`); } else if (attribute.name === 'class' && class_expression) { // Add class expression - args.push(`{ ${quote_name_if_necessary(attribute.name)}: [\`${stringify_class_attribute(attribute)}\`, \`\${${class_expression}}\`].join(' ').trim() }`); + args.push(x`{ ${attribute.name}: [${stringify_class_attribute(attribute)}, ${class_expression}}].join(' ').trim() }`); } else { - args.push(`{ ${quote_name_if_necessary(attribute.name)}: \`${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}\` }`); + args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)} }`); } } }); - opening_tag += "${@spread([" + args.join(', ') + "])}"; + renderer.add_expression(x`@spread([${args}])`); } else { node.attributes.forEach((attribute: Attribute) => { if (attribute.type !== 'Attribute') return; @@ -126,23 +126,32 @@ export default function(node: Element, renderer: Renderer, options: RenderOption if (attribute.name === 'value' && node.name === 'textarea') { node_contents = stringify_attribute(attribute, true); } else if (attribute.is_true) { - opening_tag += ` ${attribute.name}`; + renderer.add_string(` ${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 - opening_tag += '${' + snip(attribute.chunks[0]) + ' ? " ' + attribute.name + '" : "" }'; + throw new Error('here'); + renderer.add_expression(x`${attribute.chunks[0]} ? "${attribute.name}" : ""`); } else if (attribute.name === 'class' && class_expression) { add_class_attribute = false; - opening_tag += ` class="\${[\`${stringify_class_attribute(attribute)}\`, ${class_expression}].join(' ').trim() }"`; + renderer.add_string(` class="`); + renderer.add_expression(x`[${stringify_class_attribute(attribute)}, ${class_expression}].join(' ').trim()`); + renderer.add_string(`"`); } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { const { name } = attribute; - const snippet = snip(attribute.chunks[0]); - opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', ' + (boolean_attributes.has(name) ? 1 : 0) + ')}'; + const snippet = attribute.chunks[0].node; + console.log(snippet); + renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); } else { - opening_tag += ` ${attribute.name}="${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)}"`; + renderer.add_string(` ${attribute.name}="`); + attribute.chunks.forEach(chunk => { + if (chunk.type === 'Text') renderer.add_string(chunk.data); + else renderer.add_expression(x`@escape(${chunk.node})`); + }); + renderer.add_string(`"`); } }); } @@ -157,38 +166,36 @@ export default function(node: Element, renderer: Renderer, options: RenderOption if (name === 'group') { // TODO server-render group bindings } else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) { - node_contents = snip(expression); - value = name === 'textContent' ? '@escape($$value)' : '$$value'; + node_contents = expression.node; + value = name === 'textContent' ? x`@escape($$value)` : x`$$value`; } else if (binding.name === 'value' && node.name === 'textarea') { - const snippet = snip(expression); - node_contents = '${(' + snippet + ') || ""}'; + const snippet = expression.node; + node_contents = x`${snippet} || ""`; } else { - const snippet = snip(expression); - opening_tag += '${@add_attribute("' + name + '", ' + snippet + ', 1)}'; + const snippet = expression.node; + renderer.add_expression(x`@add_attribute("${name}", ${snippet}, 1)`); } }); - if (add_class_attribute) { - opening_tag += `\${@add_classes([${class_expression}].join(' ').trim())}`; - } - - opening_tag += '>'; + // if (add_class_attribute) { + // opening_tag += `\${@add_classes([${class_expression}].join(' ').trim())}`; + // } - renderer.append(opening_tag); + renderer.add_string('>'); if (node_contents !== undefined) { - if (contenteditable) { - renderer.append('${($$value => $$value === void 0 ? `'); - renderer.render(node.children, options); - renderer.append('` : ' + value + ')(' + node_contents + ')}'); - } else { - renderer.append(node_contents); - } + // if (contenteditable) { + // renderer.append('${($$value => $$value === void 0 ? `'); + // renderer.render(node.children, options); + // renderer.append('` : ' + value + ')(' + node_contents + ')}'); + // } else { + // renderer.append(node_contents); + // } } else { renderer.render(node.children, options); } if (!is_void(node.name)) { - renderer.append(``); + renderer.add_string(``); } } diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index 3bab0cfcf3..34a8055e98 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -1,6 +1,5 @@ -import { escape, escape_template, stringify } from '../../utils/stringify'; +import { escape, escape_template, stringify, string_literal } from '../../utils/stringify'; import { quote_name_if_necessary } from '../../../utils/names'; -import { snip } from '../../utils/snip'; import Renderer, { RenderOptions } from '../Renderer'; import { get_slot_scope } from './shared/get_slot_scope'; import { AppendTarget } from '../../../interfaces'; @@ -18,19 +17,15 @@ function stringify_attribute(chunk: INode) { } function get_attribute_value(attribute) { - if (attribute.is_true) 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 snip(chunk); - } - - return '`' + attribute.chunks.map(stringify_attribute).join('') + '`'; + if (attribute.is_true) return x`true`; + if (attribute.chunks.length === 0) return x`''`; + + return attribute.chunks + .map(chunk => { + if (chunk.type === 'Text') return string_literal(chunk.data); + return chunk.node; + }) + .reduce((lhs, rhs) => x`${lhs} + ${rhs}`); } export default function(node: InlineComponent, renderer: Renderer, options: RenderOptions) { @@ -41,7 +36,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend renderer.has_bindings = true; // TODO this probably won't work for contextual bindings - const snippet = snip(binding.expression); + const snippet = binding.expression.node; binding_props.push(p`${binding.name}: ${snippet}`); binding_fns.push(p`${binding.name}: $$value => { ${snippet} = $$value; $$settled = false }`); @@ -66,7 +61,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend })`; } else { props = x`{ - ${node.attributes.map(attribute => p`${attribute.name}: ${get_attribute_value(attribute)}`)} + ${node.attributes.map(attribute => p`${attribute.name}: ${get_attribute_value(attribute)}`)}, ${binding_props} }`; } @@ -115,5 +110,5 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend ${slot_fns} }`; - renderer.append(`\${@validate_component(${expression}, '${node.name}').$$render($$result, ${props}, ${bindings}, ${slots})}`); + renderer.add_expression(x`@validate_component(${expression}, "${node.name}").$$render($$result, ${props}, ${bindings}, ${slots})`); } diff --git a/src/compiler/compile/render_ssr/handlers/Tag.ts b/src/compiler/compile/render_ssr/handlers/Tag.ts index 353939049d..45c0c19089 100644 --- a/src/compiler/compile/render_ssr/handlers/Tag.ts +++ b/src/compiler/compile/render_ssr/handlers/Tag.ts @@ -1,13 +1,15 @@ -import { snip } from '../../utils/snip'; + import Renderer, { RenderOptions } from '../Renderer'; +import { x } from 'code-red'; + export default function(node, renderer: Renderer, _options: RenderOptions) { - const snippet = snip(node.expression); + const snippet = node.expression.node; - renderer.append( + renderer.add_expression( node.parent && node.parent.type === 'Element' && node.parent.name === 'style' - ? '${' + snippet + '}' - : '${@escape(' + snippet + ')}' + ? snippet + : x`@escape(${snippet})` ); } diff --git a/src/compiler/compile/render_ssr/handlers/Text.ts b/src/compiler/compile/render_ssr/handlers/Text.ts index 5e56952f13..b86b79bdb9 100644 --- a/src/compiler/compile/render_ssr/handlers/Text.ts +++ b/src/compiler/compile/render_ssr/handlers/Text.ts @@ -1,4 +1,4 @@ -import { escape_html, escape_template, escape } from '../../utils/stringify'; +import { escape_html } from '../../utils/stringify'; import Renderer, { RenderOptions } from '../Renderer'; import Text from '../../nodes/Text'; import Element from '../../nodes/Element'; @@ -13,5 +13,6 @@ export default function(node: Text, renderer: Renderer, _options: RenderOptions) // unless this Text node is inside a