1
1
/** @import { VariableDeclarator, Node, Identifier } from 'estree' */
2
2
/** @import { Visitors } from 'zimmerframe' */
3
3
/** @import { ComponentAnalysis } from '../phases/types.js' */
4
- /** @import { Scope } from '../phases/scope.js' */
4
+ /** @import { Scope, ScopeRoot } from '../phases/scope.js' */
5
5
/** @import { AST, Binding, SvelteNode, ValidatedCompileOptions } from '#compiler' */
6
6
import MagicString from 'magic-string' ;
7
7
import { walk } from 'zimmerframe' ;
8
8
import { parse } from '../phases/1-parse/index.js' ;
9
+ import { regex_valid_component_name } from '../phases/1-parse/state/element.js' ;
9
10
import { analyze_component } from '../phases/2-analyze/index.js' ;
10
11
import { get_rune } from '../phases/scope.js' ;
11
12
import { reset , reset_warning_filter } from '../state.js' ;
12
13
import { extract_identifiers } from '../utils/ast.js' ;
13
14
import { migrate_svelte_ignore } from '../utils/extract_svelte_ignore.js' ;
14
15
import { validate_component_options } from '../validate-options.js' ;
16
+ import { is_svg , is_void } from '../../utils.js' ;
15
17
16
18
const regex_style_tags = / ( < s t y l e [ ^ > ] + > ) ( [ \S \s ] * ?) ( < \/ s t y l e > ) / g;
17
19
const style_placeholder = '/*$$__STYLE_CONTENT__$$*/' ;
@@ -52,6 +54,8 @@ export function migrate(source) {
52
54
const analysis = analyze_component ( parsed , source , combined_options ) ;
53
55
const indent = guess_indent ( source ) ;
54
56
57
+ str . replaceAll ( / ( < s v e l t e : o p t i o n s \s .* ?\s ? ) a c c e s s o r s \s ? / g, ( _ , $1 ) => $1 ) ;
58
+
55
59
for ( const content of style_contents ) {
56
60
str . overwrite ( content [ 0 ] , content [ 0 ] + style_placeholder . length , content [ 1 ] ) ;
57
61
}
@@ -85,7 +89,8 @@ export function migrate(source) {
85
89
nonpassive : analysis . root . unique ( 'nonpassive' ) . name
86
90
} ,
87
91
legacy_imports : new Set ( ) ,
88
- script_insertions : new Set ( )
92
+ script_insertions : new Set ( ) ,
93
+ derived_components : new Map ( )
89
94
} ;
90
95
91
96
if ( parsed . module ) {
@@ -108,6 +113,7 @@ export function migrate(source) {
108
113
109
114
const need_script =
110
115
state . legacy_imports . size > 0 ||
116
+ state . derived_components . size > 0 ||
111
117
state . script_insertions . size > 0 ||
112
118
state . props . length > 0 ||
113
119
analysis . uses_rest_props ||
@@ -146,6 +152,7 @@ export function migrate(source) {
146
152
props = `...${ state . names . props } ` ;
147
153
} else {
148
154
props = state . props
155
+ . filter ( ( prop ) => ! prop . type_only )
149
156
. map ( ( prop ) => {
150
157
let prop_str =
151
158
prop . local === prop . exported ? prop . local : `${ prop . exported } : ${ prop . local } ` ;
@@ -232,7 +239,9 @@ export function migrate(source) {
232
239
dependencies . some (
233
240
( dep ) =>
234
241
! ids . includes ( dep ) &&
235
- /** @type {number } */ ( dep . node . start ) > /** @type {number } */ ( node . start )
242
+ ( dep . kind === 'prop' || dep . kind === 'bindable_prop'
243
+ ? state . props_insertion_point
244
+ : /** @type {number } */ ( dep . node . start ) ) > /** @type {number } */ ( node . start )
236
245
)
237
246
) {
238
247
needs_reordering = true ;
@@ -250,6 +259,24 @@ export function migrate(source) {
250
259
}
251
260
}
252
261
262
+ insertion_point = parsed . instance
263
+ ? /** @type {number } */ ( parsed . instance . content . end )
264
+ : insertion_point ;
265
+
266
+ if ( state . derived_components . size > 0 ) {
267
+ str . appendRight (
268
+ insertion_point ,
269
+ `\n${ indent } ${ [ ...state . derived_components . entries ( ) ] . map ( ( [ init , name ] ) => `const ${ name } = $derived(${ init } );` ) . join ( `\n${ indent } ` ) } \n`
270
+ ) ;
271
+ }
272
+
273
+ if ( state . props . length > 0 && state . analysis . accessors ) {
274
+ str . appendRight (
275
+ insertion_point ,
276
+ `\n${ indent } export {${ state . props . reduce ( ( acc , prop ) => ( prop . slot_name ? acc : `${ acc } \n${ indent } \t${ prop . local } ,` ) , '' ) } \n${ indent } }\n`
277
+ ) ;
278
+ }
279
+
253
280
if ( ! parsed . instance && need_script ) {
254
281
str . appendRight ( insertion_point , '\n</script>\n\n' ) ;
255
282
}
@@ -267,13 +294,14 @@ export function migrate(source) {
267
294
* str: MagicString;
268
295
* analysis: ComponentAnalysis;
269
296
* indent: string;
270
- * props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string }>;
297
+ * props: Array<{ local: string; exported: string; init: string; bindable: boolean; slot_name?: string; optional: boolean; type: string; comment?: string, type_only?: boolean }>;
271
298
* props_insertion_point: number;
272
299
* has_props_rune: boolean;
273
300
* end: number;
274
301
* names: Record<string, string>;
275
302
* legacy_imports: Set<string>;
276
- * script_insertions: Set<string>
303
+ * script_insertions: Set<string>;
304
+ * derived_components: Map<string, string>
277
305
* }} State
278
306
*/
279
307
@@ -403,6 +431,7 @@ const instance_script = {
403
431
: '' ;
404
432
prop . bindable = binding . updated ;
405
433
prop . exported = binding . prop_alias || name ;
434
+ prop . type_only = false ;
406
435
} else {
407
436
state . props . push ( {
408
437
local : name ,
@@ -560,6 +589,15 @@ const template = {
560
589
} ,
561
590
RegularElement ( node , { state, next } ) {
562
591
handle_events ( node , state ) ;
592
+ // Strip off any namespace from the beginning of the node name.
593
+ const node_name = node . name . replace ( / [ a - z A - Z - ] * : / g, '' ) ;
594
+
595
+ if ( state . analysis . source [ node . end - 2 ] === '/' && ! is_void ( node_name ) && ! is_svg ( node_name ) ) {
596
+ let trimmed_position = node . end - 2 ;
597
+ while ( state . str . original . charAt ( trimmed_position - 1 ) === ' ' ) trimmed_position -- ;
598
+ state . str . remove ( trimmed_position , node . end - 1 ) ;
599
+ state . str . appendRight ( node . end , `</${ node . name } >` ) ;
600
+ }
563
601
next ( ) ;
564
602
} ,
565
603
SvelteElement ( node , { state, next } ) {
@@ -586,6 +624,74 @@ const template = {
586
624
handle_events ( node , state ) ;
587
625
next ( ) ;
588
626
} ,
627
+ SvelteComponent ( node , { state, next, path } ) {
628
+ next ( ) ;
629
+
630
+ let expression = state . str
631
+ . snip (
632
+ /** @type {number } */ ( node . expression . start ) ,
633
+ /** @type {number } */ ( node . expression . end )
634
+ )
635
+ . toString ( ) ;
636
+
637
+ if (
638
+ ( node . expression . type !== 'Identifier' && node . expression . type !== 'MemberExpression' ) ||
639
+ ! regex_valid_component_name . test ( expression )
640
+ ) {
641
+ let current_expression = expression ;
642
+ expression = state . scope . generate ( 'SvelteComponent' ) ;
643
+ let needs_derived = true ;
644
+ for ( let i = path . length - 1 ; i >= 0 ; i -- ) {
645
+ const part = path [ i ] ;
646
+ if (
647
+ part . type === 'EachBlock' ||
648
+ part . type === 'AwaitBlock' ||
649
+ part . type === 'IfBlock' ||
650
+ part . type === 'SnippetBlock' ||
651
+ part . type === 'Component' ||
652
+ part . type === 'SvelteComponent'
653
+ ) {
654
+ let position = node . start ;
655
+ if ( i !== path . length - 1 ) {
656
+ for ( let modifier = 1 ; modifier < path . length - i ; modifier ++ ) {
657
+ const path_part = path [ i + modifier ] ;
658
+ if ( 'start' in path_part ) {
659
+ position = /** @type {number } */ ( path_part . start ) ;
660
+ break ;
661
+ }
662
+ }
663
+ }
664
+ const indent = state . str . original . substring (
665
+ state . str . original . lastIndexOf ( '\n' , position ) + 1 ,
666
+ position
667
+ ) ;
668
+ state . str . prependLeft (
669
+ position ,
670
+ `{@const ${ expression } = ${ current_expression } }\n${ indent } `
671
+ ) ;
672
+ needs_derived = false ;
673
+ break ;
674
+ }
675
+ }
676
+ if ( needs_derived ) {
677
+ if ( state . derived_components . has ( current_expression ) ) {
678
+ expression = /** @type {string } */ ( state . derived_components . get ( current_expression ) ) ;
679
+ } else {
680
+ state . derived_components . set ( current_expression , expression ) ;
681
+ }
682
+ }
683
+ }
684
+
685
+ state . str . overwrite ( node . start + 1 , node . start + node . name . length + 1 , expression ) ;
686
+
687
+ if ( state . str . original . substring ( node . end - node . name . length - 1 , node . end - 1 ) === node . name ) {
688
+ state . str . overwrite ( node . end - node . name . length - 1 , node . end - 1 , expression ) ;
689
+ }
690
+ let this_pos = state . str . original . lastIndexOf ( 'this' , node . expression . start ) ;
691
+ while ( ! state . str . original . charAt ( this_pos - 1 ) . trim ( ) ) this_pos -- ;
692
+ const end_pos = state . str . original . indexOf ( '}' , node . expression . end ) + 1 ;
693
+ state . str . remove ( this_pos , end_pos ) ;
694
+ } ,
589
695
SvelteWindow ( node , { state, next } ) {
590
696
handle_events ( node , state ) ;
591
697
next ( ) ;
@@ -914,7 +1020,8 @@ function handle_identifier(node, state, path) {
914
1020
bindable : false ,
915
1021
optional : member . optional ,
916
1022
type,
917
- comment
1023
+ comment,
1024
+ type_only : true
918
1025
} ) ;
919
1026
}
920
1027
}
0 commit comments