@ -847,18 +847,30 @@ function serialize_inline_component(node, component_name, context) {
/** @type {import('estree').Property[]} */
const serialized _slots = [ ] ;
for ( const slot _name of Object . keys ( children ) ) {
const body = create _block (
node ,
node . fragment ,
` ${ node . name } _ ${ slot _name } ` ,
children [ slot _name ] ,
context
const block = /** @type {import('estree').BlockStatement} */ (
context . visit (
{
... node . fragment ,
// @ts-expect-error
nodes : children [ slot _name ]
} ,
{
... context . state ,
scope :
context . state . scopes . get ( slot _name === 'default' ? children [ slot _name ] [ 0 ] : node ) ? ?
context . state . scope
}
)
) ;
if ( body . length === 0 ) continue ;
if ( block . body . length === 0 ) continue ;
const slot _fn = b . arrow (
[ b . id ( '$$anchor' ) , b . id ( '$$slotProps' ) ] ,
b . block ( [ ... ( slot _name === 'default' && ! slot _scope _applies _to _itself ? lets : [ ] ) , ... body ] )
b . block ( [
... ( slot _name === 'default' && ! slot _scope _applies _to _itself ? lets : [ ] ) ,
... block . body
] )
) ;
if ( slot _name === 'default' && ! has _children _prop ) {
@ -1016,189 +1028,6 @@ function serialize_locations(locations) {
) ;
}
/ * *
* Creates a new block which looks roughly like this :
* ` ` ` js
* // hoisted:
* const block _name = $ . template ( ` ... ` ) ;
*
* // for the main block:
* const id = block _name ( ) ;
* // init stuff and possibly render effect
* $ . append ( $$anchor , id ) ;
* ` ` `
* Adds the hoisted parts to ` context.state.hoisted ` and returns the statements of the main block .
* @ param { import ( '#compiler' ) . SvelteNode } parent
* @ param { import ( '#compiler' ) . Fragment } fragment
* @ param { string } name
* @ param { import ( '#compiler' ) . SvelteNode [ ] } nodes
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ returns { import ( 'estree' ) . Statement [ ] }
* /
function create _block ( parent , fragment , name , nodes , context ) {
const namespace = infer _namespace ( context . state . metadata . namespace , parent , nodes ) ;
const { hoisted , trimmed } = clean _nodes (
parent ,
nodes ,
context . path ,
namespace ,
context . state . preserve _whitespace ,
context . state . options . preserveComments
) ;
if ( hoisted . length === 0 && trimmed . length === 0 ) {
return [ ] ;
}
const is _single _element = trimmed . length === 1 && trimmed [ 0 ] . type === 'RegularElement' ;
const is _single _child _not _needing _template =
trimmed . length === 1 &&
( trimmed [ 0 ] . type === 'SvelteFragment' || trimmed [ 0 ] . type === 'TitleElement' ) ;
const template _name = context . state . scope . root . unique ( name ) ;
/** @type {import('estree').Statement[]} */
const body = [ ] ;
/** @type {import('estree').Statement | undefined} */
let close = undefined ;
/** @type {import('../types').ComponentClientTransformState} */
const state = {
... context . state ,
scope : context . state . scopes . get ( fragment ) ? ? context . state . scope ,
before _init : [ ] ,
init : [ ] ,
update : [ ] ,
after _update : [ ] ,
template : [ ] ,
locations : [ ] ,
metadata : {
context : {
template _needs _import _node : false ,
template _contains _script _tag : false
} ,
namespace ,
bound _contenteditable : context . state . metadata . bound _contenteditable
}
} ;
for ( const node of hoisted ) {
context . visit ( node , state ) ;
}
/ * *
* @ param { import ( 'estree' ) . Identifier } template _name
* @ param { import ( 'estree' ) . Expression [ ] } args
* /
const add _template = ( template _name , args ) => {
let call = b . call ( get _template _function ( namespace , state ) , ... args ) ;
if ( context . state . options . dev ) {
call = b . call (
'$.add_locations' ,
call ,
b . member ( b . id ( context . state . analysis . name ) , b . id ( 'filename' ) ) ,
serialize _locations ( state . locations )
) ;
}
context . state . hoisted . push ( b . var ( template _name , call ) ) ;
} ;
if ( is _single _element ) {
const element = /** @type {import('#compiler').RegularElement} */ ( trimmed [ 0 ] ) ;
const id = b . id ( context . state . scope . generate ( element . name ) ) ;
context . visit ( element , {
... state ,
node : id
} ) ;
/** @type {import('estree').Expression[]} */
const args = [ b . template ( [ b . quasi ( state . template . join ( '' ) , true ) ] , [ ] ) ] ;
if ( state . metadata . context . template _needs _import _node ) {
args . push ( b . literal ( TEMPLATE _USE _IMPORT _NODE ) ) ;
}
add _template ( template _name , args ) ;
body . push ( b . var ( id , b . call ( template _name ) ) , ... state . before _init , ... state . init ) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
} else if ( is _single _child _not _needing _template ) {
context . visit ( trimmed [ 0 ] , state ) ;
body . push ( ... state . before _init , ... state . init ) ;
} else if ( trimmed . length > 0 ) {
const id = b . id ( context . state . scope . generate ( 'fragment' ) ) ;
const use _space _template =
trimmed . some ( ( node ) => node . type === 'ExpressionTag' ) &&
trimmed . every ( ( node ) => node . type === 'Text' || node . type === 'ExpressionTag' ) ;
if ( use _space _template ) {
// special case — we can use `$.text` instead of creating a unique template
const id = b . id ( context . state . scope . generate ( 'text' ) ) ;
process _children ( trimmed , ( ) => id , false , {
... context ,
state
} ) ;
body . push ( b . var ( id , b . call ( '$.text' , b . id ( '$$anchor' ) ) ) , ... state . before _init , ... state . init ) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
} else {
/** @type {(is_text: boolean) => import('estree').Expression} */
const expression = ( is _text ) =>
is _text ? b . call ( '$.first_child' , id , b . true ) : b . call ( '$.first_child' , id ) ;
process _children ( trimmed , expression , false , { ... context , state } ) ;
const use _comment _template = state . template . length === 1 && state . template [ 0 ] === '<!>' ;
if ( use _comment _template ) {
// special case — we can use `$.comment` instead of creating a unique template
body . push ( b . var ( id , b . call ( '$.comment' ) ) ) ;
} else {
let flags = TEMPLATE _FRAGMENT ;
if ( state . metadata . context . template _needs _import _node ) {
flags |= TEMPLATE _USE _IMPORT _NODE ;
}
add _template ( template _name , [
b . template ( [ b . quasi ( state . template . join ( '' ) , true ) ] , [ ] ) ,
b . literal ( flags )
] ) ;
body . push ( b . var ( id , b . call ( template _name ) ) ) ;
}
body . push ( ... state . before _init , ... state . init ) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
}
} else {
body . push ( ... state . before _init , ... state . init ) ;
}
if ( state . update . length > 0 ) {
body . push ( serialize _render _stmt ( state ) ) ;
}
body . push ( ... state . after _update ) ;
if ( close !== undefined ) {
// It's important that close is the last statement in the block, as any previous statements
// could contain element insertions into the template, which the close statement needs to
// know of when constructing the list of current inner elements.
body . push ( close ) ;
}
return body ;
}
/ * *
*
* @ param { import ( '#compiler' ) . Namespace } namespace
@ -1688,7 +1517,184 @@ function serialize_template_literal(values, visit, state) {
/** @type {import('../types').ComponentVisitors} */
export const template _visitors = {
Fragment ( node , context ) {
const body = create _block ( context . path . at ( - 1 ) ? ? node , node , 'root' , node . nodes , context ) ;
// Creates a new block which looks roughly like this:
// ```js
// // hoisted:
// const block_name = $.template(`...`);
//
// // for the main block:
// const id = block_name();
// // init stuff and possibly render effect
// $.append($$anchor, id);
// ```
// Adds the hoisted parts to `context.state.hoisted` and returns the statements of the main block.
const parent = context . path . at ( - 1 ) ? ? node ;
const namespace = infer _namespace ( context . state . metadata . namespace , parent , node . nodes ) ;
const { hoisted , trimmed } = clean _nodes (
parent ,
node . nodes ,
context . path ,
namespace ,
context . state ,
context . state . preserve _whitespace ,
context . state . options . preserveComments
) ;
if ( hoisted . length === 0 && trimmed . length === 0 ) {
return b . block ( [ ] ) ;
}
const is _single _element = trimmed . length === 1 && trimmed [ 0 ] . type === 'RegularElement' ;
const is _single _child _not _needing _template =
trimmed . length === 1 &&
( trimmed [ 0 ] . type === 'SvelteFragment' || trimmed [ 0 ] . type === 'TitleElement' ) ;
const template _name = context . state . scope . root . unique ( 'root' ) ; // TODO infer name from parent
/** @type {import('estree').Statement[]} */
const body = [ ] ;
/** @type {import('estree').Statement | undefined} */
let close = undefined ;
/** @type {import('../types').ComponentClientTransformState} */
const state = {
... context . state ,
before _init : [ ] ,
init : [ ] ,
update : [ ] ,
after _update : [ ] ,
template : [ ] ,
locations : [ ] ,
metadata : {
context : {
template _needs _import _node : false ,
template _contains _script _tag : false
} ,
namespace ,
bound _contenteditable : context . state . metadata . bound _contenteditable
}
} ;
for ( const node of hoisted ) {
context . visit ( node , state ) ;
}
/ * *
* @ param { import ( 'estree' ) . Identifier } template _name
* @ param { import ( 'estree' ) . Expression [ ] } args
* /
const add _template = ( template _name , args ) => {
let call = b . call ( get _template _function ( namespace , state ) , ... args ) ;
if ( context . state . options . dev ) {
call = b . call (
'$.add_locations' ,
call ,
b . member ( b . id ( context . state . analysis . name ) , b . id ( 'filename' ) ) ,
serialize _locations ( state . locations )
) ;
}
context . state . hoisted . push ( b . var ( template _name , call ) ) ;
} ;
if ( is _single _element ) {
const element = /** @type {import('#compiler').RegularElement} */ ( trimmed [ 0 ] ) ;
const id = b . id ( context . state . scope . generate ( element . name ) ) ;
context . visit ( element , {
... state ,
node : id
} ) ;
/** @type {import('estree').Expression[]} */
const args = [ b . template ( [ b . quasi ( state . template . join ( '' ) , true ) ] , [ ] ) ] ;
if ( state . metadata . context . template _needs _import _node ) {
args . push ( b . literal ( TEMPLATE _USE _IMPORT _NODE ) ) ;
}
add _template ( template _name , args ) ;
body . push ( b . var ( id , b . call ( template _name ) ) , ... state . before _init , ... state . init ) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
} else if ( is _single _child _not _needing _template ) {
context . visit ( trimmed [ 0 ] , state ) ;
body . push ( ... state . before _init , ... state . init ) ;
} else if ( trimmed . length > 0 ) {
const id = b . id ( context . state . scope . generate ( 'fragment' ) ) ;
const use _space _template =
trimmed . some ( ( node ) => node . type === 'ExpressionTag' ) &&
trimmed . every ( ( node ) => node . type === 'Text' || node . type === 'ExpressionTag' ) ;
if ( use _space _template ) {
// special case — we can use `$.text` instead of creating a unique template
const id = b . id ( context . state . scope . generate ( 'text' ) ) ;
process _children ( trimmed , ( ) => id , false , {
... context ,
state
} ) ;
body . push (
b . var ( id , b . call ( '$.text' , b . id ( '$$anchor' ) ) ) ,
... state . before _init ,
... state . init
) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
} else {
/** @type {(is_text: boolean) => import('estree').Expression} */
const expression = ( is _text ) =>
is _text ? b . call ( '$.first_child' , id , b . true ) : b . call ( '$.first_child' , id ) ;
process _children ( trimmed , expression , false , { ... context , state } ) ;
const use _comment _template = state . template . length === 1 && state . template [ 0 ] === '<!>' ;
if ( use _comment _template ) {
// special case — we can use `$.comment` instead of creating a unique template
body . push ( b . var ( id , b . call ( '$.comment' ) ) ) ;
} else {
let flags = TEMPLATE _FRAGMENT ;
if ( state . metadata . context . template _needs _import _node ) {
flags |= TEMPLATE _USE _IMPORT _NODE ;
}
add _template ( template _name , [
b . template ( [ b . quasi ( state . template . join ( '' ) , true ) ] , [ ] ) ,
b . literal ( flags )
] ) ;
body . push ( b . var ( id , b . call ( template _name ) ) ) ;
}
body . push ( ... state . before _init , ... state . init ) ;
close = b . stmt ( b . call ( '$.append' , b . id ( '$$anchor' ) , id ) ) ;
}
} else {
body . push ( ... state . before _init , ... state . init ) ;
}
if ( state . update . length > 0 ) {
body . push ( serialize _render _stmt ( state ) ) ;
}
body . push ( ... state . after _update ) ;
if ( close !== undefined ) {
// It's important that close is the last statement in the block, as any previous statements
// could contain element insertions into the template, which the close statement needs to
// know of when constructing the list of current inner elements.
body . push ( close ) ;
}
return b . block ( body ) ;
} ,
Comment ( node , context ) {
@ -2116,6 +2122,7 @@ export const template_visitors = {
node . fragment . nodes ,
context . path ,
child _metadata . namespace ,
state ,
node . name === 'script' || state . preserve _whitespace ,
state . options . preserveComments
) ;
@ -2229,16 +2236,15 @@ export const template_visitors = {
}
inner . push ( ... inner _context . state . after _update ) ;
inner . push (
... create _block ( node , node . fragment , 'dynamic_element' , node . fragment . nodes , {
... context ,
state : {
... /** @type {import('estree').BlockStatement} */ (
context . visit ( node . fragment , {
... context . state ,
metadata : {
... context . state . metadata ,
namespace : determine _namespace _for _children ( node , context . state . metadata . namespace )
}
}
})
} )
). body
) ;
const location = context . state . options . dev && locator ( node . start ) ;
@ -2456,8 +2462,7 @@ export const template_visitors = {
}
}
// TODO should use context.visit?
const children = create _block ( node , node . body , 'each_block' , node . body . nodes , context ) ;
const block = /** @type {import('estree').BlockStatement} */ ( context . visit ( node . body ) ) ;
const key _function = node . key
? b . arrow (
@ -2490,7 +2495,7 @@ export const template_visitors = {
b . literal ( each _type ) ,
each _node _meta . array _name ? each _node _meta . array _name : b . thunk ( collection ) ,
key _function ,
b . arrow ( [ b . id ( '$$anchor' ) , item , index ] , b . block ( declarations . concat ( children ) ) )
b . arrow ( [ b . id ( '$$anchor' ) , item , index ] , b . block ( declarations . concat ( block. body ) ) )
] ;
if ( node . fallback ) {
@ -3032,13 +3037,7 @@ export const template_visitors = {
context . state . init . push ( ... lets ) ;
context . state . init . push (
... create _block (
node ,
node . fragment ,
'slot_template' ,
/** @type {import('#compiler').SvelteNode[]} */ ( node . fragment . nodes ) ,
context
)
... /** @type {import('estree').BlockStatement} */ ( context . visit ( node . fragment ) ) . body
) ;
} ,
SlotElement ( node , context ) {
@ -3088,12 +3087,13 @@ export const template_visitors = {
spreads . length === 0
? b . object ( props )
: b . call ( '$.spread_props' , b . object ( props ) , ... spreads ) ;
const fallback =
node . fragment . nodes . length === 0
? b . literal ( null )
: b . arrow (
[ b . id ( '$$anchor' ) ] ,
b . block ( create _block ( node , node . fragment , 'fallback' , node . fragment . nodes , contex t) )
/** @type {import('estree').BlockStatement} */ ( context . visit ( node . fragmen t) )
) ;
const expression = is _default
@ -3111,7 +3111,7 @@ export const template_visitors = {
'$.head' ,
b . arrow (
[ b . id ( '$$anchor' ) ] ,
b . block ( create _block ( node , node . fragment , 'head' , node . fragment . nodes , contex t) )
/** @type {import('estree').BlockStatement} */ ( context . visit ( node . fragmen t) )
)
)
)