diff --git a/documentation/docs/98-reference/.generated/client-warnings.md b/documentation/docs/98-reference/.generated/client-warnings.md index dcce04bcb8..ba5f957f8d 100644 --- a/documentation/docs/98-reference/.generated/client-warnings.md +++ b/documentation/docs/98-reference/.generated/client-warnings.md @@ -34,6 +34,14 @@ function add() { } ``` +### await_reactivity_loss + +``` +Detected reactivity loss +``` + +TODO + ### await_waterfall ``` diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index cb0645367b..eba1454bf7 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -30,6 +30,12 @@ function add() { } ``` +## await_reactivity_loss + +> Detected reactivity loss + +TODO + ## await_waterfall > Detected an unnecessary async waterfall diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js index 7a7ca628a8..b69b2fc725 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/AwaitExpression.js @@ -1,5 +1,6 @@ /** @import { AwaitExpression, Expression } from 'estree' */ /** @import { Context } from '../types' */ +import { dev } from '../../../../state.js'; import * as b from '../../../../utils/builders.js'; /** @@ -7,15 +8,19 @@ import * as b from '../../../../utils/builders.js'; * @param {Context} context */ export function AwaitExpression(node, context) { - const suspend = context.state.analysis.context_preserving_awaits.has(node); + const save = context.state.analysis.context_preserving_awaits.has(node); - if (!suspend) { - return context.next(); + if (dev || save) { + return b.call( + b.await( + b.call( + '$.save', + node.argument && /** @type {Expression} */ (context.visit(node.argument)), + !save && b.false + ) + ) + ); } - return b.call( - b.await( - b.call('$.save', node.argument && /** @type {Expression} */ (context.visit(node.argument))) - ) - ); + return context.next(); } diff --git a/packages/svelte/src/internal/client/dom/blocks/boundary.js b/packages/svelte/src/internal/client/dom/blocks/boundary.js index 97389f9624..c35bc01d84 100644 --- a/packages/svelte/src/internal/client/dom/blocks/boundary.js +++ b/packages/svelte/src/internal/client/dom/blocks/boundary.js @@ -30,6 +30,8 @@ import { import { get_next_sibling } from '../operations.js'; import { queue_boundary_micro_task } from '../task.js'; import * as e from '../../../shared/errors.js'; +import { DEV } from 'esm-env'; +import { from_async_derived, set_from_async_derived } from '../../reactivity/deriveds.js'; const ASYNC_INCREMENT = Symbol(); const ASYNC_DECREMENT = Symbol(); @@ -340,15 +342,23 @@ function move_effect(effect, fragment) { } } -export function capture() { +export function capture(track = true) { var previous_effect = active_effect; var previous_reaction = active_reaction; var previous_component_context = component_context; + if (DEV && !track) { + var was_from_async_derived = from_async_derived; + } + return function restore() { - set_active_effect(previous_effect); - set_active_reaction(previous_reaction); - set_component_context(previous_component_context); + if (track) { + set_active_effect(previous_effect); + set_active_reaction(previous_reaction); + set_component_context(previous_component_context); + } else if (DEV) { + set_from_async_derived(was_from_async_derived); + } // prevent the active effect from outstaying its welcome queue_boundary_micro_task(exit); @@ -390,10 +400,11 @@ export function suspend() { /** * @template T * @param {Promise} promise + * @param {boolean} [track] * @returns {Promise<() => T>} */ -export async function save(promise) { - var restore = capture(); +export async function save(promise, track = true) { + var restore = capture(track); var value = await promise; return () => { diff --git a/packages/svelte/src/internal/client/reactivity/deriveds.js b/packages/svelte/src/internal/client/reactivity/deriveds.js index 54915e438e..f8a8aaddac 100644 --- a/packages/svelte/src/internal/client/reactivity/deriveds.js +++ b/packages/svelte/src/internal/client/reactivity/deriveds.js @@ -29,6 +29,14 @@ import { tracing_mode_flag } from '../../flags/index.js'; import { capture, suspend } from '../dom/blocks/boundary.js'; import { component_context } from '../context.js'; +/** @type {Effect | null} */ +export let from_async_derived = null; + +/** @param {Effect | null} v */ +export function set_from_async_derived(v) { + from_async_derived = v; +} + /** * @template V * @param {() => V} fn @@ -88,8 +96,11 @@ export function async_derived(fn) { var promise = /** @type {Promise} */ (/** @type {unknown} */ (undefined)); var value = source(/** @type {V} */ (undefined)); + // TODO this isn't a block block(async () => { + if (DEV) from_async_derived = active_effect; var current = (promise = fn()); + if (DEV) from_async_derived = null; var restore = capture(); var unsuspend = suspend(); @@ -103,6 +114,8 @@ export function async_derived(fn) { if (promise === current) { restore(); + from_async_derived = null; + internal_set(value, v); } } catch (e) { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index a6460211d9..c60f4d736e 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -37,6 +37,7 @@ import { destroy_derived, destroy_derived_effects, execute_derived, + from_async_derived, update_derived } from './reactivity/deriveds.js'; import * as e from './errors.js'; @@ -51,6 +52,7 @@ import { set_dev_current_component_function } from './context.js'; import { add_boundary_effect, commit_boundary } from './dom/blocks/boundary.js'; +import * as w from './warnings.js'; const FLUSH_MICROTASK = 0; const FLUSH_SYNC = 1; @@ -967,6 +969,15 @@ export function get(signal) { captured_signals.add(signal); } + if (DEV && from_async_derived) { + var tracking = (from_async_derived.f & REACTION_IS_UPDATING) !== 0; + var was_read = from_async_derived.deps !== null && from_async_derived.deps.includes(signal); + + if (!tracking && !was_read) { + w.await_reactivity_loss(); + } + } + // Register the dependency on the current reaction signal. if (active_reaction !== null && !untracking) { if (derived_sources !== null && derived_sources.includes(signal)) { diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js index f4dcfdd650..79fbebee4c 100644 --- a/packages/svelte/src/internal/client/warnings.js +++ b/packages/svelte/src/internal/client/warnings.js @@ -18,6 +18,17 @@ export function assignment_value_stale(property, location) { } } +/** + * Detected reactivity loss + */ +export function await_reactivity_loss() { + if (DEV) { + console.warn(`%c[svelte] await_reactivity_loss\n%cDetected reactivity loss\nhttps://svelte.dev/e/await_reactivity_loss`, bold, normal); + } else { + console.warn(`https://svelte.dev/e/await_reactivity_loss`); + } +} + /** * Detected an unnecessary async waterfall */