diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index 6380843b87..d57635aaa9 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -70,6 +70,14 @@ export default function dom(
);
}
+ const uses_dispatch = component.var_lookup.has('$$dispatch');
+ let create_dispatch;
+ if (uses_dispatch) {
+ create_dispatch = b`
+ const $$dispatch = @createEventDispatcher();
+ `;
+ }
+
const uses_slots = component.var_lookup.has('$$slots');
let compute_slots;
if (uses_slots) {
@@ -417,6 +425,8 @@ export default function dom(
${resubscribable_reactive_store_unsubscribers}
+ ${create_dispatch}
+
${component.slots.size || component.compile_options.dev || uses_slots ? b`let { $$slots: #slots = {}, $$scope } = $$props;` : null}
${component.compile_options.dev && b`@validate_slots('${component.tag}', #slots, [${[...component.slots.keys()].map(key => `'${key}'`).join(',')}]);`}
${compute_slots}
diff --git a/src/compiler/compile/utils/reserved_keywords.ts b/src/compiler/compile/utils/reserved_keywords.ts
index a68819a493..d603e28730 100644
--- a/src/compiler/compile/utils/reserved_keywords.ts
+++ b/src/compiler/compile/utils/reserved_keywords.ts
@@ -1,4 +1,4 @@
-export const reserved_keywords = new Set(['$$props', '$$restProps', '$$slots']);
+export const reserved_keywords = new Set(['$$props', '$$restProps', '$$slots', '$$dispatch']);
export function is_reserved_keyword(name) {
return reserved_keywords.has(name);
diff --git a/test/runtime/samples/$$dispatch-script/_config.js b/test/runtime/samples/$$dispatch-script/_config.js
new file mode 100644
index 0000000000..2122a7e0e1
--- /dev/null
+++ b/test/runtime/samples/$$dispatch-script/_config.js
@@ -0,0 +1,24 @@
+export default {
+ props: {
+
+ },
+ html: `
+
+ `,
+
+ async test({ assert, component, target, window}) {
+ const button = target.querySelector('button');
+ const event = new window.MouseEvent('click');
+
+ const clicked = [];
+
+ component.$on('clicked', event => {
+ clicked.push(event.detail);
+ });
+
+ button.dispatchEvent(event);
+
+ assert.equal(clicked.length, 1);
+ assert.equal(clicked[0], 'info');
+ }
+};
diff --git a/test/runtime/samples/$$dispatch-script/main.svelte b/test/runtime/samples/$$dispatch-script/main.svelte
new file mode 100644
index 0000000000..9d7b549cb0
--- /dev/null
+++ b/test/runtime/samples/$$dispatch-script/main.svelte
@@ -0,0 +1,7 @@
+
+
diff --git a/test/runtime/samples/$$dispatch-template/_config.js b/test/runtime/samples/$$dispatch-template/_config.js
new file mode 100644
index 0000000000..2122a7e0e1
--- /dev/null
+++ b/test/runtime/samples/$$dispatch-template/_config.js
@@ -0,0 +1,24 @@
+export default {
+ props: {
+
+ },
+ html: `
+
+ `,
+
+ async test({ assert, component, target, window}) {
+ const button = target.querySelector('button');
+ const event = new window.MouseEvent('click');
+
+ const clicked = [];
+
+ component.$on('clicked', event => {
+ clicked.push(event.detail);
+ });
+
+ button.dispatchEvent(event);
+
+ assert.equal(clicked.length, 1);
+ assert.equal(clicked[0], 'info');
+ }
+};
diff --git a/test/runtime/samples/$$dispatch-template/main.svelte b/test/runtime/samples/$$dispatch-template/main.svelte
new file mode 100644
index 0000000000..c7339194dd
--- /dev/null
+++ b/test/runtime/samples/$$dispatch-template/main.svelte
@@ -0,0 +1,2 @@
+
+