@@ -76,7 +76,11 @@ class BroccoliTypeScriptCompiler extends Plugin {
76
76
} else if ( this . _fileRegistry [ tsFilePath ] . version >= entry . mtime ) {
77
77
// Nothing to do for this file. Just link the cached outputs.
78
78
this . _fileRegistry [ tsFilePath ] . outputs . forEach ( absoluteFilePath => {
79
- const outputFilePath = absoluteFilePath . replace ( this . cachePath , this . outputPath ) ;
79
+ let outputFilePath = absoluteFilePath . replace ( this . cachePath , this . outputPath ) ;
80
+ if ( this . _options . getDestinationPath ) {
81
+ const tmpOutputPath = this . _options . getDestinationPath ( path . relative ( this . outputPath , outputFilePath ) ) ;
82
+ outputFilePath = tmpOutputPath ? path . join ( this . outputPath , tmpOutputPath ) : outputFilePath ;
83
+ }
80
84
fse . mkdirsSync ( path . dirname ( outputFilePath ) ) ;
81
85
fs . symlinkSync ( absoluteFilePath , outputFilePath ) ;
82
86
} ) ;
@@ -159,12 +163,14 @@ class BroccoliTypeScriptCompiler extends Plugin {
159
163
160
164
this . _tsConfigFiles = tsconfig . files . splice ( 0 ) ;
161
165
162
- this . _tsOpts = ts . convertCompilerOptionsFromJson ( tsconfig . compilerOptions , '' , null ) . options ;
166
+ this . _tsOpts = ts . convertCompilerOptionsFromJson ( tsconfig [ 'compilerOptions' ] ,
167
+ this . inputPaths [ 0 ] , this . _tsConfigPath ) . options ;
163
168
this . _tsOpts . rootDir = '' ;
164
169
this . _tsOpts . outDir = '' ;
165
170
166
171
this . _tsServiceHost = new CustomLanguageServiceHost (
167
- this . _tsOpts , this . _rootFilePaths , this . _fileRegistry , this . inputPaths [ 0 ] ) ;
172
+ this . _tsOpts , this . _rootFilePaths , this . _fileRegistry , this . inputPaths [ 0 ] ,
173
+ tsconfig [ 'compilerOptions' ] . paths , this . _tsConfigPath ) ;
168
174
this . _tsService = ts . createLanguageService ( this . _tsServiceHost , ts . createDocumentRegistry ( ) ) ;
169
175
}
170
176
@@ -190,7 +196,11 @@ class BroccoliTypeScriptCompiler extends Plugin {
190
196
absoluteFilePath = path . resolve ( this . cachePath , absoluteFilePath ) ;
191
197
// Replace the input path by the output.
192
198
absoluteFilePath = absoluteFilePath . replace ( this . inputPaths [ 0 ] , this . cachePath ) ;
193
- const outputFilePath = absoluteFilePath . replace ( this . cachePath , this . outputPath ) ;
199
+ let outputFilePath = absoluteFilePath . replace ( this . cachePath , this . outputPath ) ;
200
+ if ( this . _options . getDestinationPath ) {
201
+ const tmpOutputPath = this . _options . getDestinationPath ( path . relative ( this . outputPath , outputFilePath ) ) ;
202
+ outputFilePath = tmpOutputPath ? path . join ( this . outputPath , tmpOutputPath ) : outputFilePath ;
203
+ }
194
204
195
205
if ( registry ) {
196
206
registry . outputs . add ( absoluteFilePath ) ;
@@ -249,13 +259,15 @@ class BroccoliTypeScriptCompiler extends Plugin {
249
259
}
250
260
251
261
class CustomLanguageServiceHost {
252
- constructor ( compilerOptions , fileNames , fileRegistry , treeInputPath ) {
262
+ constructor ( compilerOptions , fileNames , fileRegistry , treeInputPath , paths , tsConfigPath ) {
253
263
this . compilerOptions = compilerOptions ;
254
264
this . fileNames = fileNames ;
255
265
this . fileRegistry = fileRegistry ;
256
266
this . treeInputPath = treeInputPath ;
257
267
this . currentDirectory = treeInputPath ;
258
268
this . defaultLibFilePath = ts . getDefaultLibFilePath ( compilerOptions ) . replace ( / \\ / g, '/' ) ;
269
+ this . paths = paths ;
270
+ this . tsConfigPath = tsConfigPath ;
259
271
this . projectVersion = 0 ;
260
272
}
261
273
@@ -272,23 +284,87 @@ class CustomLanguageServiceHost {
272
284
return this . projectVersion . toString ( ) ;
273
285
}
274
286
275
- /**
276
- * This method is called quite a bit to lookup 3 kinds of paths:
277
- * 1/ files in the fileRegistry
278
- * - these are the files in our project that we are watching for changes
279
- * - in the future we could add caching for these files and invalidate the cache when
280
- * the file is changed lazily during lookup
281
- * 2/ .d.ts and library files not in the fileRegistry
282
- * - these are not our files, they come from tsd or typescript itself
283
- * - these files change only rarely but since we need them very rarely, it's not worth the
284
- * cache invalidation hassle to cache them
285
- * 3/ bogus paths that typescript compiler tries to lookup during import resolution
286
- * - these paths are tricky to cache since files come and go and paths that was bogus in the
287
- * past might not be bogus later
288
- *
289
- * In the initial experiments the impact of this caching was insignificant (single digit %) and
290
- * not worth the potential issues with stale cache records.
291
- */
287
+ _resolveModulePathWithMapping ( moduleName ) {
288
+ // check if module name should be used as-is or it should be mapped to different value
289
+ let longestMatchedPrefixLength = 0 ;
290
+ let matchedPattern ;
291
+ let matchedWildcard ;
292
+ const paths = this . paths || { } ;
293
+
294
+ for ( let pattern of Object . keys ( paths ) ) {
295
+ if ( pattern . indexOf ( '*' ) != pattern . lastIndexOf ( '*' ) ) {
296
+ throw `Invalid path mapping pattern: "${ pattern } "` ;
297
+ }
298
+
299
+ let indexOfWildcard = pattern . indexOf ( '*' ) ;
300
+ if ( indexOfWildcard !== - 1 ) {
301
+ // check if module name starts with prefix, ends with suffix and these two don't overlap
302
+ let prefix = pattern . substr ( 0 , indexOfWildcard ) ;
303
+ let suffix = pattern . substr ( indexOfWildcard + 1 ) ;
304
+ if ( moduleName . length >= prefix . length + suffix . length &&
305
+ moduleName . startsWith ( prefix ) &&
306
+ moduleName . endsWith ( suffix ) ) {
307
+
308
+ // use length of matched prefix as betterness criteria
309
+ if ( longestMatchedPrefixLength < prefix . length ) {
310
+ longestMatchedPrefixLength = prefix . length ;
311
+ matchedPattern = pattern ;
312
+ matchedWildcard = moduleName . substr ( prefix . length , moduleName . length - suffix . length ) ;
313
+ }
314
+ }
315
+ } else {
316
+ // Pattern does not contain asterisk - module name should exactly match pattern to succeed.
317
+ if ( pattern === moduleName ) {
318
+ matchedPattern = pattern ;
319
+ matchedWildcard = undefined ;
320
+ break ;
321
+ }
322
+ }
323
+ }
324
+
325
+ if ( ! matchedPattern ) {
326
+ // We fallback to the old module resolution.
327
+ return undefined ;
328
+ // // no pattern was matched so module name can be used as-is
329
+ // let p = path.join(this.treeInputPath, moduleName);
330
+ // return fs.existsSync(p) ? p : undefined;
331
+ }
332
+
333
+ // some pattern was matched - module name needs to be substituted
334
+ let substitutions = this . paths [ matchedPattern ] ;
335
+ for ( let subst of substitutions ) {
336
+ if ( subst . indexOf ( '*' ) != subst . lastIndexOf ( '*' ) ) {
337
+ throw `Invalid substitution: "${ subst } " for pattern "${ matchedPattern } ".` ;
338
+ }
339
+ // replace * in substitution with matched wildcard
340
+ let p = matchedWildcard ? subst . replace ( '*' , matchedWildcard ) : subst ;
341
+ // if substituion is a relative path - combine it with baseUrl
342
+ p = path . isAbsolute ( p ) ? p : path . join ( this . treeInputPath , path . dirname ( this . tsConfigPath ) , p ) ;
343
+ if ( fs . existsSync ( p ) ) {
344
+ return p ;
345
+ }
346
+ }
347
+
348
+ return undefined ;
349
+ }
350
+
351
+ // /**
352
+ // * This method is called quite a bit to lookup 3 kinds of paths:
353
+ // * 1/ files in the fileRegistry
354
+ // * - these are the files in our project that we are watching for changes
355
+ // * - in the future we could add caching for these files and invalidate the cache when
356
+ // * the file is changed lazily during lookup
357
+ // * 2/ .d.ts and library files not in the fileRegistry
358
+ // * - these are not our files, they come from tsd or typescript itself
359
+ // * - these files change only rarely but since we need them very rarely, it's not worth the
360
+ // * cache invalidation hassle to cache them
361
+ // * 3/ bogus paths that typescript compiler tries to lookup during import resolution
362
+ // * - these paths are tricky to cache since files come and go and paths that was bogus in the
363
+ // * past might not be bogus later
364
+ // *
365
+ // * In the initial experiments the impact of this caching was insignificant (single digit %) and
366
+ // * not worth the potential issues with stale cache records.
367
+ // */
292
368
getScriptSnapshot ( tsFilePath ) {
293
369
var absoluteTsFilePath ;
294
370
if ( tsFilePath == this . defaultLibFilePath || path . isAbsolute ( tsFilePath ) ) {
@@ -306,10 +382,41 @@ class CustomLanguageServiceHost {
306
382
// so we we just return undefined when the path is not correct.
307
383
return undefined ;
308
384
}
309
-
310
385
return ts . ScriptSnapshot . fromString ( fs . readFileSync ( absoluteTsFilePath , FS_OPTS ) ) ;
311
386
}
312
387
388
+ resolveModuleNames ( moduleNames , containingFile ) /*: ResolvedModule[]*/ {
389
+ return moduleNames . map ( ( moduleName ) => {
390
+ for ( const ext of [ 'ts' , 'd.ts' ] ) {
391
+ const name = `${ moduleName } .${ ext } ` ;
392
+ const maybeModule = this . _resolveModulePathWithMapping ( name , containingFile ) ;
393
+ if ( maybeModule ) {
394
+ return {
395
+ resolvedFileName : maybeModule ,
396
+ isExternalLibraryImport : false
397
+ } ;
398
+ }
399
+ }
400
+
401
+ return ts . resolveModuleName ( moduleName , containingFile , this . compilerOptions , {
402
+ fileExists ( fileName ) {
403
+ return fs . existsSync ( fileName ) ;
404
+ } ,
405
+ readFile ( fileName ) {
406
+ return fs . readFileSync ( fileName , 'utf-8' ) ;
407
+ } ,
408
+ directoryExists ( directoryName ) {
409
+ try {
410
+ const stats = fs . statSync ( directoryName ) ;
411
+ return stats && stats . isDirectory ( ) ;
412
+ } catch ( e ) {
413
+ return false ;
414
+ }
415
+ }
416
+ } ) . resolvedModule ;
417
+ } ) ;
418
+ }
419
+
313
420
getCurrentDirectory ( ) {
314
421
return this . currentDirectory ;
315
422
}
0 commit comments