11import * as path from 'node:path' ;
2+ import * as realFs from 'node:fs' ;
3+ import { tmpdir } from 'node:os' ;
24import { describe , it , expect , vi } from 'vitest' ;
35import { normalizePath , preprocessCSS } from 'vite' ;
46
@@ -91,7 +93,7 @@ describe('JIT resolveId', () => {
9193 expect ( result ) . not . toContain ( '?inline' ) ;
9294 } ) ;
9395
94- it ( 'should resolve template files with ?analog- raw suffix ' , ( ) => {
96+ it ( 'should resolve template files to virtual raw ids ' , ( ) => {
9597 const plugins = angular ( { jit : true } ) ;
9698 const mainPlugin = plugins . find (
9799 ( p ) => p . name === '@analogjs/vite-plugin-angular' ,
@@ -106,9 +108,9 @@ describe('JIT resolveId', () => {
106108 '/project/src/app/my-component.ts' ,
107109 ) ;
108110
109- expect ( result ) . toBeDefined ( ) ;
110- expect ( result ) . toContain ( '?analog-raw' ) ;
111- expect ( result ) . not . toContain ( '??analog-raw ' ) ;
111+ expect ( result ) . toContain ( 'virtual:@analogjs/vite-plugin-angular:raw:' ) ;
112+ expect ( result ) . not . toContain ( '?analog-raw' ) ;
113+ expect ( result ) . not . toContain ( '.html ' ) ;
112114 } ) ;
113115
114116 it ( 'should resolve bare virtual style ids to rollup virtual modules' , ( ) => {
@@ -125,7 +127,20 @@ describe('JIT resolveId', () => {
125127 expect ( resolveId ( virtualId ) ) . toBe ( `\0${ virtualId } ` ) ;
126128 } ) ;
127129
128- it ( 'should intercept .html?raw imports and remap to ?analog-raw' , ( ) => {
130+ it ( 'should resolve bare virtual raw ids to rollup virtual modules' , ( ) => {
131+ const plugins = angular ( { jit : true } ) ;
132+ const mainPlugin = plugins . find (
133+ ( p ) => p . name === '@analogjs/vite-plugin-angular' ,
134+ ) ;
135+ expect ( mainPlugin ) . toBeDefined ( ) ;
136+
137+ const resolveId = ( mainPlugin as any ) . resolveId ;
138+ const virtualId = 'virtual:@analogjs/vite-plugin-angular:raw:test-raw-id' ;
139+
140+ expect ( resolveId ( virtualId ) ) . toBe ( `\0${ virtualId } ` ) ;
141+ } ) ;
142+
143+ it ( 'should intercept .html?raw imports and remap to virtual raw ids' , ( ) => {
129144 const plugins = angular ( { jit : true } ) ;
130145 const mainPlugin = plugins . find (
131146 ( p ) => p . name === '@analogjs/vite-plugin-angular' ,
@@ -138,14 +153,16 @@ describe('JIT resolveId', () => {
138153 './my-component.html?raw' ,
139154 '/project/src/app/my-component.ts' ,
140155 ) ;
141- expect ( result ) . toContain ( '/project/src/app/my-component.html?analog-raw' ) ;
156+ expect ( result ) . toContain ( 'virtual:@analogjs/vite-plugin-angular:raw:' ) ;
157+ expect ( result ) . not . toContain ( '.html' ) ;
142158
143159 // Absolute path
144160 const result2 = resolveId (
145161 '/project/src/app/my-component.html?raw' ,
146162 '/project/src/app/other.ts' ,
147163 ) ;
148- expect ( result2 ) . toBe ( '/project/src/app/my-component.html?analog-raw' ) ;
164+ expect ( result2 ) . toContain ( 'virtual:@analogjs/vite-plugin-angular:raw:' ) ;
165+ expect ( result2 ) . not . toContain ( '.html' ) ;
149166 } ) ;
150167
151168 it ( 'should intercept .html?raw imports even without jit mode' , ( ) => {
@@ -160,34 +177,50 @@ describe('JIT resolveId', () => {
160177 './my-component.html?raw' ,
161178 '/project/src/app/my-component.ts' ,
162179 ) ;
163- expect ( result ) . toContain ( '?analog-raw' ) ;
180+ expect ( result ) . toContain ( 'virtual:@analogjs/vite-plugin-angular:raw:' ) ;
181+ } ) ;
182+
183+ it ( 'should emit virtual raw ids that do not look like asset or html resources' , ( ) => {
184+ const assetRE = / \. ( s v g | p n g | j p e ? g | g i f | w e b p | h t m l ) ( $ | \? ) / ;
185+ const plugins = angular ( { jit : true } ) ;
186+ const mainPlugin = plugins . find (
187+ ( p ) => p . name === '@analogjs/vite-plugin-angular' ,
188+ ) ;
189+
190+ const resolveId = ( mainPlugin as any ) . resolveId ;
191+ const virtualId = resolveId (
192+ 'angular:jit:template:file;./my-component.svg' ,
193+ '/project/src/app/my-component.ts' ,
194+ ) ;
195+
196+ expect ( assetRE . test ( virtualId ) ) . toBe ( false ) ;
164197 } ) ;
165198
166- it ( 'should intercept style ?inline imports and remap to ?analog-inline ' , ( ) => {
199+ it ( 'should intercept style ?inline imports and remap to virtual style ids ' , ( ) => {
167200 const plugins = angular ( { jit : true } ) ;
168201 const mainPlugin = plugins . find (
169202 ( p ) => p . name === '@analogjs/vite-plugin-angular' ,
170203 ) ;
171204
172205 const resolveId = ( mainPlugin as any ) . resolveId ;
173206 const importer = '/project/src/app/my-component.ts' ;
174- const expectedScssPath = `${ normalizePath (
175- path . resolve ( path . dirname ( importer ) , './my-component.scss' ) ,
176- ) } ?analog-inline`;
177- const expectedCssPath = `${ normalizePath (
178- '/project/src/app/my-component.css' ,
179- ) } ?analog-inline`;
180207
181208 // Relative .scss?inline
182209 const result = resolveId ( './my-component.scss?inline' , importer ) ;
183- expect ( result ) . toBe ( expectedScssPath ) ;
210+ expect ( result ) . toContain (
211+ 'virtual:@analogjs/vite-plugin-angular:inline-style:' ,
212+ ) ;
213+ expect ( result ) . not . toContain ( '.scss' ) ;
184214
185215 // Absolute .css?inline
186216 const result2 = resolveId (
187217 '/project/src/app/my-component.css?inline' ,
188218 '/project/src/app/other.ts' ,
189219 ) ;
190- expect ( result2 ) . toBe ( expectedCssPath ) ;
220+ expect ( result2 ) . toContain (
221+ 'virtual:@analogjs/vite-plugin-angular:inline-style:' ,
222+ ) ;
223+ expect ( result2 ) . not . toContain ( '.css' ) ;
191224 } ) ;
192225
193226 it ( 'should intercept style ?inline imports even without jit mode' , ( ) => {
@@ -202,7 +235,9 @@ describe('JIT resolveId', () => {
202235 './my-component.scss?inline' ,
203236 '/project/src/app/my-component.ts' ,
204237 ) ;
205- expect ( result ) . toContain ( '?analog-inline' ) ;
238+ expect ( result ) . toContain (
239+ 'virtual:@analogjs/vite-plugin-angular:inline-style:' ,
240+ ) ;
206241 } ) ;
207242
208243 it ( 'should not match Vite inline security regex /[?&]inline\\b/' , ( ) => {
@@ -245,8 +280,7 @@ describe('load ?inline style imports', () => {
245280 // module-runner. Direct ?inline imports therefore bypass the resolveId
246281 // rewrites in tests, and the load hook must still accept the original
247282 // query directly. (See issue #2263.)
248- const realFs = require ( 'node:fs' ) ;
249- const tmpDir = require ( 'node:os' ) . tmpdir ( ) ;
283+ const tmpDir = tmpdir ( ) ;
250284
251285 function getLoadHook ( ) {
252286 const plugins = angular ( ) ;
@@ -302,25 +336,95 @@ describe('load ?inline style imports', () => {
302336 }
303337 } ) ;
304338
305- it ( 'still handles the rewritten ?analog-inline query' , async ( ) => {
306- const cssPath = path . join ( tmpDir , `analog-rewrite-${ Date . now ( ) } .css` ) ;
307- realFs . writeFileSync ( cssPath , '.bar { color: blue; }' , 'utf-8' ) ;
339+ it ( 'ignores non-style ?inline imports' , async ( ) => {
340+ const load = getLoadHook ( ) ;
341+ const result = await load ( '/project/src/data.json?inline' ) ;
342+ expect ( result ) . toBeUndefined ( ) ;
343+ } ) ;
344+ } ) ;
345+
346+ describe ( 'load virtual raw template imports' , ( ) => {
347+ // Templates (.html, .svg, …) are routed through a virtual module id so
348+ // Vite's built-in asset/CSS plugins never see a file extension and can't
349+ // re-tag the id with ?import (which would otherwise return a data URI for
350+ // .svg) or ?inline. This covers both the main dev path and the Vitest
351+ // fetchModule path, since resolveId is bypassed for the module-runner.
352+ const tmpDir = tmpdir ( ) ;
353+
354+ function getMainPlugin ( ) {
355+ const plugins = angular ( { jit : true } ) ;
356+ return plugins . find ( ( p ) => p . name === '@analogjs/vite-plugin-angular' ) ;
357+ }
358+
359+ function loadHook ( ) {
360+ return ( getMainPlugin ( ) as any ) . load . bind ( { addWatchFile : vi . fn ( ) } ) ;
361+ }
362+
363+ it ( 'loads an .svg templateUrl via its virtual raw id' , async ( ) => {
364+ const svgPath = normalizePath (
365+ path . join ( tmpDir , `analog-raw-${ Date . now ( ) } .svg` ) ,
366+ ) ;
367+ realFs . writeFileSync (
368+ svgPath ,
369+ '<svg xmlns="http://www.w3.org/2000/svg"><g></g></svg>' ,
370+ 'utf-8' ,
371+ ) ;
372+
373+ try {
374+ const mainPlugin = getMainPlugin ( ) ;
375+ const resolveId = ( mainPlugin as any ) . resolveId ;
376+ const virtualId = resolveId (
377+ `angular:jit:template:file;./${ path . basename ( svgPath ) } ` ,
378+ path . join ( tmpDir , 'host.component.ts' ) ,
379+ ) ;
380+
381+ expect ( virtualId ) . toContain ( 'virtual:@analogjs/vite-plugin-angular:raw:' ) ;
382+ expect ( virtualId ) . not . toContain ( '.svg' ) ;
383+
384+ const addWatchFile = vi . fn ( ) ;
385+ const load = ( mainPlugin as any ) . load . bind ( { addWatchFile } ) ;
386+ const result = await load ( `\0${ virtualId } ` ) ;
387+
388+ expect ( result ) . toBeDefined ( ) ;
389+ expect ( result ) . toContain ( 'export default' ) ;
390+ expect ( result ) . toContain ( '<svg' ) ;
391+ expect ( result ) . toContain ( '</svg>' ) ;
392+ expect ( addWatchFile ) . toHaveBeenCalledWith ( svgPath ) ;
393+ } finally {
394+ realFs . unlinkSync ( svgPath ) ;
395+ }
396+ } ) ;
397+
398+ it ( 'handles virtual raw ids without the rollup \\0 prefix (Vitest path)' , async ( ) => {
399+ // Vitest's fetchModule path calls moduleGraph.ensureEntryFromUrl before
400+ // transformRequest, so resolveId is a no-op for the module-runner and
401+ // the id reaches load as a bare virtual id.
402+ const htmlPath = normalizePath (
403+ path . join ( tmpDir , `analog-raw-${ Date . now ( ) } .html` ) ,
404+ ) ;
405+ realFs . writeFileSync ( htmlPath , '<h1>hello</h1>' , 'utf-8' ) ;
308406
309407 try {
310- const load = getLoadHook ( ) ;
311- const result = await load ( `${ cssPath } ?analog-inline` ) ;
408+ const mainPlugin = getMainPlugin ( ) ;
409+ const resolveId = ( mainPlugin as any ) . resolveId ;
410+ const virtualId = resolveId (
411+ `angular:jit:template:file;./${ path . basename ( htmlPath ) } ` ,
412+ path . join ( tmpDir , 'host.component.ts' ) ,
413+ ) ;
414+ const load = ( mainPlugin as any ) . load . bind ( { addWatchFile : vi . fn ( ) } ) ;
415+
416+ const result = await load ( virtualId ) ;
312417 expect ( result ) . toBeDefined ( ) ;
313418 expect ( result ) . toContain ( 'export default' ) ;
314- expect ( result ) . toContain ( 'color: blue ' ) ;
419+ expect ( result ) . toContain ( '<h1>hello</h1> ' ) ;
315420 } finally {
316- realFs . unlinkSync ( cssPath ) ;
421+ realFs . unlinkSync ( htmlPath ) ;
317422 }
318423 } ) ;
319424
320- it ( 'ignores non-style ?inline imports' , async ( ) => {
321- const load = getLoadHook ( ) ;
322- const result = await load ( '/project/src/data.json?inline' ) ;
323- expect ( result ) . toBeUndefined ( ) ;
425+ it ( 'ignores unrelated ids' , async ( ) => {
426+ const load = loadHook ( ) ;
427+ expect ( await load ( '/project/src/data.json?raw' ) ) . toBeUndefined ( ) ;
324428 } ) ;
325429} ) ;
326430
0 commit comments