Repository: sveltejs/svelte (JavaScript)
Date: 2018-01-02 04:03:32+00:00
Task type: Bug Fix

Issue:
Nested custom element fragments are created twice
Custom element constructors always call `this._fragment.c()`, because they don't need a `target`. But then if a custom element is referenced as a Svelte component...

```html
<ThisIsCompiledAsACustomElement/>
```

...then the parent will call `thisiscompiledasacustomelement._fragment.c()` as well. The result is that references to the elements that are actually in the DOM get nuked, meaning they don't respond to state changes.
Nested custom element fragments are created twice
Custom element constructors always call `this._fragment.c()`, because they don't need a `target`. But then if a custom element is referenced as a Svelte component...

```html
<ThisIsCompiledAsACustomElement/>
```

...then the parent will call `thisiscompiledasacustomelement._fragment.c()` as well. The result is that references to the elements that are actually in the DOM get nuked, meaning they don't respond to state changes.


Issue discussion:

Solution patch:
diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts
--- a/src/generators/dom/index.ts
+++ b/src/generators/dom/index.ts
@@ -97,6 +97,9 @@ export default function dom(
 	parsed.html.build();
 	const { block } = parsed.html;
 
+	// prevent fragment being created twice (#1063)
+	if (options.customElement) block.builders.create.addLine(`this.c = @noop;`);
+
 	generator.stylesheet.warnOnUnusedSelectors(options.onwarn);
 
 	const builder = new CodeBuilder();


Test patch:
diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js
--- a/test/custom-elements/index.js
+++ b/test/custom-elements/index.js
@@ -19,6 +19,10 @@ describe('custom-elements', function() {
 
 	const nightmare = new Nightmare({ show: false });
 
+	nightmare.on('console', (type, ...args) => {
+		console[type](...args);
+	});
+
 	let svelte;
 	let server;
 	let bundle;
diff --git a/test/custom-elements/samples/nested/Counter.html b/test/custom-elements/samples/nested/Counter.html
new file mode 100644
--- /dev/null
+++ b/test/custom-elements/samples/nested/Counter.html
@@ -0,0 +1,13 @@
+<button on:click='set({ count: count + 1 })'>count: {{count}}</button>
+
+<script>
+	export default {
+		tag: 'my-counter',
+
+		data() {
+			return {
+				count: 0
+			};
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/custom-elements/samples/nested/main.html b/test/custom-elements/samples/nested/main.html
new file mode 100644
--- /dev/null
+++ b/test/custom-elements/samples/nested/main.html
@@ -0,0 +1,12 @@
+<Counter bind:count/>
+<p>clicked {{count}} times</p>
+
+<script>
+	import Counter from './Counter.html';
+
+	export default {
+		tag: 'my-app',
+
+		components: { Counter }
+	};
+</script>
\ No newline at end of file
diff --git a/test/custom-elements/samples/nested/test.js b/test/custom-elements/samples/nested/test.js
new file mode 100644
--- /dev/null
+++ b/test/custom-elements/samples/nested/test.js
@@ -0,0 +1,17 @@
+import * as assert from 'assert';
+import './main.html';
+
+export default function (target) {
+	target.innerHTML = '<my-app/>';
+	const el = target.querySelector('my-app');
+	const counter = el.shadowRoot.querySelector('my-counter');
+	const button = counter.shadowRoot.querySelector('button');
+
+	assert.equal(counter.count, 0);
+	assert.equal(counter.shadowRoot.innerHTML, `<button>count: 0</button>`);
+
+	button.dispatchEvent(new MouseEvent('click'));
+
+	assert.equal(counter.count, 1);
+	assert.equal(counter.shadowRoot.innerHTML, `<button>count: 1</button>`);
+}
\ No newline at end of file
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
--- a/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected-bundle.js
@@ -188,6 +188,7 @@ function create_main_fragment(state, component) {
 		c: function create() {
 			div = createElement("div");
 			div.textContent = "fades in";
+			this.c = noop;
 		},
 
 		m: function mount(target, anchor) {
diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js
--- a/test/js/samples/css-shadow-dom-keyframes/expected.js
+++ b/test/js/samples/css-shadow-dom-keyframes/expected.js
@@ -8,6 +8,7 @@ function create_main_fragment(state, component) {
 		c: function create() {
 			div = createElement("div");
 			div.textContent = "fades in";
+			this.c = noop;
 		},
 
 		m: function mount(target, anchor) {