Skip to content

Code golf each transitions #9476

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 2 commits into from
Nov 15, 2023
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
5 changes: 5 additions & 0 deletions .changeset/cuddly-pianos-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: improve bundle code size
135 changes: 10 additions & 125 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,10 @@ import {
flushSync,
expose,
safe_not_equal,
managed_pre_effect,
current_block,
set_signal_value,
source,
managed_effect,
mark_subtree_inert,
safe_equal,
push,
current_component_context,
Expand All @@ -72,7 +70,7 @@ import {
} from './hydration.js';
import { array_from, define_property, get_descriptor, get_descriptors, is_array } from './utils.js';
import { is_promise } from '../common.js';
import { bind_transition } from './transitions.js';
import { bind_transition, remove_in_transitions, trigger_transitions } from './transitions.js';

/** @type {Set<string>} */
const all_registerd_events = new Set();
Expand All @@ -81,7 +79,7 @@ const all_registerd_events = new Set();
const root_event_handles = new Set();

/** @returns {Text} */
function empty() {
export function empty() {
return document.createTextNode('');
}

Expand Down Expand Up @@ -1369,11 +1367,7 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
consequent_transitions.add(transition);
transition.finished(() => {
consequent_transitions.delete(transition);
for (let other of consequent_transitions) {
if (other.direction === 'in') {
consequent_transitions.delete(other);
}
}
remove_in_transitions(consequent_transitions);
if (consequent_transitions.size === 0) {
execute_effect(consequent_effect);
}
Expand All @@ -1382,11 +1376,7 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
alternate_transitions.add(transition);
transition.finished(() => {
alternate_transitions.delete(transition);
for (let other of alternate_transitions) {
if (other.direction === 'in') {
alternate_transitions.delete(other);
}
}
remove_in_transitions(alternate_transitions);
if (alternate_transitions.size === 0) {
execute_effect(alternate_effect);
}
Expand Down Expand Up @@ -1674,11 +1664,7 @@ export function component(anchor_node, component_fn, render_fn) {
transitions.add(transition);
transition.finished(() => {
transitions.delete(transition);
for (let other of transitions) {
if (other.direction === 'in') {
transitions.delete(other);
}
}
remove_in_transitions(transitions);
if (transitions.size === 0) {
if (render.effect !== null) {
if (render.dom !== null) {
Expand Down Expand Up @@ -1805,11 +1791,7 @@ function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
transitions.add(transition);
transition.finished(() => {
transitions.delete(transition);
for (let other of transitions) {
if (other.direction === 'in') {
transitions.delete(other);
}
}
remove_in_transitions(transitions);
if (transitions.size === 0) {
if (render.effect !== null) {
if (render.dom !== null) {
Expand Down Expand Up @@ -1863,6 +1845,7 @@ function await_block(anchor_node, input, pending_fn, then_fn, catch_fn) {
return;
}
const transitions = render.transitions;
remove_in_transitions(transitions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how come we added this here and on line 1994? was there a bug before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just noticed we weren't doing it lol. We should have been.

if (transitions.size === 0) {
if (render.dom !== null) {
remove(render.dom);
Expand Down Expand Up @@ -1967,11 +1950,7 @@ export function key(anchor_node, key, render_fn) {
transitions.add(transition);
transition.finished(() => {
transitions.delete(transition);
for (let other of transitions) {
if (other.direction === 'in') {
transitions.delete(other);
}
}
remove_in_transitions(transitions);
if (transitions.size === 0) {
if (render.effect !== null) {
if (render.dom !== null) {
Expand Down Expand Up @@ -2012,6 +1991,7 @@ export function key(anchor_node, key, render_fn) {
return;
}
const transitions = render.transitions;
remove_in_transitions(transitions);
if (transitions.size === 0) {
if (render.dom !== null) {
remove(render.dom);
Expand Down Expand Up @@ -2139,96 +2119,6 @@ export function destroy_each_item_block(
}
}

/**
* @this {import('./types.js').EachItemBlock}
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
function each_item_transition(transition) {
const block = this;
const each_block = block.parent;
const is_controlled = (each_block.flags & EACH_IS_CONTROLLED) !== 0;
// Disable optimization
if (is_controlled) {
const anchor = empty();
each_block.flags ^= EACH_IS_CONTROLLED;
append_child(/** @type {Element} */ (each_block.anchor), anchor);
each_block.anchor = anchor;
}
if (transition.direction === 'key' && (each_block.flags & EACH_IS_ANIMATED) === 0) {
each_block.flags |= EACH_IS_ANIMATED;
}
let transitions = block.transitions;
if (transitions === null) {
block.transitions = transitions = new Set();
}
transition.finished(() => {
if (transitions !== null) {
transitions.delete(transition);
if (transition.direction !== 'key') {
for (let other of transitions) {
if (other.direction === 'key' || other.direction === 'in') {
transitions.delete(other);
}
}
if (transitions.size === 0) {
block.transitions = null;
destroy_each_item_block(block, null, true);
}
}
}
});
transitions.add(transition);
}

/**
* @param {Set<import('./types.js').Transition>} transitions
* @param {'in' | 'out' | 'key'} target_direction
* @param {DOMRect} [from]
* @returns {void}
*/
export function trigger_transitions(transitions, target_direction, from) {
/** @type {Array<() => void>} */
const outros = [];
for (const transition of transitions) {
const direction = transition.direction;
if (target_direction === 'in') {
if (direction === 'in' || direction === 'both') {
if (direction === 'in') {
transition.cancel();
}
transition.in();
} else {
transition.cancel();
}
transition.dom.inert = false;
mark_subtree_inert(transition.effect, false);
} else if (target_direction === 'key') {
if (direction === 'key') {
transition.payload = transition.init(/** @type {DOMRect} */ (from));
transition.in();
}
} else {
if (direction === 'out' || direction === 'both') {
transition.payload = transition.init();
outros.push(transition.out);
}
transition.dom.inert = true;
mark_subtree_inert(transition.effect, true);
}
}
if (outros.length > 0) {
// Defer the outros to a microtask
const e = managed_pre_effect(() => {
destroy_signal(e);
const e2 = managed_effect(() => {
destroy_signal(e2);
outros.forEach(/** @param {any} o */ (o) => o());
});
}, false);
}
}

/**
* @template V
* @param {V} item
Expand All @@ -2242,7 +2132,6 @@ export function each_item_block(item, key, index, render_fn, flags) {
const item_value = (flags & EACH_ITEM_REACTIVE) === 0 ? item : source(item);
const index_value = (flags & EACH_INDEX_REACTIVE) === 0 ? index : source(index);
const block = create_each_item_block(item_value, index_value, key);
block.transition = each_item_transition;
const effect = render_effect(
/** @param {import('./types.js').EachItemBlock} block */
(block) => {
Expand Down Expand Up @@ -2290,11 +2179,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
transitions.add(transition);
transition.finished(() => {
transitions.delete(transition);
for (let other of transitions) {
if (other.direction === 'in') {
transitions.delete(other);
}
}
remove_in_transitions(transitions);
if (transitions.size === 0) {
if (fallback.effect !== null) {
if (fallback.dom !== null) {
Expand Down
108 changes: 108 additions & 0 deletions packages/svelte/src/internal/client/transitions.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { EACH_IS_ANIMATED, EACH_IS_CONTROLLED } from '../../constants.js';
import {
AWAIT_BLOCK,
DYNAMIC_COMPONENT_BLOCK,
Expand All @@ -7,12 +8,16 @@ import {
KEY_BLOCK,
ROOT_BLOCK
} from './block.js';
import { append_child } from './operations.js';
import { destroy_each_item_block, empty } from './render.js';
import {
current_block,
current_effect,
destroy_signal,
effect,
managed_effect,
managed_pre_effect,
mark_subtree_inert,
untrack
} from './runtime.js';
import { raf } from './timing.js';
Expand Down Expand Up @@ -414,6 +419,8 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
while (transition_block !== null) {
if (is_transition_block(transition_block)) {
if (transition_block.type === EACH_ITEM_BLOCK) {
// Lazily apply the each block transition
transition_block.transition = each_item_transition;
transition_block = transition_block.parent;
} else if (transition_block.type === AWAIT_BLOCK && transition_block.pending) {
skip_intro = false;
Expand Down Expand Up @@ -496,3 +503,104 @@ export function bind_transition(dom, transition_fn, props_fn, direction, global)
});
}
}

/**
* @param {Set<import('./types.js').Transition>} transitions
*/
export function remove_in_transitions(transitions) {
for (let other of transitions) {
if (other.direction === 'in') {
transitions.delete(other);
}
}
}

/**
* @param {Set<import('./types.js').Transition>} transitions
* @param {'in' | 'out' | 'key'} target_direction
* @param {DOMRect} [from]
* @returns {void}
*/
export function trigger_transitions(transitions, target_direction, from) {
/** @type {Array<() => void>} */
const outros = [];
for (const transition of transitions) {
const direction = transition.direction;
if (target_direction === 'in') {
if (direction === 'in' || direction === 'both') {
if (direction === 'in') {
transition.cancel();
}
transition.in();
} else {
transition.cancel();
}
transition.dom.inert = false;
mark_subtree_inert(transition.effect, false);
} else if (target_direction === 'key') {
if (direction === 'key') {
transition.payload = transition.init(/** @type {DOMRect} */ (from));
transition.in();
}
} else {
if (direction === 'out' || direction === 'both') {
transition.payload = transition.init();
outros.push(transition.out);
}
transition.dom.inert = true;
mark_subtree_inert(transition.effect, true);
}
}
if (outros.length > 0) {
// Defer the outros to a microtask
const e = managed_pre_effect(() => {
destroy_signal(e);
const e2 = managed_effect(() => {
destroy_signal(e2);
outros.forEach(/** @param {any} o */ (o) => o());
});
}, false);
}
}

/**
* @this {import('./types.js').EachItemBlock}
* @param {import('./types.js').Transition} transition
* @returns {void}
*/
function each_item_transition(transition) {
const block = this;
const each_block = block.parent;
const is_controlled = (each_block.flags & EACH_IS_CONTROLLED) !== 0;
// Disable optimization
if (is_controlled) {
const anchor = empty();
each_block.flags ^= EACH_IS_CONTROLLED;
append_child(/** @type {Element} */ (each_block.anchor), anchor);
each_block.anchor = anchor;
}
if (transition.direction === 'key' && (each_block.flags & EACH_IS_ANIMATED) === 0) {
each_block.flags |= EACH_IS_ANIMATED;
}
let transitions = block.transitions;
if (transitions === null) {
block.transitions = transitions = new Set();
}
transition.finished(() => {
if (transitions !== null) {
transitions.delete(transition);
if (transition.direction !== 'key') {
for (let other of transitions) {
if (other.direction === 'key' || other.direction === 'in') {
transitions.delete(other);
}
}
if (transitions.size === 0) {
block.transitions = null;
destroy_each_item_block(block, null, true);
}
}
}
});
transitions.add(transition);
}