@@ -170,6 +170,21 @@ fn resolver_with_many_extensions() -> rspack_resolver::Resolver {
170170 } )
171171}
172172
173+ fn tsconfig_resolver ( ) -> rspack_resolver:: Resolver {
174+ use rspack_resolver:: { ResolveOptions , Resolver , TsconfigOptions , TsconfigReferences } ;
175+ let config_dir = env:: current_dir ( )
176+ . unwrap ( )
177+ . join ( "fixtures/tsconfig/cases/project_references" ) ;
178+ Resolver :: new ( ResolveOptions {
179+ extensions : vec ! [ ".ts" . into( ) , ".js" . into( ) ] ,
180+ tsconfig : Some ( TsconfigOptions {
181+ config_file : config_dir. join ( "app" ) ,
182+ references : TsconfigReferences :: Auto ,
183+ } ) ,
184+ ..ResolveOptions :: default ( )
185+ } )
186+ }
187+
173188fn create_async_resolve_task (
174189 rspack_resolver : Arc < rspack_resolver:: Resolver > ,
175190 path : PathBuf ,
@@ -411,6 +426,62 @@ fn bench_resolver(c: &mut Criterion) {
411426 ) ;
412427 } ,
413428 ) ;
429+
430+ // tsconfig `paths` + project-references resolution. Each resolve hits the
431+ // warm `tsconfigs` cache once (a single key hash of the fixed config path),
432+ // so looping the case set keeps that lookup hot — this is the scenario that
433+ // exercises the `Utf8PathBuf` vs `PathBuf` map-key question.
434+ let tsconfig_dir = env:: current_dir ( )
435+ . unwrap ( )
436+ . join ( "fixtures/tsconfig/cases/project_references" ) ;
437+ let tsconfig_data = vec ! [
438+ ( tsconfig_dir. join( "app" ) , "@/index.ts" ) ,
439+ ( tsconfig_dir. join( "app" ) , "@/../index.ts" ) ,
440+ ( tsconfig_dir. join( "project_a" ) , "@/index.ts" ) ,
441+ ( tsconfig_dir. join( "project_b/src" ) , "@/index.ts" ) ,
442+ ( tsconfig_dir. join( "project_a" ) , "./index.ts" ) ,
443+ ( tsconfig_dir. join( "project_c" ) , "./index.ts" ) ,
444+ ] ;
445+
446+ // check validity
447+ runtime:: Builder :: new_current_thread ( )
448+ . enable_all ( )
449+ . build ( )
450+ . unwrap ( )
451+ . block_on ( async {
452+ let resolver = tsconfig_resolver ( ) ;
453+ for ( path, request) in & tsconfig_data {
454+ assert ! (
455+ resolver. resolve( path, request) . await . is_ok( ) ,
456+ "tsconfig resolve failed {path:?} {request}, fixtures/tsconfig/cases/project_references"
457+ ) ;
458+ }
459+ } ) ;
460+
461+ group. bench_with_input (
462+ BenchmarkId :: from_parameter ( "tsconfig resolve" ) ,
463+ & tsconfig_data,
464+ |b, data| {
465+ let runner = runtime:: Builder :: new_current_thread ( )
466+ . enable_all ( )
467+ . build ( )
468+ . expect ( "failed to create tokio runtime" ) ;
469+ let rspack_resolver = tsconfig_resolver ( ) ;
470+
471+ b. to_async ( runner) . iter_with_setup (
472+ || {
473+ rspack_resolver. clear_cache ( ) ;
474+ } ,
475+ |_| async {
476+ for _ in 0 ..100 {
477+ for ( path, request) in data {
478+ _ = rspack_resolver. resolve ( path, request) . await ;
479+ }
480+ }
481+ } ,
482+ ) ;
483+ } ,
484+ ) ;
414485}
415486
416487// Specifier microbenchmarks live in `benches/specifier.rs` (separate
0 commit comments