@ -54,6 +54,7 @@ import {
current _hydration _fragment ,
get _hydration _fragment ,
hydrate _block _anchor ,
hydrating ,
set _current _hydration _fragment
} from './hydration.js' ;
import {
@ -166,7 +167,7 @@ export function svg_replace(node) {
* @ returns { Element | DocumentFragment | Node [ ] }
* /
function open _template ( is _fragment , use _clone _node , anchor , template _element _fn ) {
if ( current_ hydratio n_fra gment !== null ) {
if ( hydrating) {
if ( anchor !== null ) {
hydrate _block _anchor ( anchor , false ) ;
}
@ -217,7 +218,7 @@ export function space(anchor) {
// if an {expression} is empty during SSR, there might be no
// text node to hydrate (or an anchor comment is falsely detected instead)
// — we must therefore create one
if ( current_ hydratio n_fra gment !== null && node ? . nodeType !== 3 ) {
if ( hydrating && node ? . nodeType !== 3 ) {
node = empty ( ) ;
// @ts-ignore in this case the anchor should always be a comment,
// if not something more fundamental is wrong and throwing here is better to bail out early
@ -251,11 +252,9 @@ function close_template(dom, is_fragment, anchor) {
? dom
: /** @type {import('./types.js').TemplateNode[]} */ ( Array . from ( dom . childNodes ) )
: dom ;
if ( anchor !== null ) {
if ( current _hydration _fragment === null ) {
if ( ! hydrating && anchor !== null ) {
insert ( current , null , anchor ) ;
}
}
block . d = current ;
}
@ -415,14 +414,13 @@ export function class_name(dom, value) {
// @ts-expect-error need to add __className to patched prototype
const prev _class _name = dom . _ _className ;
const next _class _name = to _class ( value ) ;
const is _hydrating = current _hydration _fragment !== null ;
if ( is _hydrating && dom . className === next _class _name ) {
if ( hydrating && dom . className === next _class _name ) {
// In case of hydration don't reset the class as it's already correct.
// @ts-expect-error need to add __className to patched prototype
dom . _ _className = next _class _name ;
} else if (
prev _class _name !== next _class _name ||
( is_ hydrating && dom . className !== next _class _name )
( hydrating && dom . className !== next _class _name )
) {
if ( next _class _name === '' ) {
dom . removeAttribute ( 'class' ) ;
@ -452,7 +450,7 @@ export function text(dom, value) {
// @ts-expect-error need to add __value to patched prototype
const prev _node _value = dom . _ _nodeValue ;
const next _node _value = stringify ( value ) ;
if ( current_ hydratio n_fra gment !== null && dom . nodeValue === next _node _value ) {
if ( hydrating && dom . nodeValue === next _node _value ) {
// In case of hydration don't reset the nodeValue as it's already correct.
// @ts-expect-error need to add __nodeValue to patched prototype
dom . _ _nodeValue = next _node _value ;
@ -739,7 +737,7 @@ export function bind_playback_rate(media, get_value, update) {
* @ param { ( paused : boolean ) => void } update
* /
export function bind _paused ( media , get _value , update ) {
let mounted = current_ hydratio n_fra gment !== null ;
let mounted = hydrating;
let paused = get _value ( ) ;
const callback = ( ) => {
if ( paused !== media . paused ) {
@ -1452,7 +1450,8 @@ export function slot(anchor_node, slot_fn, slot_props, fallback_fn) {
function if _block ( anchor _node , condition _fn , consequent _fn , alternate _fn ) {
const block = create _if _block ( ) ;
hydrate _block _anchor ( anchor _node ) ;
const previous _hydration _fragment = current _hydration _fragment ;
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
let mismatch = false ;
/** @type {null | import('./types.js').TemplateNode | Array<import('./types.js').TemplateNode>} */
let consequent _dom = null ;
@ -1495,7 +1494,7 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
trigger _transitions ( alternate _transitions , 'in' ) ;
}
}
} else if ( current_ hydratio n_fra gment !== null ) {
} else if ( hydrating) {
const comment _text = /** @type {Comment} */ ( current _hydration _fragment ? . [ 0 ] ) ? . data ;
if (
! comment _text ||
@ -1506,6 +1505,7 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
// This could happen using when `{#if browser} .. {/if}` in SvelteKit.
remove ( current _hydration _fragment ) ;
set _current _hydration _fragment ( null ) ;
mismatch = true ;
} else {
// Remove the ssr:if comment node or else it will confuse the subsequent hydration algorithm
current _hydration _fragment . shift ( ) ;
@ -1530,9 +1530,9 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
}
if ( result && current _branch _effect !== consequent _effect ) {
consequent _fn ( anchor _node ) ;
if ( current_branch _effect === null ) {
// Restore previous fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( previous _hydration _fragment ) ;
if ( mismatch && current_branch _effect === null ) {
// Set fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( [ ] ) ;
}
current _branch _effect = consequent _effect ;
consequent _dom = block . d ;
@ -1558,9 +1558,9 @@ function if_block(anchor_node, condition_fn, consequent_fn, alternate_fn) {
if ( alternate _fn !== null ) {
alternate _fn ( anchor _node ) ;
}
if ( current_branch _effect === null ) {
// Restore previous fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( previous _hydration _fragment ) ;
if ( mismatch && current_branch _effect === null ) {
// Set fragment so that Svelte continues to operate in hydration mode
set _current _hydration _fragment ( [ ] ) ;
}
current _branch _effect = alternate _effect ;
alternate _dom = block . d ;
@ -1593,10 +1593,15 @@ export function head(render_fn) {
const block = create _head _block ( ) ;
// The head function may be called after the first hydration pass and ssr comment nodes may still be present,
// therefore we need to skip that when we detect that we're not in hydration mode.
const hydration _fragment =
current _hydration _fragment !== null ? get _hydration _fragment ( document . head . firstChild ) : null ;
const previous _hydration _fragment = current _hydration _fragment ;
let hydration _fragment = null ;
let previous _hydration _fragment = null ;
let is _hydrating = hydrating ;
if ( is _hydrating ) {
hydration _fragment = get _hydration _fragment ( document . head . firstChild ) ;
previous _hydration _fragment = current _hydration _fragment ;
set _current _hydration _fragment ( hydration _fragment ) ;
}
try {
const head _effect = render _effect (
( ) => {
@ -1606,7 +1611,7 @@ export function head(render_fn) {
block . d = null ;
}
let anchor = null ;
if ( current _hydration _fragment === null ) {
if ( ! hydrating ) {
anchor = empty ( ) ;
document . head . appendChild ( anchor ) ;
}
@ -1623,8 +1628,10 @@ export function head(render_fn) {
} ) ;
block . e = head _effect ;
} finally {
if ( is _hydrating ) {
set _current _hydration _fragment ( previous _hydration _fragment ) ;
}
}
}
/ * *
@ -1688,7 +1695,7 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
? null
: anchor _node . parentElement ? . namespaceURI ? ? null ;
const next _element = tag
? current_ hydratio n_fra gment !== null
? hydrating
? /** @type {Element} */ ( current _hydration _fragment [ 0 ] )
: ns
? document . createElementNS ( ns , tag )
@ -1701,7 +1708,7 @@ export function element(anchor_node, tag_fn, is_svg, render_fn) {
element = next _element ;
if ( element !== null && render _fn !== undefined ) {
let anchor ;
if ( current_ hydratio n_fra gment !== null ) {
if ( hydrating) {
// Use the existing ssr comment as the anchor so that the inner open and close
// methods can pick up the existing nodes correctly
anchor = /** @type {Comment} */ ( element . firstChild ) ;
@ -2158,7 +2165,7 @@ export function cssProps(anchor, is_html, props, component) {
/** @type {Text | Comment} */
let component _anchor ;
if ( current_ hydratio n_fra gment !== null ) {
if ( hydrating) {
// Hydration: css props element is surrounded by a ssr comment ...
tag = /** @type {HTMLElement | SVGElement} */ ( current _hydration _fragment [ 0 ] ) ;
// ... and the child(ren) of the css props element is also surround by a ssr comment
@ -2324,7 +2331,7 @@ export function action(dom, action, value_fn) {
* @ returns { void }
* /
export function remove _input _attr _defaults ( dom ) {
if ( current_ hydratio n_fra gment !== null ) {
if ( hydrating) {
attr ( dom , 'value' , null ) ;
attr ( dom , 'checked' , null ) ;
}
@ -2336,7 +2343,7 @@ export function remove_input_attr_defaults(dom) {
* @ returns { void }
* /
export function remove _textarea _child ( dom ) {
if ( current_ hydratio n_fra gment !== null && dom . firstChild !== null ) {
if ( hydrating && dom . firstChild !== null ) {
dom . textContent = '' ;
}
}
@ -2366,7 +2373,7 @@ export function attr(dom, attribute, value) {
}
if (
current _hydration _fragment === null ||
! hydrating ||
( dom . getAttribute ( attribute ) !== value &&
// If we reset those, they would result in another network request, which we want to avoid.
// We assume they are the same between client and server as checking if they are equal is expensive
@ -2429,7 +2436,7 @@ export function srcset_url_equal(element, srcset) {
* @ param { string | null } value
* /
function check _src _in _dev _hydration ( dom , attribute , value ) {
if ( ! current_ hydratio n_fra gment ) return ;
if ( ! hydrating) return ;
if ( attribute !== 'src' && attribute !== 'href' && attribute !== 'srcset' ) return ;
if ( attribute === 'srcset' && srcset _url _equal ( dom , value ) ) return ;
@ -2642,7 +2649,7 @@ export function spread_attributes(dom, prev, attrs, lowercase_attributes, css_ha
check _src _in _dev _hydration ( dom , name , value ) ;
}
if (
current _hydration _fragment === null ||
! hydrating ||
// @ts-ignore see attr method for an explanation of src/srcset
( dom [ name ] !== value && name !== 'src' && name !== 'href' && name !== 'srcset' )
) {
@ -2828,7 +2835,7 @@ export function spread_props(...props) {
export function createRoot ( component , options ) {
const props = proxy ( /** @type {any} */ ( options . props ) || { } , false ) ;
let [ accessors , $destroy ] = mount ( component , { ... options , props } ) ;
let [ accessors , $destroy ] = hydrate ( component , { ... options , props } ) ;
const result =
/** @type {Exports & { $destroy: () => void; $set: (props: Partial<Props>) => void; }} */ ( {
@ -2871,43 +2878,50 @@ export function createRoot(component, options) {
* events ? : Events ;
* context ? : Map < any , any > ;
* intro ? : boolean ;
* recover ? : false ;
* } } options
* @ returns { [ Exports , ( ) => void ] }
* /
export function mount ( component , options ) {
init _operations ( ) ;
const anchor = empty ( ) ;
options . target . appendChild ( anchor ) ;
return _mount ( component , { ... options , anchor } ) ;
}
/ * *
* @ template { Record < string , any > } Props
* @ template { Record < string , any > | undefined } Exports
* @ template { Record < string , any > } Events
* @ param { import ( '../../main/public.js' ) . ComponentType < import ( '../../main/public.js' ) . SvelteComponent < Props , Events >> } component
* @ param { {
* target : Node ;
* anchor : null | Text ;
* props ? : Props ;
* events ? : Events ;
* context ? : Map < any , any > ;
* intro ? : boolean ;
* recover ? : false ;
* } } options
* @ returns { [ Exports , ( ) => void ] }
* /
function _mount ( component , options ) {
const registered _events = new Set ( ) ;
const container = options . target ;
const block = create _root _block ( options . intro || false ) ;
const first _child = /** @type {ChildNode} */ ( container . firstChild ) ;
// Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line
const hydration _fragment = get _hydration _fragment ( first _child , true ) ;
const previous _hydration _fragment = current _hydration _fragment ;
/** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously
let accessors = undefined ;
try {
/** @type {null | Text} */
let anchor = null ;
if ( hydration _fragment === null ) {
anchor = empty ( ) ;
container . appendChild ( anchor ) ;
}
set _current _hydration _fragment ( hydration _fragment ) ;
const effect = render _effect (
( ) => {
if ( options . context ) {
push ( { } ) ;
/** @type {import('../client/types.js').ComponentContext} */ (
current _component _context
) . c = options . context ;
/** @type {import('../client/types.js').ComponentContext} */ ( current _component _context ) . c =
options . context ;
}
// @ts-expect-error the public typings are not what the actual function looks like
accessors = component ( anchor , options . props || { } ) ;
accessors = component ( options . anchor , options . props || { } ) ;
if ( options . context ) {
pop ( ) ;
}
@ -2916,26 +2930,6 @@ export function mount(component, options) {
true
) ;
block . e = effect ;
} catch ( error ) {
if ( options . recover !== false && hydration _fragment !== null ) {
// eslint-disable-next-line no-console
console . error (
'ERR_SVELTE_HYDRATION_MISMATCH' +
( DEV
? ': Hydration failed because the initial UI does not match what was rendered on the server.'
: '' ) ,
error
) ;
remove ( hydration _fragment ) ;
first _child . remove ( ) ;
hydration _fragment . at ( - 1 ) ? . nextSibling ? . remove ( ) ;
return mount ( component , options ) ;
} else {
throw error ;
}
} finally {
set _current _hydration _fragment ( previous _hydration _fragment ) ;
}
const bound _event _listener = handle _event _propagation . bind ( null , container ) ;
const bound _document _event _listener = handle _event _propagation . bind ( null , document ) ;
@ -2985,14 +2979,71 @@ export function mount(component, options) {
if ( dom !== null ) {
remove ( dom ) ;
}
if ( hydration _fragment !== null ) {
remove ( hydration _fragment ) ;
}
destroy _signal ( /** @type {import('./types.js').EffectSignal} */ ( block . e ) ) ;
}
] ;
}
/ * *
* Hydrates the given component to the given target and returns the accessors of the component and a function to destroy it .
*
* If you need to interact with the component after hydrating , use ` createRoot ` instead .
*
* @ template { Record < string , any > } Props
* @ template { Record < string , any > | undefined } Exports
* @ template { Record < string , any > } Events
* @ param { import ( '../../main/public.js' ) . ComponentType < import ( '../../main/public.js' ) . SvelteComponent < Props , Events >> } component
* @ param { {
* target : Node ;
* props ? : Props ;
* events ? : Events ;
* context ? : Map < any , any > ;
* intro ? : boolean ;
* recover ? : false ;
* } } options
* @ returns { [ Exports , ( ) => void ] }
* /
export function hydrate ( component , options ) {
init _operations ( ) ;
const container = options . target ;
const first _child = /** @type {ChildNode} */ ( container . firstChild ) ;
// Call with insert_text == true to prevent empty {expressions} resulting in an empty
// fragment array, resulting in a hydration error down the line
const hydration _fragment = get _hydration _fragment ( first _child , true ) ;
const previous _hydration _fragment = current _hydration _fragment ;
try {
/** @type {null | Text} */
let anchor = null ;
if ( hydration _fragment === null ) {
anchor = empty ( ) ;
container . appendChild ( anchor ) ;
}
set _current _hydration _fragment ( hydration _fragment ) ;
return _mount ( component , { ... options , anchor } ) ;
} catch ( error ) {
if ( options . recover !== false && hydration _fragment !== null ) {
// eslint-disable-next-line no-console
console . error (
'ERR_SVELTE_HYDRATION_MISMATCH' +
( DEV
? ': Hydration failed because the initial UI does not match what was rendered on the server.'
: '' ) ,
error
) ;
remove ( hydration _fragment ) ;
first _child . remove ( ) ;
hydration _fragment . at ( - 1 ) ? . nextSibling ? . remove ( ) ;
set _current _hydration _fragment ( null ) ;
return mount ( component , options ) ;
} else {
throw error ;
}
} finally {
set _current _hydration _fragment ( previous _hydration _fragment ) ;
}
}
/ * *
* @ param { Record < string , unknown > } props
* @ returns { void }