You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/compiler/compile/render_ssr/handlers/Element.ts

234 lines
8.9 KiB

import { is_void } from '../../../../shared/utils/names';
import { get_attribute_expression, get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { boolean_attributes } from '../../../../shared/boolean_attributes';
import Renderer, { RenderOptions } from '../Renderer';
import Element from '../../nodes/Element';
import { p, x } from 'code-red';
import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns';
import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) {
const children = remove_whitespace_children(node.children, node.next);
// awkward special case
let node_contents;
const contenteditable = (
node.name !== 'textarea' &&
node.name !== 'input' &&
node.attributes.some((attribute) => attribute.name === 'contenteditable')
);
if (node.is_dynamic_element) {
renderer.push();
}
renderer.add_string('<');
add_tag_name();
const class_expression_list = node.classes.map(class_directive => {
const { expression, name } = class_directive;
const snippet = expression ? expression.node : x`#ctx.${name}`; // TODO is this right?
return x`${snippet} ? "${name}" : ""`;
});
if (node.needs_manual_style_scoping) {
class_expression_list.push(x`"${node.component.stylesheet.id}"`);
}
const class_expression =
class_expression_list.length > 0 &&
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
const style_expression_list = node.styles.map(style_directive => {
const { name, expression: { node: expression } } = style_directive;
return p`"${name}": ${expression}`;
});
const style_expression =
style_expression_list.length > 0 &&
x`{ ${style_expression_list} }`;
if (node.attributes.some(attr => attr.is_spread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
if (attribute.is_spread) {
args.push(x`@escape_object(${attribute.expression.node})`);
} else {
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name);
const name = attribute.name.toLowerCase();
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
args.push(x`{ ${attr_name}: true }`);
} else if (
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
args.push(x`{ ${attr_name}: ${(attribute.chunks[0] as Expression).node} || null }`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
args.push(x`{ ${attr_name}: @escape_attribute_value(${snippet}) }`);
} else {
args.push(x`{ ${attr_name}: ${get_attribute_value(attribute)} }`);
}
}
});
renderer.add_expression(x`@spread([${args}], { classes: ${class_expression}, styles: ${style_expression} })`);
} else {
let add_class_attribute = !!class_expression;
let add_style_attribute = !!style_expression;
node.attributes.forEach(attribute => {
const name = attribute.name.toLowerCase();
const attr_name = node.namespace === namespaces.foreign ? attribute.name : fix_attribute_casing(attribute.name);
if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
renderer.add_string(` ${attr_name}`);
} else if (
boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
renderer.add_string(' ');
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attr_name}" : ""`);
} else if (name === 'class' && class_expression) {
add_class_attribute = false;
renderer.add_string(` ${attr_name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`);
renderer.add_string('"');
} else if (name === 'style' && style_expression) {
add_style_attribute = false;
renderer.add_expression(x`@add_styles(@merge_ssr_styles(${get_attribute_value(attribute)}, ${style_expression}))`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const snippet = (attribute.chunks[0] as Expression).node;
renderer.add_expression(x`@add_attribute("${attr_name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
} else {
renderer.add_string(` ${attr_name}="`);
renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
renderer.add_string('"');
}
});
if (add_class_attribute) {
renderer.add_expression(x`@add_classes((${class_expression}).trim())`);
}
if (add_style_attribute) {
renderer.add_expression(x`@add_styles(${style_expression})`);
}
}
node.bindings.forEach(binding => {
const { name, expression } = binding;
if (binding.is_readonly) {
return;
}
if (name === 'group') {
const value_attribute = node.attributes.find(({ name }) => name === 'value');
if (value_attribute) {
const value = get_attribute_expression(value_attribute);
const type = node.get_static_attribute_value('type');
const bound = expression.node;
const condition = type === 'checkbox' ? x`~${bound}.indexOf(${value})` : x`${value} === ${bound}`;
renderer.add_expression(x`${condition} ? @add_attribute("checked", true, 1) : ""`);
}
} else if (contenteditable && (name === 'textContent' || name === 'innerHTML')) {
node_contents = expression.node;
// TODO where was this used?
// value = name === 'textContent' ? x`@escape($$value)` : x`$$value`;
} else if (binding.name === 'value' && node.name === 'textarea') {
const snippet = expression.node;
node_contents = x`${snippet} || ""`;
} else if (binding.name === 'value' && node.name === 'select') {
// NOTE: do not add "value" attribute on <select />
} else {
const snippet = expression.node;
renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
}
});
renderer.add_string('>');
if (node_contents !== undefined) {
if (contenteditable) {
renderer.push();
renderer.render(children, options);
const result = renderer.pop();
renderer.add_expression(x`($$value => $$value === void 0 ? ${result} : $$value)(${node_contents})`);
} else {
if (node.name === 'textarea') {
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const value_attribute = node.attributes.find(({ name }) => name === 'value');
if (value_attribute) {
const first = value_attribute.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
renderer.add_string('\n');
}
}
}
renderer.add_expression(node_contents);
}
add_close_tag();
} else {
if (node.name === 'pre') {
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = children[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
renderer.add_string('\n');
}
}
if (node.is_dynamic_element) renderer.push();
renderer.render(children, options);
if (node.is_dynamic_element) {
const children = renderer.pop();
renderer.add_expression(x`@is_void(#tag) ? '' : ${children}`);
}
add_close_tag();
}
if (node.is_dynamic_element) {
let content: Node = renderer.pop();
if (options.dev && node.children.length > 0) content = x`(() => { @validate_void_dynamic_element(#tag); return ${content}; })()`;
renderer.add_expression(x`((#tag) => {
${options.dev && x`@validate_dynamic_element(#tag)`}
return #tag ? ${content} : '';
})(${node.tag_expr.node})`);
}
function add_close_tag() {
if (node.tag_expr.node.type === 'Literal') {
if (!is_void(node.tag_expr.node.value as string)) {
renderer.add_string('</');
add_tag_name();
renderer.add_string('>');
}
return;
}
renderer.add_expression(x`@is_void(#tag) ? '' : \`</\${#tag}>\``);
}
function add_tag_name() {
if (node.tag_expr.node.type === 'Literal') {
renderer.add_string(node.tag_expr.node.value as string);
} else {
renderer.add_expression(node.tag_expr.node as ESExpression);
}
}
}