diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 624339e7fa..5aec24c651 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -3,43 +3,7 @@ import { current_component, set_current_component } from './lifecycle'; import { blank_object, is_empty, is_function, run, run_all, noop } from './utils'; import { children, detach, start_hydrating, end_hydrating } from './dom'; import { transition_in } from './transitions'; - -/** - * INTERNAL, DO NOT USE. Code may change at any time. - */ -export interface Fragment { - key: string | null; - first: null; - /* create */ c: () => void; - /* claim */ l: (nodes: any) => void; - /* hydrate */ h: () => void; - /* mount */ m: (target: HTMLElement, anchor: any) => void; - /* update */ p: (ctx: T$$['ctx'], dirty: T$$['dirty']) => void; - /* measure */ r: () => void; - /* fix */ f: () => void; - /* animate */ a: () => void; - /* intro */ i: (local: any) => void; - /* outro */ o: (local: any) => void; - /* destroy */ d: (detaching: 0 | 1) => void; -} -interface T$$ { - dirty: number[]; - ctx: any[]; - bound: any; - update: () => void; - callbacks: any; - after_update: any[]; - props: Record; - fragment: null | false | Fragment; - not_equal: any; - before_update: any[]; - context: Map; - on_mount: any[]; - on_destroy: any[]; - skip_bound: boolean; - on_disconnect: any[]; - root:Element | ShadowRoot -} +import { T$$ } from './types'; export function bind(component, name, callback) { const index = component.$$.props[name]; @@ -58,7 +22,7 @@ export function claim_component(block, parent_nodes) { } export function mount_component(component, target, anchor, customElement) { - const { fragment, on_mount, on_destroy, after_update } = component.$$; + const { fragment, after_update } = component.$$; fragment && fragment.m(target, anchor); @@ -66,9 +30,12 @@ export function mount_component(component, target, anchor, customElement) { // onMount happens before the initial afterUpdate add_render_callback(() => { - const new_on_destroy = on_mount.map(run).filter(is_function); - if (on_destroy) { - on_destroy.push(...new_on_destroy); + const new_on_destroy = component.$$.on_mount.map(run).filter(is_function); + // if the component was destroyed immediately + // it will update the `$$.on_destroy` reference to `null`. + // the destructured on_destroy may still reference to the old array + if (component.$$.on_destroy) { + component.$$.on_destroy.push(...new_on_destroy); } else { // Edge case - component was destroyed immediately, // most likely as a result of a binding initialising diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts index ea6e8a187f..1e09ada6cf 100644 --- a/src/runtime/internal/await_block.ts +++ b/src/runtime/internal/await_block.ts @@ -2,11 +2,36 @@ import { is_promise } from './utils'; import { check_outros, group_outros, transition_in, transition_out } from './transitions'; import { flush } from './scheduler'; import { get_current_component, set_current_component } from './lifecycle'; +import { Fragment, FragmentFactory } from './types'; + +interface PromiseInfo { + ctx: null | any; + // unique object instance as a key to compare different promises + token: {}, + hasCatch: boolean, + pending: FragmentFactory, + then: FragmentFactory, + catch: FragmentFactory, + // ctx index for resolved value and rejected error + value: number, + error: number, + // resolved value or rejected error + resolved?: T, + // the current factory function for creating the fragment + current: FragmentFactory | null, + // the current fragment + block: Fragment | null, + // tuple of the pending, then, catch fragment + blocks: [null | Fragment, null | Fragment, null | Fragment]; + // DOM elements to mount and anchor on for the {#await} block + mount: () => HTMLElement; + anchor: HTMLElement; +} -export function handle_promise(promise, info) { +export function handle_promise(promise: Promise, info: PromiseInfo) { const token = info.token = {}; - function update(type, index, key?, value?) { + function update(type: FragmentFactory, index: 0 | 1 | 2, key?: number, value?) { if (info.token !== token) return; info.resolved = value; diff --git a/src/runtime/internal/transitions.ts b/src/runtime/internal/transitions.ts index 306a5e3793..9aa45dc4f1 100644 --- a/src/runtime/internal/transitions.ts +++ b/src/runtime/internal/transitions.ts @@ -5,7 +5,7 @@ import { create_rule, delete_rule } from './style_manager'; import { custom_event } from './dom'; import { add_render_callback } from './scheduler'; import { TransitionConfig } from '../transition'; -import { Fragment } from './Component'; +import { Fragment } from './types'; let promise: Promise | null; type INTRO = 1; diff --git a/src/runtime/internal/types.ts b/src/runtime/internal/types.ts new file mode 100644 index 0000000000..41f8f1ca43 --- /dev/null +++ b/src/runtime/internal/types.ts @@ -0,0 +1,39 @@ +/** + * INTERNAL, DO NOT USE. Code may change at any time. + */ +export interface Fragment { + key: string | null; + first: null; + /* create */ c: () => void; + /* claim */ l: (nodes: any) => void; + /* hydrate */ h: () => void; + /* mount */ m: (target: HTMLElement, anchor: any) => void; + /* update */ p: (ctx: T$$['ctx'], dirty: T$$['dirty']) => void; + /* measure */ r: () => void; + /* fix */ f: () => void; + /* animate */ a: () => void; + /* intro */ i: (local: any) => void; + /* outro */ o: (local: any) => void; + /* destroy */ d: (detaching: 0 | 1) => void; +} + +export type FragmentFactory = (ctx: any) => Fragment; + +export interface T$$ { + dirty: number[]; + ctx: any[]; + bound: any; + update: () => void; + callbacks: any; + after_update: any[]; + props: Record; + fragment: null | false | Fragment; + not_equal: any; + before_update: any[]; + context: Map; + on_mount: any[]; + on_destroy: any[]; + skip_bound: boolean; + on_disconnect: any[]; + root:Element | ShadowRoot +} diff --git a/test/runtime/samples/await-mount-and-unmount-immediately/Component.svelte b/test/runtime/samples/await-mount-and-unmount-immediately/Component.svelte new file mode 100644 index 0000000000..be0c7e36b0 --- /dev/null +++ b/test/runtime/samples/await-mount-and-unmount-immediately/Component.svelte @@ -0,0 +1,13 @@ + + +{state} diff --git a/test/runtime/samples/await-mount-and-unmount-immediately/_config.js b/test/runtime/samples/await-mount-and-unmount-immediately/_config.js new file mode 100644 index 0000000000..b944091319 --- /dev/null +++ b/test/runtime/samples/await-mount-and-unmount-immediately/_config.js @@ -0,0 +1,9 @@ +export default { + html: 'Loading...', + async test({ assert, component, target }) { + await component.test(); + + assert.htmlEqual(target.innerHTML, '1'); + assert.deepEqual(component.logs, ['mount 0', 'unmount 0', 'mount 1']); + } +}; diff --git a/test/runtime/samples/await-mount-and-unmount-immediately/main.svelte b/test/runtime/samples/await-mount-and-unmount-immediately/main.svelte new file mode 100644 index 0000000000..245304be83 --- /dev/null +++ b/test/runtime/samples/await-mount-and-unmount-immediately/main.svelte @@ -0,0 +1,36 @@ + + +{#await promise} + Loading... +{:then state} + +{/await} \ No newline at end of file