Repository: sveltejs/svelte (JavaScript)
Date: 2019-08-20 03:12:36+00:00
Task type: Bug Fix

Issue:
Setting inline styles with data elements breaks style
Here's a REPL demonstration: https://svelte.technology/repl?version=2.11.0&gist=02e988596c0d0888490384ec44d7f99c

Putting a data variable into an inline style tag will try to concatenate your new `key:value;` with the preceding value. The result is that this text, which should be 0.25 opacity and yellow has no styles applied to it at all.

```html
<h1 style="opacity: 0.25;{color}">Hello {name}!</h1>

<script>
export default {
	data () {
		return {
			color: 'color: #fc0;'
		}
	}
}
</script>
```

This is the generated JavaScript:

```js
setStyle(h1, "opacity", "0.25" + ctx.color);
```


Issue discussion:
Svelte will change to using `cssText` if the entire `style` attribute is taken from a variable:
```html
<h1 style="{style}">Hello {name}!</h1>

<script>
export default {
  data() {
    return {
      style: 'opacity: 0.25; color: #fc0'
    }
  }
}
</script>
```

Just having the `color` value in a variable works as well:

```html
<h1 style="opacity: 0.25;color:{color}">Hello {name}!</h1>

<script>
export default {
  data() {
    return {
      color: '#fc0'
    }
  }
}
</script>
```

It might be possible that a combination of both can be implemented by generating appropriate `setStyle` calls at runtime.
#3309 is a duplicate of this issue, but has some more examples that we may or may not want to call bugs.
In case these examples are not considered bugs, it may be a good idea to use static analysis to enforce that only values can be interpolated. It's pretty confusing as it is right now, especically because partial interpolation works just fine for other attributes.

That said, I strongly advocate for considering these behaviors bugs. Inline styles are highly volatile properties that should be met with the according flexibility.

Coming from a Vue background, high-level class/style usage is probably one of the things I struggle with the most, but that again is a topic for another discussion. 🙂

Solution patch:
diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts
--- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts
@@ -110,8 +110,9 @@ function get_style_value(chunks: Array<Text | Expression>) {
 	let in_url = false;
 	let quote_mark = null;
 	let escaped = false;
+	let closed = false;
 
-	while (chunks.length) {
+	while (chunks.length && !closed) {
 		const chunk = chunks.shift();
 
 		if (chunk.type === 'Text') {
@@ -132,6 +133,7 @@ function get_style_value(chunks: Array<Text | Expression>) {
 				} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
 					in_url = true;
 				} else if (char === ';' && !in_url && !quote_mark) {
+					closed = true;
 					break;
 				}
 


Test patch:
diff --git a/test/runtime/samples/inline-style-optimisation-bailout/_config.js b/test/runtime/samples/inline-style-optimisation-bailout/_config.js
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/inline-style-optimisation-bailout/_config.js
@@ -0,0 +1,20 @@
+export default {
+	html: `
+		<p style="opacity: 0.5; color: red">color: red</p>
+	`,
+
+	test({ assert, component, target, window }) {
+		const p = target.querySelector('p');
+
+		let styles = window.getComputedStyle(p);
+		assert.equal(styles.opacity, '0.5');
+		assert.equal(styles.color, 'red');
+
+		component.styles = 'font-size: 20px';
+
+		styles = window.getComputedStyle(p);
+		assert.equal(styles.opacity, '0.5');
+		assert.equal(styles.color, '');
+		assert.equal(styles.fontSize, '20px');
+	}
+}
\ No newline at end of file
diff --git a/test/runtime/samples/inline-style-optimisation-bailout/main.svelte b/test/runtime/samples/inline-style-optimisation-bailout/main.svelte
new file mode 100644
--- /dev/null
+++ b/test/runtime/samples/inline-style-optimisation-bailout/main.svelte
@@ -0,0 +1,5 @@
+<script>
+	export let styles = `color: red`;
+</script>
+
+<p style="opacity: 0.5; {styles}">{styles}</p>
\ No newline at end of file