6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
import * as path from 'path' ;
9
- import * as ts from 'typescript' ;
10
- import {
11
- Callback ,
12
- NormalModuleFactoryRequest ,
13
- } from './webpack' ;
14
-
15
-
16
- export function resolveWithPaths (
17
- request : NormalModuleFactoryRequest ,
18
- callback : Callback < NormalModuleFactoryRequest > ,
19
- compilerOptions : ts . CompilerOptions ,
20
- host : ts . CompilerHost ,
21
- cache ?: ts . ModuleResolutionCache ,
22
- ) {
23
- if ( ! request || ! request . request || ! compilerOptions . paths ) {
24
- callback ( null , request ) ;
25
-
26
- return ;
27
- }
9
+ import { CompilerOptions , MapLike } from 'typescript' ;
10
+ import { NormalModuleFactoryRequest } from './webpack' ;
28
11
29
- // Only work on Javascript/TypeScript issuers.
30
- if ( ! request . contextInfo . issuer || ! request . contextInfo . issuer . match ( / \. [ j t ] s $ / ) ) {
31
- callback ( null , request ) ;
12
+ const getInnerRequest = require ( 'enhanced-resolve/lib/getInnerRequest' ) ;
32
13
33
- return ;
34
- }
14
+ export interface TypeScriptPathsPluginOptions extends Pick < CompilerOptions , 'paths' | 'baseUrl' > {
35
15
36
- const originalRequest = request . request . trim ( ) ;
16
+ }
37
17
38
- // Relative requests are not mapped
39
- if ( originalRequest . startsWith ( '.' ) || originalRequest . startsWith ( '/' ) ) {
40
- callback ( null , request ) ;
18
+ export class TypeScriptPathsPlugin {
19
+ constructor ( private _options : TypeScriptPathsPluginOptions ) { }
41
20
42
- return ;
43
- }
44
-
45
- // Amd requests are not mapped
46
- if ( originalRequest . startsWith ( '!!webpack amd' ) ) {
47
- callback ( null , request ) ;
21
+ // tslint:disable-next-line:no-any
22
+ apply ( resolver : any ) {
23
+ if ( ! this . _options . paths || Object . keys ( this . _options . paths ) . length === 0 ) {
24
+ return ;
25
+ }
48
26
49
- return ;
27
+ const target = resolver . ensureHook ( 'resolve' ) ;
28
+ const resolveAsync = ( request : NormalModuleFactoryRequest , requestContext : { } ) => {
29
+ return new Promise < NormalModuleFactoryRequest | undefined > ( ( resolve , reject ) => {
30
+ resolver . doResolve (
31
+ target ,
32
+ request ,
33
+ '' ,
34
+ requestContext ,
35
+ ( error : Error | null , result : NormalModuleFactoryRequest | undefined ) => {
36
+ if ( error ) {
37
+ reject ( error ) ;
38
+ } else {
39
+ resolve ( result ) ;
40
+ }
41
+ } ,
42
+ ) ;
43
+ } ) ;
44
+ } ;
45
+
46
+ resolver . getHook ( 'described-resolve' ) . tapPromise (
47
+ 'TypeScriptPathsPlugin' ,
48
+ async ( request : NormalModuleFactoryRequest , resolveContext : { } ) => {
49
+ if ( ! request || request . typescriptPathMapped ) {
50
+ return ;
51
+ }
52
+
53
+ const originalRequest = getInnerRequest ( resolver , request ) ;
54
+ if ( ! originalRequest ) {
55
+ return ;
56
+ }
57
+
58
+ // Only work on Javascript/TypeScript issuers.
59
+ if ( ! request . context . issuer || ! request . context . issuer . match ( / \. [ j t ] s x ? $ / ) ) {
60
+ return ;
61
+ }
62
+
63
+ // Relative or absolute requests are not mapped
64
+ if ( originalRequest . startsWith ( '.' ) || originalRequest . startsWith ( '/' ) ) {
65
+ return ;
66
+ }
67
+
68
+ // Amd requests are not mapped
69
+ if ( originalRequest . startsWith ( '!!webpack amd' ) ) {
70
+ return ;
71
+ }
72
+
73
+ const replacements = findReplacements ( originalRequest , this . _options . paths || { } ) ;
74
+ for ( const potential of replacements ) {
75
+ const potentialRequest = {
76
+ ...request ,
77
+ request : path . resolve ( this . _options . baseUrl || '' , potential ) ,
78
+ typescriptPathMapped : true ,
79
+ } ;
80
+ const result = await resolveAsync ( potentialRequest , resolveContext ) ;
81
+
82
+ if ( result ) {
83
+ return result ;
84
+ }
85
+ }
86
+ } ,
87
+ ) ;
50
88
}
89
+ }
51
90
91
+ function findReplacements (
92
+ originalRequest : string ,
93
+ paths : MapLike < string [ ] > ,
94
+ ) : Iterable < string > {
52
95
// check if any path mapping rules are relevant
53
96
const pathMapOptions = [ ] ;
54
- for ( const pattern in compilerOptions . paths ) {
97
+ for ( const pattern in paths ) {
55
98
// get potentials and remove duplicates; JS Set maintains insertion order
56
- const potentials = Array . from ( new Set ( compilerOptions . paths [ pattern ] ) ) ;
99
+ const potentials = Array . from ( new Set ( paths [ pattern ] ) ) ;
57
100
if ( potentials . length === 0 ) {
58
101
// no potential replacements so skip
59
102
continue ;
@@ -96,9 +139,7 @@ export function resolveWithPaths(
96
139
}
97
140
98
141
if ( pathMapOptions . length === 0 ) {
99
- callback ( null , request ) ;
100
-
101
- return ;
142
+ return [ ] ;
102
143
}
103
144
104
145
// exact matches take priority then largest prefix match
@@ -112,63 +153,23 @@ export function resolveWithPaths(
112
153
}
113
154
} ) ;
114
155
115
- if ( pathMapOptions [ 0 ] . potentials . length === 1 ) {
116
- const onlyPotential = pathMapOptions [ 0 ] . potentials [ 0 ] ;
117
- let replacement ;
118
- const starIndex = onlyPotential . indexOf ( '*' ) ;
119
- if ( starIndex === - 1 ) {
120
- replacement = onlyPotential ;
121
- } else if ( starIndex === onlyPotential . length - 1 ) {
122
- replacement = onlyPotential . slice ( 0 , - 1 ) + pathMapOptions [ 0 ] . partial ;
123
- } else {
124
- const [ prefix , suffix ] = onlyPotential . split ( '*' ) ;
125
- replacement = prefix + pathMapOptions [ 0 ] . partial + suffix ;
126
- }
127
-
128
- request . request = path . resolve ( compilerOptions . baseUrl || '' , replacement ) ;
129
- callback ( null , request ) ;
130
-
131
- return ;
132
- }
133
-
134
- // TODO: The following is used when there is more than one potential and will not be
135
- // needed once this is turned into a full webpack resolver plugin
136
-
137
- const moduleResolver = ts . resolveModuleName (
138
- originalRequest ,
139
- request . contextInfo . issuer ,
140
- compilerOptions ,
141
- host ,
142
- cache ,
143
- ) ;
144
-
145
- const moduleFilePath = moduleResolver . resolvedModule
146
- && moduleResolver . resolvedModule . resolvedFileName ;
147
-
148
- // If there is no result, let webpack try to resolve
149
- if ( ! moduleFilePath ) {
150
- callback ( null , request ) ;
151
-
152
- return ;
153
- }
154
-
155
- // If TypeScript gives us a `.d.ts`, it is probably a node module
156
- if ( moduleFilePath . endsWith ( '.d.ts' ) ) {
157
- // If in a package, let webpack resolve the package
158
- const packageRootPath = path . join ( path . dirname ( moduleFilePath ) , 'package.json' ) ;
159
- if ( ! host . fileExists ( packageRootPath ) ) {
160
- // Otherwise, if there is a file with a .js extension use that
161
- const jsFilePath = moduleFilePath . slice ( 0 , - 5 ) + '.js' ;
162
- if ( host . fileExists ( jsFilePath ) ) {
163
- request . request = jsFilePath ;
156
+ const replacements : string [ ] = [ ] ;
157
+ pathMapOptions . forEach ( option => {
158
+ for ( const potential of option . potentials ) {
159
+ let replacement ;
160
+ const starIndex = potential . indexOf ( '*' ) ;
161
+ if ( starIndex === - 1 ) {
162
+ replacement = potential ;
163
+ } else if ( starIndex === potential . length - 1 ) {
164
+ replacement = potential . slice ( 0 , - 1 ) + option . partial ;
165
+ } else {
166
+ const [ prefix , suffix ] = potential . split ( '*' ) ;
167
+ replacement = prefix + option . partial + suffix ;
164
168
}
165
- }
166
-
167
- callback ( null , request ) ;
168
169
169
- return ;
170
- }
170
+ replacements . push ( replacement ) ;
171
+ }
172
+ } ) ;
171
173
172
- request . request = moduleFilePath ;
173
- callback ( null , request ) ;
174
+ return replacements ;
174
175
}
0 commit comments