@@ -43,6 +43,7 @@ import {
43
43
untrack ,
44
44
effect ,
45
45
flushSync ,
46
+ flush_sync ,
46
47
safe_not_equal ,
47
48
current_block ,
48
49
managed_effect ,
@@ -64,12 +65,11 @@ import {
64
65
get_descriptors ,
65
66
is_array ,
66
67
is_function ,
67
- object_assign ,
68
- object_keys
68
+ object_assign
69
69
} from './utils.js' ;
70
70
import { is_promise } from '../common.js' ;
71
71
import { bind_transition , trigger_transitions } from './transitions.js' ;
72
- import { STATE_SYMBOL , proxy } from './proxy.js' ;
72
+ import { STATE_SYMBOL } from './proxy.js' ;
73
73
74
74
/** @type {Set<string> } */
75
75
const all_registerd_events = new Set ( ) ;
@@ -2825,14 +2825,21 @@ export function spread_props(...props) {
2825
2825
return new Proxy ( { props } , spread_props_handler ) ;
2826
2826
}
2827
2827
2828
+ // TODO 5.0 remove this
2828
2829
/**
2829
- * Mounts the given component to the given target and returns a handle to the component's public accessors
2830
- * as well as a `$set` and `$destroy` method to update the props of the component or destroy it.
2831
- *
2832
- * If you don't need to interact with the component after mounting, use `mount` instead to save some bytes.
2830
+ * @deprecated Use `mount` or `hydrate` instead
2831
+ */
2832
+ export function createRoot ( ) {
2833
+ throw new Error (
2834
+ '`createRoot` has been removed. Use `mount` or `hydrate` instead. See the updated docs for more info: https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes'
2835
+ ) ;
2836
+ }
2837
+
2838
+ /**
2839
+ * Mounts a component to the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
2833
2840
*
2834
2841
* @template {Record<string, any>} Props
2835
- * @template {Record<string, any> | undefined } Exports
2842
+ * @template {Record<string, any>} Exports
2836
2843
* @template {Record<string, any>} Events
2837
2844
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>> } component
2838
2845
* @param {{
@@ -2841,48 +2848,22 @@ export function spread_props(...props) {
2841
2848
* events?: Events;
2842
2849
* context?: Map<any, any>;
2843
2850
* intro?: boolean;
2844
- * recover?: false;
2845
2851
* }} options
2846
- * @returns {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; } }
2852
+ * @returns {Exports }
2847
2853
*/
2848
- export function createRoot ( component , options ) {
2849
- const props = proxy ( /** @type {any } */ ( options . props ) || { } , false ) ;
2850
-
2851
- let [ accessors , $destroy ] = hydrate ( component , { ...options , props } ) ;
2852
-
2853
- const result =
2854
- /** @type {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; } } */ ( {
2855
- $set : ( next ) => {
2856
- object_assign ( props , next ) ;
2857
- } ,
2858
- $destroy
2859
- } ) ;
2860
-
2861
- for ( const key of object_keys ( accessors || { } ) ) {
2862
- define_property ( result , key , {
2863
- get ( ) {
2864
- // @ts -expect-error TS doesn't know key exists on accessor
2865
- return accessors [ key ] ;
2866
- } ,
2867
- /** @param {any } value */
2868
- set ( value ) {
2869
- // @ts -expect-error TS doesn't know key exists on accessor
2870
- flushSync ( ( ) => ( accessors [ key ] = value ) ) ;
2871
- } ,
2872
- enumerable : true
2873
- } ) ;
2874
- }
2875
-
2876
- return result ;
2854
+ export function mount ( component , options ) {
2855
+ init_operations ( ) ;
2856
+ const anchor = empty ( ) ;
2857
+ options . target . appendChild ( anchor ) ;
2858
+ // Don't flush previous effects to ensure order of outer effects stays consistent
2859
+ return flush_sync ( ( ) => _mount ( component , { ...options , anchor } ) , false ) ;
2877
2860
}
2878
2861
2879
2862
/**
2880
- * Mounts the given component to the given target and returns the accessors of the component and a function to destroy it.
2881
- *
2882
- * If you need to interact with the component after mounting, use `createRoot` instead.
2863
+ * Hydrates a component on the given target and returns the exports and potentially the accessors (if compiled with `accessors: true`) of the component
2883
2864
*
2884
2865
* @template {Record<string, any>} Props
2885
- * @template {Record<string, any> | undefined } Exports
2866
+ * @template {Record<string, any>} Exports
2886
2867
* @template {Record<string, any>} Events
2887
2868
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>> } component
2888
2869
* @param {{
@@ -2891,19 +2872,65 @@ export function createRoot(component, options) {
2891
2872
* events?: Events;
2892
2873
* context?: Map<any, any>;
2893
2874
* intro?: boolean;
2875
+ * recover?: false;
2894
2876
* }} options
2895
- * @returns {[ Exports, () => void] }
2877
+ * @returns {Exports }
2896
2878
*/
2897
- export function mount ( component , options ) {
2879
+ export function hydrate ( component , options ) {
2898
2880
init_operations ( ) ;
2899
- const anchor = empty ( ) ;
2900
- options . target . appendChild ( anchor ) ;
2901
- return _mount ( component , { ...options , anchor } ) ;
2881
+ const container = options . target ;
2882
+ const first_child = /** @type {ChildNode } */ ( container . firstChild ) ;
2883
+ // Call with insert_text == true to prevent empty {expressions} resulting in an empty
2884
+ // fragment array, resulting in a hydration error down the line
2885
+ const hydration_fragment = get_hydration_fragment ( first_child , true ) ;
2886
+ const previous_hydration_fragment = current_hydration_fragment ;
2887
+ set_current_hydration_fragment ( hydration_fragment ) ;
2888
+
2889
+ /** @type {null | Text } */
2890
+ let anchor = null ;
2891
+ if ( hydration_fragment === null ) {
2892
+ anchor = empty ( ) ;
2893
+ container . appendChild ( anchor ) ;
2894
+ }
2895
+
2896
+ let finished_hydrating = false ;
2897
+
2898
+ try {
2899
+ // Don't flush previous effects to ensure order of outer effects stays consistent
2900
+ return flush_sync ( ( ) => {
2901
+ const instance = _mount ( component , { ...options , anchor } ) ;
2902
+ // flush_sync will run this callback and then synchronously run any pending effects,
2903
+ // which don't belong to the hydration phase anymore - therefore reset it here
2904
+ set_current_hydration_fragment ( null ) ;
2905
+ finished_hydrating = true ;
2906
+ return instance ;
2907
+ } , false ) ;
2908
+ } catch ( error ) {
2909
+ if ( ! finished_hydrating && options . recover !== false && hydration_fragment !== null ) {
2910
+ // eslint-disable-next-line no-console
2911
+ console . error (
2912
+ 'ERR_SVELTE_HYDRATION_MISMATCH' +
2913
+ ( DEV
2914
+ ? ': Hydration failed because the initial UI does not match what was rendered on the server.'
2915
+ : '' ) ,
2916
+ error
2917
+ ) ;
2918
+ remove ( hydration_fragment ) ;
2919
+ first_child . remove ( ) ;
2920
+ hydration_fragment . at ( - 1 ) ?. nextSibling ?. remove ( ) ;
2921
+ set_current_hydration_fragment ( null ) ;
2922
+ return mount ( component , options ) ;
2923
+ } else {
2924
+ throw error ;
2925
+ }
2926
+ } finally {
2927
+ set_current_hydration_fragment ( previous_hydration_fragment ) ;
2928
+ }
2902
2929
}
2903
2930
2904
2931
/**
2905
2932
* @template {Record<string, any>} Props
2906
- * @template {Record<string, any> | undefined } Exports
2933
+ * @template {Record<string, any>} Exports
2907
2934
* @template {Record<string, any>} Events
2908
2935
* @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>> } component
2909
2936
* @param {{
@@ -2915,7 +2942,7 @@ export function mount(component, options) {
2915
2942
* intro?: boolean;
2916
2943
* recover?: false;
2917
2944
* }} options
2918
- * @returns {[ Exports, () => void] }
2945
+ * @returns {Exports }
2919
2946
*/
2920
2947
function _mount ( component , options ) {
2921
2948
const registered_events = new Set ( ) ;
@@ -2934,7 +2961,7 @@ function _mount(component, options) {
2934
2961
options . context ;
2935
2962
}
2936
2963
// @ts -expect-error the public typings are not what the actual function looks like
2937
- accessors = component ( options . anchor , options . props || { } ) ;
2964
+ accessors = component ( options . anchor , options . props || { } ) || { } ;
2938
2965
if ( options . context ) {
2939
2966
pop ( ) ;
2940
2967
}
@@ -2981,80 +3008,38 @@ function _mount(component, options) {
2981
3008
event_handle ( array_from ( all_registerd_events ) ) ;
2982
3009
root_event_handles . add ( event_handle ) ;
2983
3010
2984
- return [
2985
- accessors ,
2986
- ( ) => {
2987
- for ( const event_name of registered_events ) {
2988
- container . removeEventListener ( event_name , bound_event_listener ) ;
2989
- }
2990
- root_event_handles . delete ( event_handle ) ;
2991
- const dom = block . d ;
2992
- if ( dom !== null ) {
2993
- remove ( dom ) ;
2994
- }
2995
- destroy_signal ( /** @type {import('./types.js').EffectSignal } */ ( block . e ) ) ;
3011
+ mounted_components . set ( accessors , ( ) => {
3012
+ for ( const event_name of registered_events ) {
3013
+ container . removeEventListener ( event_name , bound_event_listener ) ;
3014
+ }
3015
+ root_event_handles . delete ( event_handle ) ;
3016
+ const dom = block . d ;
3017
+ if ( dom !== null ) {
3018
+ remove ( dom ) ;
2996
3019
}
2997
- ] ;
3020
+ destroy_signal ( /** @type {import('./types.js').EffectSignal } */ ( block . e ) ) ;
3021
+ } ) ;
3022
+
3023
+ return accessors ;
2998
3024
}
2999
3025
3000
3026
/**
3001
- * Hydrates the given component to the given target and returns the accessors of the component and a function to destroy it.
3002
- *
3003
- * If you need to interact with the component after hydrating, use `createRoot` instead.
3004
- *
3005
- * @template {Record<string, any>} Props
3006
- * @template {Record<string, any> | undefined} Exports
3007
- * @template {Record<string, any>} Events
3008
- * @param {import('../../main/public.js').ComponentType<import('../../main/public.js').SvelteComponent<Props, Events>> } component
3009
- * @param {{
3010
- * target: Node;
3011
- * props?: Props;
3012
- * events?: Events;
3013
- * context?: Map<any, any>;
3014
- * intro?: boolean;
3015
- * recover?: false;
3016
- * }} options
3017
- * @returns {[Exports, () => void] }
3027
+ * References of the accessors of all components that were `mount`ed or `hydrate`d.
3028
+ * Uses a `WeakMap` to avoid memory leaks.
3018
3029
*/
3019
- export function hydrate ( component , options ) {
3020
- init_operations ( ) ;
3021
- const container = options . target ;
3022
- const first_child = /** @type {ChildNode } */ ( container . firstChild ) ;
3023
- // Call with insert_text == true to prevent empty {expressions} resulting in an empty
3024
- // fragment array, resulting in a hydration error down the line
3025
- const hydration_fragment = get_hydration_fragment ( first_child , true ) ;
3026
- const previous_hydration_fragment = current_hydration_fragment ;
3030
+ let mounted_components = new WeakMap ( ) ;
3027
3031
3028
- try {
3029
- /** @type {null | Text } */
3030
- let anchor = null ;
3031
- if ( hydration_fragment === null ) {
3032
- anchor = empty ( ) ;
3033
- container . appendChild ( anchor ) ;
3034
- }
3035
- set_current_hydration_fragment ( hydration_fragment ) ;
3036
- return _mount ( component , { ...options , anchor } ) ;
3037
- } catch ( error ) {
3038
- if ( options . recover !== false && hydration_fragment !== null ) {
3039
- // eslint-disable-next-line no-console
3040
- console . error (
3041
- 'ERR_SVELTE_HYDRATION_MISMATCH' +
3042
- ( DEV
3043
- ? ': Hydration failed because the initial UI does not match what was rendered on the server.'
3044
- : '' ) ,
3045
- error
3046
- ) ;
3047
- remove ( hydration_fragment ) ;
3048
- first_child . remove ( ) ;
3049
- hydration_fragment . at ( - 1 ) ?. nextSibling ?. remove ( ) ;
3050
- set_current_hydration_fragment ( null ) ;
3051
- return mount ( component , options ) ;
3052
- } else {
3053
- throw error ;
3054
- }
3055
- } finally {
3056
- set_current_hydration_fragment ( previous_hydration_fragment ) ;
3032
+ /**
3033
+ * Unmounts a component that was previously mounted using `mount` or `hydrate`.
3034
+ * @param {Record<string, any> } component
3035
+ */
3036
+ export function unmount ( component ) {
3037
+ const destroy = mounted_components . get ( component ) ;
3038
+ if ( DEV && ! destroy ) {
3039
+ // eslint-disable-next-line no-console
3040
+ console . warn ( 'Tried to unmount a component that was not mounted.' ) ;
3057
3041
}
3042
+ destroy ?. ( ) ;
3058
3043
}
3059
3044
3060
3045
/**
0 commit comments