fix: store and restore parent renderer in effect

fix-interleaving
paoloricciuti 1 month ago
parent 40d89b2ba6
commit 928d85b4af

@ -7,6 +7,14 @@ import { hydrating, set_hydrating } from '../dom/hydration.js';
*/
export let current_renderer = null;
/**
* The renderer that was active before `current_renderer` was pushed. This
* allows custom renderers to be interleaved (e.g. a custom renderer rendering
* into another one) by keeping a reference to the renderer one level up.
* @type {Renderer<any, any, any, any> | null}
*/
export let parent_renderer = null;
/**
* @param {Renderer<any, any, any, any> | null} value
*/
@ -14,11 +22,21 @@ export function set_renderer(value) {
current_renderer = value;
}
/**
* @param {Renderer<any, any, any, any> | null} value
*/
export function set_parent_renderer(value) {
parent_renderer = value;
}
/**
*
* @param {Renderer<any, any, any, any> | null} value
* @param {Renderer<any, any, any, any> | null} [parent] the renderer to restore as the
* `parent_renderer`. Defaults to the current renderer so that, in the common case, the
* renderer that was active before this push becomes the parent.
*/
export function push_renderer(value) {
export function push_renderer(value, parent = current_renderer) {
var previous_hydrating = hydrating;
var should_disable_hydration = hydrating && value != null;
// this is to allow hydration code to treeshake
@ -26,10 +44,13 @@ export function push_renderer(value) {
set_hydrating(false);
}
var previous_renderer = current_renderer;
var previous_parent_renderer = parent_renderer;
parent_renderer = parent;
current_renderer = value;
return () => {
current_renderer = previous_renderer;
parent_renderer = previous_parent_renderer;
if (should_disable_hydration) {
set_hydrating(previous_hydrating);
}

@ -230,7 +230,7 @@ export class Boundary {
this.#pending_effect = branch(() => pending(this.#anchor));
queue_micro_task(() => {
var pop_renderer = push_renderer(this.#effect.r);
var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr);
var fragment = (this.#offscreen_fragment = create_fragment());
var anchor = create_text();
@ -324,7 +324,7 @@ export class Boundary {
set_active_reaction(this.#effect);
set_component_context(this.#effect.ctx);
var pop_renderer = push_renderer(this.#effect.r);
var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr);
try {
Batch.ensure();
@ -368,7 +368,7 @@ export class Boundary {
}
if (this.#offscreen_fragment) {
var pop_renderer = push_renderer(this.#effect.r);
var pop_renderer = push_renderer(this.#effect.r, this.#effect.pr);
insert_before(this.#anchor, this.#offscreen_fragment);
this.#offscreen_fragment = null;
pop_renderer?.();

@ -20,7 +20,7 @@ import {
get_last_child
} from '../operations.js';
import { DEV } from 'esm-env';
import { push_renderer, current_renderer } from '../../custom-renderer/state.js';
import { push_renderer, current_renderer, parent_renderer } from '../../custom-renderer/state.js';
/**
* @typedef {{ effect: Effect, fragment: DocumentFragment }} Branch
@ -79,6 +79,12 @@ export class BranchManager {
*/
#renderer = null;
/**
* The parent renderer that was active when this BranchManager was created.
* @type {Renderer | null}
*/
#parent_renderer = null;
/**
* @param {TemplateNode} anchor
* @param {boolean} transition
@ -87,6 +93,7 @@ export class BranchManager {
this.anchor = anchor;
this.#transition = transition;
this.#renderer = current_renderer;
this.#parent_renderer = parent_renderer;
}
/**
@ -96,7 +103,7 @@ export class BranchManager {
// if this batch was made obsolete, bail
if (!this.#batches.has(batch)) return;
var pop_renderer = push_renderer(this.#renderer);
var pop_renderer = push_renderer(this.#renderer, this.#parent_renderer);
var key = /** @type {Key} */ (this.#batches.get(batch));

@ -50,7 +50,7 @@ import { current_batch } from '../../reactivity/batch.js';
import * as e from '../../errors.js';
import { tag } from '../../dev/tracing.js';
import { push_renderer, current_renderer } from '../../custom-renderer/state.js';
import { push_renderer, current_renderer, parent_renderer } from '../../custom-renderer/state.js';
// When making substantive changes to this file, validate them with the each block stress test:
// https://svelte.dev/playground/1972b2cf46564476ad8c8c6405b23b7b
@ -220,6 +220,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
// Needed so that the commit callback can push the correct renderer when doing
// DOM operations outside of an effect context (e.g. as a batch commit callback).
var renderer = current_renderer;
var parent = parent_renderer;
if (is_controlled) {
var parent_node = /** @type {Element} */ (node);
@ -267,7 +268,7 @@ export function each(node, flags, get_collection, get_key, render_fn, fallback_f
return;
}
var pop_renderer = push_renderer(renderer);
var pop_renderer = push_renderer(renderer, parent);
state.pending.delete(batch);

@ -9,7 +9,12 @@ import {
set_dev_stack
} from '../context.js';
import { Boundary } from '../dom/blocks/boundary.js';
import { current_renderer, set_renderer } from '../custom-renderer/state.js';
import {
current_renderer,
parent_renderer,
set_parent_renderer,
set_renderer
} from '../custom-renderer/state.js';
import { invoke_error_boundary } from '../error-handling.js';
import {
active_effect,
@ -134,6 +139,7 @@ export function capture() {
var previous_component_context = component_context;
var previous_batch = /** @type {Batch} */ (current_batch);
var previous_renderer = current_renderer;
var previous_parent_renderer = parent_renderer;
if (DEV) {
var previous_dev_stack = dev_stack;
@ -145,6 +151,7 @@ export function capture() {
set_component_context(previous_component_context);
set_renderer(previous_renderer);
set_parent_renderer(previous_parent_renderer);
if (activate_batch && (previous_effect.f & DESTROYED) === 0) {
// TODO we only need optional chaining here because `{#await ...}` blocks
@ -277,6 +284,7 @@ export function unset_context(deactivate_batch = true) {
set_active_reaction(null);
set_component_context(null);
set_renderer(null);
set_parent_renderer(null);
if (deactivate_batch) current_batch?.deactivate();
if (DEV) {

@ -45,7 +45,7 @@ import { Batch, collected_effects, current_batch } from './batch.js';
import { flatten } from './async.js';
import { without_reactive_context } from '../dom/elements/bindings/shared.js';
import { set_signal_status } from './status.js';
import { push_renderer, current_renderer } from '../custom-renderer/state.js';
import { push_renderer, current_renderer, parent_renderer } from '../custom-renderer/state.js';
/**
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
@ -114,7 +114,8 @@ function create_effect(type, fn) {
teardown: null,
wv: 0,
ac: null,
r: current_renderer
r: current_renderer,
pr: parent_renderer
};
if (DEV) {
@ -513,7 +514,7 @@ export function destroy_block_effect_children(signal) {
export function destroy_effect(effect, remove_dom = true) {
var removed = false;
var pop_renderer = push_renderer(effect.r);
var pop_renderer = push_renderer(effect.r, effect.pr);
if (
(remove_dom || (effect.f & HEAD_EFFECT) !== 0) &&
@ -564,6 +565,7 @@ export function destroy_effect(effect, remove_dom = true) {
effect.ac =
effect.b =
effect.r =
effect.pr =
null;
pop_renderer?.();
@ -738,7 +740,7 @@ export function aborted(effect = /** @type {Effect} */ (active_effect)) {
export function move_effect(effect, fragment) {
if (!effect.nodes) return;
var pop_renderer = push_renderer(effect.r);
var pop_renderer = push_renderer(effect.r, effect.pr);
/** @type {TemplateNode | null} */
var node = effect.nodes.start;

@ -97,6 +97,8 @@ export interface Effect extends Reaction {
b: Boundary | null;
/** The renderer this effect was created with */
r: Renderer | null;
/** The parent renderer this effect was created with */
pr: Renderer | null;
/** Dev only */
component_function?: any;
/** Dev only. Only set for certain block effects. Contains a reference to the stack that represents the render tree */

@ -447,7 +447,7 @@ export function update_effect(effect) {
active_effect = effect;
is_updating_effect = true;
var pop_renderer = push_renderer(effect.r);
var pop_renderer = push_renderer(effect.r, effect.pr);
if (DEV) {
var previous_component_fn = dev_current_component_function;

Loading…
Cancel
Save