@@ -2,55 +2,182 @@ import { dirname, relative, resolve } from "path";
22import ts from "typescript" ;
33import slash from "slash" ;
44
5- const transformer = < T extends ts . Node > ( _ : ts . Program ) => {
6- return ( context : ts . TransformationContext ) => ( rootNode : T ) => {
7- const compilerOptions = context . getCompilerOptions ( ) ;
5+ const transformer = ( _ : ts . Program ) => ( context : ts . TransformationContext ) => (
6+ sourceFile : ts . SourceFile
7+ ) => {
8+ const resolver =
9+ typeof ( context as any ) . getEmitResolver === "function"
10+ ? ( context as any ) . getEmitResolver ( )
11+ : undefined ;
12+ const compilerOptions = context . getCompilerOptions ( ) ;
13+ const sourceDir = dirname ( sourceFile . fileName ) ;
14+
15+ const { baseUrl = "" , paths = { } } = compilerOptions ;
16+
17+ const binds = Object . keys ( paths )
18+ . filter ( key => paths [ key ] . length )
19+ . map ( key => ( {
20+ regexp : new RegExp ( "^" + key . replace ( "*" , "(.*)" ) + "$" ) ,
21+ path : paths [ key ] [ 0 ]
22+ } ) ) ;
23+
24+ if ( ! baseUrl || binds . length === 0 ) {
25+ // There is nothing we can do without baseUrl and paths specified.
26+ return sourceFile ;
27+ }
28+
29+ function bindModuleToFile ( moduleName : string ) {
30+ for ( const { regexp, path } of binds ) {
31+ const match = regexp . exec ( moduleName ) ;
32+ if ( match ) {
33+ const out = path . replace ( / \* / g, match [ 1 ] ) ;
34+ const file = slash ( relative ( sourceDir , resolve ( baseUrl , out ) ) ) ;
35+ return file [ 0 ] === "." ? file : `./${ file } ` ;
36+ }
37+ }
38+ }
39+
40+ function visit ( node : ts . Node ) : ts . VisitResult < ts . Node > {
841 if (
9- compilerOptions . baseUrl === undefined ||
10- compilerOptions . paths === undefined
42+ resolver &&
43+ ts . isExportDeclaration ( node ) &&
44+ ! node . exportClause &&
45+ ! compilerOptions . isolatedModules &&
46+ ! resolver . moduleExportsSomeValue ( node . moduleSpecifier )
1147 ) {
12- throw new Error (
13- "Should define baseUrl and paths properties in the tsconfig"
48+ return undefined ;
49+ }
50+ if ( ts . isImportDeclaration ( node ) ) {
51+ return unpathImportDeclaration ( node ) ;
52+ }
53+ if ( ts . isExportDeclaration ( node ) ) {
54+ return unpathExportDeclaration ( node ) ;
55+ }
56+
57+ return ts . visitEachChild ( node , visit , context ) ;
58+ }
59+
60+ function unpathImportDeclaration (
61+ node : ts . ImportDeclaration
62+ ) : ts . VisitResult < ts . Statement > {
63+ if ( ! ts . isStringLiteral ( node . moduleSpecifier ) ) {
64+ return node ;
65+ }
66+ const file = bindModuleToFile ( node . moduleSpecifier . text ) ;
67+ if ( ! file ) {
68+ return node ;
69+ }
70+ const fileLiteral = ts . createLiteral ( file ) ;
71+
72+ const importClause = ts . visitNode (
73+ node . importClause ,
74+ visitImportClause as any ,
75+ ts . isImportClause
76+ ) ;
77+ return node . importClause === importClause || importClause
78+ ? ts . updateImportDeclaration (
79+ node ,
80+ node . decorators ,
81+ node . modifiers ,
82+ node . importClause ,
83+ fileLiteral
84+ )
85+ : undefined ;
86+ }
87+ function visitImportClause (
88+ node : ts . ImportClause
89+ ) : ts . VisitResult < ts . ImportClause > {
90+ const name = resolver . isReferencedAliasDeclaration ( node )
91+ ? node . name
92+ : undefined ;
93+ const namedBindings = ts . visitNode (
94+ node . namedBindings ,
95+ visitNamedImportBindings as any ,
96+ ts . isNamedImports
97+ ) ;
98+ return name || namedBindings
99+ ? ts . updateImportClause ( node , name , namedBindings )
100+ : undefined ;
101+ }
102+ function visitNamedImportBindings (
103+ node : ts . NamedImportBindings
104+ ) : ts . VisitResult < ts . NamedImportBindings > {
105+ if ( node . kind === ts . SyntaxKind . NamespaceImport ) {
106+ return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
107+ } else {
108+ const elements = ts . visitNodes (
109+ node . elements ,
110+ visitImportSpecifier as any ,
111+ ts . isImportSpecifier
14112 ) ;
113+ return elements . some ( e => e )
114+ ? ts . updateNamedImports ( node , elements )
115+ : undefined ;
15116 }
16- const baseUrl = compilerOptions . baseUrl ;
17- const paths = compilerOptions . paths ;
18- const regPaths = Object . keys ( paths ) . map ( key => ( {
19- regexp : new RegExp ( "^" + key . replace ( "*" , "(.*)" ) + "$" ) ,
20- resolve : paths [ key ] [ 0 ]
21- } ) ) ;
22- let fileDir = "" ;
23- function findFileInPaths ( text : string ) {
24- for ( const path of regPaths ) {
25- const match = text . match ( path . regexp ) ;
26- if ( match ) {
27- const out = path . resolve . replace ( / \* / g, match [ 1 ] ) ;
28- const file = slash ( relative ( fileDir , resolve ( baseUrl , out ) ) ) ;
29- return file [ 0 ] === "." ? file : `./${ file } ` ;
30- }
31- }
32- return null ;
117+ }
118+ function visitImportSpecifier (
119+ node : ts . ImportSpecifier
120+ ) : ts . VisitResult < ts . ImportSpecifier > {
121+ return resolver . isReferencedAliasDeclaration ( node ) ? node : undefined ;
122+ }
123+
124+ function unpathExportDeclaration (
125+ node : ts . ExportDeclaration
126+ ) : ts . VisitResult < ts . Statement > {
127+ if ( ! node . moduleSpecifier || ! ts . isStringLiteral ( node . moduleSpecifier ) ) {
128+ return node ;
33129 }
34- function visit ( node : ts . Node ) : ts . Node {
35- if ( ts . isSourceFile ( node ) ) {
36- fileDir = dirname ( node . fileName ) ;
37- return ts . visitEachChild ( node , visit , context ) ;
38- }
39- if (
40- ( ts . isImportDeclaration ( node ) || ts . isExportDeclaration ( node ) ) &&
41- node . moduleSpecifier &&
42- ts . isStringLiteral ( node . moduleSpecifier )
43- ) {
44- const file = findFileInPaths ( node . moduleSpecifier . text ) ;
45- if ( file ) {
46- node . moduleSpecifier . text = file ;
47- return node ;
48- }
49- }
50- return ts . visitEachChild ( node , visit , context ) ;
130+ if (
131+ ! node . exportClause &&
132+ ! compilerOptions . isolatedModules &&
133+ ! resolver . moduleExportsSomeValue ( node . moduleSpecifier )
134+ ) {
135+ return node ;
51136 }
52- return ts . visitNode ( rootNode , visit ) ;
53- } ;
137+ if ( node . exportClause && resolver . isValueAliasDeclaration ( node ) ) {
138+ return node ;
139+ }
140+
141+ const file = bindModuleToFile ( node . moduleSpecifier . text ) ;
142+ if ( ! file ) {
143+ return node ;
144+ }
145+ const fileLiteral = ts . createLiteral ( file ) ;
146+
147+ const exportClause = ts . visitNode (
148+ node . exportClause ,
149+ visitNamedExports as any ,
150+ ts . isNamedExports
151+ ) ;
152+ return node . exportClause === exportClause || exportClause
153+ ? ts . updateExportDeclaration (
154+ node ,
155+ node . decorators ,
156+ node . modifiers ,
157+ node . exportClause ,
158+ fileLiteral
159+ )
160+ : undefined ;
161+ }
162+ function visitNamedExports (
163+ node : ts . NamedExports
164+ ) : ts . VisitResult < ts . NamedExports > {
165+ const elements = ts . visitNodes (
166+ node . elements ,
167+ visitExportSpecifier as any ,
168+ ts . isExportSpecifier
169+ ) ;
170+ return elements . some ( e => e )
171+ ? ts . updateNamedExports ( node , elements )
172+ : undefined ;
173+ }
174+ function visitExportSpecifier (
175+ node : ts . ExportSpecifier
176+ ) : ts . VisitResult < ts . ExportSpecifier > {
177+ return resolver . isValueAliasDeclaration ( node ) ? node : undefined ;
178+ }
179+
180+ return ts . visitNode ( sourceFile , visit ) ;
54181} ;
55182
56183export default transformer ;
0 commit comments