@@ -40,6 +40,7 @@ import { parse } from '../parse'
40
40
import { createCache } from '../cache'
41
41
import type TS from 'typescript'
42
42
import { extname , dirname } from 'path'
43
+ import { minimatch as isMatch } from 'minimatch'
43
44
44
45
/**
45
46
* TypeResolveContext is compatible with ScriptCompileContext
@@ -77,15 +78,19 @@ interface WithScope {
77
78
type ScopeTypeNode = Node &
78
79
WithScope & { _ns ?: TSModuleDeclaration & WithScope }
79
80
80
- export interface TypeScope {
81
- filename : string
82
- source : string
83
- offset : number
84
- imports : Record < string , Import >
85
- types : Record < string , ScopeTypeNode >
86
- exportedTypes : Record < string , ScopeTypeNode >
87
- declares : Record < string , ScopeTypeNode >
88
- exportedDeclares : Record < string , ScopeTypeNode >
81
+ export class TypeScope {
82
+ constructor (
83
+ public filename : string ,
84
+ public source : string ,
85
+ public offset : number = 0 ,
86
+ public imports : Record < string , Import > = Object . create ( null ) ,
87
+ public types : Record < string , ScopeTypeNode > = Object . create ( null ) ,
88
+ public declares : Record < string , ScopeTypeNode > = Object . create ( null )
89
+ ) { }
90
+
91
+ resolvedImportSources : Record < string , string > = Object . create ( null )
92
+ exportedTypes : Record < string , ScopeTypeNode > = Object . create ( null )
93
+ exportedDeclares : Record < string , ScopeTypeNode > = Object . create ( null )
89
94
}
90
95
91
96
export interface MaybeWithScope {
@@ -716,33 +721,38 @@ function importSourceToScope(
716
721
scope
717
722
)
718
723
}
719
- let resolved
720
- if ( source . startsWith ( '.' ) ) {
721
- // relative import - fast path
722
- const filename = joinPaths ( scope . filename , '..' , source )
723
- resolved = resolveExt ( filename , fs )
724
- } else {
725
- // module or aliased import - use full TS resolution, only supported in Node
726
- if ( ! __NODE_JS__ ) {
727
- ctx . error (
728
- `Type import from non-relative sources is not supported in the browser build.` ,
729
- node ,
730
- scope
731
- )
724
+
725
+ let resolved : string | undefined = scope . resolvedImportSources [ source ]
726
+ if ( ! resolved ) {
727
+ if ( source . startsWith ( '.' ) ) {
728
+ // relative import - fast path
729
+ const filename = joinPaths ( scope . filename , '..' , source )
730
+ resolved = resolveExt ( filename , fs )
731
+ } else {
732
+ // module or aliased import - use full TS resolution, only supported in Node
733
+ if ( ! __NODE_JS__ ) {
734
+ ctx . error (
735
+ `Type import from non-relative sources is not supported in the browser build.` ,
736
+ node ,
737
+ scope
738
+ )
739
+ }
740
+ if ( ! ts ) {
741
+ ctx . error (
742
+ `Failed to resolve import source ${ JSON . stringify ( source ) } . ` +
743
+ `typescript is required as a peer dep for vue in order ` +
744
+ `to support resolving types from module imports.` ,
745
+ node ,
746
+ scope
747
+ )
748
+ }
749
+ resolved = resolveWithTS ( scope . filename , source , fs )
732
750
}
733
- if ( ! ts ) {
734
- ctx . error (
735
- `Failed to resolve import source ${ JSON . stringify ( source ) } . ` +
736
- `typescript is required as a peer dep for vue in order ` +
737
- `to support resolving types from module imports.` ,
738
- node ,
739
- scope
740
- )
751
+ if ( resolved ) {
752
+ resolved = scope . resolvedImportSources [ source ] = normalizePath ( resolved )
741
753
}
742
- resolved = resolveWithTS ( scope . filename , source , fs )
743
754
}
744
755
if ( resolved ) {
745
- resolved = normalizePath ( resolved )
746
756
// (hmr) register dependency file on ctx
747
757
; ( ctx . deps || ( ctx . deps = new Set ( ) ) ) . add ( resolved )
748
758
return fileToScope ( ctx , resolved )
@@ -768,10 +778,13 @@ function resolveExt(filename: string, fs: FS) {
768
778
)
769
779
}
770
780
771
- const tsConfigCache = createCache < {
772
- options : TS . CompilerOptions
773
- cache : TS . ModuleResolutionCache
774
- } > ( )
781
+ interface CachedConfig {
782
+ config : TS . ParsedCommandLine
783
+ cache ?: TS . ModuleResolutionCache
784
+ }
785
+
786
+ const tsConfigCache = createCache < CachedConfig [ ] > ( )
787
+ const tsConfigRefMap = new Map < string , string > ( )
775
788
776
789
function resolveWithTS (
777
790
containingFile : string ,
@@ -783,51 +796,102 @@ function resolveWithTS(
783
796
// 1. resolve tsconfig.json
784
797
const configPath = ts . findConfigFile ( containingFile , fs . fileExists )
785
798
// 2. load tsconfig.json
786
- let options : TS . CompilerOptions
787
- let cache : TS . ModuleResolutionCache | undefined
799
+ let tsCompilerOptions : TS . CompilerOptions
800
+ let tsResolveCache : TS . ModuleResolutionCache | undefined
788
801
if ( configPath ) {
802
+ let configs : CachedConfig [ ]
789
803
const normalizedConfigPath = normalizePath ( configPath )
790
804
const cached = tsConfigCache . get ( normalizedConfigPath )
791
805
if ( ! cached ) {
792
- // The only case where `fs` is NOT `ts.sys` is during tests.
793
- // parse config host requires an extra `readDirectory` method
794
- // during tests, which is stubbed.
795
- const parseConfigHost = __TEST__
796
- ? {
797
- ...fs ,
798
- useCaseSensitiveFileNames : true ,
799
- readDirectory : ( ) => [ ]
806
+ configs = loadTSConfig ( configPath , fs ) . map ( config => ( { config } ) )
807
+ tsConfigCache . set ( normalizedConfigPath , configs )
808
+ } else {
809
+ configs = cached
810
+ }
811
+ let matchedConfig : CachedConfig | undefined
812
+ if ( configs . length === 1 ) {
813
+ matchedConfig = configs [ 0 ]
814
+ } else {
815
+ // resolve which config matches the current file
816
+ for ( const c of configs ) {
817
+ const base = normalizePath (
818
+ ( c . config . options . pathsBasePath as string ) ||
819
+ dirname ( c . config . options . configFilePath as string )
820
+ )
821
+ const included : string [ ] = c . config . raw ?. include
822
+ const excluded : string [ ] = c . config . raw ?. exclude
823
+ if (
824
+ ( ! included && ( ! base || containingFile . startsWith ( base ) ) ) ||
825
+ included . some ( p => isMatch ( containingFile , joinPaths ( base , p ) ) )
826
+ ) {
827
+ if (
828
+ excluded &&
829
+ excluded . some ( p => isMatch ( containingFile , joinPaths ( base , p ) ) )
830
+ ) {
831
+ continue
800
832
}
801
- : ts . sys
802
- const parsed = ts . parseJsonConfigFileContent (
803
- ts . readConfigFile ( configPath , fs . readFile ) . config ,
804
- parseConfigHost ,
805
- dirname ( configPath ) ,
806
- undefined ,
807
- configPath
808
- )
809
- options = parsed . options
810
- cache = ts . createModuleResolutionCache (
833
+ matchedConfig = c
834
+ break
835
+ }
836
+ }
837
+ if ( ! matchedConfig ) {
838
+ matchedConfig = configs [ configs . length - 1 ]
839
+ }
840
+ }
841
+ tsCompilerOptions = matchedConfig . config . options
842
+ tsResolveCache =
843
+ matchedConfig . cache ||
844
+ ( matchedConfig . cache = ts . createModuleResolutionCache (
811
845
process . cwd ( ) ,
812
846
createGetCanonicalFileName ( ts . sys . useCaseSensitiveFileNames ) ,
813
- options
814
- )
815
- tsConfigCache . set ( normalizedConfigPath , { options, cache } )
816
- } else {
817
- ; ( { options, cache } = cached )
818
- }
847
+ tsCompilerOptions
848
+ ) )
819
849
} else {
820
- options = { }
850
+ tsCompilerOptions = { }
821
851
}
822
852
823
853
// 3. resolve
824
- const res = ts . resolveModuleName ( source , containingFile , options , fs , cache )
854
+ const res = ts . resolveModuleName (
855
+ source ,
856
+ containingFile ,
857
+ tsCompilerOptions ,
858
+ fs ,
859
+ tsResolveCache
860
+ )
825
861
826
862
if ( res . resolvedModule ) {
827
863
return res . resolvedModule . resolvedFileName
828
864
}
829
865
}
830
866
867
+ function loadTSConfig ( configPath : string , fs : FS ) : TS . ParsedCommandLine [ ] {
868
+ // The only case where `fs` is NOT `ts.sys` is during tests.
869
+ // parse config host requires an extra `readDirectory` method
870
+ // during tests, which is stubbed.
871
+ const parseConfigHost = __TEST__
872
+ ? {
873
+ ...fs ,
874
+ useCaseSensitiveFileNames : true ,
875
+ readDirectory : ( ) => [ ]
876
+ }
877
+ : ts . sys
878
+ const config = ts . parseJsonConfigFileContent (
879
+ ts . readConfigFile ( configPath , fs . readFile ) . config ,
880
+ parseConfigHost ,
881
+ dirname ( configPath ) ,
882
+ undefined ,
883
+ configPath
884
+ )
885
+ const res = [ config ]
886
+ if ( config . projectReferences ) {
887
+ for ( const ref of config . projectReferences ) {
888
+ tsConfigRefMap . set ( ref . path , configPath )
889
+ res . unshift ( ...loadTSConfig ( ref . path , fs ) )
890
+ }
891
+ }
892
+ return res
893
+ }
894
+
831
895
const fileToScopeCache = createCache < TypeScope > ( )
832
896
833
897
/**
@@ -837,6 +901,8 @@ export function invalidateTypeCache(filename: string) {
837
901
filename = normalizePath ( filename )
838
902
fileToScopeCache . delete ( filename )
839
903
tsConfigCache . delete ( filename )
904
+ const affectedConfig = tsConfigRefMap . get ( filename )
905
+ if ( affectedConfig ) tsConfigCache . delete ( affectedConfig )
840
906
}
841
907
842
908
export function fileToScope (
@@ -852,16 +918,7 @@ export function fileToScope(
852
918
const fs = ctx . options . fs || ts ?. sys
853
919
const source = fs . readFile ( filename ) || ''
854
920
const body = parseFile ( filename , source , ctx . options . babelParserPlugins )
855
- const scope : TypeScope = {
856
- filename,
857
- source,
858
- offset : 0 ,
859
- imports : recordImports ( body ) ,
860
- types : Object . create ( null ) ,
861
- exportedTypes : Object . create ( null ) ,
862
- declares : Object . create ( null ) ,
863
- exportedDeclares : Object . create ( null )
864
- }
921
+ const scope = new TypeScope ( filename , source , 0 , recordImports ( body ) )
865
922
recordTypes ( ctx , body , scope , asGlobal )
866
923
fileToScopeCache . set ( filename , scope )
867
924
return scope
@@ -923,19 +980,12 @@ function ctxToScope(ctx: TypeResolveContext): TypeScope {
923
980
? [ ...ctx . scriptAst . body , ...ctx . scriptSetupAst ! . body ]
924
981
: ctx . scriptSetupAst ! . body
925
982
926
- const scope : TypeScope = {
927
- filename : ctx . filename ,
928
- source : ctx . source ,
929
- offset : 'startOffset' in ctx ? ctx . startOffset ! : 0 ,
930
- imports :
931
- 'userImports' in ctx
932
- ? Object . create ( ctx . userImports )
933
- : recordImports ( body ) ,
934
- types : Object . create ( null ) ,
935
- exportedTypes : Object . create ( null ) ,
936
- declares : Object . create ( null ) ,
937
- exportedDeclares : Object . create ( null )
938
- }
983
+ const scope = new TypeScope (
984
+ ctx . filename ,
985
+ ctx . source ,
986
+ 'startOffset' in ctx ? ctx . startOffset ! : 0 ,
987
+ 'userImports' in ctx ? Object . create ( ctx . userImports ) : recordImports ( body )
988
+ )
939
989
940
990
recordTypes ( ctx , body , scope )
941
991
@@ -950,14 +1000,15 @@ function moduleDeclToScope(
950
1000
if ( node . _resolvedChildScope ) {
951
1001
return node . _resolvedChildScope
952
1002
}
953
- const scope : TypeScope = {
954
- ...parentScope ,
955
- imports : Object . create ( parentScope . imports ) ,
956
- types : Object . create ( parentScope . types ) ,
957
- declares : Object . create ( parentScope . declares ) ,
958
- exportedTypes : Object . create ( null ) ,
959
- exportedDeclares : Object . create ( null )
960
- }
1003
+
1004
+ const scope = new TypeScope (
1005
+ parentScope . filename ,
1006
+ parentScope . source ,
1007
+ parentScope . offset ,
1008
+ Object . create ( parentScope . imports ) ,
1009
+ Object . create ( parentScope . types ) ,
1010
+ Object . create ( parentScope . declares )
1011
+ )
961
1012
962
1013
if ( node . body . type === 'TSModuleDeclaration' ) {
963
1014
const decl = node . body as TSModuleDeclaration & WithScope
0 commit comments