Skip to content

Commit e8ce418

Browse files
chore: simplify transitions (#10798)
* replace transition code * get rid of some stuff * simplify * remove some junk * not sure how we solved this before, but i guess this makes sense * oh hey i don't think we need this * make elseif transparent for transition purposes * oops * edge case * fix * do not want * rename * transition out ecah blocks when emptying * baby steps * hydration fix * tidy up * tidy up * tidy up * fallbacks * man i love deleting code * tidy up * note to self * why was this an effect * tidy up * tidy up * key blocks * temporary * fix * WIP * fix * simplify * emit events * delete delete delete * destroy child effects * simplify helper * simplify helper * fix * remove commented out code * fix wonky test * fix test * fix test * fix test * dynamic components * fix test * await * tidy up * fix * fix * fix test * tidy up * we dont need to reconcile during hydration * simplify * tidy up * fix * reduce indentation * simplify * remove some junk * remove some junk * simplify * tidy up * prefer while over do-while (this appears to have the same behaviour) * group fast paths * rename * unused import * unused exports * try this * simplify * simplify * simplify * simplify * tidy up * simplify * simplify * tidy up * simplify * simplify * more compact names * simplify * better comments * simplify * tidy up * we don't actually gain anything from this * fix binding group order bug (revealed by previous commit, but exists separately from it) * tidy up * simplify * tidy up * remove some junk * simplify * note to self * tidy up * revert this bit * tidy up * simplify * simplify * simplify * symmetry * tidy up * var * rename some stuff * tidy up * simplify * keyed each transitions * make elements inert * deferred transitions * fix * fix test * fix some tests * simplify * fix * fix test * fix * eh that'll do for now * fix * revert all these random changes * fix * fix * simplify * tidy up * simplify * simplify * tidy up * tidy up * tidy up * WIP * WIP * working * tidy up * fix * tidy up * tidy up * lerp * tidy up * rename * rename * almost everything working * tidy up * ALL TESTS PASSING * fix treeshaking * Apply suggestions from code review Co-authored-by: Simon H <[email protected]> * comment * explain elseif locality * explain flushSync * comments * this is accounted for * add some comments * remove outdated comment * add comment * add comments * rename * a few naming tweaks * explain each_item_block stuff * remove unused arg * optimise * add some comments * fix test post-optimisation * explicit comparisons * some docs * fix intro events * move effect.ran into the bitmask * docs * rename run_transitions to should_intro, add explanatory jsdoc * add some more docs * remove animation before measuring * only animate blocks that persist * note to self --------- Co-authored-by: Simon H <[email protected]>
1 parent f6eca83 commit e8ce418

File tree

47 files changed

+1608
-2462
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1608
-2462
lines changed

packages/svelte/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@
1111
/motion.d.ts
1212
/store.d.ts
1313
/transition.d.ts
14+
15+
/scripts/_bundle.js

packages/svelte/scripts/check-treeshakeability.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ for (const key in pkg.exports) {
7474
}
7575

7676
const client_main = path.resolve(pkg.exports['.'].browser);
77-
const without_hydration = await bundle_code(
77+
const bundle = await bundle_code(
7878
// Use all features which contain hydration code to ensure it's treeshakeable
7979
compile(
8080
`
@@ -109,15 +109,17 @@ const without_hydration = await bundle_code(
109109
).js.code
110110
);
111111

112-
if (!without_hydration.includes('current_hydration_fragment')) {
112+
if (!bundle.includes('current_hydration_fragment')) {
113113
// eslint-disable-next-line no-console
114114
console.error(`✅ Hydration code treeshakeable`);
115115
} else {
116116
// eslint-disable-next-line no-console
117-
console.error(without_hydration);
117+
console.error(bundle);
118118
// eslint-disable-next-line no-console
119119
console.error(`❌ Hydration code not treeshakeable`);
120120
failed = true;
121+
122+
fs.writeFileSync('scripts/_bundle.js', bundle);
121123
}
122124

123125
// eslint-disable-next-line no-console

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,14 @@ import {
2828
AttributeAliases,
2929
DOMBooleanAttributes,
3030
EACH_INDEX_REACTIVE,
31+
EACH_IS_ANIMATED,
3132
EACH_IS_CONTROLLED,
3233
EACH_IS_STRICT_EQUALS,
3334
EACH_ITEM_REACTIVE,
34-
EACH_KEYED
35+
EACH_KEYED,
36+
TRANSITION_GLOBAL,
37+
TRANSITION_IN,
38+
TRANSITION_OUT
3539
} from '../../../../../constants.js';
3640
import { regex_is_valid_identifier } from '../../../patterns.js';
3741
import { javascript_visitors_runes } from './javascript-runes.js';
@@ -1922,7 +1926,7 @@ export const template_visitors = {
19221926
state.init.push(
19231927
b.stmt(
19241928
b.call(
1925-
'$.animate',
1929+
'$.animation',
19261930
state.node,
19271931
b.thunk(
19281932
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
@@ -1939,25 +1943,21 @@ export const template_visitors = {
19391943
error(node, 'INTERNAL', 'Node should have been handled elsewhere');
19401944
},
19411945
TransitionDirective(node, { state, visit }) {
1942-
const type = node.intro && node.outro ? '$.transition' : node.intro ? '$.in' : '$.out';
1943-
const expression =
1944-
node.expression === null
1945-
? b.literal(null)
1946-
: b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression)));
1946+
let flags = node.modifiers.includes('global') ? TRANSITION_GLOBAL : 0;
1947+
if (node.intro) flags |= TRANSITION_IN;
1948+
if (node.outro) flags |= TRANSITION_OUT;
19471949

1948-
state.init.push(
1949-
b.stmt(
1950-
b.call(
1951-
type,
1952-
state.node,
1953-
b.thunk(
1954-
/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name)))
1955-
),
1956-
expression,
1957-
node.modifiers.includes('global') ? b.true : b.false
1958-
)
1959-
)
1960-
);
1950+
const args = [
1951+
b.literal(flags),
1952+
state.node,
1953+
b.thunk(/** @type {import('estree').Expression} */ (visit(parse_directive_name(node.name))))
1954+
];
1955+
1956+
if (node.expression) {
1957+
args.push(b.thunk(/** @type {import('estree').Expression} */ (visit(node.expression))));
1958+
}
1959+
1960+
state.init.push(b.stmt(b.call('$.transition', ...args)));
19611961
},
19621962
RegularElement(node, context) {
19631963
if (node.name === 'noscript') {
@@ -2345,6 +2345,19 @@ export const template_visitors = {
23452345
each_type |= EACH_ITEM_REACTIVE;
23462346
}
23472347

2348+
// Since `animate:` can only appear on elements that are the sole child of a keyed each block,
2349+
// we can determine at compile time whether the each block is animated or not (in which
2350+
// case it should measure animated elements before and after reconciliation).
2351+
if (
2352+
node.key &&
2353+
node.body.nodes.some((child) => {
2354+
if (child.type !== 'RegularElement' && child.type !== 'SvelteElement') return false;
2355+
return child.attributes.some((attr) => attr.type === 'AnimateDirective');
2356+
})
2357+
) {
2358+
each_type |= EACH_IS_ANIMATED;
2359+
}
2360+
23482361
if (each_node_meta.is_controlled) {
23492362
each_type |= EACH_IS_CONTROLLED;
23502363
}
@@ -2557,22 +2570,44 @@ export const template_visitors = {
25572570
context.visit(node.consequent)
25582571
);
25592572

2560-
context.state.after_update.push(
2561-
b.stmt(
2562-
b.call(
2563-
'$.if',
2564-
context.state.node,
2565-
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.test))),
2566-
b.arrow([b.id('$$anchor')], consequent),
2567-
node.alternate
2568-
? b.arrow(
2569-
[b.id('$$anchor')],
2570-
/** @type {import('estree').BlockStatement} */ (context.visit(node.alternate))
2571-
)
2572-
: b.literal(null)
2573-
)
2574-
)
2575-
);
2573+
const args = [
2574+
context.state.node,
2575+
b.thunk(/** @type {import('estree').Expression} */ (context.visit(node.test))),
2576+
b.arrow([b.id('$$anchor')], consequent),
2577+
node.alternate
2578+
? b.arrow(
2579+
[b.id('$$anchor')],
2580+
/** @type {import('estree').BlockStatement} */ (context.visit(node.alternate))
2581+
)
2582+
: b.literal(null)
2583+
];
2584+
2585+
if (node.elseif) {
2586+
// We treat this...
2587+
//
2588+
// {#if x}
2589+
// ...
2590+
// {:else}
2591+
// {#if y}
2592+
// <div transition:foo>...</div>
2593+
// {/if}
2594+
// {/if}
2595+
//
2596+
// ...slightly differently to this...
2597+
//
2598+
// {#if x}
2599+
// ...
2600+
// {:else if y}
2601+
// <div transition:foo>...</div>
2602+
// {/if}
2603+
//
2604+
// ...even though they're logically equivalent. In the first case, the
2605+
// transition will only play when `y` changes, but in the second it
2606+
// should play when `x` or `y` change — both are considered 'local'
2607+
args.push(b.literal(true));
2608+
}
2609+
2610+
context.state.after_update.push(b.stmt(b.call('$.if', ...args)));
25762611
},
25772612
AwaitBlock(node, context) {
25782613
context.state.template.push('<!>');

packages/svelte/src/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export const PROPS_IS_RUNES = 1 << 1;
1212
export const PROPS_IS_UPDATED = 1 << 2;
1313
export const PROPS_IS_LAZY_INITIAL = 1 << 3;
1414

15+
export const TRANSITION_IN = 1;
16+
export const TRANSITION_OUT = 1 << 1;
17+
export const TRANSITION_GLOBAL = 1 << 2;
18+
1519
/** List of Element events that will be delegated */
1620
export const DelegatedEvents = [
1721
'beforeinput',

packages/svelte/src/internal/client/constants.js

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,8 @@ export const DIRTY = 1 << 9;
99
export const MAYBE_DIRTY = 1 << 10;
1010
export const INERT = 1 << 11;
1111
export const DESTROYED = 1 << 12;
12-
13-
export const ROOT_BLOCK = 0;
14-
export const IF_BLOCK = 1;
15-
export const EACH_BLOCK = 2;
16-
export const EACH_ITEM_BLOCK = 3;
17-
export const AWAIT_BLOCK = 4;
18-
export const KEY_BLOCK = 5;
19-
export const HEAD_BLOCK = 6;
20-
export const DYNAMIC_COMPONENT_BLOCK = 7;
21-
export const DYNAMIC_ELEMENT_BLOCK = 8;
22-
export const SNIPPET_BLOCK = 9;
12+
export const IS_ELSEIF = 1 << 13;
13+
export const EFFECT_RAN = 1 << 14;
2314

2415
export const UNINITIALIZED = Symbol();
2516
export const STATE_SYMBOL = Symbol('$state');

0 commit comments

Comments
 (0)