@@ -4,34 +4,34 @@ import MagicString from "magic-string";
44import type { Plugin } from "vite" ;
55
66export interface InlineSvelteOptions {
7- /** Template-tag names treated as Svelte markup. Default: `["html", "svelte"]` */
7+ /** Template-tag names treated as Svelte markup – default `["html", "svelte"]` */
88 tags ?: string [ ] ;
9- /** Comment that *starts* an import fence. Default: `/* svelte:imports` */
9+ /** Comment that *starts* an import fence – default `/* svelte:imports` */
1010 fenceStart ?: string ;
11- /** Comment that *ends* an import fence. Default: `*\/` */
11+ /** Comment that *ends* an import fence – default `*\/` */
1212 fenceEnd ?: string ;
13- /** Comment that *starts* a global components fence. Default: `/* svelte:globals` */
14- globalsFenceStart ?: string ;
13+ /** Comment that *starts* a globals fence – default `/* svelte:globals` */
14+ globalsStart ?: string ;
15+ /** Comment that *ends* a globals fence – default `*\/` */
16+ globalsEnd ?: string ;
1517}
1618
1719/* ───────── helpers ───────── */
1820
1921const esc = ( s : string ) => s . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ;
2022const isUserSource = ( id : string ) => ! id . includes ( "/node_modules/" ) && / \. ( c ? [ t j ] s x ? ) $ / . test ( id ) ;
2123
22- /** Injects a string of script content into a Svelte component's markup. */
23- function applyScriptContent ( markup : string , scriptContent : string ) : string {
24- if ( ! scriptContent ) return markup ;
24+ /** Inject shared imports without duplicating instance `<script>` blocks */
25+ function applyImports ( markup : string , imports : string ) : string {
26+ if ( ! imports ) return markup ;
2527
2628 const scriptRE = / < s c r i p t (? ! [ ^ > ] * c o n t e x t = [ " ' ] m o d u l e [ " ' ] ) [ ^ > ] * > / i;
27- const match = scriptRE . exec ( markup ) ;
28-
29- if ( match ) {
30- const idx = match . index + match [ 0 ] . length ;
31- return `${ markup . slice ( 0 , idx ) } \n${ scriptContent } \n${ markup . slice ( idx ) } ` ;
32- } else {
33- return `<script>\n${ scriptContent } \n</script>\n${ markup } ` ;
29+ const m = scriptRE . exec ( markup ) ;
30+ if ( m ) {
31+ const idx = m . index + m [ 0 ] . length ;
32+ return markup . slice ( 0 , idx ) + "\n" + imports + "\n" + markup . slice ( idx ) ;
3433 }
34+ return `<script>\n${ imports } \n</script>\n` + markup ;
3535}
3636
3737/* ───────── plugin ───────── */
@@ -40,149 +40,132 @@ export default function inlineSveltePlugin({
4040 tags = [ "html" , "svelte" ] ,
4141 fenceStart = "/* svelte:imports" ,
4242 fenceEnd = "*/" ,
43- globalsFenceStart = "/* svelte:globals" ,
43+ globalsStart = "/* svelte:globals" ,
44+ globalsEnd = "*/" ,
4445} : InlineSvelteOptions = { } ) : Plugin {
4546 const tagGroup = tags . map ( esc ) . join ( "|" ) ;
46- const tplRE = new RegExp (
47- `(?:const|let|var)\\s+([a-zA-Z0-9_$]+)\\s*=\\s*(?:${ tagGroup } )\\s*\`([\\s\\S]*?)\`` ,
48- "g"
49- ) ;
50- const importsFenceRE = new RegExp ( `${ esc ( fenceStart ) } ([\\s\\S]*?)${ esc ( fenceEnd ) } ` , "m" ) ;
51- const globalsFenceRE = new RegExp ( `${ esc ( globalsFenceStart ) } ([\\s\\S]*?)${ esc ( fenceEnd ) } ` , "m" ) ;
47+ const tplRE = new RegExp ( `(?:${ tagGroup } )\\s*\`([\\s\\S]*?)\`` , "g" ) ;
48+ const fenceRE = new RegExp ( `${ esc ( fenceStart ) } ([\\s\\S]*?)${ esc ( fenceEnd ) } ` , "m" ) ;
49+ const globalsRE = new RegExp ( `${ esc ( globalsStart ) } ([\\s\\S]*?)${ esc ( globalsEnd ) } ` , "m" ) ;
50+ const globalDefRE = / c o n s t \s + ( [ a - z A - Z 0 - 9 _ $ ] + ) \s * = \s * (?: h t m l | s v e l t e ) \s * \` ( [ \s \S ] * ?) \` / g;
5251
53- const VIRT_PREFIX = "virtual:inline-svelte/" ;
54- const RSLV_PREFIX = `\0 ${ VIRT_PREFIX } ` ;
52+ const VIRT = "virtual:inline-svelte/" ;
53+ const RSLV = "\0" + VIRT ;
5554
55+ /** virtualId → full markup (with injected imports) */
5656 const cache = new Map < string , string > ( ) ;
5757
5858 return {
59- name : "@hvniel/vite-plugin-svelte-inline-component-v3 " ,
59+ name : "@hvniel/vite-plugin-svelte-inline-component" ,
6060 enforce : "pre" ,
6161
6262 transform ( code , id ) {
63- if ( ! isUserSource ( id ) || ! tags . some ( tag => code . includes ( tag ) ) ) {
64- return ;
65- }
63+ if ( ! isUserSource ( id ) ) return ;
6664
6765 const ms = new MagicString ( code ) ;
6866 let edited = false ;
6967
70- // === STAGE 1: Process `svelte:imports` fence ===
71- const importsMatch = importsFenceRE . exec ( code ) ;
72- const standardImports = ( importsMatch ?. [ 1 ] ?? "" ) . trim ( ) ;
68+ /* file‑level shared imports (may be empty) */
69+ const imports = ( fenceRE . exec ( code ) ?. [ 1 ] ?? "" ) . trim ( ) ;
7370
74- // === STAGE 2: Process `svelte:globals` fence ===
75- const globalsMatch = globalsFenceRE . exec ( code ) ;
76- // This will hold the full script block (imports + consts) for global components
77- let globalScriptBlock = "" ;
78- const hashToLocal = new Map < string , string > ( ) ;
71+ let importsToAdd = "" ;
72+ let constsToAdd = "" ;
73+ let globalImportsForTpl = "" ;
74+ const hashToLocal = new Map < string , string > ( ) ; // dedupe within THIS pass
7975
76+ /* 1. Process globals */
77+ const globalsMatch = globalsRE . exec ( code ) ;
8078 if ( globalsMatch ) {
81- const globalsContent = globalsMatch [ 1 ] ;
82- const globalsStartIndex = globalsMatch . index ;
83- let match : RegExpExecArray | null ;
84-
85- tplRE . lastIndex = 0 ; // Reset regex before scanning fence content
86- while ( ( match = tplRE . exec ( globalsContent ) ) ) {
87- const [ fullMatch , componentName , rawMarkup ] = match ;
88-
89- const finalMarkup = applyScriptContent ( rawMarkup , standardImports ) ;
90- const hash = createHash ( "sha1" ) . update ( finalMarkup ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
79+ edited = true ;
80+ const globalsContent = globalsMatch [ 1 ] ?? "" ;
81+ // Overwrite the entire globals block so it's removed from the final output
82+ ms . overwrite ( globalsMatch . index , globalsMatch . index + globalsMatch [ 0 ] . length , "" ) ;
83+
84+ let defMatch : RegExpExecArray | null ;
85+ while ( ( defMatch = globalDefRE . exec ( globalsContent ) ) ) {
86+ const compName = defMatch [ 1 ] ;
87+ const rawMarkup = defMatch [ 2 ] ;
88+ // Globals can also use file-level imports from the `svelte:imports` fence
89+ const markup = applyImports ( rawMarkup , imports ) ;
90+ const hash = createHash ( "sha1" ) . update ( markup ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
9191
9292 let local = hashToLocal . get ( hash ) ;
9393 if ( ! local ) {
94- local = `_Inline_ ${ hash } ` ;
94+ local = `Inline_ ${ hash } ` ;
9595 hashToLocal . set ( hash , local ) ;
9696
97- const virtId = `${ VIRT_PREFIX } ${ hash } .js` ;
98- if ( ! cache . has ( virtId ) ) cache . set ( virtId , finalMarkup ) ;
99-
100- const ns = `_InlineNS_${ hash } ` ;
101- const importStatement = `import * as ${ ns } from '${ virtId } ';` ;
102- const localDefStatement = `const ${ local } = Object.assign(${ ns } .default, ${ ns } );` ;
97+ const virt = `${ VIRT } ${ hash } .js` ;
98+ if ( ! cache . has ( virt ) ) cache . set ( virt , markup ) ;
10399
104- // Prepend the import to the top of the file for resolution
105- ms . prepend ( `${ importStatement } \n${ localDefStatement } \n` ) ;
100+ const ns = `__InlineNS_${ hash } ` ;
101+ importsToAdd +=
102+ `import * as ${ ns } from '${ virt } ';\n` +
103+ `const ${ local } =Object.assign(${ ns } .default, ${ ns } );\n` ;
106104
107- // **FIX**: Build a full script block to inject into local components
108- // This includes the necessary import and the const declarations.
109- const aliasStatement = `const ${ componentName } = ${ local } ;` ;
110- globalScriptBlock += `${ importStatement } \n${ localDefStatement } \n${ aliasStatement } \n` ;
111- } else {
112- // If the component was already processed, just add its alias declaration
113- globalScriptBlock += `const ${ componentName } = ${ local } ;\n` ;
105+ // This import will be injected into the <script> of other templates
106+ globalImportsForTpl += `import ${ compName } from '${ virt } ';\n` ;
114107 }
115-
116- ms . overwrite (
117- globalsStartIndex + match . index ,
118- globalsStartIndex + match . index + fullMatch . length ,
119- `const ${ componentName } = ${ local } ;`
120- ) ;
121- edited = true ;
108+ // Create the top-level constant for the global component
109+ constsToAdd += `const ${ compName } = ${ local } ;\n` ;
122110 }
123- ms . remove ( globalsStartIndex , globalsStartIndex + globalsMatch [ 0 ] . length ) ;
124111 }
125112
126- // === STAGE 3: Process remaining "local" inline components ===
127- let match : RegExpExecArray | null ;
128- tplRE . lastIndex = 0 ;
129-
130- while ( ( match = tplRE . exec ( code ) ) ) {
113+ /* 2. Process all regular templates */
114+ let m : RegExpExecArray | null ;
115+ tplRE . lastIndex = 0 ; // Reset regex state
116+ while ( ( m = tplRE . exec ( code ) ) ) {
117+ // Skip any templates found inside the globals block, as they've already been processed.
131118 if (
132119 globalsMatch &&
133- match . index >= globalsMatch . index &&
134- match . index < globalsMatch . index + globalsMatch [ 0 ] . length
120+ m . index >= globalsMatch . index &&
121+ m . index < globalsMatch . index + globalsMatch [ 0 ] . length
135122 ) {
136123 continue ;
137124 }
138125
139- const [ fullMatch , componentName , rawMarkup ] = match ;
140-
141- // Inject standard imports AND the full script block for global components
142- const scriptToInject = `${ standardImports } \n${ globalScriptBlock } ` ;
143- const finalMarkup = applyScriptContent ( rawMarkup , scriptToInject ) ;
144-
145- const hash = createHash ( "sha1" ) . update ( finalMarkup ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
126+ const rawMarkup = m [ 1 ] ;
127+ // Inject imports for global components first, then file-level imports
128+ const markupWithGlobals = applyImports ( rawMarkup , globalImportsForTpl ) ;
129+ const markup = applyImports ( markupWithGlobals , imports ) ;
130+ const hash = createHash ( "sha1" ) . update ( markup ) . digest ( "hex" ) . slice ( 0 , 8 ) ;
146131
147132 let local = hashToLocal . get ( hash ) ;
148133 if ( ! local ) {
149- local = `_Inline_ ${ hash } ` ;
134+ local = `Inline_ ${ hash } ` ;
150135 hashToLocal . set ( hash , local ) ;
151136
152- const virtId = `${ VIRT_PREFIX } ${ hash } .js` ;
153- if ( ! cache . has ( virtId ) ) cache . set ( virtId , finalMarkup ) ;
137+ const virt = `${ VIRT } ${ hash } .js` ;
138+ if ( ! cache . has ( virt ) ) cache . set ( virt , markup ) ;
154139
155- const ns = `_InlineNS_${ hash } ` ;
156- ms . prepend (
157- `import * as ${ ns } from '${ virtId } ';\n` +
158- `const ${ local } = Object.assign(${ ns } .default, ${ ns } );\n`
159- ) ;
140+ const ns = `__InlineNS_${ hash } ` ;
141+ importsToAdd +=
142+ `import * as ${ ns } from '${ virt } ';\n` +
143+ `const ${ local } =Object.assign(${ ns } .default, ${ ns } );\n` ;
160144 }
161145
162- ms . overwrite (
163- match . index ,
164- match . index + fullMatch . length ,
165- `const ${ componentName } = ${ local } ;`
166- ) ;
146+ ms . overwrite ( m . index , tplRE . lastIndex , local ) ;
167147 edited = true ;
168148 }
169149
170- if ( ! edited ) return null ;
150+ /* 3. Prepend all generated code */
151+ if ( edited ) {
152+ // Prepend consts first, then imports, to respect declaration order.
153+ ms . prepend ( constsToAdd ) ;
154+ ms . prepend ( importsToAdd ) ;
155+ return { code : ms . toString ( ) , map : ms . generateMap ( { hires : true } ) } ;
156+ }
171157
172- return {
173- code : ms . toString ( ) ,
174- map : ms . generateMap ( { hires : true } ) ,
175- } ;
158+ return null ;
176159 } ,
177160
178161 resolveId ( id ) {
179- return id . startsWith ( VIRT_PREFIX ) ? RSLV_PREFIX + id . slice ( VIRT_PREFIX . length ) : undefined ;
162+ return id . startsWith ( VIRT ) ? RSLV + id . slice ( VIRT . length ) : undefined ;
180163 } ,
181164
182165 load ( id ) {
183- if ( ! id . startsWith ( RSLV_PREFIX ) ) return ;
166+ if ( ! id . startsWith ( RSLV ) ) return ;
184167
185- const markup = cache . get ( VIRT_PREFIX + id . slice ( RSLV_PREFIX . length ) ) ! ;
168+ const markup = cache . get ( VIRT + id . slice ( RSLV . length ) ) ! ;
186169 return compile ( markup , {
187170 generate : "client" ,
188171 css : "injected" ,
0 commit comments