attributes and innerHTML

pull/3539/head
Richard Harris 6 years ago
parent e2d6ce1de5
commit d731766f34

@ -300,6 +300,11 @@ export default class Component {
walk(program, {
enter: (node) => {
if (node.type === 'Identifier' && node.name[0] === '@') {
// TODO temp
if (!/@\w+$/.test(node.name)) {
throw new Error(`wut "${node.name}"`);
}
if (node.name[1] === '_') {
const alias = this.global(node.name.slice(2));
node.name = alias.name;

@ -1,4 +1,4 @@
import { stringify } from '../utils/stringify';
import { stringify, string_literal } from '../utils/stringify';
import add_to_set from '../utils/add_to_set';
import Component from '../Component';
import Node from './shared/Node';
@ -82,11 +82,9 @@ export default class Attribute extends Node {
if (this.chunks.length === 0) return `""`;
if (this.chunks.length === 1) {
return this.chunks[0].type === 'Text'
? stringify((this.chunks[0] as Text).data)
// @ts-ignore todo: probably error
: this.chunks[0].render(block);
? string_literal((this.chunks[0] as Text).data)
: (this.chunks[0] as Expression).manipulate(block);
}
return (this.chunks[0].type === 'Text' ? '' : `"" + `) +

@ -3,9 +3,10 @@ import Block from '../../Block';
import fix_attribute_casing from './fix_attribute_casing';
import ElementWrapper from './index';
import { stringify } from '../../../utils/stringify';
import { b } from 'code-red';
import { b, x } from 'code-red';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export default class AttributeWrapper {
node: Attribute;
@ -80,14 +81,14 @@ export default class AttributeWrapper {
// single {tag} — may be a non-string
value = (this.node.chunks[0] as Expression).manipulate(block);
} else {
// '{foo} {bar}' — treat as string concatenation
const prefix = this.node.chunks[0].type === 'Text' ? '' : `"" + `;
const text = this.node.name === 'class'
value = this.node.name === 'class'
? this.get_class_name_text()
: this.render_chunks().join(' + ');
: this.render_chunks().reduce((lhs, rhs) => x`${lhs} + ${rhs}`)
value = `${prefix}${text}`;
// '{foo} {bar}' — treat as string concatenation
if (this.node.chunks[0].type !== 'Text') {
value = x`"" + ${value}`;
}
}
const is_select_value_attribute =
@ -96,19 +97,19 @@ export default class AttributeWrapper {
const should_cache = (this.node.should_cache() || is_select_value_attribute);
const last = should_cache && block.get_unique_name(
`${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
);
if (should_cache) block.add_variable(last);
let updater;
const init = should_cache ? `${last} = ${value}` : value;
const init = should_cache ? x`${last} = ${value}` : value;
if (is_legacy_input_type) {
block.chunks.hydrate.push(
b`@set_input_type(${element.var}, ${init});`
);
updater = `@set_input_type(${element.var}, ${should_cache ? last : value});`;
updater = b`@set_input_type(${element.var}, ${should_cache ? last : value});`;
} else if (is_select_value_attribute) {
// annoying special case
const is_multiple_select = element.node.get_static_attribute_value('multiple');
@ -141,27 +142,29 @@ export default class AttributeWrapper {
b`${element.var}.${property_name} = ${init};`
);
updater = block.renderer.options.dev
? `@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: `${element.var}.${property_name} = ${should_cache ? last : value};`;
? b`@prop_dev(${element.var}, "${property_name}", ${should_cache ? last : value});`
: b`${element.var}.${property_name} = ${should_cache ? last : value};`;
} else {
block.chunks.hydrate.push(
b`${method}(${element.var}, "${name}", ${init});`
);
updater = `${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
updater = b`${method}(${element.var}, "${name}", ${should_cache ? last : value});`;
}
const changed_check = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
let condition = changed(dependencies);
const update_cached_value = `${last} !== (${last} = ${value})`;
if (should_cache) {
condition = x`${condition} && (${last} !== (${last} = ${value}))`;
}
const condition = should_cache
? (dependencies.length ? `(${changed_check}) && ${update_cached_value}` : update_cached_value)
: changed_check;
if (block.has_outros) {
condition = x`!#current || ${condition}`;
}
block.chunks.update.push(b`if (${condition}) ${updater}`);
block.chunks.update.push(b`
if (${condition}) {
${updater}
}`);
} else {
const value = this.node.get_value(block);
@ -195,10 +198,10 @@ export default class AttributeWrapper {
if (scoped_css && rendered.length === 2) {
// we have a situation like class={possiblyUndefined}
rendered[0] = `@null_to_empty(${rendered[0]})`;
rendered[0] = x`@null_to_empty(${rendered[0]})`;
}
return rendered.join(' + ');
return rendered.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);
}
render_chunks() {

@ -1,4 +1,4 @@
import { b } from 'code-red';
import { b, x } from 'code-red';
import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import AttributeWrapper from './Attribute';
@ -7,6 +7,7 @@ import { stringify } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set';
import Expression from '../../../nodes/shared/Expression';
import Text from '../../../nodes/Text';
import { changed } from '../shared/changed';
export interface StyleProp {
key: string;
@ -26,42 +27,45 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
let value;
if (is_dynamic(prop.value)) {
const prop_dependencies = new Set();
const prop_dependencies: Set<string> = new Set();
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk) => {
value = prop.value
.map(chunk => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const snippet = chunk.manipulate();
const snippet = chunk.manipulate(block);
add_to_set(prop_dependencies, chunk.dynamic_dependencies());
return snippet;
}
})
.join(' + ');
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`)
if (prop.value.length === 1 || prop.value[0].type === 'Text') {
value = x`"" + ${value}`;
}
if (prop_dependencies.size) {
const dependencies = Array.from(prop_dependencies);
const condition = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
let condition = changed(Array.from(prop_dependencies));
block.chunks.update.push(
b`if (${condition}) {
@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});
}`
);
if (block.has_outros) {
condition = x`!#current || ${condition}`;
}
const update = b`
if (${condition}) {
@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});
}`;
block.chunks.update.push(update);
}
} else {
value = stringify((prop.value[0] as Text).data);
}
block.chunks.hydrate.push(
b`@set_style(${this.parent.var}, "${prop.key}", ${value}${prop.important ? ', 1' : ''});`
b`@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : ''});`
);
});
}

@ -4,7 +4,7 @@ import Wrapper from '../shared/Wrapper';
import Block from '../../Block';
import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names';
import FragmentWrapper from '../Fragment';
import { escape_html, escape, string_literal } from '../../../utils/stringify';
import { escape_html, string_literal } from '../../../utils/stringify';
import TextWrapper from '../Text';
import fix_attribute_casing from './fix_attribute_casing';
import { b, x } from 'code-red';
@ -294,14 +294,24 @@ export default class ElementWrapper extends Wrapper {
b`${node}.textContent = ${string_literal(this.fragment.nodes[0].data)};`
);
} else {
const inner_html = escape(
this.fragment.nodes
.map(to_html)
.join('')
);
const state = {
quasi: {
type: 'TemplateElement',
value: { raw: '' }
}
};
const literal = {
type: 'TemplateLiteral',
expressions: [],
quasis: []
};
to_html((this.fragment.nodes as unknown as (ElementWrapper | TextWrapper)[]), block, literal, state);
literal.quasis.push(state.quasi);
block.chunks.create.push(
b`${node}.innerHTML = \`${inner_html}\`;`
b`${node}.innerHTML = ${literal};`
);
}
} else {
@ -338,36 +348,6 @@ export default class ElementWrapper extends Wrapper {
);
}
function to_html(wrapper: ElementWrapper | TextWrapper) {
if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) return ' ';
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style'
);
return (raw ? wrapper.node.data : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
}
if (wrapper.node.name === 'noscript') return '';
let open = `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
open += ` ${fix_attribute_casing(attr.node.name)}${attr.stringify()}`;
});
if (is_void(wrapper.node.name)) return open + '>';
return `${open}>${(wrapper as ElementWrapper).fragment.nodes.map(to_html).join('')}</${wrapper.node.name}>`;
}
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push(
@ -836,3 +816,60 @@ export default class ElementWrapper extends Wrapper {
});
}
}
function to_html(wrappers: (ElementWrapper | TextWrapper)[], block: Block, literal: any, state: any) {
wrappers.forEach(wrapper => {
if (wrapper.node.type === 'Text') {
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
const parent = wrapper.node.parent as Element;
const raw = parent && (
parent.name === 'script' ||
parent.name === 'style'
);
state.quasi.value.raw += (raw ? wrapper.node.data : escape_html(wrapper.node.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
}
else if (wrapper.node.name === 'noscript') {
// do nothing
}
else {
// element
state.quasi.value.raw += `<${wrapper.node.name}`;
(wrapper as ElementWrapper).attributes.forEach((attr: AttributeWrapper) => {
state.quasi.value.raw += ` ${fix_attribute_casing(attr.node.name)}="`;
attr.node.chunks.forEach(chunk => {
if (chunk.type === 'Text') {
state.quasi.value.raw += chunk.data;
} else {
literal.quasis.push(state.quasi);
literal.expressions.push(chunk.manipulate(block))
state.quasi = {
type: 'TemplateElement',
value: { raw: '' }
};
}
});
state.quasi.value.raw += `"`;
});
state.quasi.value.raw += '>';
if (!is_void(wrapper.node.name)) {
to_html((wrapper as ElementWrapper).fragment.nodes as (ElementWrapper | TextWrapper)[], block, literal, state);
state.quasi.value.raw += `</${wrapper.node.name}>`;
}
}
});
}

@ -0,0 +1,7 @@
import { x } from 'code-red';
export function changed(dependencies: string[]) {
return dependencies
.map(d => x`#changed.${d}`)
.reduce((lhs, rhs) => x`${lhs} || ${rhs}`);
}

@ -65,6 +65,7 @@ describe.only("runtime", () => {
unhandled_rejection = null;
config.preserveIdentifiers = true; // TODO remove later
compile = (config.preserveIdentifiers ? svelte : svelte$).compile;
const cwd = path.resolve(`test/runtime/samples/${dir}`);

Loading…
Cancel
Save