Skip to content

Commit

Permalink
Merge branch 'async' into async-changeset
Browse files Browse the repository at this point in the history
  • Loading branch information
Rich-Harris committed Feb 13, 2025
2 parents 3a5895b + ef28490 commit 292f255
Show file tree
Hide file tree
Showing 33 changed files with 321 additions and 135 deletions.
5 changes: 0 additions & 5 deletions .changeset/hip-singers-vanish.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/short-fireants-talk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: ignore typescript abstract methods
2 changes: 0 additions & 2 deletions documentation/docs/06-runtime/03-lifecycle-hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ If a function is returned from `onMount`, it will be called when the component i
## `onDestroy`

> EXPORT_SNIPPET: svelte#onDestroy
Schedules a callback to run immediately before the component is unmounted.

Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the only one that runs inside a server-side component.
Expand Down
18 changes: 18 additions & 0 deletions packages/svelte/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# svelte

## 5.20.0

### Minor Changes

- feat: SSR-safe ID generation with `$props.id()` ([#15185](https://github.com/sveltejs/svelte/pull/15185))

### Patch Changes

- fix: take private and public into account for `constant_assignment` of derived state ([#15276](https://github.com/sveltejs/svelte/pull/15276))

- fix: value/checked not correctly set using spread ([#15239](https://github.com/sveltejs/svelte/pull/15239))

- chore: tweak effect self invalidation logic, run transition dispatches without reactive context ([#15275](https://github.com/sveltejs/svelte/pull/15275))

- fix: use `importNode` to clone templates for Firefox ([#15272](https://github.com/sveltejs/svelte/pull/15272))

- fix: recurse into `$derived` for ownership validation ([#15166](https://github.com/sveltejs/svelte/pull/15166))

## 5.19.10

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "svelte",
"description": "Cybernetically enhanced web apps",
"license": "MIT",
"version": "5.19.10",
"version": "5.20.0",
"type": "module",
"types": "./types/index.d.ts",
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ const visitors = {
delete node.implements;
return context.next();
},
MethodDefinition(node, context) {
if (node.abstract) {
return b.empty;
}
return context.next();
},
VariableDeclaration(node, context) {
if (node.declare) {
return b.empty;
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/compiler/phases/2-analyze/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface AnalysisState {
component_slots: Set<string>;
/** Information about the current expression/directive/block value */
expression: ExpressionMetadata | null;
derived_state: string[];
derived_state: { name: string; private: boolean }[];
function_depth: number;

// legacy stuff
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ export function Attribute(node, context) {
if (node.name === 'value' && parent.name === 'option') {
mark_subtree_dynamic(context.path);
}

// special case <img loading="lazy" />
if (node.name === 'loading' && parent.name === 'img') {
mark_subtree_dynamic(context.path);
}
}

if (is_event_attribute(node)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { get_rune } from '../../scope.js';
* @param {Context} context
*/
export function ClassBody(node, context) {
/** @type {string[]} */
/** @type {{name: string, private: boolean}[]} */
const derived_state = [];

for (const definition of node.body) {
Expand All @@ -18,7 +18,10 @@ export function ClassBody(node, context) {
) {
const rune = get_rune(definition.value, context.state.scope);
if (rune === '$derived' || rune === '$derived.by') {
derived_state.push(definition.key.name);
derived_state.push({
name: definition.key.name,
private: definition.key.type === 'PrivateIdentifier'
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @import { AssignmentExpression, Expression, Literal, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
/** @import { AssignmentExpression, Expression, Identifier, Literal, Node, Pattern, PrivateIdentifier, Super, UpdateExpression, VariableDeclarator } from 'estree' */
/** @import { AST, Binding } from '#compiler' */
/** @import { AnalysisState, Context } from '../../types' */
/** @import { Scope } from '../../../scope' */
Expand Down Expand Up @@ -38,16 +38,22 @@ export function validate_assignment(node, argument, state) {
e.snippet_parameter_assignment(node);
}
}

if (
argument.type === 'MemberExpression' &&
argument.object.type === 'ThisExpression' &&
(((argument.property.type === 'PrivateIdentifier' || argument.property.type === 'Identifier') &&
state.derived_state.includes(argument.property.name)) ||
state.derived_state.some(
(derived) =>
derived.name === /** @type {PrivateIdentifier | Identifier} */ (argument.property).name &&
derived.private === (argument.property.type === 'PrivateIdentifier')
)) ||
(argument.property.type === 'Literal' &&
argument.property.value &&
typeof argument.property.value === 'string' &&
state.derived_state.includes(argument.property.value)))
state.derived_state.some(
(derived) =>
derived.name === /** @type {Literal} */ (argument.property).value && !derived.private
)))
) {
e.constant_assignment(node, 'derived state');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,22 +190,21 @@ export function ClassBody(node, context) {
'method',
b.id('$.ADD_OWNER'),
[b.id('owner')],
Array.from(public_state)
// Only run ownership addition on $state fields.
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`,
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case.
.filter(([_, { kind }]) => kind === 'state')
.map(([name]) =>
b.stmt(
b.call(
'$.add_owner',
b.call('$.get', b.member(b.this, b.private_id(name))),
b.id('owner'),
b.literal(false),
is_ignored(node, 'ownership_invalid_binding') && b.true
)
[
b.stmt(
b.call(
'$.add_owner_to_class',
b.this,
b.id('owner'),
b.array(
Array.from(public_state).map(([name]) =>
b.thunk(b.call('$.get', b.member(b.this, b.private_id(name))))
)
),
is_ignored(node, 'ownership_invalid_binding') && b.true
)
),
)
],
true
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,6 @@ export function RegularElement(node, context) {
build_class_directives(class_directives, node_id, context, is_attributes_reactive);
build_style_directives(style_directives, node_id, context, is_attributes_reactive);

// Apply the src and loading attributes for <img> elements after the element is appended to the document
if (node.name === 'img' && (has_spread || lookup.has('loading'))) {
context.state.after_update.push(b.stmt(b.call('$.handle_lazy_img', node_id)));
}

if (
is_load_error_element(node.name) &&
(has_spread || has_use || lookup.has('onload') || lookup.has('onerror'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,37 +204,18 @@ export function build_component(node, component_name, context, anchor = context.
const expression = /** @type {Expression} */ (context.visit(attribute.expression));

if (dev && attribute.name !== 'this') {
let should_add_owner = true;

if (attribute.expression.type !== 'SequenceExpression') {
const left = object(attribute.expression);

if (left?.type === 'Identifier') {
const binding = context.state.scope.get(left.name);

// Only run ownership addition on $state fields.
// Theoretically someone could create a `$state` while creating `$state.raw` or inside a `$derived.by`,
// but that feels so much of an edge case that it doesn't warrant a perf hit for the common case.
if (binding?.kind === 'derived' || binding?.kind === 'raw_state') {
should_add_owner = false;
}
}
}

if (should_add_owner) {
binding_initializers.push(
b.stmt(
b.call(
b.id('$.add_owner_effect'),
expression.type === 'SequenceExpression'
? expression.expressions[0]
: b.thunk(expression),
b.id(component_name),
is_ignored(node, 'ownership_invalid_binding') && b.true
)
binding_initializers.push(
b.stmt(
b.call(
b.id('$.add_owner_effect'),
expression.type === 'SequenceExpression'
? expression.expressions[0]
: b.thunk(expression),
b.id(component_name),
is_ignored(node, 'ownership_invalid_binding') && b.true
)
);
}
)
);
}

if (expression.type === 'SequenceExpression') {
Expand Down
42 changes: 37 additions & 5 deletions packages/svelte/src/internal/client/dev/ownership.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { render_effect, user_pre_effect } from '../reactivity/effects.js';
import { dev_current_component_function } from '../context.js';
import { get_prototype_of } from '../../shared/utils.js';
import * as w from '../warnings.js';
import { FILENAME } from '../../../constants.js';
import { FILENAME, UNINITIALIZED } from '../../../constants.js';

/** @type {Record<string, Array<{ start: Location, end: Location, component: Function }>>} */
const boundaries = {};
Expand Down Expand Up @@ -140,6 +140,25 @@ export function add_owner_effect(get_object, Component, skip_warning = false) {
});
}

/**
* @param {any} _this
* @param {Function} owner
* @param {Array<() => any>} getters
* @param {boolean} skip_warning
*/
export function add_owner_to_class(_this, owner, getters, skip_warning) {
_this[ADD_OWNER].current ||= getters.map(() => UNINITIALIZED);

for (let i = 0; i < getters.length; i += 1) {
const current = getters[i]();
// For performance reasons we only re-add the owner if the state has changed
if (current !== _this[ADD_OWNER][i]) {
_this[ADD_OWNER].current[i] = current;
add_owner(current, owner, false, skip_warning);
}
}
}

/**
* @param {ProxyMetadata | null} from
* @param {ProxyMetadata} to
Expand Down Expand Up @@ -196,7 +215,19 @@ function add_owner_to_object(object, owner, seen) {
if (proto === Object.prototype) {
// recurse until we find a state proxy
for (const key in object) {
add_owner_to_object(object[key], owner, seen);
if (Object.getOwnPropertyDescriptor(object, key)?.get) {
// Similar to the class case; the getter could update with a new state
let current = UNINITIALIZED;
render_effect(() => {
const next = object[key];
if (current !== next) {
current = next;
add_owner_to_object(next, owner, seen);
}
});
} else {
add_owner_to_object(object[key], owner, seen);
}
}
} else if (proto === Array.prototype) {
// recurse until we find a state proxy
Expand All @@ -221,9 +252,10 @@ function has_owner(metadata, component) {
return (
metadata.owners.has(component) ||
// This helps avoid false positives when using HMR, where the component function is replaced
[...metadata.owners].some(
(owner) => /** @type {any} */ (owner)[FILENAME] === /** @type {any} */ (component)?.[FILENAME]
) ||
(FILENAME in component &&
[...metadata.owners].some(
(owner) => /** @type {any} */ (owner)[FILENAME] === component[FILENAME]
)) ||
(metadata.parent !== null && has_owner(metadata.parent, component))
);
}
Expand Down
38 changes: 8 additions & 30 deletions packages/svelte/src/internal/client/dom/elements/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -399,15 +399,18 @@ export function set_attributes(
if (name === 'value' || name === 'checked') {
// removing value/checked also removes defaultValue/defaultChecked — preserve
let input = /** @type {HTMLInputElement} */ (element);

const use_default = prev === undefined;
if (name === 'value') {
let prev = input.defaultValue;
let previous = input.defaultValue;
input.removeAttribute(name);
input.defaultValue = prev;
input.defaultValue = previous;
// @ts-ignore
input.value = input.__value = use_default ? previous : null;
} else {
let prev = input.defaultChecked;
let previous = input.defaultChecked;
input.removeAttribute(name);
input.defaultChecked = prev;
input.defaultChecked = previous;
input.checked = use_default ? previous : false;
}
} else {
element.removeAttribute(key);
Expand Down Expand Up @@ -520,28 +523,3 @@ function srcset_url_equal(element, srcset) {
)
);
}

/**
* @param {HTMLImageElement} element
* @returns {void}
*/
export function handle_lazy_img(element) {
// If we're using an image that has a lazy loading attribute, we need to apply
// the loading and src after the img element has been appended to the document.
// Otherwise the lazy behaviour will not work due to our cloneNode heuristic for
// templates.
if (!hydrating && element.loading === 'lazy') {
var src = element.src;
// @ts-expect-error
element[LOADING_ATTR_SYMBOL] = null;
element.loading = 'eager';
element.removeAttribute('src');
requestAnimationFrame(() => {
// @ts-expect-error
if (element[LOADING_ATTR_SYMBOL] !== 'eager') {
element.loading = 'lazy';
}
element.src = src;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import { current_each_item } from '../blocks/each.js';
import { TRANSITION_GLOBAL, TRANSITION_IN, TRANSITION_OUT } from '../../../../constants.js';
import { BLOCK_EFFECT, EFFECT_RAN, EFFECT_TRANSPARENT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
import { without_reactive_context } from './bindings/shared.js';

/**
* @param {Element} element
* @param {'introstart' | 'introend' | 'outrostart' | 'outroend'} type
* @returns {void}
*/
function dispatch_event(element, type) {
element.dispatchEvent(new CustomEvent(type));
without_reactive_context(() => {
element.dispatchEvent(new CustomEvent(type));
});
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/svelte/src/internal/client/dom/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export var $window;
/** @type {Document} */
export var $document;

/** @type {boolean} */
export var is_firefox;

/** @type {() => Node | null} */
var first_child_getter;
/** @type {() => Node | null} */
Expand All @@ -29,6 +32,7 @@ export function init_operations() {

$window = window;
$document = document;
is_firefox = /Firefox/.test(navigator.userAgent);

var element_prototype = Element.prototype;
var node_prototype = Node.prototype;
Expand Down
Loading

0 comments on commit 292f255

Please sign in to comment.