@@ -12,12 +12,17 @@ import type { NodePath } from '@babel/traverse';
12
12
import { getRelativePath } from './files' ;
13
13
import { findModuleDir } from './modules' ;
14
14
import * as t from '@babel/types' ;
15
+ import { moduleResolve } from '@dual-bundle/import-meta-resolve' ;
16
+ import url from 'url' ;
15
17
16
18
import * as nodePath from 'path' ;
17
19
18
20
type ImportModifierPlugin = $ReadOnly < {
19
21
visitor : {
20
- Program : { enter ( path : NodePath < t . Program > ) : void } ,
22
+ Program : {
23
+ enter ?: ( path : NodePath < t . Program > ) => void ,
24
+ exit ?: ( path : NodePath < t . Program > ) => void ,
25
+ } ,
21
26
} ,
22
27
} > ;
23
28
@@ -116,3 +121,143 @@ export const createModuleImportModifierPlugin = (
116
121
} ,
117
122
} ;
118
123
} ;
124
+
125
+ export const createAliasRewritePlugin = (
126
+ sourceFilePath : string ,
127
+ aliasConfig : $ReadOnly < { [ string ] : string | $ReadOnlyArray < string > } > ,
128
+ ) : ImportModifierPlugin => {
129
+ return {
130
+ visitor : {
131
+ Program : {
132
+ exit ( path : NodePath < t . Program > ) {
133
+ path. traverse ( {
134
+ ImportDeclaration : {
135
+ enter ( path : NodePath < t . ImportDeclaration > ) {
136
+ const source = path . node . source . value ;
137
+
138
+ const aliases =
139
+ aliasConfig == null
140
+ ? aliasConfig
141
+ : Object . fromEntries (
142
+ Object . entries ( aliasConfig ) . map ( ( [ key , value ] ) => {
143
+ if ( typeof value === 'string' ) {
144
+ return [ key , [ value ] ] ;
145
+ }
146
+ return [ key , value ] ;
147
+ } ) ,
148
+ ) ;
149
+
150
+ const themeFileExtension = '.stylex' ;
151
+ if ( ! matchesFileSuffix ( themeFileExtension ) ( source ) ) {
152
+ return;
153
+ }
154
+ const resolvedFilePath = filePathResolver (
155
+ source ,
156
+ sourceFilePath ,
157
+ aliases ,
158
+ ) ;
159
+
160
+ if ( resolvedFilePath == null ) {
161
+ return;
162
+ }
163
+
164
+ let relativeFilePath = getRelativePath (
165
+ sourceFilePath ,
166
+ resolvedFilePath ,
167
+ ) ;
168
+
169
+ const extension = EXTENSIONS . find ( ( ext ) =>
170
+ relativeFilePath . endsWith ( ext ) ,
171
+ ) ;
172
+ if ( extension != null ) {
173
+ relativeFilePath = relativeFilePath . slice (
174
+ 0 ,
175
+ - extension . length ,
176
+ ) ;
177
+ }
178
+
179
+ path . node . source . value = relativeFilePath ;
180
+ } ,
181
+ } ,
182
+ } ) ;
183
+ } ,
184
+ } ,
185
+ } ,
186
+ } ;
187
+ } ;
188
+
189
+ const matchesFileSuffix = ( allowedSuffix : string ) => ( filename : string ) =>
190
+ filename . endsWith ( `${ allowedSuffix } .js` ) ||
191
+ filename . endsWith ( `${ allowedSuffix } .ts` ) ||
192
+ filename . endsWith ( `${ allowedSuffix } .tsx` ) ||
193
+ filename . endsWith ( `${ allowedSuffix } .jsx` ) ||
194
+ filename . endsWith ( `${ allowedSuffix } .mjs` ) ||
195
+ filename . endsWith ( `${ allowedSuffix } .cjs` ) ||
196
+ filename . endsWith ( allowedSuffix ) ;
197
+
198
+ const EXTENSIONS = [ '.js' , '.ts' , '.tsx' , '.jsx' , '.mjs' , '.cjs' ] ;
199
+ const filePathResolver = (
200
+ relativeFilePath : string ,
201
+ sourceFilePath : string ,
202
+ aliases : $ReadOnly < { [ string ] : $ReadOnlyArray < string > } > ,
203
+ ) : ?string => {
204
+ // Try importing without adding any extension
205
+ // and then every supported extension
206
+ for ( const ext of [ '' , ...EXTENSIONS ] ) {
207
+ const importPathStr = relativeFilePath + ext ;
208
+
209
+ // Try to resolve relative paths as is
210
+ if ( importPathStr . startsWith ( '.' ) ) {
211
+ try {
212
+ return moduleResolve ( importPathStr , url . pathToFileURL ( sourceFilePath ) )
213
+ . pathname ;
214
+ } catch {
215
+ continue ;
216
+ }
217
+ }
218
+
219
+ // Otherwise, try to resolve the path with aliases
220
+ const allAliases = possibleAliasedPaths ( importPathStr , aliases ) ;
221
+ for ( const possiblePath of allAliases ) {
222
+ try {
223
+ return moduleResolve ( possiblePath , url . pathToFileURL ( sourceFilePath ) )
224
+ . pathname ;
225
+ } catch {
226
+ continue ;
227
+ }
228
+ }
229
+ }
230
+ // Failed to resolve the file path
231
+ return null ;
232
+ } ;
233
+
234
+ function possibleAliasedPaths (
235
+ importPath : string ,
236
+ aliases : $ReadOnly < { [ string ] : $ReadOnlyArray < string > } > ,
237
+ ) : $ReadOnlyArray < string > {
238
+ const result = [ importPath ] ;
239
+ if ( aliases == null || Object . keys ( aliases ) . length === 0 ) {
240
+ return result ;
241
+ }
242
+
243
+ for ( const [ alias , value ] of Object . entries ( aliases ) ) {
244
+ if ( alias . includes ( '*' ) ) {
245
+ const [ before , after ] = alias . split ( '*' ) ;
246
+ if ( importPath . startsWith ( before ) && importPath . endsWith ( after ) ) {
247
+ const replacementString = importPath . slice (
248
+ before . length ,
249
+ after . length > 0 ? - after . length : undefined ,
250
+ ) ;
251
+ value . forEach ( ( v ) => {
252
+ result . push ( v . split ( '*' ) . join ( replacementString ) ) ;
253
+ } ) ;
254
+ }
255
+ } else if ( alias === importPath ) {
256
+ value . forEach ( ( v ) => {
257
+ result . push ( v ) ;
258
+ } ) ;
259
+ }
260
+ }
261
+
262
+ return result ;
263
+ }
0 commit comments