@ -12,13 +12,7 @@ import {
escape _html ,
escape _html ,
infer _namespace
infer _namespace
} from '../../utils.js' ;
} from '../../utils.js' ;
import {
import { DOMProperties , PassiveEvents , VoidElements } from '../../../constants.js' ;
AttributeAliases ,
DOMBooleanAttributes ,
DOMProperties ,
PassiveEvents ,
VoidElements
} from '../../../constants.js' ;
import { is _custom _element _node , is _element _node } from '../../../nodes.js' ;
import { is _custom _element _node , is _element _node } from '../../../nodes.js' ;
import * as b from '../../../../utils/builders.js' ;
import * as b from '../../../../utils/builders.js' ;
import { error } from '../../../../errors.js' ;
import { error } from '../../../../errors.js' ;
@ -29,12 +23,35 @@ import {
serialize _set _binding
serialize _set _binding
} from '../utils.js' ;
} from '../utils.js' ;
import {
import {
AttributeAliases ,
DOMBooleanAttributes ,
EACH _INDEX _REACTIVE ,
EACH _INDEX _REACTIVE ,
EACH _IS _CONTROLLED ,
EACH _IS _CONTROLLED ,
EACH _ITEM _REACTIVE ,
EACH _ITEM _REACTIVE ,
EACH _KEYED
EACH _KEYED
} from '../../../../../constants.js' ;
} from '../../../../../constants.js' ;
import { regex _is _valid _identifier } from '../../../patterns.js' ;
import { regex _is _valid _identifier } from '../../../patterns.js' ;
import { javascript _visitors _runes } from './javascript-runes.js' ;
/ * *
* @ param { import ( '#compiler' ) . RegularElement | import ( '#compiler' ) . SvelteElement } element
* @ param { import ( '#compiler' ) . Attribute } attribute
* @ param { { state : { metadata : { namespace : import ( '#compiler' ) . Namespace } } } } context
* /
function get _attribute _name ( element , attribute , context ) {
let name = attribute . name ;
if (
element . type === 'RegularElement' &&
! element . metadata . svg &&
context . state . metadata . namespace !== 'foreign'
) {
name = name . toLowerCase ( ) ;
if ( name in AttributeAliases ) {
name = AttributeAliases [ name ] ;
}
}
return name ;
}
/ * *
/ * *
* Serializes each style directive into something like ` $ .style(element, style_property, value) `
* Serializes each style directive into something like ` $ .style(element, style_property, value) `
@ -255,13 +272,14 @@ function setup_select_synchronization(value_binding, context) {
* value = $ . spread _attributes ( element , value , [ ... ] )
* value = $ . spread _attributes ( element , value , [ ... ] )
* } ) ;
* } ) ;
* ` ` `
* ` ` `
* Returns the id of the spread _attribute varia l be if spread is deemed reactive , ` null ` otherwise .
* Returns the id of the spread _attribute varia bl e if spread is deemed reactive , ` null ` otherwise .
* @ param { Array < import ( '#compiler' ) . Attribute | import ( '#compiler' ) . SpreadAttribute > } attributes
* @ param { Array < import ( '#compiler' ) . Attribute | import ( '#compiler' ) . SpreadAttribute > } attributes
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '#compiler' ) . RegularElement } element
* @ param { import ( 'estree' ) . Identifier } element _id
* @ param { import ( 'estree' ) . Identifier } element _id
* @ returns { string | null }
* @ returns { string | null }
* /
* /
function serialize _element _spread _attributes ( attributes , context , element _id ) {
function serialize _element _spread _attributes ( attributes , context , element , element _id ) {
let is _reactive = false ;
let is _reactive = false ;
/** @type {import('estree').Expression[]} */
/** @type {import('estree').Expression[]} */
@ -269,7 +287,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) {
for ( const attribute of attributes ) {
for ( const attribute of attributes ) {
if ( attribute . type === 'Attribute' ) {
if ( attribute . type === 'Attribute' ) {
const name = get _attribute _name ( attribute, context . state ) ;
const name = get _attribute _name ( element, attribute, context ) ;
// TODO: handle contains_call_expression
// TODO: handle contains_call_expression
const [ , value ] = serialize _attribute _value ( attribute . value , context ) ;
const [ , value ] = serialize _attribute _value ( attribute . value , context ) ;
values . push ( b . object ( [ b . init ( name , value ) ] ) ) ;
values . push ( b . object ( [ b . init ( name , value ) ] ) ) ;
@ -280,6 +298,9 @@ function serialize_element_spread_attributes(attributes, context, element_id) {
is _reactive || = attribute . metadata . dynamic ;
is _reactive || = attribute . metadata . dynamic ;
}
}
const lowercase _attributes =
element . metadata . svg || is _custom _element _node ( element ) ? b . false : b . true ;
if ( is _reactive ) {
if ( is _reactive ) {
const id = context . state . scope . generate ( 'spread_attributes' ) ;
const id = context . state . scope . generate ( 'spread_attributes' ) ;
context . state . init . push ( b . let ( id , undefined ) ) ;
context . state . init . push ( b . let ( id , undefined ) ) ;
@ -293,6 +314,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) {
element _id ,
element _id ,
b . id ( id ) ,
b . id ( id ) ,
b . array ( values ) ,
b . array ( values ) ,
lowercase _attributes ,
b . literal ( context . state . analysis . stylesheet . id )
b . literal ( context . state . analysis . stylesheet . id )
)
)
)
)
@ -307,6 +329,7 @@ function serialize_element_spread_attributes(attributes, context, element_id) {
element _id ,
element _id ,
b . literal ( null ) ,
b . literal ( null ) ,
b . array ( values ) ,
b . array ( values ) ,
lowercase _attributes ,
b . literal ( context . state . analysis . stylesheet . id )
b . literal ( context . state . analysis . stylesheet . id )
)
)
)
)
@ -376,7 +399,7 @@ function serialize_dynamic_element_spread_attributes(attributes, context, elemen
}
}
/ * *
/ * *
* Serializes an assig ment to an element property by adding relevant statements to either only
* Serializes an assig n ment to an element property by adding relevant statements to either only
* the init or the the init and update arrays , depending on whether or not the value is dynamic .
* the init or the the init and update arrays , depending on whether or not the value is dynamic .
* Resulting code for static looks something like this :
* Resulting code for static looks something like this :
* ` ` ` js
* ` ` ` js
@ -397,14 +420,15 @@ function serialize_dynamic_element_spread_attributes(attributes, context, elemen
* } ) ;
* } ) ;
* ` ` `
* ` ` `
* Returns true if attribute is deemed reactive , false otherwise .
* Returns true if attribute is deemed reactive , false otherwise .
* @ param { import ( '#compiler' ) . RegularElement } element
* @ param { import ( 'estree' ) . Identifier } node _id
* @ param { import ( 'estree' ) . Identifier } node _id
* @ param { import ( '#compiler' ) . Attribute } attribute
* @ param { import ( '#compiler' ) . Attribute } attribute
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ param { import ( '../types.js' ) . ComponentContext } context
* @ returns { boolean }
* @ returns { boolean }
* /
* /
function serialize _element _attribute _update _assignment ( node_id , attribute , context ) {
function serialize _element _attribute _update _assignment ( element, node_id , attribute , context ) {
const state = context . state ;
const state = context . state ;
const name = get _attribute _name ( attribute, state ) ;
const name = get _attribute _name ( element, attribute , context ) ;
let [ contains _call _expression , value ] = serialize _attribute _value ( attribute . value , context ) ;
let [ contains _call _expression , value ] = serialize _attribute _value ( attribute . value , context ) ;
// The foreign namespace doesn't have any special handling, everything goes through the attr function
// The foreign namespace doesn't have any special handling, everything goes through the attr function
@ -551,7 +575,7 @@ function serialize_custom_element_attribute_update_assignment(node_id, attribute
}
}
/ * *
/ * *
* Serializes an assig ment to the value property of a ` <select> ` , ` <option> ` or ` <input> ` element
* Serializes an assig n ment to the value property of a ` <select> ` , ` <option> ` or ` <input> ` element
* that needs the hidden ` __value ` property .
* that needs the hidden ` __value ` property .
* Returns true if attribute is deemed reactive , false otherwise .
* Returns true if attribute is deemed reactive , false otherwise .
* @ param { string } element
* @ param { string } element
@ -567,13 +591,17 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
const inner _assignment = b . assignment (
const inner _assignment = b . assignment (
'=' ,
'=' ,
b . member ( node _id , b . id ( 'value' ) ) ,
b . member ( node _id , b . id ( 'value' ) ) ,
b . assignment ( '=' , b . member ( node _id , b . id ( '__value' ) ) , value )
b . conditional (
b . binary ( '==' , b . literal ( null ) , b . assignment ( '=' , b . member ( node _id , b . id ( '__value' ) ) , value ) ) ,
b . literal ( '' ) , // render null/undefined values as empty string to support placeholder options
value
)
) ;
) ;
const is _reactive = attribute . metadata . dynamic ;
const is _reactive = attribute . metadata . dynamic ;
const needs _selected _call =
const needs _selected _call =
element === 'option' && ( is _reactive || collect _parent _each _blocks ( context ) . length > 0 ) ;
element === 'option' && ( is _reactive || collect _parent _each _blocks ( context ) . length > 0 ) ;
const needs _option _call = element === 'select' && is _reactive ;
const needs _option _call = element === 'select' && is _reactive ;
const assig ment = b . stmt (
const assig n ment = b . stmt (
needs _selected _call
needs _selected _call
? b . sequence ( [
? b . sequence ( [
inner _assignment ,
inner _assignment ,
@ -582,9 +610,12 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
b . call ( '$.selected' , node _id )
b . call ( '$.selected' , node _id )
] )
] )
: needs _option _call
: needs _option _call
? // This ensures a one-way street to the DOM in case it's <select {value}>
? b . sequence ( [
// and not <select bind:value>
inner _assignment ,
b . call ( '$.select_option' , node _id , inner _assignment )
// This ensures a one-way street to the DOM in case it's <select {value}>
// and not <select bind:value>
b . call ( '$.select_option' , node _id , value )
] )
: inner _assignment
: inner _assignment
) ;
) ;
@ -595,12 +626,12 @@ function serialize_element_special_value_attribute(element, node_id, attribute,
id ,
id ,
undefined ,
undefined ,
value ,
value ,
{ grouped : assig ment } ,
{ grouped : assig n ment } ,
contains _call _expression
contains _call _expression
) ;
) ;
return true ;
return true ;
} else {
} else {
state . init . push ( assig ment) ;
state . init . push ( assig n ment) ;
return false ;
return false ;
}
}
}
}
@ -664,21 +695,6 @@ function collect_parent_each_blocks(context) {
) ;
) ;
}
}
/ * *
* @ param { import ( '#compiler' ) . Attribute } attribute
* @ param { import ( '../types.js' ) . ComponentClientTransformState } state
* /
function get _attribute _name ( attribute , state ) {
let name = attribute . name ;
if ( state . metadata . namespace !== 'foreign' ) {
name = name . toLowerCase ( ) ;
if ( name !== 'class' && name in AttributeAliases ) {
name = AttributeAliases [ name ] ;
}
}
return name ;
}
/ * *
/ * *
* @ param { import ( '#compiler' ) . Component | import ( '#compiler' ) . SvelteComponent | import ( '#compiler' ) . SvelteSelf } node
* @ param { import ( '#compiler' ) . Component | import ( '#compiler' ) . SvelteComponent | import ( '#compiler' ) . SvelteSelf } node
* @ param { string } component _name
* @ param { string } component _name
@ -883,6 +899,10 @@ function serialize_inline_component(node, component_name, context) {
if ( bind _this !== null ) {
if ( bind _this !== null ) {
const prev = fn ;
const prev = fn ;
const assignment = b . assignment ( '=' , bind _this , b . id ( '$$value' ) ) ;
const assignment = b . assignment ( '=' , bind _this , b . id ( '$$value' ) ) ;
const bind _this _id = /** @type {import('estree').Expression} */ (
// if expression is not an identifier, we know it can't be a signal
bind _this . type === 'Identifier' ? bind _this : undefined
) ;
fn = ( node _id ) =>
fn = ( node _id ) =>
b . call (
b . call (
'$.bind_this' ,
'$.bind_this' ,
@ -890,7 +910,8 @@ function serialize_inline_component(node, component_name, context) {
b . arrow (
b . arrow (
[ b . id ( '$$value' ) ] ,
[ b . id ( '$$value' ) ] ,
serialize _set _binding ( assignment , context , ( ) => context . visit ( assignment ) )
serialize _set _binding ( assignment , context , ( ) => context . visit ( assignment ) )
)
) ,
bind _this _id
) ;
) ;
}
}
@ -966,9 +987,6 @@ function create_block(parent, name, nodes, context) {
/** @type {import('estree').Statement | undefined} */
/** @type {import('estree').Statement | undefined} */
let close = undefined ;
let close = undefined ;
/** @type {import('estree').Identifier | undefined} */
let id = undefined ;
/** @type {import('../types').ComponentClientTransformState} */
/** @type {import('../types').ComponentClientTransformState} */
const state = {
const state = {
... context . state ,
... context . state ,
@ -991,7 +1009,7 @@ function create_block(parent, name, nodes, context) {
if ( is _single _element ) {
if ( is _single _element ) {
const element = /** @type {import('#compiler').RegularElement} */ ( trimmed [ 0 ] ) ;
const element = /** @type {import('#compiler').RegularElement} */ ( trimmed [ 0 ] ) ;
id = b . id ( context . state . scope . generate ( element . name ) ) ;
const id = b . id ( context . state . scope . generate ( element . name ) ) ;
context . visit ( element , {
context . visit ( element , {
... state ,
... state ,
@ -1006,7 +1024,7 @@ function create_block(parent, name, nodes, context) {
body . push (
body . push (
b . var (
b . var (
id .name ,
id ,
b . call (
b . call (
'$.open' ,
'$.open' ,
b . id ( '$$anchor' ) ,
b . id ( '$$anchor' ) ,
@ -1020,15 +1038,30 @@ function create_block(parent, name, nodes, context) {
} else if ( is _single _child _not _needing _template ) {
} else if ( is _single _child _not _needing _template ) {
context . visit ( trimmed [ 0 ] , state ) ;
context . visit ( trimmed [ 0 ] , state ) ;
body . push ( ... state . init ) ;
body . push ( ... state . init ) ;
} else {
} else if ( trimmed . length > 0 ) {
id = b . id ( context . state . scope . generate ( 'fragment' ) ) ;
const id = b . id ( context . state . scope . generate ( 'fragment' ) ) ;
const node _id = b . id ( context . state . scope . generate ( 'node' ) ) ;
process _children ( trimmed , b. call ( '$.child_frag' , id ) , {
process _children ( trimmed , node_id , {
... context ,
... context ,
state
state
} ) ;
} ) ;
if ( state . template . length > 0 ) {
const template = state . template [ 0 ] ;
if ( state . template . length === 1 && ( template === ' ' || template === '<!>' ) ) {
if ( template === ' ' ) {
body . push ( b . var ( node _id , b . call ( '$.space' , b . id ( '$$anchor' ) ) ) , ... state . init ) ;
close = b . stmt ( b . call ( '$.close' , b . id ( '$$anchor' ) , node _id ) ) ;
} else {
body . push (
b . var ( id , b . call ( '$.comment' , b . id ( '$$anchor' ) ) ) ,
b . var ( node _id , b . call ( '$.child_frag' , id ) ) ,
... state . init
) ;
close = b . stmt ( b . call ( '$.close_frag' , b . id ( '$$anchor' ) , id ) ) ;
}
} else {
const callee = namespace === 'svg' ? '$.svg_template' : '$.template' ;
const callee = namespace === 'svg' ? '$.svg_template' : '$.template' ;
state . hoisted . push (
state . hoisted . push (
@ -1040,7 +1073,7 @@ function create_block(parent, name, nodes, context) {
body . push (
body . push (
b . var (
b . var (
id .name ,
id ,
b . call (
b . call (
'$.open_frag' ,
'$.open_frag' ,
b . id ( '$$anchor' ) ,
b . id ( '$$anchor' ) ,
@ -1048,12 +1081,14 @@ function create_block(parent, name, nodes, context) {
template _name
template _name
)
)
) ,
) ,
b . var ( node _id , b . call ( '$.child_frag' , id ) ) ,
... state . init
... state . init
) ;
) ;
close = b . stmt ( b . call ( '$.close_frag' , b . id ( '$$anchor' ) , id ) ) ;
close = b . stmt ( b . call ( '$.close_frag' , b . id ( '$$anchor' ) , id ) ) ;
} else {
body . push ( ... state . init ) ;
}
}
} else {
body . push ( ... state . init ) ;
}
}
if ( state . update . length > 0 || state . update _effects . length > 0 ) {
if ( state . update . length > 0 || state . update _effects . length > 0 ) {
@ -1069,26 +1104,7 @@ function create_block(parent, name, nodes, context) {
}
}
}
}
if ( state . update . length > 0 ) {
if ( state . update . length > 0 ) {
let render ;
const render = serialize _render _stmt ( state , body ) ;
if ( state . update . length === 1 && state . update [ 0 ] . singular ) {
render = state . update [ 0 ] . singular ;
} else {
render = b . stmt (
b . call (
'$.render_effect' ,
b . thunk (
b . block (
state . update . map ( ( n ) => {
if ( n . init ) {
body . push ( n . init ) ;
}
return n . grouped ;
} )
)
)
)
) ;
}
if ( ! update ) {
if ( ! update ) {
update = render ;
update = render ;
}
}
@ -1098,9 +1114,7 @@ function create_block(parent, name, nodes, context) {
/** @type {import('estree').Statement} */ ( update ) . leadingComments = [
/** @type {import('estree').Statement} */ ( update ) . leadingComments = [
{
{
type : 'Block' ,
type : 'Block' ,
value : ` Update ` ,
value : ` Update `
// @ts-expect-error
has _trailing _newline : true
}
}
] ;
] ;
}
}
@ -1118,9 +1132,7 @@ function create_block(parent, name, nodes, context) {
body [ 0 ] . leadingComments = [
body [ 0 ] . leadingComments = [
{
{
type : 'Block' ,
type : 'Block' ,
value : ` Init ` ,
value : ` Init `
// @ts-expect-error
has _trailing _newline : true
}
}
] ;
] ;
}
}
@ -1128,6 +1140,35 @@ function create_block(parent, name, nodes, context) {
return body ;
return body ;
}
}
/ * *
*
* @ param { import ( '../types.js' ) . ComponentClientTransformState } state
* @ param { import ( 'estree' ) . Statement [ ] } body
* /
function serialize _render _stmt ( state , body ) {
let render ;
if ( state . update . length === 1 && state . update [ 0 ] . singular ) {
render = state . update [ 0 ] . singular ;
} else {
render = b . stmt (
b . call (
'$.render_effect' ,
b . thunk (
b . block (
state . update . map ( ( n ) => {
if ( n . init ) {
body . push ( n . init ) ;
}
return n . grouped ;
} )
)
)
)
) ;
}
return render ;
}
/ * *
/ * *
* Serializes the event handler function of the ` on: ` directive
* Serializes the event handler function of the ` on: ` directive
* @ param { Pick < import ( '#compiler' ) . OnDirective , 'name' | 'modifiers' | 'expression' > } node
* @ param { Pick < import ( '#compiler' ) . OnDirective , 'name' | 'modifiers' | 'expression' > } node
@ -1345,13 +1386,11 @@ function process_children(nodes, parent, { visit, state }) {
state . template . push ( ' ' ) ;
state . template . push ( ' ' ) ;
const name = state . scope . generate ( 'text' ) ;
const text _id = get _node _id ( expression , state , 'text' ) ;
state . init . push ( b . var ( name , expression ) ) ;
const singular = b . stmt (
const singular = b . stmt (
b . call (
b . call (
'$.text_effect' ,
'$.text_effect' ,
b. id ( name ) ,
text_id ,
b . thunk ( /** @type {import('estree').Expression} */ ( visit ( node . expression ) ) )
b . thunk ( /** @type {import('estree').Expression} */ ( visit ( node . expression ) ) )
)
)
) ;
) ;
@ -1364,7 +1403,7 @@ function process_children(nodes, parent, { visit, state }) {
grouped : b . stmt (
grouped : b . stmt (
b . call (
b . call (
'$.text' ,
'$.text' ,
b. id ( name ) ,
text_id ,
/** @type {import('estree').Expression} */ ( visit ( node . expression ) )
/** @type {import('estree').Expression} */ ( visit ( node . expression ) )
)
)
)
)
@ -1374,7 +1413,7 @@ function process_children(nodes, parent, { visit, state }) {
b . stmt (
b . stmt (
b . assignment (
b . assignment (
'=' ,
'=' ,
b . id( ` ${ name } .nodeValue ` ) ,
b . member( text _id , b . id ( 'nodeValue' ) ) ,
b . call (
b . call (
'$.stringify' ,
'$.stringify' ,
/** @type {import('estree').Expression} */ ( visit ( node . expression ) )
/** @type {import('estree').Expression} */ ( visit ( node . expression ) )
@ -1389,17 +1428,16 @@ function process_children(nodes, parent, { visit, state }) {
state . template . push ( ' ' ) ;
state . template . push ( ' ' ) ;
const name = state . scope . generate ( 'text' ) ;
const text_id = get _node _id ( expression , state , 'text' ) ;
const contains _call _expression = sequence . some (
const contains _call _expression = sequence . some (
( n ) => n . type === 'ExpressionTag' && n . metadata . contains _call _expression
( n ) => n . type === 'ExpressionTag' && n . metadata . contains _call _expression
) ;
) ;
state . init . push ( b . var ( name , expression ) ) ;
const assignment = serialize _template _literal ( sequence , visit , state ) [ 1 ] ;
const assignment = serialize _template _literal ( sequence , visit , state ) [ 1 ] ;
const init = b . stmt ( b . assignment ( '=' , b . id( ` ${ name } .nodeValue ` ) , assignment ) ) ;
const init = b . stmt ( b . assignment ( '=' , b . member( text _id , b . id ( 'nodeValue' ) ) , assignment ) ) ;
const singular = b . stmt (
const singular = b . stmt (
b . call (
b . call (
'$.text_effect' ,
'$.text_effect' ,
b. id ( name ) ,
text_id ,
b . thunk ( serialize _template _literal ( sequence , visit , state ) [ 1 ] )
b . thunk ( serialize _template _literal ( sequence , visit , state ) [ 1 ] )
)
)
) ;
) ;
@ -1412,13 +1450,13 @@ function process_children(nodes, parent, { visit, state }) {
) {
) {
state . update . push ( {
state . update . push ( {
singular ,
singular ,
grouped : b . stmt ( b . call ( '$.text' , b. id ( name ) , assignment ) )
grouped : b . stmt ( b . call ( '$.text' , text_id , assignment ) )
} ) ;
} ) ;
} else {
} else {
state . init . push ( init ) ;
state . init . push ( init ) ;
}
}
expression = b . call ( '$.sibling' , b. id ( name ) ) ;
expression = b . call ( '$.sibling' , text_id ) ;
}
}
for ( let i = 0 ; i < nodes . length ; i += 1 ) {
for ( let i = 0 ; i < nodes . length ; i += 1 ) {
@ -1442,9 +1480,6 @@ function process_children(nodes, parent, { visit, state }) {
// get hoisted inside clean_nodes?
// get hoisted inside clean_nodes?
visit ( node , state ) ;
visit ( node , state ) ;
} else {
} else {
const name = state . scope . generate ( node . type === 'RegularElement' ? node . name : 'node' ) ;
const id = b . id ( name ) ;
// Optimization path for each blocks. If the parent isn't a fragment and it only has
// Optimization path for each blocks. If the parent isn't a fragment and it only has
// a single child, then we can classify the block as being "controlled".
// a single child, then we can classify the block as being "controlled".
if (
if (
@ -1457,7 +1492,12 @@ function process_children(nodes, parent, { visit, state }) {
node . metadata . is _controlled = true ;
node . metadata . is _controlled = true ;
visit ( node , state ) ;
visit ( node , state ) ;
} else {
} else {
state . init . push ( b . var ( name , expression ) ) ;
const id = get _node _id (
expression ,
state ,
node . type === 'RegularElement' ? node . name : 'node'
) ;
expression = b . call ( '$.sibling' , id ) ;
expression = b . call ( '$.sibling' , id ) ;
visit ( node , {
visit ( node , {
@ -1474,6 +1514,22 @@ function process_children(nodes, parent, { visit, state }) {
}
}
}
}
/ * *
* @ param { import ( 'estree' ) . Expression } expression
* @ param { import ( '../types.js' ) . ComponentClientTransformState } state
* @ param { string } name
* /
function get _node _id ( expression , state , name ) {
let id = expression ;
if ( id . type !== 'Identifier' ) {
id = b . id ( state . scope . generate ( name ) ) ;
state . init . push ( b . var ( id , expression ) ) ;
}
return id ;
}
/ * *
/ * *
* @ param { true | Array < import ( '#compiler' ) . Text | import ( '#compiler' ) . ExpressionTag > } attribute _value
* @ param { true | Array < import ( '#compiler' ) . Text | import ( '#compiler' ) . ExpressionTag > } attribute _value
* @ param { import ( '../types' ) . ComponentContext } context
* @ param { import ( '../types' ) . ComponentContext } context
@ -1568,19 +1624,20 @@ export const template_visitors = {
) ;
) ;
} ,
} ,
ConstTag ( node , { state , visit } ) {
ConstTag ( node , { state , visit } ) {
const declaration = node . declaration . declarations [ 0 ] ;
// TODO we can almost certainly share some code with $derived(...)
// TODO we can almost certainly share some code with $derived(...)
if ( node. expression . left . type === 'Identifier' ) {
if ( declaration. id . type === 'Identifier' ) {
state . init . push (
state . init . push (
b . const (
b . const (
node. expression . left ,
declaration. id ,
b . call (
b . call (
'$.derived' ,
'$.derived' ,
b . thunk ( /** @type {import('estree').Expression} */ ( visit ( node. expression . righ t) ) )
b . thunk ( /** @type {import('estree').Expression} */ ( visit ( declaration. ini t) ) )
)
)
)
)
) ;
) ;
} else {
} else {
const identifiers = extract _identifiers ( node. expression . left ) ;
const identifiers = extract _identifiers ( declaration. id ) ;
const tmp = b . id ( state . scope . generate ( 'computed_const' ) ) ;
const tmp = b . id ( state . scope . generate ( 'computed_const' ) ) ;
// Make all identifiers that are declared within the following computed regular
// Make all identifiers that are declared within the following computed regular
@ -1596,8 +1653,8 @@ export const template_visitors = {
[ ] ,
[ ] ,
b . block ( [
b . block ( [
b . const (
b . const (
/** @type {import('estree').Pattern} */ ( visit ( node. expression . left ) ) ,
/** @type {import('estree').Pattern} */ ( visit ( declaration. id ) ) ,
/** @type {import('estree').Expression} */ ( visit ( node. expression . righ t) )
/** @type {import('estree').Expression} */ ( visit ( declaration. ini t) )
) ,
) ,
b . return ( b . object ( identifiers . map ( ( node ) => b . prop ( 'init' , node , node ) ) ) )
b . return ( b . object ( identifiers . map ( ( node ) => b . prop ( 'init' , node , node ) ) ) )
] )
] )
@ -1649,18 +1706,20 @@ export const template_visitors = {
if ( node . argument ) {
if ( node . argument ) {
args . push ( b . thunk ( /** @type {import('estree').Expression} */ ( context . visit ( node . argument ) ) ) ) ;
args . push ( b . thunk ( /** @type {import('estree').Expression} */ ( context . visit ( node . argument ) ) ) ) ;
}
}
const snippet _function = /** @type {import('estree').Expression} */ (
let snippet _function = /** @type {import('estree').Expression} */ (
context . visit ( node . expression )
context . visit ( node . expression )
) ;
) ;
const init = b . call (
if ( context . state . options . dev ) {
context . state . options . dev ? b . call ( '$.validate_snippet' , snippet _function ) : snippet _function ,
snippet _function = b . call ( '$.validate_snippet' , snippet _function ) ;
... args
}
) ;
if ( is _reactive ) {
if ( is _reactive ) {
context . state . init . push ( b . stmt ( b . call ( '$.snippet_effect' , b . thunk ( init ) ) ) ) ;
context . state . init . push (
b . stmt ( b . call ( '$.snippet_effect' , b . thunk ( snippet _function ) , ... args ) )
) ;
} else {
} else {
context . state . init . push ( b . stmt ( init ) ) ;
context . state . init . push ( b . stmt ( b. call ( snippet _function , ... args ) ) ) ;
}
}
} ,
} ,
AnimateDirective ( node , { state , visit } ) {
AnimateDirective ( node , { state , visit } ) {
@ -1825,7 +1884,7 @@ export const template_visitors = {
// Then do attributes
// Then do attributes
let is _attributes _reactive = false ;
let is _attributes _reactive = false ;
if ( node . metadata . has _spread ) {
if ( node . metadata . has _spread ) {
const spread _id = serialize _element _spread _attributes ( attributes , context , node _id ) ;
const spread _id = serialize _element _spread _attributes ( attributes , context , node , node _id ) ;
if ( child _metadata . namespace !== 'foreign' ) {
if ( child _metadata . namespace !== 'foreign' ) {
add _select _to _spread _update ( spread _id , node , context , node _id ) ;
add _select _to _spread _update ( spread _id , node , context , node _id ) ;
}
}
@ -1846,7 +1905,7 @@ export const template_visitors = {
attribute . name !== 'autofocus' &&
attribute . name !== 'autofocus' &&
( attribute . value === true || is _text _attribute ( attribute ) )
( attribute . value === true || is _text _attribute ( attribute ) )
) {
) {
const name = get _attribute _name ( attribute, context . state ) ;
const name = get _attribute _name ( node, attribute, context ) ;
const literal _value = /** @type {import('estree').Literal} */ (
const literal _value = /** @type {import('estree').Literal} */ (
serialize _attribute _value ( attribute . value , context ) [ 1 ]
serialize _attribute _value ( attribute . value , context ) [ 1 ]
) . value ;
) . value ;
@ -1867,7 +1926,7 @@ export const template_visitors = {
const is =
const is =
is _custom _element && child _metadata . namespace !== 'foreign'
is _custom _element && child _metadata . namespace !== 'foreign'
? serialize _custom _element _attribute _update _assignment ( node _id , attribute , context )
? serialize _custom _element _attribute _update _assignment ( node _id , attribute , context )
: serialize _element _attribute _update _assignment ( node _id , attribute , context ) ;
: serialize _element _attribute _update _assignment ( node , node _id , attribute , context ) ;
if ( is ) is _attributes _reactive = true ;
if ( is ) is _attributes _reactive = true ;
}
}
}
}
@ -2005,27 +2064,7 @@ export const template_visitors = {
}
}
}
}
if ( inner _context . state . update . length > 0 ) {
if ( inner _context . state . update . length > 0 ) {
let render ;
inner . push ( serialize _render _stmt ( inner _context . state , inner ) ) ;
if ( inner _context . state . update . length === 1 && inner _context . state . update [ 0 ] . singular ) {
render = inner _context . state . update [ 0 ] . singular ;
} else {
render = b . stmt (
b . call (
'$.render_effect' ,
b . thunk (
b . block (
inner _context . state . update . map ( ( n ) => {
if ( n . init ) {
inner . push ( n . init ) ;
}
return n . grouped ;
} )
)
)
)
) ;
}
inner . push ( render ) ;
}
}
}
}
inner . push ( ... inner _context . state . after _update ) ;
inner . push ( ... inner _context . state . after _update ) ;
@ -2116,17 +2155,15 @@ export const template_visitors = {
/ * *
/ * *
* @ param { import ( 'estree' ) . Pattern } expression _for _id
* @ param { import ( 'estree' ) . Pattern } expression _for _id
* @ param { import ( 'estree' ) . Expression } expression _for _other
* @ returns { import ( '#compiler' ) . Binding [ 'mutation' ] }
* @ returns { import ( '#compiler' ) . Binding [ 'mutation' ] }
* /
* /
const create _mutation = ( expression _for _id , expression _for _other ) => {
const create _mutation = ( expression _for _id ) => {
return ( assignment , context ) => {
return ( assignment , context ) => {
if ( assignment . left . type !== 'Identifier' && assignment . left . type !== 'MemberExpression' ) {
if ( assignment . left . type !== 'Identifier' && assignment . left . type !== 'MemberExpression' ) {
// serialize_set_binding turns other patterns into IIFEs and separates the assignments
// serialize_set_binding turns other patterns into IIFEs and separates the assignments
// into separate expressions, at which point this is called again with an identifier or member expression
// into separate expressions, at which point this is called again with an identifier or member expression
return serialize _set _binding ( assignment , context , ( ) => assignment ) ;
return serialize _set _binding ( assignment , context , ( ) => assignment ) ;
}
}
const left = object ( assignment . left ) ;
const left = object ( assignment . left ) ;
const value = get _assignment _value ( assignment , context ) ;
const value = get _assignment _value ( assignment , context ) ;
const invalidate = b . call (
const invalidate = b . call (
@ -2139,11 +2176,7 @@ export const template_visitors = {
return context . state . analysis . runes ? assign : b . sequence ( [ assign , invalidate ] ) ;
return context . state . analysis . runes ? assign : b . sequence ( [ assign , invalidate ] ) ;
} else {
} else {
const original _left = /** @type {import('estree').MemberExpression} */ ( assignment . left ) ;
const original _left = /** @type {import('estree').MemberExpression} */ ( assignment . left ) ;
const left = b . member (
const left = context . visit ( original _left ) ;
expression _for _other ,
context . visit ( original _left ) . property ,
original _left . computed
) ;
const assign = b . assignment ( assignment . operator , left , value ) ;
const assign = b . assignment ( assignment . operator , left , value ) ;
return context . state . analysis . runes ? assign : b . sequence ( [ assign , invalidate ] ) ;
return context . state . analysis . runes ? assign : b . sequence ( [ assign , invalidate ] ) ;
}
}
@ -2169,8 +2202,7 @@ export const template_visitors = {
each _node _meta . array _name ? b . call ( each _node _meta . array _name ) : collection ,
each _node _meta . array _name ? b . call ( each _node _meta . array _name ) : collection ,
index ,
index ,
true
true
) ,
)
binding . expression
) ;
) ;
} else {
} else {
const unwrapped = binding . expression ;
const unwrapped = binding . expression ;
@ -2198,8 +2230,7 @@ export const template_visitors = {
binding . expression = b . call ( name ) ;
binding . expression = b . call ( name ) ;
binding . mutation = create _mutation (
binding . mutation = create _mutation (
/** @type {import('estree').Pattern} */ ( path . update _expression ( unwrapped ) ) ,
/** @type {import('estree').Pattern} */ ( path . update _expression ( unwrapped ) )
binding . expression
) ;
) ;
}
}
}
}
@ -2225,12 +2256,6 @@ export const template_visitors = {
)
)
: b . literal ( null ) ;
: b . literal ( null ) ;
if ( context . state . options . dev && key _function . type !== 'Literal' ) {
context . state . init . push (
b . stmt ( b . call ( '$.validate_each_keys' , b . thunk ( collection ) , key _function ) )
) ;
}
if ( node . index && each _node _meta . contains _group _binding ) {
if ( node . index && each _node _meta . contains _group _binding ) {
// We needed to create a unique identifier for the index above, but we want to use the
// We needed to create a unique identifier for the index above, but we want to use the
// original index name in the template, therefore create another binding
// original index name in the template, therefore create another binding
@ -2238,6 +2263,12 @@ export const template_visitors = {
}
}
if ( ( each _type & EACH _KEYED ) !== 0 ) {
if ( ( each _type & EACH _KEYED ) !== 0 ) {
if ( context . state . options . dev && key _function . type !== 'Literal' ) {
context . state . init . push (
b . stmt ( b . call ( '$.validate_each_keys' , b . thunk ( collection ) , key _function ) )
) ;
}
context . state . after _update . push (
context . state . after _update . push (
b . stmt (
b . stmt (
b . call (
b . call (
@ -2437,7 +2468,7 @@ export const template_visitors = {
next ( ) ;
next ( ) ;
} ,
} ,
BindDirective ( node , context ) {
BindDirective ( node , context ) {
const { state , path } = context ;
const { state , path , visit } = context ;
/** @type {import('estree').Expression[]} */
/** @type {import('estree').Expression[]} */
const properties = [ ] ;
const properties = [ ] ;
@ -2568,9 +2599,16 @@ export const template_visitors = {
}
}
case 'this' :
case 'this' :
call _expr = b . call ( ` $ .bind_this ` , state . node , setter ) ;
call _expr = b . call (
` $ .bind_this ` ,
state . node ,
setter ,
/** @type {import('estree').Expression} */ (
// if expression is not an identifier, we know it can't be a signal
node . expression . type === 'Identifier' ? node . expression : undefined
)
) ;
break ;
break ;
case 'textContent' :
case 'textContent' :
case 'innerHTML' :
case 'innerHTML' :
case 'innerText' :
case 'innerText' :
@ -2895,5 +2933,6 @@ export const template_visitors = {
... context . state ,
... context . state ,
node : b . id ( '$.document' )
node : b . id ( '$.document' )
} ) ;
} ) ;
}
} ,
CallExpression : javascript _visitors _runes . CallExpression
} ;
} ;