Skip to content

chore: remove stack-based module boundaries #15711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ A component is attempting to bind to a non-bindable property `%key%` belonging t
### component_api_changed

```
%parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
```

See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/messages/client-errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

## component_api_changed

> %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
> Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5

See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more information.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,9 +536,6 @@ export function client_component(analysis, options) {
b.assignment('=', b.member(b.id(analysis.name), '$.FILENAME', true), b.literal(filename))
)
);

body.unshift(b.stmt(b.call(b.id('$.mark_module_start'))));
body.push(b.stmt(b.call(b.id('$.mark_module_end'), b.id(analysis.name))));
}

if (!analysis.runes) {
Expand Down
5 changes: 1 addition & 4 deletions packages/svelte/src/internal/client/dev/legacy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as e from '../errors.js';
import { component_context } from '../context.js';
import { FILENAME } from '../../../constants.js';
import { get_component } from './ownership.js';

/** @param {Function & { [FILENAME]: string }} target */
export function check_target(target) {
Expand All @@ -15,9 +14,7 @@ export function legacy_api() {

/** @param {string} method */
function error(method) {
// @ts-expect-error
const parent = get_component()?.[FILENAME] ?? 'Something';
e.component_api_changed(parent, method, component[FILENAME]);
e.component_api_changed(method, component[FILENAME]);
}

return {
Expand Down
97 changes: 0 additions & 97 deletions packages/svelte/src/internal/client/dev/ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,103 +7,6 @@ import { component_context } from '../context.js';
import * as w from '../warnings.js';
import { sanitize_location } from '../../../utils.js';

/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
const boundaries = {};

const chrome_pattern = /at (?:.+ \()?(.+):(\d+):(\d+)\)?$/;
const firefox_pattern = /@(.+):(\d+):(\d+)$/;

function get_stack() {
const stack = new Error().stack;
if (!stack) return null;

const entries = [];

for (const line of stack.split('\n')) {
let match = chrome_pattern.exec(line) ?? firefox_pattern.exec(line);

if (match) {
entries.push({
file: match[1],
line: +match[2],
column: +match[3]
});
}
}

return entries;
}

/**
* Determines which `.svelte` component is responsible for a given state change
* @returns {Function | null}
*/
export function get_component() {
// first 4 lines are svelte internals; adjust this number if we change the internal call stack
const stack = get_stack()?.slice(4);
if (!stack) return null;

for (let i = 0; i < stack.length; i++) {
const entry = stack[i];
const modules = boundaries[entry.file];
if (!modules) {
// If the first entry is not a component, that means the modification very likely happened
// within a .svelte.js file, possibly triggered by a component. Since these files are not part
// of the bondaries/component context heuristic, we need to bail in this case, else we would
// have false positives when the .svelte.ts file provides a state creator function, encapsulating
// the state and its mutations, and is being called from a component other than the one who
// called the state creator function.
if (i === 0) return null;
continue;
}

for (const module of modules) {
if (module.end == null) {
return null;
}
if (module.start.line < entry.line && module.end.line > entry.line) {
return module.component;
}
}
}

return null;
}

/**
* Together with `mark_module_end`, this function establishes the boundaries of a `.svelte` file,
* such that subsequent calls to `get_component` can tell us which component is responsible
* for a given state change
*/
export function mark_module_start() {
const start = get_stack()?.[2];

if (start) {
(boundaries[start.file] ??= []).push({
start,
// @ts-expect-error
end: null,
// @ts-expect-error we add the component at the end, since HMR will overwrite the function
component: null
});
}
}

/**
* @param {Function} component
*/
export function mark_module_end(component) {
const end = get_stack()?.[2];

if (end) {
const boundaries_file = boundaries[end.file];
const boundary = boundaries_file[boundaries_file.length - 1];

boundary.end = end;
boundary.component = component;
}
}

/**
* Sets up a validator that
* - traverses the path of a prop to find out if it is allowed to be mutated
Expand Down
7 changes: 3 additions & 4 deletions packages/svelte/src/internal/client/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,14 @@ export function bind_not_bindable(key, component, name) {
}

/**
* %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5
* @param {string} parent
* Calling `%method%` on a component instance (of %component%) is no longer valid in Svelte 5
* @param {string} method
* @param {string} component
* @returns {never}
*/
export function component_api_changed(parent, method, component) {
export function component_api_changed(method, component) {
if (DEV) {
const error = new Error(`component_api_changed\n${parent} called \`${method}\` on an instance of ${component}, which is no longer valid in Svelte 5\nhttps://svelte.dev/e/component_api_changed`);
const error = new Error(`component_api_changed\nCalling \`${method}\` on a component instance (of ${component}) is no longer valid in Svelte 5\nhttps://svelte.dev/e/component_api_changed`);

error.name = 'Svelte error';
throw error;
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export { assign, assign_and, assign_or, assign_nullish } from './dev/assign.js';
export { cleanup_styles } from './dev/css.js';
export { add_locations } from './dev/elements.js';
export { hmr } from './dev/hmr.js';
export { mark_module_start, mark_module_end, create_ownership_validator } from './dev/ownership.js';
export { create_ownership_validator } from './dev/ownership.js';
export { check_target, legacy_api } from './dev/legacy.js';
export { trace } from './dev/tracing.js';
export { inspect } from './dev/inspect.js';
Expand Down