diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 3e4801054757..9de5e6ccb3d4 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -3,25 +3,8 @@ 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'; +import { Fragment } from './types'; -/** - * 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: any, dirty: any) => 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: null | any; @@ -67,7 +50,10 @@ export function mount_component(component, target, anchor, customElement) { add_render_callback(() => { const new_on_destroy = on_mount.map(run).filter(is_function); - if (on_destroy) { + // 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) { on_destroy.push(...new_on_destroy); } else { // Edge case - component was destroyed immediately, diff --git a/src/runtime/internal/await_block.ts b/src/runtime/internal/await_block.ts index ea6e8a187f90..1e09ada6cf2c 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 306a5e3793b4..9aa45dc4f10b 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 000000000000..7068c1c61fc2 --- /dev/null +++ b/src/runtime/internal/types.ts @@ -0,0 +1,20 @@ +/** + * 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: any, dirty: any) => 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; 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 000000000000..be0c7e36b036 --- /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 000000000000..c3a17aa37080 --- /dev/null +++ b/test/runtime/samples/await-mount-and-unmount-immediately/_config.js @@ -0,0 +1,11 @@ +export default { + html: 'Loading...', + async test({ assert, component, target, window }) { + const button = target.querySelector('button'); + + 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 000000000000..245304be8332 --- /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