@@ -6,40 +6,58 @@ import { parseAst } from 'rollup/parseAst';
66
77import { createBackendModuleGraphCollector } from './backend-module-graph-collector' ;
88
9+ type FakeModuleInfo = { id : string ; code : string | null } ;
10+
11+ /**
12+ * Calls the `moduleParsed` hook with a plugin context exposing `parse`, which is
13+ * where it takes its parser from. `rollup/parseAst` is what Rollup's real
14+ * context supplies. The hook reads only the few `ModuleInfo` fields these fakes
15+ * model, so building a complete one would be noise.
16+ */
17+ const getModuleParsedHook = ( collector : ReturnType < typeof createBackendModuleGraphCollector > ) => {
18+ const hook = collector . plugin . moduleParsed ;
19+ if ( typeof hook !== 'function' ) {
20+ throw new Error ( 'Expected "moduleParsed" to be a function hook.' ) ;
21+ }
22+
23+ const parse = jest . fn ( parseAst ) ;
24+ const callHook = ( moduleInfo : object ) => Reflect . apply ( hook , { parse } , [ moduleInfo ] ) ;
25+
26+ return { callHook, parse } ;
27+ } ;
28+
29+ const getEmit = ( collector : ReturnType < typeof createBackendModuleGraphCollector > ) => {
30+ const { callHook, parse } = getModuleParsedHook ( collector ) ;
31+
32+ const emit = ( moduleInfo : FakeModuleInfo , importedIds : string [ ] = [ ] ) => {
33+ const importedIdResolutions = importedIds . map ( ( id ) => ( { id } ) ) ;
34+ callHook ( { ...moduleInfo , importedIds, importedIdResolutions } ) ;
35+ } ;
36+
37+ return { emit, parse } ;
38+ } ;
39+
940describe ( 'Backend Functions - backend module graph collector' , ( ) => {
10- test ( 'Should collect parsed local module records from Rollup moduleParsed hooks' , ( ) => {
41+ test ( 'Should collect parsed local module records from moduleParsed hooks' , ( ) => {
1142 const collector = createBackendModuleGraphCollector ( '/project' ) ;
12- const moduleParsed = collector . plugin . moduleParsed as ( moduleInfo : unknown ) => void ;
13-
14- moduleParsed ( {
15- id : '/project/src/backend/actions.backend.js?import' ,
16- ast : parseAst ( `
17- import { getEcho } from './helpers/http.js';
18- export function run() {
19- return getEcho();
20- }
21- ` ) ,
22- importedIds : [ '/project/src/backend/helpers/http.js?import' ] ,
23- importedIdResolutions : [ { id : '/project/src/backend/helpers/http.js?import' } ] ,
24- } ) ;
25- moduleParsed ( {
26- id : '/project/node_modules/package/index.js' ,
27- ast : parseAst ( 'export const value = true;' ) ,
28- importedIds : [ ] ,
29- importedIdResolutions : [ ] ,
30- } ) ;
31- moduleParsed ( {
32- id : '\0virtual-helper.js' ,
33- ast : parseAst ( 'export const value = true;' ) ,
34- importedIds : [ ] ,
35- importedIdResolutions : [ ] ,
36- } ) ;
37- moduleParsed ( {
38- id : 'virtual:dd-backend-dev:example.js' ,
39- ast : parseAst ( 'export const value = true;' ) ,
40- importedIds : [ ] ,
41- importedIdResolutions : [ ] ,
42- } ) ;
43+ const { emit, parse } = getEmit ( collector ) ;
44+
45+ emit (
46+ {
47+ id : '/project/src/backend/actions.backend.js?import' ,
48+ code : `
49+ import { getEcho } from './helpers/http.js';
50+ export function run() {
51+ return getEcho();
52+ }
53+ ` ,
54+ } ,
55+ [ '/project/src/backend/helpers/http.js?import' ] ,
56+ ) ;
57+ emit ( { id : '/project/node_modules/package/index.js' , code : 'export const value = true;' } ) ;
58+ emit ( { id : '\0virtual-helper.js' , code : 'export const value = true;' } ) ;
59+ emit ( { id : 'virtual:dd-backend-dev:example.js' , code : 'export const value = true;' } ) ;
60+ emit ( { id : '/project/src/backend/external.js' , code : null } ) ;
4361
4462 expect ( [ ...collector . getModuleRecords ( ) . keys ( ) ] ) . toEqual ( [
4563 '/project/src/backend/actions.backend.js' ,
@@ -54,5 +72,38 @@ describe('Backend Functions - backend module graph collector', () => {
5472 } ,
5573 ] ,
5674 } ) ;
75+ // Filtering happens before the parse, so the skipped modules above never
76+ // reach the parser. Without that ordering every `node_modules` module in
77+ // the backend graph would be parsed just to be discarded.
78+ expect ( parse ) . toHaveBeenCalledTimes ( 1 ) ;
79+ } ) ;
80+
81+ test ( 'Should collect records under a bundler that does not support ModuleInfo#ast' , ( ) => {
82+ const collector = createBackendModuleGraphCollector ( '/project' ) ;
83+ const { callHook } = getModuleParsedHook ( collector ) ;
84+
85+ // Rolldown, Vite 8's default bundler, keeps `ast` on its Rollup-compat
86+ // object but stubs the getter to throw. Reading the property at all is
87+ // the failure, so it has to throw rather than be absent — which is also
88+ // why this is assembled in place instead of going through `getEmit`,
89+ // whose spread would trigger the getter during setup.
90+ const moduleInfo = {
91+ id : '/project/src/backend/actions.backend.ts' ,
92+ code : 'export const id = "conn-1";' ,
93+ importedIds : [ ] ,
94+ importedIdResolutions : [ ] ,
95+ } ;
96+ Object . defineProperty ( moduleInfo , 'ast' , {
97+ get ( ) {
98+ throw new Error ( 'UNSUPPORTED: ModuleInfo#ast' ) ;
99+ } ,
100+ enumerable : true ,
101+ } ) ;
102+
103+ callHook ( moduleInfo ) ;
104+
105+ expect ( [ ...collector . getModuleRecords ( ) . keys ( ) ] ) . toEqual ( [
106+ '/project/src/backend/actions.backend.ts' ,
107+ ] ) ;
57108 } ) ;
58109} ) ;
0 commit comments