diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index 5fd13990c2..66bf2763f9 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -1,7 +1,7 @@ import { is_void, quote_prop_if_necessary } from '../../../utils/names'; import Attribute from '../../nodes/Attribute'; import Class from '../../nodes/Class'; -import { stringify_attribute, stringify_class_attribute } from '../../utils/stringify_attribute'; +import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value'; import { get_slot_scope } from './shared/get_slot_scope'; import Renderer, { RenderOptions } from '../Renderer'; import Element from '../../nodes/Element'; @@ -99,7 +99,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption args.push(attribute.expression.node); } else { if (attribute.name === 'value' && node.name === 'textarea') { - node_contents = stringify_attribute(attribute, true); + node_contents = get_attribute_value(attribute); } else if (attribute.is_true) { args.push(x`{ ${attribute.name}: true }`); } else if ( @@ -111,9 +111,9 @@ export default function(node: Element, renderer: Renderer, options: RenderOption args.push(x`{ ${attribute.name}: ${attribute.chunks[0].node} }`); } else if (attribute.name === 'class' && class_expression) { // Add class expression - args.push(x`{ ${attribute.name}: [${stringify_class_attribute(attribute)}, ${class_expression}}].join(' ').trim() }`); + args.push(x`{ ${attribute.name}: [${get_class_attribute_value(attribute)}, ${class_expression}}].join(' ').trim() }`); } else { - args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? stringify_class_attribute(attribute) : stringify_attribute(attribute, true)} }`); + args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute, true)} }`); } } }); @@ -124,7 +124,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption if (attribute.type !== 'Attribute') return; if (attribute.name === 'value' && node.name === 'textarea') { - node_contents = stringify_attribute(attribute, true); + node_contents = get_attribute_value(attribute); } else if (attribute.is_true) { renderer.add_string(` ${attribute.name}`); } else if ( @@ -133,12 +133,12 @@ export default function(node: Element, renderer: Renderer, options: RenderOption attribute.chunks[0].type !== 'Text' ) { // a boolean attribute with one non-Text chunk - throw new Error('here'); - renderer.add_expression(x`${attribute.chunks[0]} ? "${attribute.name}" : ""`); + renderer.add_string(` `); + renderer.add_expression(x`${attribute.chunks[0].node} ? "${attribute.name}" : ""`); } else if (attribute.name === 'class' && class_expression) { add_class_attribute = false; renderer.add_string(` class="`); - renderer.add_expression(x`[${stringify_class_attribute(attribute)}, ${class_expression}].join(' ').trim()`); + renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`); renderer.add_string(`"`); } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { const { name } = attribute; @@ -146,10 +146,7 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`); } else { 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_expression(attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute)); renderer.add_string(`"`); } }); diff --git a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts index c90046e550..775e8afd0c 100644 --- a/src/compiler/compile/render_ssr/handlers/InlineComponent.ts +++ b/src/compiler/compile/render_ssr/handlers/InlineComponent.ts @@ -6,18 +6,7 @@ import InlineComponent from '../../nodes/InlineComponent'; import { INode } from '../../nodes/interfaces'; import { p, x } from 'code-red'; -function stringify_attribute(chunk: INode) { - if (chunk.type === 'Text') { - return string_literal(chunk.data); - // return escape_template(escape((chunk as Text).data)); - } - - return x`@escape(${chunk})`; - - return '${@escape(' + snip(chunk) + ')}'; -} - -function get_attribute_value(attribute) { +function get_prop_value(attribute) { if (attribute.is_true) return x`true`; if (attribute.chunks.length === 0) return x`''`; @@ -54,7 +43,7 @@ export default function(node: InlineComponent, renderer: Renderer, options: Rend if (attribute.is_spread) { return snip(attribute.expression); } else { - return `{ ${quote_name_if_necessary(attribute.name)}: ${get_attribute_value(attribute)} }`; + return `{ ${quote_name_if_necessary(attribute.name)}: ${get_prop_value(attribute)} }`; } }) .concat(binding_props.map(p => `{ ${p} }`)) @@ -62,7 +51,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_prop_value(attribute)}`)}, ${binding_props} }`; } diff --git a/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts new file mode 100644 index 0000000000..5c89a7a008 --- /dev/null +++ b/src/compiler/compile/render_ssr/handlers/shared/get_attribute_value.ts @@ -0,0 +1,28 @@ +import Attribute from '../../../nodes/Attribute'; +import { string_literal } from '../../../utils/stringify'; +import Text from '../../../nodes/Text'; +import { x } from 'code-red'; +import Expression from '../../../nodes/shared/Expression'; +import { Expression as ESTreeExpression } from 'estree'; + +export function get_class_attribute_value(attribute: Attribute): ESTreeExpression { + // handle special case — `class={possiblyUndefined}` with scoped CSS + if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) { + const value = (attribute.chunks[0] as Expression).node; + return x`@escape(@null_to_empty(${value})) + "${(attribute.chunks[1] as Text).data}"`; + } + + return get_attribute_value(attribute); +} + +export function get_attribute_value(attribute: Attribute): ESTreeExpression { + if (attribute.chunks.length === 0) return x`""`; + + return attribute.chunks + .map((chunk) => { + return chunk.type === 'Text' + ? string_literal(chunk.data.replace(/"/g, '"')) as ESTreeExpression + : x`@escape(${chunk.node})`; + }) + .reduce((lhs, rhs) => x`${lhs} + ${rhs}`); +} diff --git a/src/compiler/compile/utils/stringify_attribute.ts b/src/compiler/compile/utils/stringify_attribute.ts deleted file mode 100644 index e907445766..0000000000 --- a/src/compiler/compile/utils/stringify_attribute.ts +++ /dev/null @@ -1,28 +0,0 @@ -import Attribute from '../nodes/Attribute'; -import { string_literal } from './stringify'; -import { snip } from './snip'; -import Text from '../nodes/Text'; -import { x } from 'code-red'; - -export function stringify_class_attribute(attribute: Attribute) { - // handle special case — `class={possiblyUndefined}` with scoped CSS - if (attribute.chunks.length === 2 && (attribute.chunks[1] as Text).synthetic) { - return '${@escape(@null_to_empty(' + snip(attribute.chunks[0]) + '))}' + (attribute.chunks[1] as Text).data; - } - - return stringify_attribute(attribute, true); -} - -export function stringify_attribute(attribute: Attribute, is_ssr: boolean) { - return attribute.chunks - .map((chunk) => { - if (chunk.type === 'Text') { - return string_literal(chunk.data.replace(/"/g, '"')); - } - - return is_ssr - ? x`@escape(${chunk.node})` - : chunk.node; - }) - .reduce((lhs, rhs) => x`${lhs} + ${rhs}`); -} diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index 0958b95195..3e2c812b6c 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -81,9 +81,6 @@ describe.only("ssr", () => { }); }); - // TODO re-enable SSR tests - return; - // duplicate client-side tests, as far as possible fs.readdirSync("test/runtime/samples").forEach(dir => { if (dir[0] === ".") return; @@ -133,6 +130,8 @@ describe.only("ssr", () => { showOutput(cwd, compileOptions); } } catch (err) { + err.stack += `\n\ncmd-click: ${path.relative(process.cwd(), cwd)}/main.svelte`; + if (config.error) { if (typeof config.error === 'function') { config.error(assert, err);