@@ -9,7 +9,10 @@ import { merge, mergeWith } from '@modern-js/utils/lodash';
9
9
import { ROUTE_MANIFEST } from '@modern-js/utils/universal/constants' ;
10
10
import type { ScriptLoading } from '@rsbuild/core' ;
11
11
12
- const PLUGIN_NAME = 'ModernjsRoutePlugin' ;
12
+ const PLUGIN_NAME = 'ModernjsRouterPlugin' ;
13
+ export const ROUTE_DEPS_COMPILATION_KEY = Symbol . for (
14
+ `${ PLUGIN_NAME } RouteSources` ,
15
+ ) ;
13
16
14
17
export interface RouteAssets {
15
18
[ routeId : string ] : {
@@ -22,6 +25,8 @@ export interface RouteAssets {
22
25
type Compiler = webpack . Compiler | Rspack . Compiler ;
23
26
type Compilation = webpack . Compilation | Rspack . Compilation ;
24
27
type Chunks = webpack . StatsChunk [ ] ;
28
+ type StatsChunk = webpack . StatsChunk ;
29
+ type StatsModule = webpack . StatsModule ;
25
30
26
31
type Options = {
27
32
HtmlBundlerPlugin : typeof HtmlWebpackPlugin ;
@@ -149,13 +154,16 @@ export class RouterPlugin {
149
154
chunkGroups : true ,
150
155
chunks : true ,
151
156
ids : true ,
157
+ modules : true ,
158
+ chunkModules : true ,
152
159
} ) ;
153
160
const {
154
161
publicPath,
155
162
chunks = [ ] ,
156
163
namedChunkGroups,
157
164
} = stats as webpack . StatsCompilation ;
158
165
const routeAssets : RouteAssets = { } ;
166
+ const route2Sources : Record < string , string [ ] > = { } ;
159
167
160
168
if ( ! namedChunkGroups ) {
161
169
return ;
@@ -222,6 +230,124 @@ export class RouterPlugin {
222
230
routeAssets,
223
231
} ;
224
232
233
+ // Build dependencies using compilation chunkGroups and modules for better coverage
234
+ try {
235
+ const getChunkModules = ( chunk : StatsChunk ) : StatsModule [ ] => {
236
+ if ( ! chunk ) return [ ] ;
237
+ if ( typeof chunk . getModules === 'function' ) {
238
+ return chunk . getModules ( ) || [ ] ;
239
+ }
240
+ const cg = ( compilation as any ) . chunkGraph ;
241
+ if ( cg && typeof cg . getChunkModules === 'function' ) {
242
+ return cg . getChunkModules ( chunk ) || [ ] ;
243
+ }
244
+ return [ ] ;
245
+ } ;
246
+
247
+ const addModuleResources = (
248
+ mod : StatsModule ,
249
+ bucket : Set < string > ,
250
+ ) => {
251
+ if ( ! mod ) return ;
252
+ if ( mod . resource ) {
253
+ if ( ! / [ \\ / ] n o d e _ m o d u l e s [ \\ / ] / . test ( mod . resource ) ) {
254
+ bucket . add ( mod . resource ) ;
255
+ }
256
+ }
257
+ if ( Array . isArray ( mod . modules ) ) {
258
+ mod . modules . forEach ( m => addModuleResources ( m , bucket ) ) ;
259
+ }
260
+ } ;
261
+
262
+ const depsByName = new Map < string , Set < string > > ( ) ;
263
+
264
+ const namedGroups = compilation . namedChunkGroups ;
265
+ const groupNames =
266
+ namedGroups && typeof namedGroups . forEach === 'function'
267
+ ? ( ( ) => {
268
+ const names : string [ ] = [ ] ;
269
+ namedGroups . forEach ( ( _ : any , key : string ) =>
270
+ names . push ( key ) ,
271
+ ) ;
272
+ return names ;
273
+ } ) ( )
274
+ : Object . keys ( namedChunkGroups || { } ) ;
275
+
276
+ groupNames . forEach ( ( name : string ) => {
277
+ const resourceSet = new Set < string > ( ) ;
278
+ const group =
279
+ namedGroups && typeof namedGroups . get === 'function'
280
+ ? namedGroups . get ( name )
281
+ : undefined ;
282
+
283
+ if ( group && Array . isArray ( group . chunks ) ) {
284
+ group . chunks . forEach ( ( chunk : any ) => {
285
+ const modules = getChunkModules ( chunk ) ;
286
+ modules . forEach ( m => addModuleResources ( m , resourceSet ) ) ;
287
+ } ) ;
288
+ } else {
289
+ const matched : Chunks = Array . from (
290
+ ( ( compilation as any ) . chunks as unknown as Iterable < any > ) ||
291
+ [ ] ,
292
+ ) . filter ( c => c ?. name === name || c ?. names ?. includes ?.( name ) ) ;
293
+ matched . forEach ( chunk => {
294
+ const modules = getChunkModules ( chunk ) ;
295
+ modules . forEach ( m => addModuleResources ( m , resourceSet ) ) ;
296
+ } ) ;
297
+ }
298
+
299
+ depsByName . set ( name , resourceSet ) ;
300
+ } ) ;
301
+
302
+ // Map to routeIds (keys of routeAssets)
303
+ Object . keys ( routeAssets ) . forEach ( routeId => {
304
+ const set = depsByName . get ( routeId ) ;
305
+ if ( set && set . size > 0 ) {
306
+ route2Sources [ routeId ] = Array . from ( set ) ;
307
+ }
308
+ } ) ;
309
+
310
+ // Merge async-* into sync route names similar to assets merging
311
+ if ( asyncEntryNames . length > 0 ) {
312
+ for ( const asyncEntryName of asyncEntryNames ) {
313
+ const syncEntryName = asyncEntryName . replace ( 'async-' , '' ) ;
314
+ const asyncSet = depsByName . get ( asyncEntryName ) ;
315
+ if ( asyncSet && asyncSet . size > 0 ) {
316
+ const base = new Set < string > (
317
+ route2Sources [ syncEntryName ] || [ ] ,
318
+ ) ;
319
+ asyncSet . forEach ( v => base . add ( v ) ) ;
320
+ route2Sources [ syncEntryName ] = Array . from ( base ) ;
321
+ }
322
+ }
323
+ }
324
+
325
+ ( compilation as any ) [ ROUTE_DEPS_COMPILATION_KEY ] = route2Sources ;
326
+
327
+ // Also emit to dist for external consumption
328
+ const ROUTE_SOURCE_MANIFEST_FILE = 'routes-source-manifest.json' ;
329
+ const routeSourceManifest = { route2Sources : route2Sources } ;
330
+ const existed = compilation . getAsset ( ROUTE_SOURCE_MANIFEST_FILE ) ;
331
+ if ( existed ) {
332
+ compilation . updateAsset (
333
+ ROUTE_SOURCE_MANIFEST_FILE ,
334
+ new RawSource (
335
+ JSON . stringify ( routeSourceManifest , null , 2 ) ,
336
+ ) as any ,
337
+ undefined as any ,
338
+ ) ;
339
+ } else {
340
+ compilation . emitAsset (
341
+ ROUTE_SOURCE_MANIFEST_FILE ,
342
+ new RawSource (
343
+ JSON . stringify ( routeSourceManifest , null , 2 ) ,
344
+ ) as any ,
345
+ ) ;
346
+ }
347
+ } catch {
348
+ // swallow silently; do not block build if compilation shape is unexpected
349
+ }
350
+
225
351
const entryNames = Array . from ( compilation . entrypoints . keys ( ) ) ;
226
352
let entryChunks = [ ] ;
227
353
if ( isRspack ) {
0 commit comments