@@ -10,7 +10,11 @@ import { regex_valid_component_name } from '../phases/1-parse/state/element.js';
1010import { analyze_component } from '../phases/2-analyze/index.js' ;
1111import { get_rune } from '../phases/scope.js' ;
1212import { reset , reset_warning_filter } from '../state.js' ;
13- import { extract_identifiers , extract_all_identifiers_from_expression } from '../utils/ast.js' ;
13+ import {
14+ extract_identifiers ,
15+ extract_all_identifiers_from_expression ,
16+ is_text_attribute
17+ } from '../utils/ast.js' ;
1418import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js' ;
1519import { validate_component_options } from '../validate-options.js' ;
1620import { is_svg , is_void } from '../../utils.js' ;
@@ -711,7 +715,8 @@ const template = {
711715 Identifier ( node , { state, path } ) {
712716 handle_identifier ( node , state , path ) ;
713717 } ,
714- RegularElement ( node , { state, next } ) {
718+ RegularElement ( node , { state, path, next } ) {
719+ migrate_slot_usage ( node , path , state ) ;
715720 handle_events ( node , state ) ;
716721 // Strip off any namespace from the beginning of the node name.
717722 const node_name = node . name . replace ( / [ a - z A - Z - ] * : / g, '' ) ;
@@ -724,7 +729,9 @@ const template = {
724729 }
725730 next ( ) ;
726731 } ,
727- SvelteElement ( node , { state, next } ) {
732+ SvelteElement ( node , { state, path, next } ) {
733+ migrate_slot_usage ( node , path , state ) ;
734+
728735 if ( node . tag . type === 'Literal' ) {
729736 let is_static = true ;
730737
@@ -748,9 +755,15 @@ const template = {
748755 handle_events ( node , state ) ;
749756 next ( ) ;
750757 } ,
758+ Component ( node , { state, path, next } ) {
759+ next ( ) ;
760+ migrate_slot_usage ( node , path , state ) ;
761+ } ,
751762 SvelteComponent ( node , { state, next, path } ) {
752763 next ( ) ;
753764
765+ migrate_slot_usage ( node , path , state ) ;
766+
754767 let expression = state . str
755768 . snip (
756769 /** @type {number } */ ( node . expression . start ) ,
@@ -789,7 +802,7 @@ const template = {
789802 state . str . original . lastIndexOf ( '\n' , position ) + 1 ,
790803 position
791804 ) ;
792- state . str . prependLeft (
805+ state . str . appendRight (
793806 position ,
794807 `{@const ${ expression } = ${ current_expression } }\n${ indent } `
795808 ) ;
@@ -816,6 +829,10 @@ const template = {
816829 const end_pos = state . str . original . indexOf ( '}' , node . expression . end ) + 1 ;
817830 state . str . remove ( this_pos , end_pos ) ;
818831 } ,
832+ SvelteFragment ( node , { state, path, next } ) {
833+ migrate_slot_usage ( node , path , state ) ;
834+ next ( ) ;
835+ } ,
819836 SvelteWindow ( node , { state, next } ) {
820837 handle_events ( node , state ) ;
821838 next ( ) ;
@@ -828,7 +845,9 @@ const template = {
828845 handle_events ( node , state ) ;
829846 next ( ) ;
830847 } ,
831- SlotElement ( node , { state, next, visit } ) {
848+ SlotElement ( node , { state, path, next, visit } ) {
849+ migrate_slot_usage ( node , path , state ) ;
850+
832851 if ( state . analysis . custom_element ) return ;
833852 let name = 'children' ;
834853 let slot_name = 'default' ;
@@ -915,6 +934,129 @@ const template = {
915934 }
916935} ;
917936
937+ /**
938+ * @param {AST.RegularElement | AST.SvelteElement | AST.SvelteComponent | AST.Component | AST.SlotElement | AST.SvelteFragment } node
939+ * @param {SvelteNode[] } path
940+ * @param {State } state
941+ */
942+ function migrate_slot_usage ( node , path , state ) {
943+ const parent = path . at ( - 2 ) ;
944+ // Bail on custom element slot usage
945+ if (
946+ parent ?. type !== 'Component' &&
947+ parent ?. type !== 'SvelteComponent' &&
948+ node . type !== 'Component' &&
949+ node . type !== 'SvelteComponent'
950+ ) {
951+ return ;
952+ }
953+
954+ let snippet_name = 'children' ;
955+ let snippet_props = [ ] ;
956+
957+ for ( let attribute of node . attributes ) {
958+ if (
959+ attribute . type === 'Attribute' &&
960+ attribute . name === 'slot' &&
961+ is_text_attribute ( attribute )
962+ ) {
963+ snippet_name = attribute . value [ 0 ] . data ;
964+ state . str . remove ( attribute . start , attribute . end ) ;
965+ }
966+ if ( attribute . type === 'LetDirective' ) {
967+ snippet_props . push (
968+ attribute . name +
969+ ( attribute . expression
970+ ? `: ${ state . str . original . substring ( /** @type {number } */ ( attribute . expression . start ) , /** @type {number } */ ( attribute . expression . end ) ) } `
971+ : '' )
972+ ) ;
973+ state . str . remove ( attribute . start , attribute . end ) ;
974+ }
975+ }
976+
977+ if ( node . type === 'SvelteFragment' && node . fragment . nodes . length > 0 ) {
978+ // remove node itself, keep content
979+ state . str . remove ( node . start , node . fragment . nodes [ 0 ] . start ) ;
980+ state . str . remove ( node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end , node . end ) ;
981+ }
982+
983+ const props = snippet_props . length > 0 ? `{ ${ snippet_props . join ( ', ' ) } }` : '' ;
984+
985+ if ( snippet_name === 'children' && node . type !== 'SvelteFragment' ) {
986+ if ( snippet_props . length === 0 ) return ; // nothing to do
987+
988+ let inner_start = 0 ;
989+ let inner_end = 0 ;
990+ for ( let i = 0 ; i < node . fragment . nodes . length ; i ++ ) {
991+ const inner = node . fragment . nodes [ i ] ;
992+ const is_empty_text = inner . type === 'Text' && ! inner . data . trim ( ) ;
993+
994+ if (
995+ ( inner . type === 'RegularElement' ||
996+ inner . type === 'SvelteElement' ||
997+ inner . type === 'Component' ||
998+ inner . type === 'SvelteComponent' ||
999+ inner . type === 'SlotElement' ||
1000+ inner . type === 'SvelteFragment' ) &&
1001+ inner . attributes . some ( ( attr ) => attr . type === 'Attribute' && attr . name === 'slot' )
1002+ ) {
1003+ if ( inner_start && ! inner_end ) {
1004+ // End of default slot content
1005+ inner_end = inner . start ;
1006+ }
1007+ } else if ( ! inner_start && ! is_empty_text ) {
1008+ // Start of default slot content
1009+ inner_start = inner . start ;
1010+ } else if ( inner_end && ! is_empty_text ) {
1011+ // There was default slot content before, then some named slot content, now some default slot content again.
1012+ // We're moving the last character back by one to avoid the closing {/snippet} tag inserted afterwards
1013+ // to come before the opening {#snippet} tag of the named slot.
1014+ state . str . update ( inner_end - 1 , inner_end , '' ) ;
1015+ state . str . prependLeft ( inner_end - 1 , state . str . original [ inner_end - 1 ] ) ;
1016+ state . str . move ( inner . start , inner . end , inner_end - 1 ) ;
1017+ }
1018+ }
1019+
1020+ if ( ! inner_end ) {
1021+ inner_end = node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end ;
1022+ }
1023+
1024+ state . str . appendLeft (
1025+ inner_start ,
1026+ `{#snippet ${ snippet_name } (${ props } )}\n${ state . indent . repeat ( path . length ) } `
1027+ ) ;
1028+ state . str . indent ( state . indent , {
1029+ exclude : [
1030+ [ 0 , inner_start ] ,
1031+ [ inner_end , state . str . original . length ]
1032+ ]
1033+ } ) ;
1034+ if ( inner_end < node . fragment . nodes [ node . fragment . nodes . length - 1 ] . end ) {
1035+ // Named slots coming afterwards
1036+ state . str . prependLeft ( inner_end , `{/snippet}\n${ state . indent . repeat ( path . length ) } ` ) ;
1037+ } else {
1038+ // No named slots coming afterwards
1039+ state . str . prependLeft (
1040+ inner_end ,
1041+ `${ state . indent . repeat ( path . length ) } {/snippet}\n${ state . indent . repeat ( path . length - 1 ) } `
1042+ ) ;
1043+ }
1044+ } else {
1045+ // Named slot or `svelte:fragment`: wrap element itself in a snippet
1046+ state . str . prependLeft (
1047+ node . start ,
1048+ `{#snippet ${ snippet_name } (${ props } )}\n${ state . indent . repeat ( path . length - 2 ) } `
1049+ ) ;
1050+ state . str . indent ( state . indent , {
1051+ exclude : [
1052+ [ 0 , node . start ] ,
1053+ [ node . end , state . str . original . length ]
1054+ ]
1055+ } ) ;
1056+ state . str . appendLeft ( node . end , `\n${ state . indent . repeat ( path . length - 2 ) } {/snippet}` ) ;
1057+ }
1058+ }
1059+
9181060/**
9191061 * @param {VariableDeclarator } declarator
9201062 * @param {MagicString } str
0 commit comments