init lifecycle functions after user code

pull/10408/head
Rich Harris 3 years ago
parent 2ed717fe5d
commit fff5c38283

@ -262,6 +262,7 @@ export function client_component(source, analysis, options) {
...legacy_reactive_declarations,
...group_binding_declarations,
.../** @type {import('estree').Statement[]} */ (instance.body),
analysis.runes ? b.empty : b.stmt(b.call('$.init')),
.../** @type {import('estree').Statement[]} */ (template.body),
...static_bindings
]);

@ -1,6 +1,6 @@
import { DEV } from 'esm-env';
import { subscribe_to_store } from '../../store/utils.js';
import { noop, run_all } from '../common.js';
import { noop, run, run_all } from '../common.js';
import {
array_prototype,
get_descriptor,
@ -1308,17 +1308,8 @@ function bind_signal_to_component_context(signal) {
if (signals) {
signals.push(signal);
// update dummy signal
const previous_ignore_mutation_validation = ignore_mutation_validation;
ignore_mutation_validation = true;
set_signal_value(signals[0], signals[0].v + 1);
ignore_mutation_validation = previous_ignore_mutation_validation;
} else {
// if we're creating the array, insert a dummy signal whose value we can
// increment whenever we add new sources. This ensures that late-declared
// signals will still cause `beforeUpdate` and `afterUpdate` etc to fire
current_component_context.d = [create_source_signal(SOURCE | CLEAN, 0), signal];
current_component_context.d = [signal];
}
}
@ -1936,6 +1927,47 @@ export function pop(accessors) {
}
}
export function init() {
const context = /** @type {import('./types.js').ComponentContext} */ (current_component_context);
const callbacks = context.u;
if (!callbacks) return;
function observe_all() {
const signals = (context.d ??= []);
for (const signal of signals) get(signal);
const props = get_descriptors(context.s);
for (const descriptor of Object.values(props)) {
if (descriptor.get) descriptor.get();
}
}
// beforeUpdate
pre_effect(() => {
observe_all();
callbacks.b.forEach(run);
});
// onMount (must run before afterUpdate)
user_effect(() => {
const fns = untrack(() => callbacks.m.map(run));
return () => {
for (const fn of fns) {
if (typeof fn === 'function') {
fn();
}
}
};
});
// afterUpdate
user_effect(() => {
observe_all();
callbacks.a.forEach(run);
});
}
/**
* @param {any} value
* @param {Set<any>} visited

@ -38,7 +38,8 @@ export {
user_root_effect,
inspect,
unwrap,
freeze
freeze,
init
} from './client/runtime.js';
export * from './client/each.js';
export * from './client/render.js';

@ -2,15 +2,9 @@ import {
current_component_context,
get_or_init_context_map,
untrack,
user_effect,
pre_effect,
get,
create_source_signal,
SOURCE,
CLEAN
user_effect
} from '../internal/client/runtime.js';
import { get_descriptors, is_array } from '../internal/client/utils.js';
import { run } from '../internal/common.js';
import { is_array } from '../internal/client/utils.js';
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
@ -31,9 +25,32 @@ export function onMount(fn) {
throw new Error('onMount can only be used during component initialisation.');
}
console.error('onMount');
if (current_component_context.r) {
user_effect(() => {
const cleanup = untrack(fn);
if (typeof cleanup === 'function') return /** @type {() => void} */ (cleanup);
});
} else {
init_update_callbacks(current_component_context).m.push(fn);
}
}
init_update_callbacks(current_component_context).m.push(fn);
/**
* Schedules a callback to run immediately before the component is unmounted.
*
* Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the
* only one that runs inside a server-side component.
*
* https://svelte.dev/docs/svelte#ondestroy
* @param {() => any} fn
* @returns {void}
*/
export function onDestroy(fn) {
if (current_component_context === null) {
throw new Error('onDestroy can only be used during component initialisation.');
}
onMount(() => () => untrack(fn));
}
/**
@ -180,7 +197,6 @@ export function beforeUpdate(fn) {
}
init_update_callbacks(current_component_context).b.push(fn);
fn();
}
/**
@ -212,66 +228,9 @@ export function afterUpdate(fn) {
* @param {import('../internal/client/types.js').ComponentContext} context
*/
function init_update_callbacks(context) {
if (context.u) return context.u;
const callbacks = (context.u = { a: [], b: [], m: [] });
function observe_all() {
const signals = (context.d ??= [create_source_signal(SOURCE | CLEAN, 0)]);
for (const signal of signals) get(signal);
const props = get_descriptors(context.s);
for (const descriptor of Object.values(props)) {
if (descriptor.get) descriptor.get();
}
}
let skip = false;
// beforeUpdate
pre_effect(() => {
observe_all();
if (skip) return;
callbacks.b.forEach(run);
});
pre_effect(() => {
skip = true;
});
// onMount (must run before afterUpdate)
user_effect(() => {
const fns = untrack(() => callbacks.m.map(run));
return () => {
for (const fn of fns) {
if (typeof fn === 'function') {
fn();
}
}
};
});
// afterUpdate
user_effect(() => {
observe_all();
callbacks.a.forEach(run);
});
user_effect(() => {
skip = false;
});
return callbacks;
return (context.u ??= { a: [], b: [], m: [] });
}
// TODO bring implementations in here
// (except probably untrack — do we want to expose that, if there's also a rune?)
export {
flushSync,
createRoot,
mount,
tick,
untrack,
unstate,
onDestroy
} from '../internal/index.js';
export { flushSync, createRoot, mount, tick, untrack, unstate } from '../internal/index.js';

@ -5,6 +5,7 @@ import * as $ from "svelte/internal";
export default function Each_string_template($$anchor, $$props) {
$.push($$props, false);
$.init();
/* Init */
var fragment = $.comment($$anchor);
@ -27,4 +28,4 @@ export default function Each_string_template($$anchor, $$props) {
$.close_frag($$anchor, fragment);
$.pop();
}
}

@ -7,10 +7,11 @@ var frag = $.template(`<h1>hello world</h1>`);
export default function Hello_world($$anchor, $$props) {
$.push($$props, false);
$.init();
/* Init */
var h1 = $.open($$anchor, true, frag);
$.close($$anchor, h1);
$.pop();
}
}

Loading…
Cancel
Save