@ -1,46 +1,36 @@
/** @import { Expression, ExpressionStatement, Identifier, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { Expression, ExpressionStatement, Identifier, Literal, MemberExpression, ObjectExpression, Statement } from 'estree' */
/** @import { AST } from '#compiler' */
/** @import { AST } from '#compiler' */
/** @import { SourceLocation } from '#shared' */
/** @import { SourceLocation } from '#shared' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
/** @import { ComponentClientTransformState, ComponentContext } from '../types' */
/** @import { Scope } from '../../../scope' */
/** @import { Scope } from '../../../scope' */
import { escape _html } from '../../../../../escaping.js' ;
import {
import {
is _boolean _attribute ,
is _boolean _attribute ,
is _dom _property ,
is _dom _property ,
is _load _error _element ,
is _load _error _element ,
is _void
is _void
} from '../../../../../utils.js' ;
} from '../../../../../utils.js' ;
import { escape _html } from '../../../../../escaping.js' ;
import { dev , is _ignored , locator } from '../../../../state.js' ;
import { dev , is _ignored , locator } from '../../../../state.js' ;
import {
import { is _event _attribute , is _text _attribute } from '../../../../utils/ast.js' ;
get _attribute _expression ,
is _event _attribute ,
is _text _attribute
} from '../../../../utils/ast.js' ;
import * as b from '../../../../utils/builders.js' ;
import * as b from '../../../../utils/builders.js' ;
import { is _custom _element _node } from '../../../nodes.js' ;
import { is _custom _element _node } from '../../../nodes.js' ;
import { clean _nodes , determine _namespace _for _children } from '../../utils.js' ;
import { clean _nodes , determine _namespace _for _children } from '../../utils.js' ;
import { build _getter , create _derived } from '../utils.js' ;
import {
import {
build _getter ,
can _inline _variable ,
create _derived ,
is _inlinable _expression
} from '../utils.js' ;
import {
get _attribute _name ,
build _attribute _value ,
build _attribute _value ,
build _class _directives ,
build _class _directives ,
build _set _attributes ,
build _style _directives ,
build _style _directives ,
build_set _attributes
get _attribute _name
} from './shared/element.js' ;
} from './shared/element.js' ;
import { visit _event _attribute } from './shared/events.js' ;
import { process _children } from './shared/fragment.js' ;
import { process _children } from './shared/fragment.js' ;
import {
import {
build _render _statement ,
build _render _statement ,
build _template _ literal ,
build _template _ chunk ,
build _update ,
build _update ,
build _update _assignment ,
build _update _assignment
get _states _and _calls
} from './shared/utils.js' ;
} from './shared/utils.js' ;
import { visit _event _attribute } from './shared/events.js' ;
/ * *
/ * *
* @ param { AST . RegularElement } node
* @ param { AST . RegularElement } node
@ -362,28 +352,32 @@ export function RegularElement(node, context) {
// special case — if an element that only contains text, we don't need
// special case — if an element that only contains text, we don't need
// to descend into it if the text is non-reactive
// to descend into it if the text is non-reactive
const states _and _calls =
const is _text = trimmed . every ( ( node ) => node . type === 'Text' || node . type === 'ExpressionTag' ) ;
trimmed . every ( ( node ) => node . type === 'Text' || node . type === 'ExpressionTag' ) &&
trimmed . some ( ( node ) => node . type === 'ExpressionTag' ) &&
// in the rare case that we have static text that can't be inlined
get _states _and _calls ( trimmed ) ;
// (e.g. `<span>{location}</span>`), set `textContent` programmatically
const use _text _content =
is _text &&
trimmed . every ( ( node ) => node . type === 'Text' || ! node . metadata . expression . has _state ) &&
trimmed . some ( ( node ) => node . type === 'ExpressionTag' && ! node . metadata . expression . can _inline ) ;
if ( use _text _content ) {
let { value } = build _template _chunk ( trimmed , context . visit , child _state ) ;
if ( states _and _calls && states _and _calls . states === 0 ) {
child _state . init . push (
child _state . init . push (
b . stmt (
b . stmt ( b . assignment ( '=' , b . member ( context . state . node , 'textContent' ) , value ) )
b . assignment (
'=' ,
b . member ( context . state . node , 'textContent' ) ,
build _template _literal ( trimmed , context . visit , child _state ) . value
)
)
) ;
) ;
} else {
} else {
/** @type {Expression} */
/** @type {Expression} */
let arg = context . state . node ;
let arg = context . state . node ;
// If `hydrate_node` is set inside the element, we need to reset it
// If `hydrate_node` is set inside the element, we need to reset it
// after the element has been hydrated
// after the element has been hydrated (we don't need to reset if it's been inlined)
let needs _reset = trimmed . some ( ( node ) => node . type !== 'Text' ) ;
let needs _reset = ! trimmed . every (
( node ) =>
node . type === 'Text' ||
( node . type === 'ExpressionTag' && node . metadata . expression . can _inline )
) ;
// The same applies if it's a `<template>` element, since we need to
// The same applies if it's a `<template>` element, since we need to
// set the value of `hydrate_node` to `node.content`
// set the value of `hydrate_node` to `node.content`
@ -393,7 +387,7 @@ export function RegularElement(node, context) {
arg = b . member ( arg , 'content' ) ;
arg = b . member ( arg , 'content' ) ;
}
}
process _children ( trimmed , ( is _text ) => b . call ( '$.child' , arg , is _text && b . true ) , tru e, {
process _children ( trimmed , ( is _text ) => b . call ( '$.child' , arg , is _text && b . true ) , nod e, {
... context ,
... context ,
state : child _state
state : child _state
} ) ;
} ) ;
@ -586,10 +580,6 @@ function build_element_attribute_update_assignment(element, node_id, attribute,
) ;
) ;
}
}
const inlinable _expression =
attribute . value === true
? false // not an expression
: is _inlinable _expression ( attribute . value , context . state ) ;
if ( attribute . metadata . expression . has _state ) {
if ( attribute . metadata . expression . has _state ) {
if ( has _call ) {
if ( has _call ) {
state . init . push ( build _update ( update ) ) ;
state . init . push ( build _update ( update ) ) ;
@ -597,14 +587,44 @@ function build_element_attribute_update_assignment(element, node_id, attribute,
state . update . push ( update ) ;
state . update . push ( update ) ;
}
}
return true ;
return true ;
} else {
}
if ( inlinable _expression ) {
context . state . template . push ( ` ${ name } =" ` , value , '"' ) ;
// we need to special case textarea value because it's not an actual attribute
const can _inline =
( attribute . name !== 'value' || element . name !== 'textarea' ) &&
attribute . metadata . expression . can _inline ;
if ( can _inline ) {
/** @type {Literal | undefined} */
let literal = undefined ;
if ( value . type === 'Literal' ) {
literal = value ;
} else if ( value . type === 'Identifier' ) {
const binding = context . state . scope . get ( value . name ) ;
if ( binding && binding . initial ? . type === 'Literal' && ! binding . reassigned ) {
literal = binding . initial ;
}
}
if ( literal && escape _html ( literal . value , true ) === String ( literal . value ) ) {
if ( is _boolean _attribute ( name ) ) {
if ( literal . value ) {
context . state . template . push ( ` ${ name } ` ) ;
}
} else {
context . state . template . push ( ` ${ name } =" ` , value , '"' ) ;
}
} else {
} else {
state . init . push ( update ) ;
context . state . template . push (
b . call ( '$.attr' , b . literal ( name ) , value , is _boolean _attribute ( name ) && b . true )
) ;
}
}
return false ;
} else {
state . init . push ( update ) ;
}
}
return false ;
}
}
/ * *
/ * *