pull/15844/head
Rich Harris 3 months ago
parent 591aeb0cbc
commit 7144f11b5b

@ -155,8 +155,7 @@ export function build_render_statement(state) {
: b.block(state.update) : b.block(state.update)
), ),
all.length > 0 && b.array(sync.map(({ expression }) => b.thunk(expression))), all.length > 0 && b.array(sync.map(({ expression }) => b.thunk(expression))),
async.length > 0 && b.array(async.map(({ expression }) => b.thunk(expression, true))), async.length > 0 && b.array(async.map(({ expression }) => b.thunk(expression, true)))
!state.analysis.runes && sync.length > 0 && b.id('$.derived_safe_equal')
) )
); );
} }

@ -1,37 +1,24 @@
/** @import { Effect, TemplateNode, Value } from '#client' */ /** @import { TemplateNode, Value } from '#client' */
import { DESTROYED } from '#client/constants'; import { flatten } from '../../reactivity/async.js';
import { async_derived } from '../../reactivity/deriveds.js'; import { get } from '../../runtime.js';
import { active_effect, get } from '../../runtime.js'; import { get_pending_boundary } from './boundary.js';
import { capture, get_pending_boundary } from './boundary.js';
/** /**
* @param {TemplateNode} node * @param {TemplateNode} node
* @param {Array<() => Promise<any>>} expressions * @param {Array<() => Promise<any>>} expressions
* @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn * @param {(anchor: TemplateNode, ...deriveds: Value[]) => void} fn
*/ */
export async function async(node, expressions, fn) { export function async(node, expressions, fn) {
// TODO handle hydration
var parent = /** @type {Effect} */ (active_effect);
var restore = capture();
var boundary = get_pending_boundary(); var boundary = get_pending_boundary();
// TODO why is this necessary? doesn't it happen inside `async_derived` inside `flatten`?
boundary.update_pending_count(1); boundary.update_pending_count(1);
try { flatten([], expressions, (values) => {
const deriveds = await Promise.all(expressions.map((fn) => async_derived(fn))); // get values eagerly to avoid creating blocks if they reject
for (const d of values) get(d);
// get deriveds eagerly to avoid creating blocks if they reject
for (const d of deriveds) get(d);
if ((parent.f & DESTROYED) !== 0) return;
restore(); fn(node, ...values);
fn(node, ...deriveds);
} catch (error) {
boundary.error(error);
} finally {
boundary.update_pending_count(-1); boundary.update_pending_count(-1);
} });
} }

@ -23,6 +23,7 @@ import { ATTACHMENT_KEY, NAMESPACE_HTML } from '../../../../constants.js';
import { block, branch, destroy_effect } from '../../reactivity/effects.js'; import { block, branch, destroy_effect } from '../../reactivity/effects.js';
import { derived } from '../../reactivity/deriveds.js'; import { derived } from '../../reactivity/deriveds.js';
import { init_select, select_option } from './bindings/select.js'; import { init_select, select_option } from './bindings/select.js';
import { flatten } from '../../reactivity/async.js';
export const CLASS = Symbol('class'); export const CLASS = Symbol('class');
export const STYLE = Symbol('style'); export const STYLE = Symbol('style');
@ -473,72 +474,54 @@ export function attribute_effect(
sync = [], sync = [],
async = [], async = [],
css_hash, css_hash,
skip_warning = false,
d = derived
) {
const deriveds = sync.map(d);
create_attribute_effect(element, fn, deriveds, css_hash, skip_warning);
}
/**
* @param {Element & ElementCSSInlineStyle} element
* @param {(...expressions: any) => Record<string | symbol, any>} fn
* @param {Value[]} deriveds
* @param {string} [css_hash]
* @param {boolean} [skip_warning]
*/
export function create_attribute_effect(
element,
fn,
deriveds = [],
css_hash,
skip_warning = false skip_warning = false
) { ) {
/** @type {Record<string | symbol, any> | undefined} */ flatten(sync, async, (values) => {
var prev = undefined; /** @type {Record<string | symbol, any> | undefined} */
var prev = undefined;
/** @type {Record<symbol, Effect>} */ /** @type {Record<symbol, Effect>} */
var effects = {}; var effects = {};
var is_select = element.nodeName === 'SELECT'; var is_select = element.nodeName === 'SELECT';
var inited = false; var inited = false;
block(() => { block(() => {
var next = fn(...deriveds.map(get)); var next = fn(...values.map(get));
/** @type {Record<string | symbol, any>} */ /** @type {Record<string | symbol, any>} */
var current = set_attributes(element, prev, next, css_hash, skip_warning); var current = set_attributes(element, prev, next, css_hash, skip_warning);
if (inited && is_select && 'value' in next) { if (inited && is_select && 'value' in next) {
select_option(/** @type {HTMLSelectElement} */ (element), next.value, false); select_option(/** @type {HTMLSelectElement} */ (element), next.value, false);
} }
for (let symbol of Object.getOwnPropertySymbols(effects)) { for (let symbol of Object.getOwnPropertySymbols(effects)) {
if (!next[symbol]) destroy_effect(effects[symbol]); if (!next[symbol]) destroy_effect(effects[symbol]);
} }
for (let symbol of Object.getOwnPropertySymbols(next)) { for (let symbol of Object.getOwnPropertySymbols(next)) {
var n = next[symbol]; var n = next[symbol];
if (symbol.description === ATTACHMENT_KEY && (!prev || n !== prev[symbol])) { if (symbol.description === ATTACHMENT_KEY && (!prev || n !== prev[symbol])) {
if (effects[symbol]) destroy_effect(effects[symbol]); if (effects[symbol]) destroy_effect(effects[symbol]);
effects[symbol] = branch(() => attach(element, () => n)); effects[symbol] = branch(() => attach(element, () => n));
}
current[symbol] = n;
} }
current[symbol] = n; prev = current;
});
if (is_select) {
init_select(
/** @type {HTMLSelectElement} */ (element),
() => /** @type {Record<string | symbol, any>} */ (prev).value
);
} }
prev = current; inited = true;
}); });
if (is_select) {
init_select(
/** @type {HTMLSelectElement} */ (element),
() => /** @type {Record<string | symbol, any>} */ (prev).value
);
}
inited = true;
} }
/** /**

@ -0,0 +1,50 @@
/** @import { Effect, Value } from '#client' */
import { DESTROYED } from '#client/constants';
import { is_runes } from '../context.js';
import { capture, get_pending_boundary } from '../dom/blocks/boundary.js';
import { invoke_error_boundary } from '../error-handling.js';
import { active_effect } from '../runtime.js';
import { current_batch } from './batch.js';
import { async_derived, derived, derived_safe_equal } from './deriveds.js';
/**
*
* @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async
* @param {(values: Value[]) => any} fn
*/
export function flatten(sync, async, fn) {
const d = is_runes() ? derived : derived_safe_equal;
if (async.length > 0) {
var batch = current_batch;
var parent = /** @type {Effect} */ (active_effect);
var restore = capture();
var boundary = get_pending_boundary();
Promise.all(async.map((expression) => async_derived(expression)))
.then((result) => {
if ((parent.f & DESTROYED) !== 0) return;
batch?.restore();
restore();
try {
fn([...sync.map(d), ...result]);
} catch (error) {
invoke_error_boundary(error, parent);
}
batch?.flush();
})
.catch((error) => {
boundary.error(error);
});
} else {
fn(sync.map(d));
}
}

@ -1,4 +1,4 @@
/** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager, Value } from '#client' */ /** @import { ComponentContext, ComponentContextLegacy, Derived, Effect, TemplateNode, TransitionManager } from '#client' */
import { import {
check_dirtiness, check_dirtiness,
active_effect, active_effect,
@ -38,11 +38,9 @@ import * as e from '../errors.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { define_property } from '../../shared/utils.js'; import { define_property } from '../../shared/utils.js';
import { get_next_sibling } from '../dom/operations.js'; import { get_next_sibling } from '../dom/operations.js';
import { async_derived, derived } from './deriveds.js';
import { capture } from '../dom/blocks/boundary.js';
import { component_context, dev_current_component_function } from '../context.js'; import { component_context, dev_current_component_function } from '../context.js';
import { Batch, current_batch } from './batch.js'; import { Batch } from './batch.js';
import { invoke_error_boundary } from '../error-handling.js'; import { flatten } from './async.js';
/** /**
* @param {'$effect' | '$effect.pre' | '$inspect'} rune * @param {'$effect' | '$effect.pre' | '$inspect'} rune
@ -338,43 +336,11 @@ export function render_effect(fn, flags = 0) {
* @param {(...expressions: any) => void | (() => void)} fn * @param {(...expressions: any) => void | (() => void)} fn
* @param {Array<() => any>} sync * @param {Array<() => any>} sync
* @param {Array<() => Promise<any>>} async * @param {Array<() => Promise<any>>} async
* @param {<T>(fn: () => T) => Derived<T>} d
*/ */
export function template_effect(fn, sync = [], async = [], d = derived) { export function template_effect(fn, sync = [], async = []) {
if (async.length > 0) { flatten(sync, async, (values) => {
var batch = current_batch; create_effect(RENDER_EFFECT, () => fn(...values.map(get)), true);
var parent = /** @type {Effect} */ (active_effect); });
var restore = capture();
Promise.all(async.map((expression) => async_derived(expression))).then((result) => {
if ((parent.f & DESTROYED) !== 0) return;
// TODO probably need to do this in async.js as well
batch?.restore();
restore();
try {
create_template_effect(fn, [...sync.map(d), ...result]);
} catch (error) {
invoke_error_boundary(error, parent);
}
batch?.flush();
});
} else {
create_template_effect(fn, sync.map(d));
}
}
/**
* @param {(...expressions: any) => void | (() => void)} fn
* @param {Value[]} deriveds
*/
function create_template_effect(fn, deriveds) {
var effect = () => fn(...deriveds.map(get));
create_effect(RENDER_EFFECT, effect, true);
} }
/** /**

Loading…
Cancel
Save