diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index f44170cda1..5b2883850a 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -425,7 +425,7 @@ export default class InlineComponentWrapper extends Wrapper { } if (${switch_value}) { - ${name} = new ${switch_value}(${switch_props}(#ctx)); + ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx)); ${munged_bindings} ${munged_handlers} @@ -473,7 +473,7 @@ export default class InlineComponentWrapper extends Wrapper { if (${switch_value}) { ${update_insert} - ${name} = new ${switch_value}(${switch_props}(#ctx)); + ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx)); ${munged_bindings} ${munged_handlers} diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 32d14571d9..9d24ad2a07 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -123,6 +123,24 @@ export function validate_void_dynamic_element(tag: undefined | string) { } } +export function construct_svelte_component_dev(component, props) { + const error_message = 'this={...} of should specify a Svelte component.'; + try { + const instance = new component(props); + if (!instance.$$ || !instance.$set || !instance.$on || !instance.$destroy) { + throw new Error(error_message); + } + return instance; + } catch (err) { + const { message } = err; + if (typeof message === 'string' && message.indexOf('is not a constructor') !== -1) { + throw new Error(error_message); + } else { + throw err; + } + } +} + type Props = Record; export interface SvelteComponentDev { $set(props?: Props): void; diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index d359513eb4..a1c0e1c0aa 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -768,3 +768,7 @@ export function get_custom_elements_slots(element: HTMLElement) { }); return result; } + +export function construct_svelte_component(component, props) { + return new component(props); +} diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts index dc4c16dffb..571a25f1ac 100644 --- a/src/runtime/internal/ssr.ts +++ b/src/runtime/internal/ssr.ts @@ -124,7 +124,7 @@ export const missing_component = { export function validate_component(component, name) { if (!component || !component.$$render) { if (name === 'svelte:component') name += ' this={...}'; - throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`); + throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules. Otherwise you may need to fix a <${name}>.`); } return component; diff --git a/test/runtime/samples/component-not-constructor-dev/_config.js b/test/runtime/samples/component-not-constructor-dev/_config.js new file mode 100644 index 0000000000..15ea469a0f --- /dev/null +++ b/test/runtime/samples/component-not-constructor-dev/_config.js @@ -0,0 +1,8 @@ +export default { + skip_if_ssr: true, + skip_if_hydrate_from_ssr: true, + compileOptions: { + dev: true + }, + error: 'this={...} of should specify a Svelte component.' +}; diff --git a/test/runtime/samples/component-not-constructor-dev/main.svelte b/test/runtime/samples/component-not-constructor-dev/main.svelte new file mode 100644 index 0000000000..80f9f969e6 --- /dev/null +++ b/test/runtime/samples/component-not-constructor-dev/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/component-not-constructor/Sub.svelte b/test/runtime/samples/component-not-constructor/Sub.svelte new file mode 100644 index 0000000000..dd6d0bb1a9 --- /dev/null +++ b/test/runtime/samples/component-not-constructor/Sub.svelte @@ -0,0 +1 @@ +
Sub
\ No newline at end of file diff --git a/test/runtime/samples/component-not-constructor/_config.js b/test/runtime/samples/component-not-constructor/_config.js new file mode 100644 index 0000000000..8c0dcced41 --- /dev/null +++ b/test/runtime/samples/component-not-constructor/_config.js @@ -0,0 +1,8 @@ +export default { + skip_if_ssr: true, + skip_if_hydrate_from_ssr: true, + props: { + selected: false + }, + error: 'component is not a constructor' +}; diff --git a/test/runtime/samples/component-not-constructor/main.svelte b/test/runtime/samples/component-not-constructor/main.svelte new file mode 100644 index 0000000000..dec7e2af79 --- /dev/null +++ b/test/runtime/samples/component-not-constructor/main.svelte @@ -0,0 +1,9 @@ + + + diff --git a/test/runtime/samples/component-not-constructor2-dev/Sub.svelte b/test/runtime/samples/component-not-constructor2-dev/Sub.svelte new file mode 100644 index 0000000000..dd6d0bb1a9 --- /dev/null +++ b/test/runtime/samples/component-not-constructor2-dev/Sub.svelte @@ -0,0 +1 @@ +
Sub
\ No newline at end of file diff --git a/test/runtime/samples/component-not-constructor2-dev/_config.js b/test/runtime/samples/component-not-constructor2-dev/_config.js new file mode 100644 index 0000000000..98e3cc942b --- /dev/null +++ b/test/runtime/samples/component-not-constructor2-dev/_config.js @@ -0,0 +1,19 @@ +export default { + compileOptions: { + dev: true + }, + props: { + componentName: 'Sub' + }, + html: '
Sub
', + test({ assert, component, target }) { + component.componentName = 'Proxy'; + assert.htmlEqual(target.innerHTML, '
Sub
'); + try { + component.componentName = 'banana'; + throw new Error('Expected an error'); + } catch (err) { + assert.equal(err.message, 'this={...} of should specify a Svelte component.'); + } + } +}; diff --git a/test/runtime/samples/component-not-constructor2-dev/main.svelte b/test/runtime/samples/component-not-constructor2-dev/main.svelte new file mode 100644 index 0000000000..331fdfc385 --- /dev/null +++ b/test/runtime/samples/component-not-constructor2-dev/main.svelte @@ -0,0 +1,14 @@ + + + diff --git a/test/runtime/samples/component-not-constructor2/Sub.svelte b/test/runtime/samples/component-not-constructor2/Sub.svelte new file mode 100644 index 0000000000..dd6d0bb1a9 --- /dev/null +++ b/test/runtime/samples/component-not-constructor2/Sub.svelte @@ -0,0 +1 @@ +
Sub
\ No newline at end of file diff --git a/test/runtime/samples/component-not-constructor2/_config.js b/test/runtime/samples/component-not-constructor2/_config.js new file mode 100644 index 0000000000..8fae65afdd --- /dev/null +++ b/test/runtime/samples/component-not-constructor2/_config.js @@ -0,0 +1,16 @@ +export default { + props: { + componentName: 'Sub' + }, + html: '
Sub
', + test({ assert, component, target }) { + component.componentName = 'Proxy'; + assert.htmlEqual(target.innerHTML, '
Sub
'); + try { + component.componentName = 'banana'; + throw new Error('Expected an error'); + } catch (err) { + assert.equal(err.message, 'component is not a constructor'); + } + } +}; diff --git a/test/runtime/samples/component-not-constructor2/main.svelte b/test/runtime/samples/component-not-constructor2/main.svelte new file mode 100644 index 0000000000..331fdfc385 --- /dev/null +++ b/test/runtime/samples/component-not-constructor2/main.svelte @@ -0,0 +1,14 @@ + + +