@@ -276,6 +276,15 @@ namespace ts {
276276 return Debug . fail ( `project ${ configFile . options . configFilePath } expected to have at least one output` ) ;
277277 }
278278
279+ /*@internal */
280+ export function getCacheDtsEmitResultKey ( sourceFileOrBundle : SourceFile | Bundle , options : CompilerOptions ) {
281+ return isSourceFile ( sourceFileOrBundle ) ? sourceFileOrBundle . resolvedPath : outFile ( options ) as Path ;
282+ }
283+
284+ function isForceDtsEmitResult ( result : CacheDtsEmitResult | undefined ) : result is ForceDtsEmitResult {
285+ return ! ! result && ! ( result as TransformationResult < SourceFile | Bundle > ) . transformed ;
286+ }
287+
279288 /*@internal */
280289 // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature
281290 export function emitFiles ( resolver : EmitResolver , host : EmitHost , targetSourceFile : SourceFile | undefined , { scriptTransformers, declarationTransformers } : EmitTransformers , emitOnlyDtsFiles ?: boolean , onlyBuildInfo ?: boolean , forceDtsEmit ?: boolean ) : EmitResult {
@@ -288,7 +297,6 @@ namespace ts {
288297 const { enter, exit } = performance . createTimer ( "printTime" , "beforePrint" , "afterPrint" ) ;
289298 let bundleBuildInfo : BundleBuildInfo | undefined ;
290299 let emitSkipped = false ;
291- let exportedModulesFromDeclarationEmit : ExportedModulesFromDeclarationEmit | undefined ;
292300
293301 // Emit each output file
294302 enter ( ) ;
@@ -308,7 +316,6 @@ namespace ts {
308316 diagnostics : emitterDiagnostics . getDiagnostics ( ) ,
309317 emittedFiles : emittedFilesList ,
310318 sourceMaps : sourceMapDataList ,
311- exportedModulesFromDeclarationEmit
312319 } ;
313320
314321 function emitSourceFileOrBundle ( { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath } : EmitFileNames , sourceFileOrBundle : SourceFile | Bundle | undefined ) {
@@ -411,8 +418,7 @@ namespace ts {
411418 substituteNode : transform . substituteNode ,
412419 } ) ;
413420
414- Debug . assert ( transform . transformed . length === 1 , "Should only see one output from the transform" ) ;
415- printSourceFileOrBundle ( jsFilePath , sourceMapFilePath , transform . transformed [ 0 ] , printer , compilerOptions ) ;
421+ printSourceFileOrBundle ( jsFilePath , sourceMapFilePath , transform , printer , compilerOptions ) ;
416422
417423 // Clean up emit nodes on parse tree
418424 transform . dispose ( ) ;
@@ -433,12 +439,48 @@ namespace ts {
433439 const filesForEmit = forceDtsEmit ? sourceFiles : filter ( sourceFiles , isSourceFileNotJson ) ;
434440 // Setup and perform the transformation to retrieve declarations from the input files
435441 const inputListOrBundle = outFile ( compilerOptions ) ? [ factory . createBundle ( filesForEmit , ! isSourceFile ( sourceFileOrBundle ) ? sourceFileOrBundle . prepends : undefined ) ] : filesForEmit ;
436- if ( emitOnlyDtsFiles && ! getEmitDeclarations ( compilerOptions ) ) {
442+ // Use existing result:
443+ const key = getCacheDtsEmitResultKey ( sourceFileOrBundle , compilerOptions ) ;
444+ const cache = host . getDtsEmitResultCache ( ) ;
445+ const existing = declarationTransformers . length === 1 ? cache . get ( key ) : undefined ;
446+ if ( isForceDtsEmitResult ( existing ) ) {
447+ if ( existing . diagnostics ?. length ) {
448+ for ( const diagnostic of existing . diagnostics ) {
449+ emitterDiagnostics . add ( diagnostic ) ;
450+ }
451+ }
452+ const declBlocked = ! ! existing . diagnostics ?. length || ! ! host . isEmitBlocked ( declarationFilePath ) || ! ! compilerOptions . noEmit ;
453+ emitSkipped = emitSkipped || declBlocked ;
454+ if ( ! declBlocked || forceDtsEmit ) {
455+ // Write the output file
456+ if ( declarationMapPath && ! forceDtsEmit ) {
457+ if ( sourceMapDataList && existing . sourceMapData ) {
458+ sourceMapDataList . push ( existing . sourceMapData ) ;
459+ }
460+ writeFile ( host , emitterDiagnostics , declarationMapPath , existing . sourceMap ! , /*writeByteOrderMark*/ false , sourceFiles ) ;
461+ }
462+ writeFile (
463+ host ,
464+ emitterDiagnostics ,
465+ declarationFilePath ,
466+ existing . text ,
467+ ! ! compilerOptions . emitBOM ,
468+ filesForEmit ,
469+ {
470+ sourceMapUrlPos : existing . sourceMapUrlPos ,
471+ exportedModulesFromDeclarationEmit : existing . exportedModulesFromDeclarationEmit ,
472+ }
473+ ) ;
474+ }
475+ return ;
476+ }
477+
478+ if ( ! existing && emitOnlyDtsFiles && ! getEmitDeclarations ( compilerOptions ) ) {
437479 // Checker wont collect the linked aliases since thats only done when declaration is enabled.
438480 // Do that here when emitting only dts files
439481 filesForEmit . forEach ( collectLinkedAliases ) ;
440482 }
441- const declarationTransform = transformNodes ( resolver , host , factory , compilerOptions , inputListOrBundle , declarationTransformers , /*allowDtsFiles*/ false ) ;
483+ const declarationTransform = existing || transformNodes ( resolver , host , factory , compilerOptions , inputListOrBundle , declarationTransformers , /*allowDtsFiles*/ false ) ;
442484 if ( length ( declarationTransform . diagnostics ) ) {
443485 for ( const diagnostic of declarationTransform . diagnostics ! ) {
444486 emitterDiagnostics . add ( diagnostic ) ;
@@ -451,8 +493,8 @@ namespace ts {
451493 noEmitHelpers : true ,
452494 module : compilerOptions . module ,
453495 target : compilerOptions . target ,
454- sourceMap : compilerOptions . sourceMap ,
455- inlineSourceMap : compilerOptions . inlineSourceMap ,
496+ // Emit maps when there is noEmit not set with forceDtsEmit
497+ sourceMap : ( ! forceDtsEmit || ! compilerOptions . noEmit ) && compilerOptions . declarationMap ,
456498 extendedDiagnostics : compilerOptions . extendedDiagnostics ,
457499 onlyPrintJsDocStyle : true ,
458500 writeBundleFileInfo : ! ! bundleBuildInfo ,
@@ -472,24 +514,19 @@ namespace ts {
472514 const declBlocked = ( ! ! declarationTransform . diagnostics && ! ! declarationTransform . diagnostics . length ) || ! ! host . isEmitBlocked ( declarationFilePath ) || ! ! compilerOptions . noEmit ;
473515 emitSkipped = emitSkipped || declBlocked ;
474516 if ( ! declBlocked || forceDtsEmit ) {
475- Debug . assert ( declarationTransform . transformed . length === 1 , "Should only see one output from the decl transform" ) ;
476517 printSourceFileOrBundle (
477518 declarationFilePath ,
478519 declarationMapPath ,
479- declarationTransform . transformed [ 0 ] ,
520+ declarationTransform ,
480521 declarationPrinter ,
481522 {
482- sourceMap : ! forceDtsEmit && compilerOptions . declarationMap ,
523+ sourceMap : printerOptions . sourceMap ,
483524 sourceRoot : compilerOptions . sourceRoot ,
484525 mapRoot : compilerOptions . mapRoot ,
485526 extendedDiagnostics : compilerOptions . extendedDiagnostics ,
486527 // Explicitly do not passthru either `inline` option
487528 }
488529 ) ;
489- if ( forceDtsEmit && declarationTransform . transformed [ 0 ] . kind === SyntaxKind . SourceFile ) {
490- const sourceFile = declarationTransform . transformed [ 0 ] ;
491- exportedModulesFromDeclarationEmit = sourceFile . exportedModulesFromDeclarationEmit ;
492- }
493530 }
494531 declarationTransform . dispose ( ) ;
495532 if ( bundleBuildInfo ) bundleBuildInfo . dts = declarationPrinter . bundleFileInfo ;
@@ -509,7 +546,10 @@ namespace ts {
509546 forEachChild ( node , collectLinkedAliases ) ;
510547 }
511548
512- function printSourceFileOrBundle ( jsFilePath : string , sourceMapFilePath : string | undefined , sourceFileOrBundle : SourceFile | Bundle , printer : Printer , mapOptions : SourceMapOptions ) {
549+ function printSourceFileOrBundle (
550+ jsFilePath : string , sourceMapFilePath : string | undefined , transform : TransformationResult < SourceFile | Bundle > , printer : Printer , mapOptions : SourceMapOptions ) {
551+ Debug . assert ( transform . transformed . length === 1 , "Should only see one output from the transform" ) ;
552+ const sourceFileOrBundle = transform . transformed [ 0 ] ;
513553 const bundle = sourceFileOrBundle . kind === SyntaxKind . Bundle ? sourceFileOrBundle : undefined ;
514554 const sourceFile = sourceFileOrBundle . kind === SyntaxKind . SourceFile ? sourceFileOrBundle : undefined ;
515555 const sourceFiles = bundle ? bundle . sourceFiles : [ sourceFile ! ] ;
@@ -532,9 +572,11 @@ namespace ts {
532572 }
533573
534574 let sourceMapUrlPos ;
575+ let sourceMap ;
576+ let sourceMapData ;
535577 if ( sourceMapGenerator ) {
536578 if ( sourceMapDataList ) {
537- sourceMapDataList . push ( {
579+ sourceMapDataList . push ( sourceMapData = {
538580 inputSourceFileNames : sourceMapGenerator . getSources ( ) ,
539581 sourceMap : sourceMapGenerator . toJSON ( )
540582 } ) ;
@@ -555,16 +597,39 @@ namespace ts {
555597
556598 // Write the source map
557599 if ( sourceMapFilePath ) {
558- const sourceMap = sourceMapGenerator . toString ( ) ;
559- writeFile ( host , emitterDiagnostics , sourceMapFilePath , sourceMap , /*writeByteOrderMark*/ false , sourceFiles ) ;
600+ sourceMap = sourceMapGenerator . toString ( ) ;
601+ if ( ! forceDtsEmit ) writeFile ( host , emitterDiagnostics , sourceMapFilePath , sourceMap , /*writeByteOrderMark*/ false , sourceFiles ) ;
560602 }
561603 }
562604 else {
563605 writer . writeLine ( ) ;
564606 }
565607
566608 // Write the output file
567- writeFile ( host , emitterDiagnostics , jsFilePath , writer . getText ( ) , ! ! compilerOptions . emitBOM , sourceFiles , { sourceMapUrlPos } ) ;
609+ writeFile (
610+ host ,
611+ emitterDiagnostics ,
612+ jsFilePath ,
613+ writer . getText ( ) ,
614+ ! ! compilerOptions . emitBOM ,
615+ sourceFiles ,
616+ {
617+ sourceMapUrlPos,
618+ exportedModulesFromDeclarationEmit : sourceFile ?. exportedModulesFromDeclarationEmit ,
619+ }
620+ ) ;
621+ if ( forceDtsEmit ) {
622+ // Store the result
623+ const key = getCacheDtsEmitResultKey ( sourceFileOrBundle , compilerOptions ) ;
624+ host . getDtsEmitResultCache ( ) . set ( key , {
625+ diagnostics : transform . diagnostics ,
626+ text : writer . getText ( ) ,
627+ sourceMap,
628+ sourceMapData,
629+ sourceMapUrlPos,
630+ exportedModulesFromDeclarationEmit : sourceFile ?. exportedModulesFromDeclarationEmit ,
631+ } ) ;
632+ }
568633
569634 // Reset state
570635 writer . clear ( ) ;
@@ -833,6 +898,7 @@ namespace ts {
833898 getSourceFileFromReference : returnUndefined ,
834899 redirectTargetsMap : createMultiMap ( ) ,
835900 getFileIncludeReasons : notImplemented ,
901+ getDtsEmitResultCache : ( ) => new Map ( ) ,
836902 } ;
837903 emitFiles (
838904 notImplementedResolver ,
0 commit comments