[fix] Improve error message if `this` attribute of `<svelte:component>` is not valid (#7551)

* add test

* improve error message if this attribute of <svelte:component> is not SvelteComponent

* add more tests

* improve validation

* simplify test

Co-authored-by: Tan Li Hau <tanhauhau@users.noreply.github.com>
pull/7833/head
Yuichiro Yamashita 2 years ago committed by GitHub
parent be70a898f3
commit 01a91163a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -425,7 +425,7 @@ export default class InlineComponentWrapper extends Wrapper {
} }
if (${switch_value}) { if (${switch_value}) {
${name} = new ${switch_value}(${switch_props}(#ctx)); ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx));
${munged_bindings} ${munged_bindings}
${munged_handlers} ${munged_handlers}
@ -473,7 +473,7 @@ export default class InlineComponentWrapper extends Wrapper {
if (${switch_value}) { if (${switch_value}) {
${update_insert} ${update_insert}
${name} = new ${switch_value}(${switch_props}(#ctx)); ${name} = @construct_svelte_component(${switch_value}, ${switch_props}(#ctx));
${munged_bindings} ${munged_bindings}
${munged_handlers} ${munged_handlers}

@ -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 <svelte:component> 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<string, any>; type Props = Record<string, any>;
export interface SvelteComponentDev { export interface SvelteComponentDev {
$set(props?: Props): void; $set(props?: Props): void;

@ -768,3 +768,7 @@ export function get_custom_elements_slots(element: HTMLElement) {
}); });
return result; return result;
} }
export function construct_svelte_component(component, props) {
return new component(props);
}

@ -124,7 +124,7 @@ export const missing_component = {
export function validate_component(component, name) { export function validate_component(component, name) {
if (!component || !component.$$render) { if (!component || !component.$$render) {
if (name === 'svelte:component') name += ' this={...}'; 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; return component;

@ -0,0 +1,8 @@
export default {
skip_if_ssr: true,
skip_if_hydrate_from_ssr: true,
compileOptions: {
dev: true
},
error: 'this={...} of <svelte:component> should specify a Svelte component.'
};

@ -0,0 +1,5 @@
<script>
let banana = {};
</script>
<svelte:component this={banana} />

@ -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'
};

@ -0,0 +1,9 @@
<script>
import Sub from './Sub.svelte';
export let selected;
let banana = {};
let component = banana;
$: selected ? component = Sub : component = banana;
</script>
<svelte:component this={component} />

@ -0,0 +1,19 @@
export default {
compileOptions: {
dev: true
},
props: {
componentName: 'Sub'
},
html: '<div>Sub</div>',
test({ assert, component, target }) {
component.componentName = 'Proxy';
assert.htmlEqual(target.innerHTML, '<div>Sub</div>');
try {
component.componentName = 'banana';
throw new Error('Expected an error');
} catch (err) {
assert.equal(err.message, 'this={...} of <svelte:component> should specify a Svelte component.');
}
}
};

@ -0,0 +1,14 @@
<script>
import Sub from './Sub.svelte';
export let componentName = 'Sub';
let proxy = new Proxy(Sub, {});
let banana = {};
let component;
$: {
if (componentName === 'Sub') component = Sub;
else if (componentName === 'Proxy') component = proxy;
else component = banana;
};
</script>
<svelte:component this={component} />

@ -0,0 +1,16 @@
export default {
props: {
componentName: 'Sub'
},
html: '<div>Sub</div>',
test({ assert, component, target }) {
component.componentName = 'Proxy';
assert.htmlEqual(target.innerHTML, '<div>Sub</div>');
try {
component.componentName = 'banana';
throw new Error('Expected an error');
} catch (err) {
assert.equal(err.message, 'component is not a constructor');
}
}
};

@ -0,0 +1,14 @@
<script>
import Sub from './Sub.svelte';
export let componentName = 'Sub';
let proxy = new Proxy(Sub, {});
let banana = {};
let component;
$: {
if (componentName === 'Sub') component = Sub;
else if (componentName === 'Proxy') component = proxy;
else component = banana;
};
</script>
<svelte:component this={component} />
Loading…
Cancel
Save