@ -51,6 +51,7 @@ import { javascript_visitors_runes } from './javascript-runes.js';
import { sanitize _template _string } from '../../../../utils/sanitize_template_string.js' ;
import { walk } from 'zimmerframe' ;
import { locator } from '../../../../state.js' ;
import is _reference from 'is-reference' ;
/ * *
* @ param { import ( '#compiler' ) . RegularElement | import ( '#compiler' ) . SvelteElement } element
@ -939,11 +940,7 @@ function serialize_inline_component(node, component_name, context, anchor = cont
const prev = fn ;
fn = ( node _id ) => {
return serialize _bind _this (
/** @type {Identifier | MemberExpression} */ ( bind _this ) ,
context ,
prev ( node _id )
) ;
return serialize _bind _this ( bind _this , prev ( node _id ) , context ) ;
} ;
}
@ -996,71 +993,69 @@ function serialize_inline_component(node, component_name, context, anchor = cont
/ * *
* Serializes ` bind:this ` for components and elements .
* @ param { Identifier | MemberExpression } bind _this
* @ param { Identifier | MemberExpression } expression
* @ param { Expression } value
* @ param { import ( 'zimmerframe' ) . Context < import ( '#compiler' ) . SvelteNode , import ( '../types.js' ) . ComponentClientTransformState > } context
* @ param { Expression } node
* @ returns
* /
function serialize _bind _this ( bind _this , context , node ) {
let i = 0 ;
/** @type {Map<import('#compiler').Binding, [arg_idx: number, transformed: Expression, expression: import('#compiler').Binding['expression']]>} */
const each _ids = new Map ( ) ;
// Transform each reference to an each block context variable into a $$value_<i> variable
// by temporarily changing the `expression` of the corresponding binding.
// These $$value_<i> variables will be filled in by the bind_this runtime function through its last argument.
function serialize _bind _this ( expression , value , { state , visit } ) {
/** @type {Identifier[]} */
const ids = [ ] ;
/** @type {Expression[]} */
const values = [ ] ;
/** @type {typeof state.getters} */
const getters = { } ;
// Pass in each context variables to the get/set functions, so that we can null out old values on teardown.
// Note that we only do this for each context variables, the consequence is that the value might be stale in
// some scenarios where the value is a member expression with changing computed parts or using a combination of multiple
// variables, but that was the same case in Svelte 4, too. Once legacy mode is gone completely, we can revisit this.
walk (
bind _this ,
{ } ,
{
Identifier ( node ) {
const binding = context . state . scope . get ( node . name ) ;
if ( ! binding || each _ids . has ( binding ) ) return ;
const associated _node = Array . from ( context . state . scopes . entries ( ) ) . find (
( [ _ , scope ] ) => scope === binding ? . scope
) ? . [ 0 ] ;
if ( associated _node ? . type === 'EachBlock' ) {
each _ids . set ( binding , [
i ,
/** @type {Expression} */ ( context . visit ( node ) ) ,
binding . expression
] ) ;
binding . expression = b . id ( '$$value_' + i ) ;
i ++ ;
walk ( expression , null , {
Identifier ( node , { path } ) {
if ( Object . hasOwn ( getters , node . name ) ) return ;
const parent = /** @type {Expression} */ ( path . at ( - 1 ) ) ;
if ( ! is _reference ( node , parent ) ) return ;
const binding = state . scope . get ( node . name ) ;
if ( ! binding ) return ;
for ( const [ owner , scope ] of state . scopes ) {
if ( owner . type === 'EachBlock' && scope === binding . scope ) {
ids . push ( node ) ;
values . push ( /** @type {Expression} */ ( visit ( node ) ) ) ;
getters [ node . name ] = node ;
break ;
}
}
}
);
} );
const bind _this _id = /** @type {Expression} */ ( context . visit ( bind _this ) ) ;
const ids = Array . from ( each _ids . values ( ) ) . map ( ( id ) => b . id ( '$$value_' + id [ 0 ] ) ) ;
const assignment = b . assignment ( '=' , bind _this , b . id ( '$$value' ) ) ;
const update = serialize _set _binding ( assignment , context , ( ) => context . visit ( assignment ) ) ;
const child _state = { ... state , getters : { ... state . getters , ... getters } } ;
for ( const [ binding , [ , , expression ] ] of each _ids ) {
// reset expressions to what they were before
binding. expression = expression ;
}
const get = /** @type {Expression} */ ( visit ( expression , child _state ) ) ;
const set = /** @type {Expression} */ (
visit( b . assignment ( '=' , expression , b . id ( '$$value' ) ) , child _state )
);
/** @type {Expression[]} */
const args = [ node , b . arrow ( [ b . id ( '$$value' ) , ... ids ] , update ) , b . arrow ( [ ... ids ] , bind _this _id ) ] ;
// If we're mutating a property, then it might already be non-existent.
// If we make all the object nodes optional, then it avoids any runtime exceptions.
/** @type {Expression | Super} */
let bind_node = bind _this _id ;
let node = get ;
while ( bind _node ? . type === 'MemberExpression' ) {
bind _node . optional = true ;
bind _node = bind _node . object ;
}
if ( each _ids . size ) {
args . push ( b . thunk ( b . array ( Array . from ( each _ids . values ( ) ) . map ( ( id ) => id [ 1 ] ) ) ) ) ;
while ( node . type === 'MemberExpression' ) {
node . optional = true ;
node = node . object ;
}
return b . call ( '$.bind_this' , ... args ) ;
return b . call (
'$.bind_this' ,
value ,
b . arrow ( [ b . id ( '$$value' ) , ... ids ] , set ) ,
b . arrow ( [ ... ids ] , get ) ,
values . length > 0 && b . thunk ( b . array ( values ) )
) ;
}
/ * *
@ -1394,7 +1389,7 @@ function process_children(nodes, expression, is_element, { visit, state }) {
const text _id = get _node _id ( expression ( true ) , state , 'text' ) ;
const update = b . stmt (
b . call ( '$.set_text' , text _id , /** @type {Expression} */ ( visit ( node . expression )) )
b . call ( '$.set_text' , text _id , /** @type {Expression} */ ( visit ( node . expression , state )) )
) ;
if ( node . metadata . contains _call _expression && ! within _bound _contenteditable ) {
@ -1654,6 +1649,7 @@ export const template_visitors = {
after _update : [ ] ,
template : [ ] ,
locations : [ ] ,
getters : { ... context . state . getters } ,
metadata : {
context : {
template _needs _import _node : false ,
@ -1816,6 +1812,8 @@ export const template_visitors = {
)
) ;
state . getters [ declaration . id . name ] = b . call ( '$.get' , declaration . id ) ;
// we need to eagerly evaluate the expression in order to hit any
// 'Cannot access x before initialization' errors
if ( state . options . dev ) {
@ -1825,21 +1823,24 @@ export const template_visitors = {
const identifiers = extract _identifiers ( declaration . id ) ;
const tmp = b . id ( state . scope . generate ( 'computed_const' ) ) ;
const getters = { ... state . getters } ;
// Make all identifiers that are declared within the following computed regular
// variables, as they are not signals in that context yet
for ( const node of identifiers ) {
const binding = /** @type {import('#compiler').Binding} */ ( state . scope . get ( node . name ) ) ;
binding . expression = node ;
getters [ node . name ] = node ;
}
const child _state = { ... state , getters } ;
// TODO optimise the simple `{ x } = y` case — we can just return `y`
// instead of destructuring it only to return a new object
const fn = b . arrow (
[ ] ,
b . block ( [
b . const (
/** @type {Pattern} */ ( visit ( declaration . id )) ,
/** @type {Expression} */ ( visit ( declaration . init ))
/** @type {Pattern} */ ( visit ( declaration . id , child _state )) ,
/** @type {Expression} */ ( visit ( declaration . init , child _state ))
) ,
b . return ( b . object ( identifiers . map ( ( node ) => b . prop ( 'init' , node , node ) ) ) )
] )
@ -1854,8 +1855,7 @@ export const template_visitors = {
}
for ( const node of identifiers ) {
const binding = /** @type {import('#compiler').Binding} */ ( state . scope . get ( node . name ) ) ;
binding . expression = b . member ( b . call ( '$.get' , tmp ) , node ) ;
state . getters [ node . name ] = b . member ( b . call ( '$.get' , tmp ) , node ) ;
}
}
} ,
@ -2484,6 +2484,11 @@ export const template_visitors = {
indirect _dependencies . push ( ... transitive _dependencies ) ;
}
const child _state = {
... context . state ,
getters : { ... context . state . getters }
} ;
/ * *
* @ param { Pattern } expression _for _id
* @ returns { import ( '#compiler' ) . Binding [ 'mutation' ] }
@ -2532,17 +2537,14 @@ export const template_visitors = {
: b . id ( node . index ) ;
const item = each _node _meta . item ;
const binding = /** @type {import('#compiler').Binding} */ ( context . state . scope . get ( item . name ) ) ;
binding . expression = ( /** @type {import("estree").Identifier} */ id ) => {
const getter = ( /** @type {import("estree").Identifier} */ id ) => {
const item _with _loc = with _loc ( item , id ) ;
return b . call ( '$.unwrap' , item _with _loc ) ;
} ;
child _state . getters [ item . name ] = getter ;
if ( node . index ) {
const index _binding = /** @type {import('#compiler').Binding} */ (
context . state . scope . get ( node . index )
) ;
index _binding . expression = ( id ) => {
child _state . getters [ node . index ] = ( id ) => {
const index _with _loc = with _loc ( index , id ) ;
return b . call ( '$.unwrap' , index _with _loc ) ;
} ;
@ -2560,19 +2562,23 @@ export const template_visitors = {
)
) ;
} else {
const unwrapped = binding. expression ( binding . node ) ;
const unwrapped = getter ( binding . node ) ;
const paths = extract _paths ( node . context ) ;
for ( const path of paths ) {
const name = /** @type {Identifier} */ ( path . node ) . name ;
const binding = /** @type {import('#compiler').Binding} */ ( context . state . scope . get ( name ) ) ;
const needs _derived = path . has _default _value ; // to ensure that default value is only called once
const fn = b . thunk ( /** @type {Expression} */ ( context . visit ( path . expression ? . ( unwrapped ) ) ) ) ;
const fn = b . thunk (
/** @type {Expression} */ ( context . visit ( path . expression ? . ( unwrapped ) , child _state ) )
) ;
declarations . push (
b . let ( path . node , needs _derived ? b . call ( '$.derived_safe_equal' , fn ) : fn )
) ;
binding . expression = needs _derived ? b . call ( '$.get' , b . id ( name ) ) : b . call ( name ) ;
const getter = needs _derived ? b . call ( '$.get' , b . id ( name ) ) : b . call ( name ) ;
child _state . getters [ name ] = getter ;
binding . mutation = create _mutation (
/** @type {Pattern} */ ( path . update _expression ( unwrapped ) )
) ;
@ -2580,21 +2586,23 @@ export const template_visitors = {
// we need to eagerly evaluate the expression in order to hit any
// 'Cannot access x before initialization' errors
if ( context . state . options . dev ) {
declarations . push ( b . stmt ( binding. expression ) ) ;
declarations . push ( b . stmt ( getter ) ) ;
}
}
}
const block = /** @type {BlockStatement} */ ( context . visit ( node . body )) ;
const block = /** @type {BlockStatement} */ ( context . visit ( node . body , child _state )) ;
const key _function = node . key
? b . arrow (
[ node . context . type === 'Identifier' ? node . context : b . id ( '$$item' ) , index ] ,
declarations . length > 0
? b . block (
declarations . concat ( b . return ( /** @type {Expression} */ ( context . visit ( node . key ) ) ) )
declarations . concat (
b . return ( /** @type {Expression} */ ( context . visit ( node . key , child _state ) ) )
)
)
: /** @type {Expression} */ ( context . visit ( node . key ) )
: /** @type {Expression} */ ( context . visit ( node . key , child _state ))
)
: b . id ( '$.index' ) ;
@ -2755,6 +2763,9 @@ export const template_visitors = {
/** @type {Statement[]} */
const declarations = [ ] ;
const getters = { ... context . state . getters } ;
const child _state = { ... context . state , getters } ;
for ( let i = 0 ; i < node . parameters . length ; i ++ ) {
const argument = node . parameters [ i ] ;
@ -2766,10 +2777,8 @@ export const template_visitors = {
left : argument ,
right : b . id ( '$.noop' )
} ) ;
const binding = /** @type {import('#compiler').Binding} */ (
context . state . scope . get ( argument . name )
) ;
binding . expression = b . call ( argument ) ;
getters [ argument . name ] = b . call ( argument ) ;
continue ;
}
@ -2791,19 +2800,20 @@ export const template_visitors = {
declarations . push (
b . let ( path . node , needs _derived ? b . call ( '$.derived_safe_equal' , fn ) : fn )
) ;
binding . expression = needs _derived ? b . call ( '$.get' , b . id ( name ) ) : b . call ( name ) ;
getters [ name ] = needs _derived ? b . call ( '$.get' , b . id ( name ) ) : b . call ( name ) ;
// we need to eagerly evaluate the expression in order to hit any
// 'Cannot access x before initialization' errors
if ( context . state . options . dev ) {
declarations . push ( b . stmt ( binding. expression ) ) ;
declarations . push ( b . stmt ( getters[ name ] ) ) ;
}
}
}
body = b . block ( [
... declarations ,
... /** @type {BlockStatement} */ ( context . visit ( node . body )) . body
... /** @type {BlockStatement} */ ( context . visit ( node . body , child _state )) . body
] ) ;
/** @type {Expression} */
@ -2996,7 +3006,7 @@ export const template_visitors = {
break ;
case 'this' :
call _expr = serialize _bind _this ( node . expression , context, state. node ) ;
call _expr = serialize _bind _this ( node . expression , state. node , context ) ;
break ;
case 'textContent' :
case 'innerHTML' :
@ -3118,7 +3128,10 @@ export const template_visitors = {
const bindings = state . scope . get _bindings ( node ) ;
for ( const binding of bindings ) {
binding . expression = b . member ( b . call ( '$.get' , b . id ( name ) ) , b . id ( binding . node . name ) ) ;
state . getters [ binding . node . name ] = b . member (
b . call ( '$.get' , b . id ( name ) ) ,
b . id ( binding . node . name )
) ;
}
return b . const (