@ -1,19 +1,12 @@
import { b , x , p } from "code-red" ;
import Component from "../Component" ;
import Renderer from "./Renderer" ;
import { CompileOptions , CssResult } from "../../interfaces" ;
import { walk } from "estree-walker" ;
import { extract_names , Scope } from "../utils/scope" ;
import { invalidate } from "./invalidate" ;
import Block from "./Block" ;
import {
ClassDeclaration ,
FunctionExpression ,
Node ,
Statement ,
ObjectExpression ,
Expression ,
} from "estree" ;
import { b , x , p } from 'code-red' ;
import Component from '../Component' ;
import Renderer from './Renderer' ;
import { CompileOptions , CssResult } from '../../interfaces' ;
import { walk } from 'estree-walker' ;
import { extract_names , Scope } from '../utils/scope' ;
import { invalidate } from './invalidate' ;
import Block from './Block' ;
import { ClassDeclaration , FunctionExpression , Node , Statement , ObjectExpression , Expression } from 'estree' ;
export default function dom (
component : Component ,
@ -36,19 +29,18 @@ export default function dom(
body . push ( b ` const ${ renderer . file_var } = ${ file } ; ` ) ;
}
const css = component . stylesheet . render (
options . filename ,
! options . customElement
) ;
const styles =
component . stylesheet . has_styles && options . dev
const css = component . stylesheet . render ( options . filename , ! options . customElement ) ;
const styles = component . stylesheet . has_styles && options . dev
? ` ${ css . code } \ n/*# sourceMappingURL= ${ css . map . toUrl ( ) } */ `
: css . code ;
const add_css = component . get_unique_name ( "add_css" ) ;
const add_css = component . get_unique_name ( 'add_css' ) ;
const should_add_css =
! options . customElement && ! ! styles && options . css !== false ;
const should_add_css = (
! options . customElement &&
! ! styles &&
options . css !== false
) ;
if ( should_add_css ) {
body . push ( b `
@ -65,14 +57,12 @@ export default function dom(
// TODO the deconflicted names of blocks are reversed... should set them here
const blocks = renderer . blocks . slice ( ) . reverse ( ) ;
body . push (
. . . blocks . map ( ( block ) = > {
body . push ( . . . blocks . map ( block = > {
// TODO this is a horrible mess — renderer.blocks
// contains a mixture of Blocks and Nodes
if ( ( block as Block ) . render ) return ( block as Block ) . render ( ) ;
return block ;
} )
) ;
} ) ) ;
if ( options . dev && ! options . hydratable ) {
block . chunks . claim . push (
@ -155,119 +145,91 @@ export default function dom(
let capture_state : Expression ;
let props_inject : Node [ ] | Node ;
props . forEach ( ( prop ) = > {
props . forEach ( prop = > {
const variable = component . var_lookup . get ( prop . name ) ;
if ( ! variable . writable || component . component_options . accessors ) {
accessors . push ( {
type : "MethodDefinition" ,
kind : "get" ,
key : { type : "Identifier" , name : prop.export_name } ,
type : 'MethodDefinition' ,
kind : 'get' ,
key : { type : 'Identifier' , name : prop.export_name } ,
value : x ` function() {
return $ {
prop . hoistable
? prop . name
: x ` this. $ $ .ctx[ ${ renderer . context_lookup . get ( prop . name ) . index } ] `
}
return $ { prop . hoistable ? prop.name : x ` this. $ $ .ctx[ ${ renderer . context_lookup . get ( prop . name ) . index } ] ` }
} ` ,
} ) ;
} else if ( component . compile_options . dev ) {
accessors . push ( {
type : "MethodDefinition" ,
kind : "get" ,
key : { type : "Identifier" , name : prop.export_name } ,
type : 'MethodDefinition' ,
kind : 'get' ,
key : { type : 'Identifier' , name : prop.export_name } ,
value : x ` function() {
throw new @_Error ( "<${component.tag}>: Props cannot be read directly from the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'" ) ;
} ` ,
} `
} ) ;
}
if ( component . component_options . accessors ) {
if ( variable . writable && ! renderer . readonly . has ( prop . name ) ) {
accessors . push ( {
type : "MethodDefinition" ,
kind : "set" ,
key : { type : "Identifier" , name : prop.export_name } ,
type : 'MethodDefinition' ,
kind : 'set' ,
key : { type : 'Identifier' , name : prop.export_name } ,
value : x ` function( ${ prop . name } ) {
this . $set ( { $ { prop . export_name } : $ { prop . name } } ) ;
@flush ( ) ;
} ` ,
} `
} ) ;
} else if ( component . compile_options . dev ) {
accessors . push ( {
type : "MethodDefinition" ,
kind : "set" ,
key : { type : "Identifier" , name : prop.export_name } ,
type : 'MethodDefinition' ,
kind : 'set' ,
key : { type : 'Identifier' , name : prop.export_name } ,
value : x ` function(value) {
throw new @_Error ( "<${component.tag}>: Cannot set read-only property '${prop.export_name}'" ) ;
} ` ,
} `
} ) ;
}
} else if ( component . compile_options . dev ) {
accessors . push ( {
type : "MethodDefinition" ,
kind : "set" ,
key : { type : "Identifier" , name : prop.export_name } ,
type : 'MethodDefinition' ,
kind : 'set' ,
key : { type : 'Identifier' , name : prop.export_name } ,
value : x ` function(value) {
throw new @_Error ( "<${component.tag}>: Props cannot be set directly on the component instance unless compiling with 'accessors: true' or '<svelte:options accessors/>'" ) ;
} ` ,
} `
} ) ;
}
} ) ;
if ( component . compile_options . dev ) {
// checking that expected ones were passed
const expected = props . filter ( ( prop ) = > prop . writable && ! prop . initialised ) ;
const expected = props . filter ( prop = > prop . writable && ! prop . initialised ) ;
if ( expected . length ) {
dev_props_check = b `
const { ctx : # ctx } = this . $ $ ;
const props = $ {
options . customElement ? x ` this.attributes ` : x ` options.props || {} `
} ;
$ { expected . map (
( prop ) = > b `
if ( $ { renderer . reference ( prop . name ) } === undefined && ! ( ' $ {
prop . export_name
} ' in props ) ) {
@_console . warn ( " < $ { component . tag } > was created without expected prop ' $ {
prop . export_name
} ' " ) ;
} `
) }
const props = $ { options . customElement ? x ` this.attributes ` : x ` options.props || {} ` } ;
$ { expected . map ( prop = > b `
if ( $ { renderer . reference ( prop . name ) } === undefined && ! ( '${prop.export_name}' in props ) ) {
@_console . warn ( "<${component.tag}> was created without expected prop '${prop.export_name}'" ) ;
} ` )}
` ;
}
const capturable_vars = component . vars . filter (
( v ) = > ! v . internal && ! v . global && ! v . name . startsWith ( "$$" )
) ;
const capturable_vars = component . vars . filter ( v = > ! v . internal && ! v . global && ! v . name . startsWith ( '$$' ) ) ;
if ( capturable_vars . length > 0 ) {
capture_state = x ` () => ({ ${ capturable_vars . map (
( prop ) = > p ` ${ prop . name } `
) } } ) ` ;
capture_state = x ` () => ({ ${ capturable_vars . map ( prop = > p ` ${ prop . name } ` ) } }) ` ;
}
const injectable_vars = capturable_vars . filter (
( v ) = > ! v . module && v . writable && v . name [ 0 ] !== "$"
) ;
const injectable_vars = capturable_vars . filter ( v = > ! v . module && v . writable && v . name [ 0 ] !== '$' ) ;
if ( uses_props || injectable_vars . length > 0 ) {
inject_state = x `
$ { $ $props } = > {
$ {
uses_props &&
renderer . invalidate (
"$$props" ,
x ` $ $ props = { ... $ $ props, ... $ $ new_props } `
)
}
$ { uses_props && renderer . invalidate ( '$$props' , x ` $ $ props = { ... $ $ props, ... $ $ new_props } ` ) }
$ { injectable_vars . map (
( v ) = >
b ` if (' ${ v . name } ' in $ $ props) ${ renderer . invalidate (
v . name ,
x ` ${ v . name } = ${ $ $props } . ${ v . name } `
) } ; `
v = > b ` if (' ${ v . name } ' in $ $ props) ${ renderer . invalidate ( v . name , x ` ${ v . name } = ${ $ $props } . ${ v . name } ` ) } ; `
) }
}
` ;
@ -294,11 +256,7 @@ export default function dom(
if ( ! execution_context && ! scope . block ) {
execution_context = node ;
}
} else if (
! execution_context &&
node . type === "LabeledStatement" &&
node . label . name === "$"
) {
} else if ( ! execution_context && node . type === 'LabeledStatement' && node . label . name === '$' ) {
execution_context = node ;
}
} ,
@ -312,12 +270,8 @@ export default function dom(
execution_context = null ;
}
if (
node . type === "AssignmentExpression" ||
node . type === "UpdateExpression"
) {
const assignee =
node . type === "AssignmentExpression" ? node.left : node.argument ;
if ( node . type === 'AssignmentExpression' || node . type === 'UpdateExpression' ) {
const assignee = node . type === 'AssignmentExpression' ? node.left : node.argument ;
// normally (`a = 1`, `b.c = 2`), there'll be a single name
// (a or b). In destructuring cases (`[d, e] = [e, d]`) there
@ -325,21 +279,18 @@ export default function dom(
// onto the initial function call
const names = new Set ( extract_names ( assignee ) ) ;
this . replace (
invalidate ( renderer , scope , node , names , execution_context === null )
) ;
this . replace ( invalidate ( renderer , scope , node , names , execution_context === null ) ) ;
}
}
} ,
} ) ;
component . rewrite_props ( ( { name , reassigned , export_name } ) = > {
const value = ` $ ${ name } ` ;
const i = renderer . context_lookup . get ( ` $ ${ name } ` ) . index ;
const insert =
reassigned || export_name
const insert = ( reassigned || export_name )
? b ` ${ ` $ $ subscribe_ ${ name } ` } () `
: b ` @component_subscribe( $ $ self, ${ name } , #value => $ $ invalidate( ${ i } , ${ value } = #value)) ` ;
: b ` $ $ self. $ $ .on_destroy.push(@subscribe( ${ name } , #value => $ $ invalidate( ${ i } , ${ value } = #value)) ` ;
if ( component . compile_options . dev ) {
return b ` @validate_store_dev( ${ name } , ' ${ name } '); ${ insert } ` ;
@ -350,8 +301,7 @@ export default function dom(
}
const args = [ x ` $ $ self ` ] ;
const has_invalidate =
props . length > 0 ||
const has_invalidate = props . length > 0 ||
component . has_reactive_assignments ||
component . slots . size > 0 ||
capture_state ||
@ -363,8 +313,7 @@ export default function dom(
args . push ( x ` $ $ props ` ) ;
}
const has_create_fragment =
component . compile_options . dev || block . has_content ( ) ;
const has_create_fragment = component . compile_options . dev || block . has_content ( ) ;
if ( has_create_fragment ) {
body . push ( b `
function create_fragment ( # ctx ) {
@ -379,21 +328,17 @@ export default function dom(
$ { component . fully_hoisted }
` );
const filtered_props = props . filter ( ( prop ) = > {
const filtered_props = props . filter ( prop = > {
const variable = component . var_lookup . get ( prop . name ) ;
if ( variable . hoistable ) return false ;
if ( prop . name [ 0 ] === "$" ) return false ;
if ( prop . name [ 0 ] === '$' ) return false ;
return true ;
} ) ;
const reactive_stores = component . vars . filter (
( variable ) = > variable . name [ 0 ] === "$" && variable . name [ 1 ] !== "$"
) ;
const reactive_stores = component . vars . filter ( ( variable ) = > variable . name [ 0 ] === '$' && variable . name [ 1 ] !== '$' ) ;
const instance_javascript = component . extract_javascript (
component . ast . instance
) ;
const instance_javascript = component . extract_javascript ( component . ast . instance ) ;
let i = renderer . context . length ;
while ( i -- ) {
@ -406,7 +351,7 @@ export default function dom(
}
const initial_context = renderer . context . slice ( 0 , i + 1 ) ;
const has_definition =
const has_definition = (
component . compile_options . dev ||
( instance_javascript && instance_javascript . length > 0 ) ||
filtered_props . length > 0 ||
@ -415,23 +360,21 @@ export default function dom(
initial_context . length > 0 ||
component . reactive_declarations . length > 0 ||
capture_state ||
inject_state ;
inject_state
) ;
const definition = has_definition
? component . alias ( "instance" )
: { type : "Literal" , value : null } ;
? component . alias ( 'instance' )
: { type : 'Literal' , value : null } ;
const reactive_store_subscriptions = reactive_stores
. filter ( ( store ) = > {
. filter ( store = > {
const variable = component . var_lookup . get ( store . name . slice ( 1 ) ) ;
return ! variable || variable . hoistable ;
} )
. map (
( { name } ) = > b `
$ {
component . compile_options . dev &&
b ` @validate_store_dev( ${ name . slice ( 1 ) } , ' ${ name . slice ( 1 ) } '); `
}
$ { component . compile_options . dev && b ` @validate_store_dev( ${ name . slice ( 1 ) } , ' ${ name . slice ( 1 ) } '); ` }
$ $self . $ $ . on_destroy . push ( @subscribe ( $ { name . slice ( 1 ) } , # value = > {
$ $invalidate ( $ { renderer . context_lookup . get ( name ) . index } , $ { name } = # value ) ;
} ) ) ;
@ -439,44 +382,30 @@ export default function dom(
) ;
const resubscribable_reactive_store_unsubscribers = reactive_stores
. filter ( ( store ) = > {
. filter ( store = > {
const variable = component . var_lookup . get ( store . name . slice ( 1 ) ) ;
return variable && ( variable . reassigned || variable . export_name ) ;
} )
. map (
( { name } ) = >
b ` $ $ self. $ $ .on_destroy.push(() => ${ ` $ $ unsubscribe_ ${ name . slice (
1
) } ` }()); `
) ;
. map ( ( { name } ) = > b ` $ $ self. $ $ .on_destroy.push(() => ${ ` $ $ unsubscribe_ ${ name . slice ( 1 ) } ` } ()); ` ) ;
if ( has_definition ) {
const reactive_declarations : Node | Node [ ] = [ ] ;
const reactive_declarations : ( Node | Node [ ] ) = [ ] ;
const fixed_reactive_declarations = [ ] ; // not really 'reactive' but whatever
component . reactive_declarations . forEach ( ( d ) = > {
component . reactive_declarations . forEach ( d = > {
const dependencies = Array . from ( d . dependencies ) ;
const uses_rest_or_props = ! ! dependencies . find (
( n ) = > n === "$$props" || n === "$$restProps"
) ;
const uses_rest_or_props = ! ! dependencies . find ( n = > n === '$$props' || n === '$$restProps' ) ;
const writable = dependencies . filter ( ( n ) = > {
const writable = dependencies . filter ( n = > {
const variable = component . var_lookup . get ( n ) ;
return (
variable &&
( variable . export_name || variable . mutated || variable . reassigned )
) ;
return variable && ( variable . export_name || variable . mutated || variable . reassigned ) ;
} ) ;
const condition =
! uses_rest_or_props &&
writable . length > 0 &&
renderer . dirty ( writable , true ) ;
const condition = ! uses_rest_or_props && writable . length > 0 && renderer . dirty ( writable , true ) ;
let statement = d . node ; // TODO remove label (use d.node.body) if it's not referenced
if ( condition )
statement = b ` if ( ${ condition } ) { ${ statement } } ` [ 0 ] as Statement ;
if ( condition ) statement = b ` if ( ${ condition } ) { ${ statement } } ` [ 0 ] as Statement ;
if ( condition || uses_rest_or_props ) {
reactive_declarations . push ( statement ) ;
@ -485,14 +414,12 @@ export default function dom(
}
} ) ;
const injected = Array . from (
component . injected_reactive_declaration_vars
) . filter ( ( name ) = > {
const injected = Array . from ( component . injected_reactive_declaration_vars ) . filter ( name = > {
const variable = component . var_lookup . get ( name ) ;
return variable . injected && variable . name [ 0 ] !== "$" ;
return variable . injected && variable . name [ 0 ] !== '$' ;
} ) ;
const reactive_store_declarations = reactive_stores . map ( ( variable ) = > {
const reactive_store_declarations = reactive_stores . map ( variable = > {
const $name = variable . name ;
const name = $name . slice ( 1 ) ;
@ -517,26 +444,19 @@ export default function dom(
let unknown_props_check ;
if ( component . compile_options . dev && ! ( uses_props || uses_rest ) ) {
unknown_props_check = b `
const writable_props = [ $ { writable_props . map (
( prop ) = > x ` ' ${ prop . export_name } ' `
) } ] ;
const writable_props = [ $ { writable_props . map ( prop = > x ` ' ${ prop . export_name } ' ` ) } ] ;
@_Object . keys ( $ $props ) . forEach ( key = > {
if ( ! ~ writable_props . indexOf ( key ) && key . slice ( 0 , 2 ) !== '$$' ) @_console . warn ( \ ` < ${
component . tag
} > was created with unknown prop '\${key}' \ ` );
if ( ! ~ writable_props . indexOf ( key ) && key . slice ( 0 , 2 ) !== '$$' ) @_console . warn ( \ ` < ${ component . tag } > was created with unknown prop ' \ ${ key } ' \` );
} ) ;
` ;
}
const return_value = {
type : "ArrayExpression" ,
elements : initial_context.map (
( member ) = >
( {
type : "Identifier" ,
name : member.name ,
} as Expression )
) ,
type : 'ArrayExpression' ,
elements : initial_context.map ( member = > ( {
type : 'Identifier' ,
name : member.name
} ) as Expression )
} ;
body . push ( b `
@ -553,19 +473,8 @@ export default function dom(
$ { unknown_props_check }
$ {
component . slots . size || component . compile_options . dev
? b ` let { $ $ slots = {}, $ $ scope } = $ $ props; `
: null
}
$ {
component . compile_options . dev &&
b ` @validate_slots_dev(' ${ component . tag } ', $ $ slots, [ ${ [
. . . component . slots . keys ( ) ,
]
. map ( ( key ) = > ` ' ${ key } ' ` )
. join ( "," ) } ] ) ; `
}
$ { component . slots . size || component . compile_options . dev ? b ` let { $ $ slots = {}, $ $ scope } = $ $ props; ` : null }
$ { component . compile_options . dev && b ` @validate_slots_dev(' ${ component . tag } ', $ $ slots, [ ${ [ . . . component . slots . keys ( ) ] . map ( key = > ` ' ${ key } ' ` ) . join ( ',' ) } ]); ` }
$ {
renderer . binding_groups . length
@ -583,18 +492,15 @@ export default function dom(
$ { inject_state && b ` $ $ self. $ inject_state = ${ inject_state } ; ` }
$ { injected . map ( ( name ) = > b ` let ${ name } ; ` ) }
$ { injected . map ( name = > b ` let ${ name } ; ` ) }
$ { /* before reactive declarations */ props_inject }
$ {
reactive_declarations . length > 0 &&
b `
$ { reactive_declarations . length > 0 && b `
$ $self . $ $ . update = ( ) = > {
$ { reactive_declarations }
} ;
`
}
` }
$ { fixed_reactive_declarations }
@ -611,11 +517,7 @@ export default function dom(
}
const prop_indexes = x ` {
$ { props
. filter ( ( v ) = > v . export_name && ! v . module )
. map (
( v ) = > p ` ${ v . export_name } : ${ renderer . context_lookup . get ( v . name ) . index } `
) }
$ { props . filter ( v = > v . export_name && ! v . module ) . map ( v = > p ` ${ v . export_name } : ${ renderer . context_lookup . get ( v . name ) . index } ` ) }
} ` as ObjectExpression;
let dirty ;
@ -632,19 +534,9 @@ export default function dom(
constructor ( options ) {
super ( ) ;
$ {
css . code &&
b ` this.shadowRoot.innerHTML = \` <style> ${ css . code . replace (
/\\/g ,
"\\\\"
) } $ {
options . dev ? ` \ n/*# sourceMappingURL= ${ css . map . toUrl ( ) } */ ` : ""
} < / style > \ ` ; `
}
$ { css . code && b ` this.shadowRoot.innerHTML = \` <style> ${ css . code . replace ( /\\/g , '\\\\' ) } ${ options . dev ? ` \ n/*# sourceMappingURL= ${ css . map . toUrl ( ) } */ ` : '' } </style> \` ; ` }
@init ( this , { target : this.shadowRoot } , $ { definition } , $ {
has_create_fragment ? "create_fragment" : "null"
} , $ { not_equal } , $ { prop_indexes } , $ { dirty } ) ;
@init ( this , { target : this.shadowRoot } , $ { definition } , $ { has_create_fragment ? 'create_fragment' : 'null' } , $ { not_equal } , $ { prop_indexes } , $ { dirty } ) ;
$ { dev_props_check }
@ -665,11 +557,11 @@ export default function dom(
if ( props . length > 0 ) {
declaration . body . body . push ( {
type : "MethodDefinition" ,
kind : "get" ,
type : 'MethodDefinition' ,
kind : 'get' ,
static : true ,
computed : false ,
key : { type : "Identifier" , name : "observedAttributes" } ,
key : { type : 'Identifier' , name : 'observedAttributes' } ,
value : x ` function() {
return [ $ { props . map ( prop = > x ` " ${ prop . export_name } " ` ) } ] ;
} ` as FunctionExpression
@ -687,8 +579,8 @@ export default function dom(
}
} else {
const superclass = {
type : "Identifier" ,
name : options.dev ? "@SvelteComponentDev" : "@SvelteComponent"
type : 'Identifier' ,
name : options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
} ;
const declaration = b `