mirror of https://github.com/sveltejs/svelte
commit
9c2d613188
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
perf: speed up $.exclude_from_object
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: ensure custom elements do not sync flush on mount
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: correctly handle SvelteDate methods with arguments
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
chore: publish package provenance info
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
"svelte": minor
|
||||
---
|
||||
|
||||
feat: make custom element `tag` property optional
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
feat: treat tag with `.` as a component, even if lowercase
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: improve prop binding warning validation for stores
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: add touch events on microtask to avoid Chromium bug
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: ensure custom element styles append correctly during prod
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: allow deletion of $$restProps properties
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
chore: add error for derived self referencing
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: skip `is_standalone` optimisation for dynamic components
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: invalidate signals following ++/-- inside each block
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: ensure unowned deriveds correctly update
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
feat: better code generation for destructuring assignments
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: propagate custom element component prop changes
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: prevent numerous transition/animation memory leaks
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: order of arguments for `push_element` in `svelte:element`
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: avoid recreating handlers for component events
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: ensure event handlers referencing $host are not hoisted
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: provide more hydration mismatch coverage
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: improved memory profile for transitions/animations
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
feat: simplify derived object destructuring
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
fix: call correct event handler for properties of non-reactive objects
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
'svelte': patch
|
||||
---
|
||||
|
||||
chore: simpler fallback values
|
||||
@ -1,11 +1,129 @@
|
||||
/** @import { AssignmentExpression } from 'estree' */
|
||||
/** @import { Context } from '../types' */
|
||||
import { build_setter } from '../utils.js';
|
||||
/** @import { AssignmentExpression, AssignmentOperator, Expression, Pattern } from 'estree' */
|
||||
/** @import { Context } from '../types.js' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { build_assignment_value } from '../../../../utils/ast.js';
|
||||
import { is_ignored } from '../../../../state.js';
|
||||
import { build_proxy_reassignment, should_proxy_or_freeze } from '../utils.js';
|
||||
import { visit_assignment_expression } from '../../shared/assignments.js';
|
||||
|
||||
/**
|
||||
* @param {AssignmentExpression} node
|
||||
* @param {Context} context
|
||||
*/
|
||||
export function AssignmentExpression(node, context) {
|
||||
return build_setter(node, context, context.next);
|
||||
return visit_assignment_expression(node, context, build_assignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AssignmentOperator} operator
|
||||
* @param {Pattern} left
|
||||
* @param {Expression} right
|
||||
* @param {Context} context
|
||||
* @returns {Expression | null}
|
||||
*/
|
||||
export function build_assignment(operator, left, right, context) {
|
||||
// Handle class private/public state assignment cases
|
||||
if (
|
||||
context.state.analysis.runes &&
|
||||
left.type === 'MemberExpression' &&
|
||||
left.object.type === 'ThisExpression'
|
||||
) {
|
||||
if (left.property.type === 'PrivateIdentifier') {
|
||||
const private_state = context.state.private_state.get(left.property.name);
|
||||
|
||||
if (private_state !== undefined) {
|
||||
let transformed = false;
|
||||
let value = /** @type {Expression} */ (
|
||||
context.visit(build_assignment_value(operator, left, right))
|
||||
);
|
||||
|
||||
if (should_proxy_or_freeze(value, context.state.scope)) {
|
||||
transformed = true;
|
||||
value =
|
||||
private_state.kind === 'frozen_state'
|
||||
? b.call('$.freeze', value)
|
||||
: build_proxy_reassignment(value, private_state.id);
|
||||
}
|
||||
|
||||
if (!context.state.in_constructor) {
|
||||
return b.call('$.set', left, value);
|
||||
} else if (transformed) {
|
||||
return b.assignment(operator, /** @type {Pattern} */ (context.visit(left)), value);
|
||||
}
|
||||
}
|
||||
} else if (left.property.type === 'Identifier' && context.state.in_constructor) {
|
||||
const public_state = context.state.public_state.get(left.property.name);
|
||||
|
||||
if (public_state !== undefined && should_proxy_or_freeze(right, context.state.scope)) {
|
||||
const value = /** @type {Expression} */ (context.visit(right));
|
||||
|
||||
return b.assignment(
|
||||
operator,
|
||||
/** @type {Pattern} */ (context.visit(left)),
|
||||
public_state.kind === 'frozen_state'
|
||||
? b.call('$.freeze', value)
|
||||
: build_proxy_reassignment(value, public_state.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let object = left;
|
||||
|
||||
while (object.type === 'MemberExpression') {
|
||||
// @ts-expect-error
|
||||
object = object.object;
|
||||
}
|
||||
|
||||
if (object.type !== 'Identifier') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const binding = context.state.scope.get(object.name);
|
||||
if (!binding) return null;
|
||||
|
||||
const transform = Object.hasOwn(context.state.transform, object.name)
|
||||
? context.state.transform[object.name]
|
||||
: null;
|
||||
|
||||
// reassignment
|
||||
if (object === left && transform?.assign) {
|
||||
let value = /** @type {Expression} */ (
|
||||
context.visit(build_assignment_value(operator, left, right))
|
||||
);
|
||||
|
||||
// special case — if an element binding, we know it's a primitive
|
||||
const path = context.path.map((node) => node.type);
|
||||
const is_primitive = path.at(-1) === 'BindDirective' && path.at(-2) === 'RegularElement';
|
||||
|
||||
if (
|
||||
!is_primitive &&
|
||||
binding.kind !== 'prop' &&
|
||||
context.state.analysis.runes &&
|
||||
should_proxy_or_freeze(value, context.state.scope)
|
||||
) {
|
||||
value =
|
||||
binding.kind === 'frozen_state'
|
||||
? b.call('$.freeze', value)
|
||||
: build_proxy_reassignment(value, object.name);
|
||||
}
|
||||
|
||||
return transform.assign(object, value);
|
||||
}
|
||||
|
||||
/** @type {Expression} */
|
||||
let mutation = b.assignment(
|
||||
operator,
|
||||
/** @type {Pattern} */ (context.visit(left)),
|
||||
/** @type {Expression} */ (context.visit(right))
|
||||
);
|
||||
|
||||
// mutation
|
||||
if (transform?.mutate) {
|
||||
mutation = transform.mutate(object, mutation);
|
||||
}
|
||||
|
||||
return is_ignored(left, 'ownership_invalid_mutation')
|
||||
? b.call('$.skip_ownership_validation', b.thunk(mutation))
|
||||
: mutation;
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
/** @import { BlockStatement } from 'estree' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import { add_state_transformers } from './shared/declarations.js';
|
||||
|
||||
/**
|
||||
* @param {BlockStatement} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function BlockStatement(node, context) {
|
||||
add_state_transformers(context);
|
||||
context.next();
|
||||
}
|
||||
@ -1,11 +1,38 @@
|
||||
/** @import { OnDirective } from '#compiler' */
|
||||
/** @import { OnDirective, SvelteNode } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import { build_event } from './shared/element.js';
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { build_event, build_event_handler } from './shared/events.js';
|
||||
|
||||
const modifiers = [
|
||||
'stopPropagation',
|
||||
'stopImmediatePropagation',
|
||||
'preventDefault',
|
||||
'self',
|
||||
'trusted',
|
||||
'once'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {OnDirective} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function OnDirective(node, context) {
|
||||
build_event(node, node.metadata.expression, context);
|
||||
if (!node.expression) {
|
||||
context.state.analysis.needs_props = true;
|
||||
}
|
||||
|
||||
let handler = build_event_handler(node.expression, node.metadata.expression, context);
|
||||
|
||||
for (const modifier of modifiers) {
|
||||
if (node.modifiers.includes(modifier)) {
|
||||
handler = b.call('$.' + modifier, handler);
|
||||
}
|
||||
}
|
||||
|
||||
const capture = node.modifiers.includes('capture');
|
||||
const passive =
|
||||
node.modifiers.includes('passive') ||
|
||||
(node.modifiers.includes('nonpassive') ? false : undefined);
|
||||
|
||||
return build_event(node.name, context.state.node, handler, capture, passive);
|
||||
}
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
/** @import { Expression, MemberExpression, Program } from 'estree' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import { build_getter, is_prop_source } from '../utils.js';
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { add_state_transformers } from './shared/declarations.js';
|
||||
|
||||
/**
|
||||
* @param {Program} _
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function Program(_, context) {
|
||||
if (!context.state.analysis.runes) {
|
||||
context.state.transform['$$props'] = {
|
||||
read: (node) => ({ ...node, name: '$$sanitized_props' })
|
||||
};
|
||||
|
||||
for (const [name, binding] of context.state.scope.declarations) {
|
||||
if (binding.declaration_kind === 'import' && binding.mutated) {
|
||||
const id = b.id('$$_import_' + name);
|
||||
|
||||
context.state.transform[name] = {
|
||||
read: (_) => b.call(id),
|
||||
mutate: (_, mutation) => b.call(id, mutation)
|
||||
};
|
||||
|
||||
context.state.legacy_reactive_imports.push(
|
||||
b.var(id, b.call('$.reactive_import', b.thunk(b.id(name))))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [name, binding] of context.state.scope.declarations) {
|
||||
if (binding.kind === 'store_sub') {
|
||||
const store = /** @type {Expression} */ (context.visit(b.id(name.slice(1))));
|
||||
|
||||
context.state.transform[name] = {
|
||||
read: b.call,
|
||||
assign: (_, value) => b.call('$.store_set', store, value),
|
||||
mutate: (node, mutation) => {
|
||||
// We need to untrack the store read, for consistency with Svelte 4
|
||||
const untracked = b.call('$.untrack', node);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Expression} n
|
||||
* @returns {Expression}
|
||||
*/
|
||||
function replace(n) {
|
||||
if (n.type === 'MemberExpression') {
|
||||
return {
|
||||
...n,
|
||||
object: replace(/** @type {Expression} */ (n.object)),
|
||||
property: n.property
|
||||
};
|
||||
}
|
||||
|
||||
return untracked;
|
||||
}
|
||||
|
||||
return b.call(
|
||||
'$.store_mutate',
|
||||
store,
|
||||
b.assignment(
|
||||
mutation.operator,
|
||||
/** @type {MemberExpression} */ (
|
||||
replace(/** @type {MemberExpression} */ (mutation.left))
|
||||
),
|
||||
mutation.right
|
||||
),
|
||||
untracked
|
||||
);
|
||||
},
|
||||
update: (node) => {
|
||||
return b.call(
|
||||
node.prefix ? '$.update_pre_store' : '$.update_store',
|
||||
build_getter(b.id(name.slice(1)), context.state),
|
||||
b.call(node.argument),
|
||||
node.operator === '--' && b.literal(-1)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
|
||||
if (is_prop_source(binding, context.state)) {
|
||||
context.state.transform[name] = {
|
||||
read: b.call,
|
||||
assign: (node, value) => b.call(node, value),
|
||||
mutate: (node, value) => {
|
||||
if (binding.kind === 'bindable_prop') {
|
||||
// only necessary for interop with legacy parent bindings
|
||||
return b.call(node, value, b.true);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
update: (node) => {
|
||||
return b.call(
|
||||
node.prefix ? '$.update_pre_prop' : '$.update_prop',
|
||||
node.argument,
|
||||
node.operator === '--' && b.literal(-1)
|
||||
);
|
||||
}
|
||||
};
|
||||
} else if (binding.prop_alias) {
|
||||
const key = b.key(binding.prop_alias);
|
||||
|
||||
context.state.transform[name] = {
|
||||
read: (_) => b.member(b.id('$$props'), key, key.type === 'Literal')
|
||||
};
|
||||
} else {
|
||||
context.state.transform[name] = {
|
||||
read: (node) => b.member(b.id('$$props'), node)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_state_transformers(context);
|
||||
|
||||
context.next();
|
||||
}
|
||||
@ -1,14 +1,11 @@
|
||||
/** @import { SvelteBody } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { visit_special_element } from './shared/special_element.js';
|
||||
|
||||
/**
|
||||
* @param {SvelteBody} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function SvelteBody(node, context) {
|
||||
context.next({
|
||||
...context.state,
|
||||
node: b.id('$.document.body')
|
||||
});
|
||||
visit_special_element(node, '$.document.body', context);
|
||||
}
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
/** @import { SvelteDocument } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { visit_special_element } from './shared/special_element.js';
|
||||
|
||||
/**
|
||||
* @param {SvelteDocument} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function SvelteDocument(node, context) {
|
||||
context.next({
|
||||
...context.state,
|
||||
node: b.id('$.document')
|
||||
});
|
||||
visit_special_element(node, '$.document', context);
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { SvelteWindow } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../types' */
|
||||
import * as b from '../../../../utils/builders.js';
|
||||
import { visit_special_element } from './shared/special_element.js';
|
||||
|
||||
/**
|
||||
* @param {SvelteWindow} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function SvelteWindow(node, context) {
|
||||
context.next({
|
||||
...context.state,
|
||||
node: b.id('$.window')
|
||||
});
|
||||
visit_special_element(node, '$.window', context);
|
||||
}
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
/** @import { BinaryOperator, Expression, Identifier } from 'estree' */
|
||||
/** @import { ComponentContext, Context } from '../../types' */
|
||||
import { build_proxy_reassignment, is_state_source, should_proxy_or_freeze } from '../../utils.js';
|
||||
import * as b from '../../../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
* Turns `foo` into `$.get(foo)`
|
||||
* @param {Identifier} node
|
||||
*/
|
||||
export function get_value(node) {
|
||||
return b.call('$.get', node);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Context | ComponentContext} context
|
||||
*/
|
||||
export function add_state_transformers(context) {
|
||||
for (const [name, binding] of context.state.scope.declarations) {
|
||||
if (
|
||||
is_state_source(binding, context.state) ||
|
||||
binding.kind === 'derived' ||
|
||||
binding.kind === 'legacy_reactive'
|
||||
) {
|
||||
context.state.transform[name] = {
|
||||
read: get_value,
|
||||
assign: (node, value) => {
|
||||
let call = b.call('$.set', node, value);
|
||||
|
||||
if (context.state.scope.get(`$${node.name}`)?.kind === 'store_sub') {
|
||||
call = b.call('$.store_unsub', call, b.literal(`$${node.name}`), b.id('$$stores'));
|
||||
}
|
||||
|
||||
return call;
|
||||
},
|
||||
mutate: (node, mutation) => {
|
||||
if (context.state.analysis.runes) {
|
||||
return mutation;
|
||||
}
|
||||
|
||||
return b.call('$.mutate', node, mutation);
|
||||
},
|
||||
update: (node) => {
|
||||
return b.call(
|
||||
node.prefix ? '$.update_pre' : '$.update',
|
||||
node.argument,
|
||||
node.operator === '--' && b.literal(-1)
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,144 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { Attribute, ExpressionMetadata, ExpressionTag, OnDirective, SvelteNode } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../../types' */
|
||||
import { is_capture_event, is_passive_event } from '../../../../../../utils.js';
|
||||
import * as b from '../../../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
* @param {Attribute} node
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function visit_event_attribute(node, context) {
|
||||
let capture = false;
|
||||
|
||||
let event_name = node.name.slice(2);
|
||||
if (is_capture_event(event_name)) {
|
||||
event_name = event_name.slice(0, -7);
|
||||
capture = true;
|
||||
}
|
||||
|
||||
// we still need to support the weird `onclick="{() => {...}}" form
|
||||
const tag = Array.isArray(node.value)
|
||||
? /** @type {ExpressionTag} */ (node.value[0])
|
||||
: /** @type {ExpressionTag} */ (node.value);
|
||||
|
||||
let handler = build_event_handler(tag.expression, tag.metadata.expression, context);
|
||||
|
||||
if (node.metadata.delegated) {
|
||||
let delegated_assignment;
|
||||
|
||||
if (!context.state.events.has(event_name)) {
|
||||
context.state.events.add(event_name);
|
||||
}
|
||||
|
||||
// Hoist function if we can, otherwise we leave the function as is
|
||||
if (node.metadata.delegated.hoisted) {
|
||||
if (node.metadata.delegated.function === tag.expression) {
|
||||
const func_name = context.state.scope.root.unique('on_' + event_name);
|
||||
context.state.hoisted.push(b.var(func_name, handler));
|
||||
handler = func_name;
|
||||
}
|
||||
|
||||
const hoisted_params = /** @type {Expression[]} */ (
|
||||
node.metadata.delegated.function.metadata.hoisted_params
|
||||
);
|
||||
|
||||
// When we hoist a function we assign an array with the function and all
|
||||
// hoisted closure params.
|
||||
const args = [handler, ...hoisted_params];
|
||||
delegated_assignment = b.array(args);
|
||||
} else {
|
||||
delegated_assignment = handler;
|
||||
}
|
||||
|
||||
context.state.init.push(
|
||||
b.stmt(
|
||||
b.assignment(
|
||||
'=',
|
||||
b.member(context.state.node, b.id('__' + event_name)),
|
||||
delegated_assignment
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const statement = b.stmt(
|
||||
build_event(event_name, context.state.node, handler, capture, undefined)
|
||||
);
|
||||
|
||||
const type = /** @type {SvelteNode} */ (context.path.at(-1)).type;
|
||||
|
||||
if (type === 'SvelteDocument' || type === 'SvelteWindow' || type === 'SvelteBody') {
|
||||
// These nodes are above the component tree, and its events should run parent first
|
||||
context.state.init.push(statement);
|
||||
} else {
|
||||
context.state.after_update.push(statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `$.event(...)` call for non-delegated event handlers
|
||||
* @param {string} event_name
|
||||
* @param {Expression} node
|
||||
* @param {Expression} handler
|
||||
* @param {boolean} capture
|
||||
* @param {boolean | undefined} passive
|
||||
*/
|
||||
export function build_event(event_name, node, handler, capture, passive) {
|
||||
return b.call(
|
||||
'$.event',
|
||||
b.literal(event_name),
|
||||
node,
|
||||
handler,
|
||||
capture && b.true,
|
||||
passive === undefined ? undefined : b.literal(passive)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event handler
|
||||
* @param {Expression | null} node
|
||||
* @param {ExpressionMetadata} metadata
|
||||
* @param {ComponentContext} context
|
||||
* @returns {Expression}
|
||||
*/
|
||||
export function build_event_handler(node, metadata, context) {
|
||||
if (node === null) {
|
||||
// bubble event
|
||||
return b.function(
|
||||
null,
|
||||
[b.id('$$arg')],
|
||||
b.block([b.stmt(b.call('$.bubble_event.call', b.this, b.id('$$props'), b.id('$$arg')))])
|
||||
);
|
||||
}
|
||||
|
||||
let handler = /** @type {Expression} */ (context.visit(node));
|
||||
|
||||
// inline handler
|
||||
if (handler.type === 'ArrowFunctionExpression' || handler.type === 'FunctionExpression') {
|
||||
return handler;
|
||||
}
|
||||
|
||||
// function declared in the script
|
||||
if (
|
||||
handler.type === 'Identifier' &&
|
||||
context.state.scope.get(handler.name)?.declaration_kind !== 'import'
|
||||
) {
|
||||
return handler;
|
||||
}
|
||||
|
||||
if (metadata.has_call) {
|
||||
// memoize where necessary
|
||||
const id = b.id(context.state.scope.generate('event_handler'));
|
||||
|
||||
context.state.init.push(b.var(id, b.call('$.derived', b.thunk(handler))));
|
||||
handler = b.call('$.get', id);
|
||||
}
|
||||
|
||||
// wrap the handler in a function, so the expression is re-evaluated for each event
|
||||
return b.function(
|
||||
null,
|
||||
[b.rest(b.id('$$args'))],
|
||||
b.block([b.stmt(b.call(b.member(handler, b.id('apply'), false, true), b.this, b.id('$$args')))])
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
/** @import { Expression } from 'estree' */
|
||||
/** @import { SvelteBody, SvelteDocument, SvelteWindow } from '#compiler' */
|
||||
/** @import { ComponentContext } from '../../types' */
|
||||
import { is_event_attribute } from '../../../../../utils/ast.js';
|
||||
import * as b from '../../../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {SvelteBody | SvelteDocument | SvelteWindow} node
|
||||
* @param {string} id
|
||||
* @param {ComponentContext} context
|
||||
*/
|
||||
export function visit_special_element(node, id, context) {
|
||||
const state = { ...context.state, node: b.id(id) };
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type === 'OnDirective') {
|
||||
context.state.init.push(b.stmt(/** @type {Expression} */ (context.visit(attribute, state))));
|
||||
} else {
|
||||
context.visit(attribute, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
/** @import { AssignmentExpression, AssignmentOperator, Expression, Node, Pattern } from 'estree' */
|
||||
/** @import { Context as ClientContext } from '../client/types.js' */
|
||||
/** @import { Context as ServerContext } from '../server/types.js' */
|
||||
import { extract_paths, is_expression_async } from '../../../utils/ast.js';
|
||||
import * as b from '../../../utils/builders.js';
|
||||
|
||||
/**
|
||||
* @template {ClientContext | ServerContext} Context
|
||||
* @param {AssignmentExpression} node
|
||||
* @param {Context} context
|
||||
* @param {(operator: AssignmentOperator, left: Pattern, right: Expression, context: Context) => Expression | null} build_assignment
|
||||
* @returns
|
||||
*/
|
||||
export function visit_assignment_expression(node, context, build_assignment) {
|
||||
if (
|
||||
node.left.type === 'ArrayPattern' ||
|
||||
node.left.type === 'ObjectPattern' ||
|
||||
node.left.type === 'RestElement'
|
||||
) {
|
||||
const value = /** @type {Expression} */ (context.visit(node.right));
|
||||
const should_cache = value.type !== 'Identifier';
|
||||
const rhs = should_cache ? b.id('$$value') : value;
|
||||
|
||||
let changed = false;
|
||||
|
||||
const assignments = extract_paths(node.left).map((path) => {
|
||||
const value = path.expression?.(rhs);
|
||||
|
||||
let assignment = build_assignment('=', path.node, value, context);
|
||||
if (assignment !== null) changed = true;
|
||||
|
||||
return (
|
||||
assignment ??
|
||||
b.assignment(
|
||||
'=',
|
||||
/** @type {Pattern} */ (context.visit(path.node)),
|
||||
/** @type {Expression} */ (context.visit(value))
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
if (!changed) {
|
||||
// No change to output -> nothing to transform -> we can keep the original assignment
|
||||
return context.next();
|
||||
}
|
||||
|
||||
const is_standalone = /** @type {Node} */ (context.path.at(-1)).type.endsWith('Statement');
|
||||
const sequence = b.sequence(assignments);
|
||||
|
||||
if (!is_standalone) {
|
||||
// this is part of an expression, we need the sequence to end with the value
|
||||
sequence.expressions.push(rhs);
|
||||
}
|
||||
|
||||
if (should_cache) {
|
||||
// the right hand side is a complex expression, wrap in an IIFE to cache it
|
||||
const iife = b.arrow([rhs], sequence);
|
||||
|
||||
const iife_is_async =
|
||||
is_expression_async(value) ||
|
||||
assignments.some((assignment) => is_expression_async(assignment));
|
||||
|
||||
return iife_is_async ? b.await(b.call(b.async(iife), value)) : b.call(iife, value);
|
||||
}
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
if (node.left.type !== 'Identifier' && node.left.type !== 'MemberExpression') {
|
||||
throw new Error(`Unexpected assignment type ${node.left.type}`);
|
||||
}
|
||||
|
||||
return (
|
||||
build_assignment(node.operator, node.left, node.right, context) ??
|
||||
/** @type {Expression} */ (context.next())
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
/** @import { SvelteNode } from '#compiler' */
|
||||
import { is_element_node } from '../phases/nodes.js';
|
||||
import { is_text_attribute } from './ast.js';
|
||||
|
||||
/**
|
||||
* @param {SvelteNode} node
|
||||
*/
|
||||
export function determine_slot(node) {
|
||||
if (!is_element_node(node)) return null;
|
||||
|
||||
for (const attribute of node.attributes) {
|
||||
if (attribute.type !== 'Attribute') continue;
|
||||
if (attribute.name !== 'slot') continue;
|
||||
if (!is_text_attribute(attribute)) continue;
|
||||
|
||||
return /** @type {string} */ (attribute.value[0].data);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue