Repository: sveltejs/svelte (JavaScript)
Date: 2018-04-29 02:26:41+00:00
Task type: Bug Fix

Issue:
Custom element missing methods
I've created a basic custom element based on [template-custom-element](https://github.com/sveltejs/template-custom-element) but the component's methods don't seem to being added to the compiled JS.

My component
```HTML
<h1>Hello {{name}}!</h1>

<button on:click="saySomething()">Click me</button>

<script>
  export default {
    tag: 'my-app',

    data() {
      return {
        'name': 'World'
      }
    },

    methods: {
      saySomething() {
        alert('Again, please don\'t do this');
      }
    }
  };
</script>
```

Clicking on the button throws an error:
```
Uncaught TypeError: component.saySomething is not a function
    at HTMLButtonElement.click_handler (App.html:3)
```

Full example is here: https://github.com/willtj/template-custom-element


Issue discussion:

Solution patch:
diff --git a/src/compile/dom/index.ts b/src/compile/dom/index.ts
--- a/src/compile/dom/index.ts
+++ b/src/compile/dom/index.ts
@@ -113,8 +113,6 @@ export default function dom(
 		? 'svelte/shared.js'
 		: options.shared || '';
 
-	let prototypeBase = `${name}.prototype`;
-
 	const proto = sharedPath
 		? `@proto`
 		: deindent`
@@ -294,8 +292,9 @@ export default function dom(
 				`}
 			}
 
-			customElements.define("${compiler.tag}", ${name});
-			@assign(@assign(${prototypeBase}, ${proto}), {
+			@assign(${name}.prototype, ${proto});
+			${templateProperties.methods && `@assign(${name}.prototype, %methods);`}
+			@assign(${name}.prototype, {
 				_mount(target, anchor) {
 					target.insertBefore(this, anchor);
 				},
@@ -304,6 +303,8 @@ export default function dom(
 					this.parentNode.removeChild(this);
 				}
 			});
+
+			customElements.define("${compiler.tag}", ${name});
 		`);
 	} else {
 		builder.addBlock(deindent`
@@ -311,8 +312,8 @@ export default function dom(
 				${constructorBody}
 			}
 
-			@assign(${prototypeBase}, ${proto});
-			${templateProperties.methods && `@assign(${prototypeBase}, %methods);`}
+			@assign(${name}.prototype, ${proto});
+			${templateProperties.methods && `@assign(${name}.prototype, %methods);`}
 		`);
 	}
 


Test patch:
diff --git a/test/custom-elements/samples/custom-method/main.html b/test/custom-elements/samples/custom-method/main.html
new file mode 100644
--- /dev/null
+++ b/test/custom-elements/samples/custom-method/main.html
@@ -0,0 +1,13 @@
+<p>{foo}</p>
+
+<script>
+	export default {
+		tag: 'custom-element',
+
+		methods: {
+			updateFoo(value) {
+				this.foo = value;
+			}
+		}
+	};
+</script>
\ No newline at end of file
diff --git a/test/custom-elements/samples/custom-method/test.js b/test/custom-elements/samples/custom-method/test.js
new file mode 100644
--- /dev/null
+++ b/test/custom-elements/samples/custom-method/test.js
@@ -0,0 +1,12 @@
+import * as assert from 'assert';
+import './main.html';
+
+export default function (target) {
+	target.innerHTML = '<custom-element name="world"></custom-element>';
+	const el = target.querySelector('custom-element');
+
+	el.updateFoo(42);
+
+	const p = el.shadowRoot.querySelector('p');
+	assert.equal(p.textContent, '42');
+}
\ No newline at end of file
diff --git a/test/js/samples/action/expected-bundle.js b/test/js/samples/action/expected-bundle.js
--- a/test/js/samples/action/expected-bundle.js
+++ b/test/js/samples/action/expected-bundle.js
@@ -136,7 +136,7 @@ var proto = {
 /* generated by Svelte vX.Y.Z */
 
 function link(node) {
-
+  
   function onClick(event) {
     event.preventDefault();
     history.pushState(null, null, event.target.href);
diff --git a/test/js/samples/action/expected.js b/test/js/samples/action/expected.js
--- a/test/js/samples/action/expected.js
+++ b/test/js/samples/action/expected.js
@@ -2,7 +2,7 @@
 import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js";
 
 function link(node) {
-
+  
   function onClick(event) {
     event.preventDefault();
     history.pushState(null, null, event.target.href);
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
@@ -185,8 +185,8 @@ class SvelteComponent extends HTMLElement {
 	}
 }
 
-customElements.define("custom-element", SvelteComponent);
-assign(assign(SvelteComponent.prototype, proto), {
+assign(SvelteComponent.prototype, proto);
+assign(SvelteComponent.prototype, {
 	_mount(target, anchor) {
 		target.insertBefore(this, anchor);
 	},
@@ -196,4 +196,6 @@ assign(assign(SvelteComponent.prototype, proto), {
 	}
 });
 
+customElements.define("custom-element", SvelteComponent);
+
 export default SvelteComponent;
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
@@ -51,8 +51,8 @@ class SvelteComponent extends HTMLElement {
 	}
 }
 
-customElements.define("custom-element", SvelteComponent);
-assign(assign(SvelteComponent.prototype, proto), {
+assign(SvelteComponent.prototype, proto);
+assign(SvelteComponent.prototype, {
 	_mount(target, anchor) {
 		target.insertBefore(this, anchor);
 	},
@@ -61,4 +61,6 @@ assign(assign(SvelteComponent.prototype, proto), {
 		this.parentNode.removeChild(this);
 	}
 });
+
+customElements.define("custom-element", SvelteComponent);
 export default SvelteComponent;
\ No newline at end of file