From ba017db89d744b827267bd8f1470db6336d21780 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:33:02 +0200 Subject: [PATCH] docs: make legacy.componentApi more visible (#11924) * docs: make legacy.componentApi more visible People didn't know that this exists, so we should make it more visible through having it be part of the error message, and calling it out in the docs with more details * clarify * flesh out message, put error behind a function * make it work with HMR * fix * fix * this makes it work. i don't fully understand why * changeset * changeset * Delete .changeset/twelve-foxes-press.md --------- Co-authored-by: Rich Harris --- .changeset/flat-ghosts-fly.md | 5 +++++ .../svelte/messages/client-errors/errors.md | 4 ++++ .../3-transform/client/transform-client.js | 12 ++---------- packages/svelte/src/internal/client/dev/hmr.js | 8 ++++++-- .../svelte/src/internal/client/dev/legacy.js | 7 +++++++ packages/svelte/src/internal/client/errors.js | 18 ++++++++++++++++++ packages/svelte/src/internal/client/index.js | 2 +- .../content/03-appendix/02-breaking-changes.md | 11 ++++++++++- 8 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 .changeset/flat-ghosts-fly.md diff --git a/.changeset/flat-ghosts-fly.md b/.changeset/flat-ghosts-fly.md new file mode 100644 index 000000000..bcd10f706 --- /dev/null +++ b/.changeset/flat-ghosts-fly.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: make `legacy.componentApi` option more visible diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index bb8773a3b..ec543765c 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -14,6 +14,10 @@ > %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information +## component_api_invalid_new + +> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information + ## each_key_duplicate > Keyed each block has duplicate key at indexes %a% and %b% diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 0cdfd1e5d..da2502b6f 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -452,7 +452,7 @@ export function client_component(source, analysis, options) { body.unshift(b.imports([['createClassComponent', '$$_createClassComponent']], 'svelte/legacy')); component_block.body.unshift( b.if( - b.binary('===', b.id('new.target'), b.id(analysis.name)), + b.id('new.target'), b.return( b.call( '$$_createClassComponent', @@ -463,15 +463,7 @@ export function client_component(source, analysis, options) { ) ); } else if (options.dev) { - component_block.body.unshift( - b.if( - b.binary('===', b.id('new.target'), b.id(analysis.name)), - b.throw_error( - `Instantiating a component with \`new\` is no longer valid in Svelte 5. ` + - 'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information' - ) - ) - ); + component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target')))); } if (state.events.size > 0) { diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 0f7960770..9ba2a6989 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -1,6 +1,7 @@ import { block, branch, destroy_effect } from '../reactivity/effects.js'; import { set_should_intro } from '../render.js'; import { get } from '../runtime.js'; +import { check_target } from './legacy.js'; /** * @template {(anchor: Comment, props: any) => any} Component @@ -11,7 +12,7 @@ export function hmr(source) { * @param {Comment} anchor * @param {any} props */ - return (anchor, props) => { + return function (anchor, props) { let instance = {}; /** @type {import("#client").Effect} */ @@ -31,7 +32,10 @@ export function hmr(source) { // preserve getters/setters Object.defineProperties( instance, - Object.getOwnPropertyDescriptors(component(anchor, props)) + Object.getOwnPropertyDescriptors( + // @ts-expect-error + new.target ? new component(anchor, props) : component(anchor, props) + ) ); set_should_intro(true); }); diff --git a/packages/svelte/src/internal/client/dev/legacy.js b/packages/svelte/src/internal/client/dev/legacy.js index 758e1a95b..1262c62db 100644 --- a/packages/svelte/src/internal/client/dev/legacy.js +++ b/packages/svelte/src/internal/client/dev/legacy.js @@ -2,6 +2,13 @@ import * as e from '../errors.js'; import { current_component_context } from '../runtime.js'; import { get_component } from './ownership.js'; +/** @param {Function & { filename: string }} target */ +export function check_target(target) { + if (target) { + e.component_api_invalid_new(target.filename ?? 'a component', target.name); + } +} + export function legacy_api() { const component = current_component_context?.function; diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 2ead3963c..981f40a21 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -75,6 +75,24 @@ export function component_api_changed(parent, method, component) { } } +/** + * Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information + * @param {string} component + * @param {string} name + * @returns {never} + */ +export function component_api_invalid_new(component, name) { + if (DEV) { + const error = new Error(`${"component_api_invalid_new"}\n${`Attempted to instantiate ${component} with \`new ${name}\`, which is no longer valid in Svelte 5. If this component is not under your control, set the \`legacy.componentApi\` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information`}`); + + error.name = 'Svelte error'; + throw error; + } else { + // TODO print a link to the documentation + throw new Error("component_api_invalid_new"); + } +} + /** * Keyed each block has duplicate key `%value%` at indexes %a% and %b% * @param {string} a diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 65983da3e..1194570c8 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -7,7 +7,7 @@ export { mark_module_end, add_owner_effect } from './dev/ownership.js'; -export { legacy_api } from './dev/legacy.js'; +export { check_target, legacy_api } from './dev/legacy.js'; export { inspect } from './dev/inspect.js'; export { await_block as await } from './dom/blocks/await.js'; export { if_block as if } from './dom/blocks/if.js'; diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index 81b28ce81..5b08103d8 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -70,7 +70,16 @@ import App from './App.svelte' export default app; ``` -If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`. +If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility, which means code using `new Component(...)` keeps working without adjustments (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`. + +```js +/// svelte.config.js +export default { + compilerOptions: { + legacy: { componentApi: true } + } +}; +``` ### Server API changes