66 * found in the LICENSE file at https://angular.io/license
77 */
88import * 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' ;
2811
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' ) ;
3213
33- return ;
34- }
14+ export interface TypeScriptPathsPluginOptions extends Pick < CompilerOptions , 'paths' | 'baseUrl' > {
3515
36- const originalRequest = request . request . trim ( ) ;
16+ }
3717
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 ) { }
4120
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+ }
4826
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+ ) ;
5088 }
89+ }
5190
91+ function findReplacements (
92+ originalRequest : string ,
93+ paths : MapLike < string [ ] > ,
94+ ) : Iterable < string > {
5295 // check if any path mapping rules are relevant
5396 const pathMapOptions = [ ] ;
54- for ( const pattern in compilerOptions . paths ) {
97+ for ( const pattern in paths ) {
5598 // 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 ] ) ) ;
57100 if ( potentials . length === 0 ) {
58101 // no potential replacements so skip
59102 continue ;
@@ -96,9 +139,7 @@ export function resolveWithPaths(
96139 }
97140
98141 if ( pathMapOptions . length === 0 ) {
99- callback ( null , request ) ;
100-
101- return ;
142+ return [ ] ;
102143 }
103144
104145 // exact matches take priority then largest prefix match
@@ -112,63 +153,23 @@ export function resolveWithPaths(
112153 }
113154 } ) ;
114155
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 ;
164168 }
165- }
166-
167- callback ( null , request ) ;
168169
169- return ;
170- }
170+ replacements . push ( replacement ) ;
171+ }
172+ } ) ;
171173
172- request . request = moduleFilePath ;
173- callback ( null , request ) ;
174+ return replacements ;
174175}
0 commit comments