Skip to content

chore: transformers #12780

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 88 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
7f56662
clear out getters on new scope
Rich-Harris Aug 6, 2024
d36d9c4
fix
Rich-Harris Aug 6, 2024
ffedcf7
fix
Rich-Harris Aug 6, 2024
002b7f7
fix
Rich-Harris Aug 6, 2024
4fd80f8
fix
Rich-Harris Aug 6, 2024
cfa5419
consolidate legacy_reactive_import logic
Rich-Harris Aug 6, 2024
f4ccd6b
unused
Rich-Harris Aug 6, 2024
0e7884f
$$sanitized_props
Rich-Harris Aug 6, 2024
20f1e66
use getters mechanism for store_sub
Rich-Harris Aug 6, 2024
4315ac3
snapshot
Rich-Harris Aug 7, 2024
4c6120a
fix
Rich-Harris Aug 7, 2024
6b55ee1
tests passing
Rich-Harris Aug 8, 2024
c74ba5a
remove some stuff
Rich-Harris Aug 8, 2024
8e7e95b
more
Rich-Harris Aug 8, 2024
690f012
fix
Rich-Harris Aug 8, 2024
7192ece
tidy up
Rich-Harris Aug 8, 2024
e8ec10d
simplify
Rich-Harris Aug 8, 2024
804772f
simplify
Rich-Harris Aug 8, 2024
8b38326
getters -> transformers
Rich-Harris Aug 8, 2024
099cb88
update
Rich-Harris Aug 8, 2024
65f125e
use update transformers
Rich-Harris Aug 8, 2024
c4683ea
add assign transformer
Rich-Harris Aug 8, 2024
6c8a8f4
more
Rich-Harris Aug 9, 2024
433141d
tweak
Rich-Harris Aug 9, 2024
c4d4052
remove junk
Rich-Harris Aug 9, 2024
37be909
unused
Rich-Harris Aug 9, 2024
e5b1739
simplify
Rich-Harris Aug 9, 2024
75d1350
tidy up
Rich-Harris Aug 9, 2024
361da49
tweak
Rich-Harris Aug 9, 2024
690d3dc
assign_property
Rich-Harris Aug 9, 2024
5189d80
fix
Rich-Harris Aug 9, 2024
1acc3c5
tidy up
Rich-Harris Aug 9, 2024
e9635a5
tidy up
Rich-Harris Aug 9, 2024
e239d23
move store code
Rich-Harris Aug 9, 2024
549a488
this appears to be unused
Rich-Harris Aug 9, 2024
5d1ea4c
tidy up
Rich-Harris Aug 9, 2024
dc6879d
tweak
Rich-Harris Aug 9, 2024
2769d36
simplify
Rich-Harris Aug 9, 2024
420fc63
move code
Rich-Harris Aug 9, 2024
2f1bcb1
move stuff
Rich-Harris Aug 9, 2024
2d99cf7
note to self
Rich-Harris Aug 9, 2024
7a846ac
move stuff
Rich-Harris Aug 9, 2024
c7990ef
each blocks
Rich-Harris Aug 9, 2024
cdd2f24
note to self
Rich-Harris Aug 9, 2024
c7060fd
lengthen stack trace
Rich-Harris Aug 9, 2024
fc7fdb6
tweak
Rich-Harris Aug 9, 2024
ace6134
more
Rich-Harris Aug 9, 2024
81544e0
tidy up
Rich-Harris Aug 9, 2024
e29bee5
tidy up
Rich-Harris Aug 9, 2024
c649da8
remove some junk
Rich-Harris Aug 9, 2024
3d6afdf
tidy up
Rich-Harris Aug 9, 2024
84cc1e7
move stuff
Rich-Harris Aug 9, 2024
41b355e
remove stuff
Rich-Harris Aug 9, 2024
2a7d864
tweak
Rich-Harris Aug 9, 2024
08c0fdd
tweak
Rich-Harris Aug 9, 2024
71e057e
fix
Rich-Harris Aug 10, 2024
833b749
tweak
Rich-Harris Aug 10, 2024
95b517f
tidy up
Rich-Harris Aug 10, 2024
a4d5767
tidy up
Rich-Harris Aug 10, 2024
768db25
tidy up
Rich-Harris Aug 10, 2024
94f6e65
tweak
Rich-Harris Aug 10, 2024
21d787b
simplify
Rich-Harris Aug 10, 2024
0a6e48c
tidy up
Rich-Harris Aug 10, 2024
cff694a
simplify
Rich-Harris Aug 10, 2024
ce4166d
tidy up
Rich-Harris Aug 10, 2024
0a0b039
improve output
Rich-Harris Aug 10, 2024
f1ebccd
delete comments
Rich-Harris Aug 10, 2024
a738c99
more
Rich-Harris Aug 10, 2024
7d1cf54
Merge branch 'main' into transformers
Rich-Harris Aug 10, 2024
a74f663
Merge branch 'main' into transformers
Rich-Harris Aug 10, 2024
771daba
unused
Rich-Harris Aug 10, 2024
8f579d5
tidy up
Rich-Harris Aug 10, 2024
61ad129
tidy up
Rich-Harris Aug 10, 2024
09d714d
fix
Rich-Harris Aug 10, 2024
bf5fb61
move some stuff
Rich-Harris Aug 10, 2024
ee666d5
tweak
Rich-Harris Aug 10, 2024
e9fda1e
tidy up
Rich-Harris Aug 10, 2024
b1eaeae
DRY
Rich-Harris Aug 10, 2024
e7a390d
synchronise
Rich-Harris Aug 10, 2024
7a43542
DRY out
Rich-Harris Aug 10, 2024
1a1b099
tidy up
Rich-Harris Aug 10, 2024
115e285
tidy up
Rich-Harris Aug 10, 2024
97f3bd7
tidy up
Rich-Harris Aug 10, 2024
8a665b9
add test that fails on main
Rich-Harris Aug 10, 2024
b241c6f
snapshot test
Rich-Harris Aug 10, 2024
46c63ad
changesets
Rich-Harris Aug 10, 2024
b8b07e0
lint
Rich-Harris Aug 10, 2024
576763a
ugh
Rich-Harris Aug 10, 2024
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
5 changes: 5 additions & 0 deletions .changeset/nine-ants-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: invalidate signals following ++/-- inside each block
5 changes: 5 additions & 0 deletions .changeset/perfect-hairs-matter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

feat: better code generation for destructuring assignments
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,7 @@ function get_delegated_event(event_name, handler, context) {
// Bail out if we reference anything from the EachBlock (for now) that mutates in non-runes mode,
(((!context.state.analysis.runes && binding.kind === 'each') ||
// or any normal not reactive bindings that are mutated.
binding.kind === 'normal' ||
// or any reactive imports (those are rewritten) (can only happen in legacy mode)
binding.kind === 'legacy_reactive_import') &&
binding.kind === 'normal') &&
binding.mutated))
) {
return unhoisted;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @import { EachBlock } from '#compiler' */
/** @import { Context } from '../types' */
/** @import { Scope } from '../../scope' */
import * as e from '../../../errors.js';
import { validate_block_not_empty, validate_opening_tag } from './shared/utils.js';

Expand All @@ -25,5 +26,13 @@ export function EachBlock(node, context) {
node.key.type !== 'Identifier' || !node.index || node.key.name !== node.index;
}

context.next();
// evaluate expression in parent scope
context.visit(node.expression, {
...context.state,
scope: /** @type {Scope} */ (context.state.scope.parent)
});

context.visit(node.body);
if (node.key) context.visit(node.key);
if (node.fallback) context.visit(node.fallback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,16 +80,8 @@ export function Identifier(node, context) {
binding.declaration_kind !== 'function') ||
binding.declaration_kind === 'import')
) {
if (binding.declaration_kind === 'import') {
if (
binding.mutated &&
// TODO could be more fine-grained - not every mention in the template implies a state binding
(context.state.reactive_statement || context.state.ast_type === 'template') &&
parent.type === 'MemberExpression'
) {
binding.kind = 'legacy_reactive_import';
}
} else if (
if (
binding.declaration_kind !== 'import' &&
binding.mutated &&
// TODO could be more fine-grained - not every mention in the template implies a state binding
(context.state.reactive_statement || context.state.ast_type === 'template')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/** @import { Component, SvelteComponent, SvelteSelf } from '#compiler' */
/** @import { Comment, Component, Fragment, SvelteComponent, SvelteSelf } from '#compiler' */
/** @import { Context } from '../../types' */
import * as e from '../../../../errors.js';
import { get_attribute_expression, is_expression_attribute } from '../../../../utils/ast.js';
import { determine_slot } from '../../../../utils/slot.js';
import {
validate_attribute,
validate_attribute_name,
Expand Down Expand Up @@ -60,9 +61,45 @@ export function visit_component(node, context) {
}
}

context.next({
...context.state,
parent_element: null,
component_slots: new Set()
});
// If the component has a slot attribute — `<Foo slot="whatever" .../>` —
// then `let:` directives apply to other attributes, instead of just the
// top-level contents of the component. Yes, this is very weird.
const default_state = determine_slot(node)
? context.state
: { ...context.state, scope: node.metadata.scopes.default };

for (const attribute of node.attributes) {
context.visit(attribute, attribute.type === 'LetDirective' ? default_state : context.state);
}

/** @type {Comment[]} */
let comments = [];

/** @type {Record<string, Fragment['nodes']>} */
const nodes = { default: [] };

for (const child of node.fragment.nodes) {
if (child.type === 'Comment') {
comments.push(child);
continue;
}

const slot_name = determine_slot(child) ?? 'default';
(nodes[slot_name] ??= []).push(...comments, child);

if (slot_name !== 'default') comments = [];
}

const component_slots = new Set();

for (const slot_name in nodes) {
const state = {
...context.state,
scope: node.metadata.scopes[slot_name],
parent_element: null,
component_slots
};

context.visit({ ...node.fragment, nodes: nodes[slot_name] }, state);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
/** @import { Visitors, ComponentClientTransformState, ClientTransformState } from './types' */
import { walk } from 'zimmerframe';
import * as b from '../../../utils/builders.js';
import { set_scope } from '../../scope.js';
import { build_getter } from './utils.js';
import { render_stylesheet } from '../css/index.js';
import { dev, filename } from '../../../state.js';
Expand All @@ -15,6 +14,7 @@ import { Attribute } from './visitors/Attribute.js';
import { AwaitBlock } from './visitors/AwaitBlock.js';
import { BinaryExpression } from './visitors/BinaryExpression.js';
import { BindDirective } from './visitors/BindDirective.js';
import { BlockStatement } from './visitors/BlockStatement.js';
import { BreakStatement } from './visitors/BreakStatement.js';
import { CallExpression } from './visitors/CallExpression.js';
import { ClassBody } from './visitors/ClassBody.js';
Expand All @@ -37,6 +37,7 @@ import { LabeledStatement } from './visitors/LabeledStatement.js';
import { LetDirective } from './visitors/LetDirective.js';
import { MemberExpression } from './visitors/MemberExpression.js';
import { OnDirective } from './visitors/OnDirective.js';
import { Program } from './visitors/Program.js';
import { RegularElement } from './visitors/RegularElement.js';
import { RenderTag } from './visitors/RenderTag.js';
import { SlotElement } from './visitors/SlotElement.js';
Expand All @@ -58,14 +59,31 @@ import { VariableDeclaration } from './visitors/VariableDeclaration.js';

/** @type {Visitors} */
const visitors = {
_: set_scope,
_: function set_scope(node, { next, state }) {
const scope = state.scopes.get(node);

if (scope && scope !== state.scope) {
const transform = { ...state.transform };

for (const [name, binding] of scope.declarations) {
if (binding.kind === 'normal') {
delete transform[name];
}
}

next({ ...state, transform, scope });
} else {
next();
}
},
AnimateDirective,
ArrowFunctionExpression,
AssignmentExpression,
Attribute,
AwaitBlock,
BinaryExpression,
BindDirective,
BlockStatement,
BreakStatement,
CallExpression,
ClassBody,
Expand All @@ -88,6 +106,7 @@ const visitors = {
LetDirective,
MemberExpression,
OnDirective,
Program,
RegularElement,
RenderTag,
SlotElement,
Expand Down Expand Up @@ -123,6 +142,7 @@ export function client_component(analysis, options) {
is_instance: false,
hoisted: [b.import_all('$', 'svelte/internal/client')],
node: /** @type {any} */ (null), // populated by the root node
legacy_reactive_imports: [],
legacy_reactive_statements: new Map(),
metadata: {
context: {
Expand All @@ -136,8 +156,7 @@ export function client_component(analysis, options) {
preserve_whitespace: options.preserveWhitespace,
public_state: new Map(),
private_state: new Map(),
getters: {},
setters: {},
transform: {},
in_constructor: false,

// these are set inside the `Fragment` visitor, and cannot be used until then
Expand All @@ -155,6 +174,7 @@ export function client_component(analysis, options) {

const instance_state = {
...state,
transform: { ...state.transform },
scope: analysis.instance.scope,
scopes: analysis.instance.scopes,
is_instance: true
Expand All @@ -167,21 +187,17 @@ export function client_component(analysis, options) {
const template = /** @type {ESTree.Program} */ (
walk(
/** @type {SvelteNode} */ (analysis.template.ast),
{ ...state, scope: analysis.instance.scope, scopes: analysis.template.scopes },
{
...state,
transform: instance_state.transform,
scope: analysis.instance.scope,
scopes: analysis.template.scopes
},
visitors
)
);

// Very very dirty way of making import statements reactive in legacy mode if needed
if (!analysis.runes) {
for (const [name, binding] of analysis.module.scope.declarations) {
if (binding.kind === 'legacy_reactive_import') {
instance.body.unshift(
b.var('$$_import_' + name, b.call('$.reactive_import', b.thunk(b.id(name))))
);
}
}
}
instance.body.unshift(...state.legacy_reactive_imports);

/** @type {ESTree.Statement[]} */
const store_setup = [];
Expand Down Expand Up @@ -623,11 +639,9 @@ export function client_module(analysis, options) {
options,
scope: analysis.module.scope,
scopes: analysis.module.scopes,
legacy_reactive_statements: new Map(),
public_state: new Map(),
private_state: new Map(),
getters: {},
setters: {},
transform: {},
in_constructor: false
};

Expand Down
32 changes: 19 additions & 13 deletions packages/svelte/src/compiler/phases/3-transform/client/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type {
Identifier,
PrivateIdentifier,
Expression,
AssignmentExpression
AssignmentExpression,
UpdateExpression
} from 'estree';
import type { Namespace, SvelteNode, ValidatedCompileOptions } from '#compiler';
import type { TransformState } from '../types.js';
Expand All @@ -22,19 +23,18 @@ export interface ClientTransformState extends TransformState {
*/
readonly in_constructor: boolean;

/** The $: calls, which will be ordered in the end */
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
/**
* A map of `[name, node]` pairs, where `Identifier` nodes matching `name`
* will be replaced with `node` (e.g. `x` -> `$.get(x)`)
*/
readonly getters: Record<string, Expression | ((id: Identifier) => Expression)>;
/**
* Counterpart to `getters`
*/
readonly setters: Record<
readonly transform: Record<
string,
(assignment: AssignmentExpression, context: Context) => Expression
{
/** turn `foo` into e.g. `$.get(foo)` */
read: (id: Identifier) => Expression;
/** turn `foo = bar` into e.g. `$.set(foo, bar)` */
assign?: (node: Identifier, value: Expression) => Expression;
/** turn `foo.bar = baz` into e.g. `$.mutate(foo, $.get(foo).bar = baz);` */
mutate?: (node: Identifier, mutation: AssignmentExpression) => Expression;
/** turn `foo++` into e.g. `$.update(foo)` */
update?: (node: UpdateExpression) => Expression;
}
>;
}

Expand Down Expand Up @@ -79,6 +79,12 @@ export interface ComponentClientTransformState extends ClientTransformState {

/** The anchor node for the current context */
readonly node: Identifier;

/** Imports that should be re-evaluated in legacy mode following a mutation */
readonly legacy_reactive_imports: Statement[];

/** The $: calls, which will be ordered in the end */
readonly legacy_reactive_statements: Map<LabeledStatement, Statement>;
}

export interface StateField {
Expand Down
Loading
Loading