boundary-batch-first-run
Simon Holthausen 2 days ago
parent 0f620e8efc
commit 3d731c7d17

@ -1,4 +1,5 @@
import { run_all } from '../../shared/utils.js'; import { run_all } from '../../shared/utils.js';
import { is_flushing_sync } from '../reactivity/batch.js';
// Fallback for when requestIdleCallback is not available // Fallback for when requestIdleCallback is not available
const request_idle_callback = const request_idle_callback =
@ -24,11 +25,15 @@ function run_idle_tasks() {
run_all(tasks); run_all(tasks);
} }
export function has_pending_tasks() {
return micro_tasks.length > 0 || idle_tasks.length > 0;
}
/** /**
* @param {() => void} fn * @param {() => void} fn
*/ */
export function queue_micro_task(fn) { export function queue_micro_task(fn) {
if (micro_tasks.length === 0) { if (micro_tasks.length === 0 && !is_flushing_sync) {
queueMicrotask(run_micro_tasks); queueMicrotask(run_micro_tasks);
} }

@ -25,7 +25,7 @@ import {
update_effect update_effect
} from '../runtime.js'; } from '../runtime.js';
import * as e from '../errors.js'; import * as e from '../errors.js';
import { flush_tasks } from '../dom/task.js'; import { flush_tasks, has_pending_tasks, queue_micro_task } from '../dom/task.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { invoke_error_boundary } from '../error-handling.js'; import { invoke_error_boundary } from '../error-handling.js';
import { old_values } from './sources.js'; import { old_values } from './sources.js';
@ -56,19 +56,6 @@ export let batch_deriveds = null;
/** @type {Set<() => void>} */ /** @type {Set<() => void>} */
export let effect_pending_updates = new Set(); export let effect_pending_updates = new Set();
/** @type {Array<() => void>} */
let tasks = [];
function dequeue() {
const task = /** @type {() => void} */ (tasks.shift());
if (tasks.length > 0) {
queueMicrotask(dequeue);
}
task();
}
/** @type {Effect[]} */ /** @type {Effect[]} */
let queued_root_effects = []; let queued_root_effects = [];
@ -76,7 +63,7 @@ let queued_root_effects = [];
let last_scheduled_effect = null; let last_scheduled_effect = null;
let is_flushing = false; let is_flushing = false;
let is_flushing_sync = false; export let is_flushing_sync = false;
export class Batch { export class Batch {
/** /**
@ -418,6 +405,8 @@ export class Batch {
if (this.#pending === 0) { if (this.#pending === 0) {
Batch.enqueue(() => { Batch.enqueue(() => {
this.activate();
for (const e of this.#dirty_effects) { for (const e of this.#dirty_effects) {
set_signal_status(e, DIRTY); set_signal_status(e, DIRTY);
schedule_effect(e); schedule_effect(e);
@ -469,11 +458,7 @@ export class Batch {
/** @param {() => void} task */ /** @param {() => void} task */
static enqueue(task) { static enqueue(task) {
if (tasks.length === 0) { queue_micro_task(task);
queueMicrotask(dequeue);
}
tasks.unshift(task);
} }
} }
@ -504,7 +489,7 @@ export function flushSync(fn) {
while (true) { while (true) {
flush_tasks(); flush_tasks();
if (queued_root_effects.length === 0) { if (queued_root_effects.length === 0 && !has_pending_tasks()) {
current_batch?.flush(); current_batch?.flush();
// we need to check again, in case we just updated an `$effect.pending()` // we need to check again, in case we just updated an `$effect.pending()`
@ -690,5 +675,4 @@ export function suspend() {
*/ */
export function clear() { export function clear() {
batches.clear(); batches.clear();
tasks.length = 0;
} }

@ -5,6 +5,8 @@ export default test({
async test({ assert, target }) { async test({ assert, target }) {
const [b1, b2, b3] = target.querySelectorAll('button'); const [b1, b2, b3] = target.querySelectorAll('button');
await Promise.resolve();
// not flushing means we wait a tick before showing the pending state ... // not flushing means we wait a tick before showing the pending state ...
b2.click(); b2.click();
await Promise.resolve(); await Promise.resolve();
@ -45,6 +47,7 @@ export default test({
); );
// when not flushing ... // when not flushing ...
await Promise.resolve();
b3.click(); b3.click();
await Promise.resolve(); await Promise.resolve();
assert.htmlEqual( assert.htmlEqual(

@ -3,6 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
async test({ assert, target, logs }) { async test({ assert, target, logs }) {
const [b1, b2, b3, b4] = target.querySelectorAll('button'); const [b1, b2, b3, b4] = target.querySelectorAll('button');
await Promise.resolve();
b1.click(); b1.click();
await Promise.resolve(); await Promise.resolve();
b2.click(); b2.click();

@ -3,6 +3,7 @@ import { test } from '../../test';
export default test({ export default test({
async test({ assert, target, logs }) { async test({ assert, target, logs }) {
const [b1, b2] = target.querySelectorAll('button'); const [b1, b2] = target.querySelectorAll('button');
await Promise.resolve();
b1.click(); b1.click();
await Promise.resolve(); await Promise.resolve();
assert.htmlEqual( assert.htmlEqual(

@ -5,7 +5,8 @@ export default test({
async test({ assert, target, raf }) { async test({ assert, target, raf }) {
const btn = target.querySelector('button'); const btn = target.querySelector('button');
raf.tick(0); // one tick to not be at 0. Else the flushSync would revert the in-transition which hasn't started, and directly remove the button
raf.tick(1);
flushSync(() => { flushSync(() => {
btn?.click(); btn?.click();
@ -13,7 +14,7 @@ export default test({
assert.htmlEqual(target.innerHTML, `<h1>Outside</h1><button style="opacity: 0;">Hide</button>`); assert.htmlEqual(target.innerHTML, `<h1>Outside</h1><button style="opacity: 0;">Hide</button>`);
raf.tick(100); raf.tick(101);
assert.htmlEqual(target.innerHTML, `<h1>Outside</h1>`); assert.htmlEqual(target.innerHTML, `<h1>Outside</h1>`);
} }

@ -5,7 +5,8 @@ export default test({
async test({ assert, target, raf }) { async test({ assert, target, raf }) {
const btn = target.querySelector('button'); const btn = target.querySelector('button');
raf.tick(0); // one tick to not be at 0. Else the flushSync would revert the in-transition which hasn't started, and directly remove the button
raf.tick(1);
flushSync(() => { flushSync(() => {
btn?.click(); btn?.click();
@ -13,7 +14,7 @@ export default test({
assert.htmlEqual(target.innerHTML, `<h1>Outside</h1><button style="opacity: 0;">Hide</button>`); assert.htmlEqual(target.innerHTML, `<h1>Outside</h1><button style="opacity: 0;">Hide</button>`);
raf.tick(100); raf.tick(101);
assert.htmlEqual(target.innerHTML, `<h1>Outside</h1>`); assert.htmlEqual(target.innerHTML, `<h1>Outside</h1>`);
} }

Loading…
Cancel
Save