1+ import * as fs from 'fs' ;
12import * as path from 'path' ;
23import * as tsickle from 'tsickle' ;
34import * as ts from 'typescript' ;
@@ -7,6 +8,7 @@ import {PLUGIN as bazelConformancePlugin} from '../tsetse/runner';
78import { CachedFileLoader , FileLoader , ProgramAndFileCache , UncachedFileLoader } from './cache' ;
89import { CompilerHost } from './compiler_host' ;
910import * as bazelDiagnostics from './diagnostics' ;
11+ import { constructManifest } from './manifest' ;
1012import * as perfTrace from './perf_trace' ;
1113import { PLUGIN as strictDepsPlugin } from './strict_deps' ;
1214import { BazelOptions , parseTsconfig , resolveNormalizedPath } from './tsconfig' ;
@@ -200,50 +202,135 @@ function runFromOptions(
200202 cache . putProgram ( bazelOpts . target , program ) ;
201203
202204
203- const compilationTargets =
204- program . getSourceFiles ( ) . filter ( f => isCompilationTarget ( bazelOpts , f ) ) ;
205- const emitResults : ts . EmitResult [ ] = [ ] ;
206- const diagnostics : ts . Diagnostic [ ] = [ ] ;
207- if ( bazelOpts . tsickle ) {
208- // The 'tsickle' import above is only used in type positions, so it won't
209- // result in a runtime dependency on tsickle.
210- // If the user requests the tsickle emit, then we dynamically require it
211- // here for use at runtime.
212- let optTsickle : typeof tsickle ;
213- try {
214- // tslint:disable-next-line:no-require-imports dependency on tsickle only
215- // if requested
216- optTsickle = require ( 'tsickle' ) ;
217- } catch {
218- throw new Error (
219- 'When setting bazelOpts { tsickle: true }, ' +
220- 'you must also add a devDependency on the tsickle npm package' ) ;
221- }
222- for ( const sf of compilationTargets ) {
223- emitResults . push ( optTsickle . emitWithTsickle (
224- program , compilerHost , compilerHost , options , sf ) ) ;
225- }
226- diagnostics . push (
227- ...optTsickle . mergeEmitResults ( emitResults as tsickle . EmitResult [ ] )
228- . diagnostics ) ;
229- } else {
230- for ( const sf of compilationTargets ) {
231- emitResults . push ( program . emit ( sf ) ) ;
232- }
205+ const compilationTargets = program . getSourceFiles ( ) . filter (
206+ fileName => isCompilationTarget ( bazelOpts , fileName ) ) ;
233207
234- for ( const d of emitResults ) {
235- diagnostics . push ( ...d . diagnostics ) ;
236- }
208+ let diagnostics : ts . Diagnostic [ ] = [ ] ;
209+ let useTsickleEmit = bazelOpts . tsickle ;
210+ if ( useTsickleEmit ) {
211+ diagnostics = emitWithTsickle (
212+ program , compilerHost , compilationTargets , options , bazelOpts ) ;
213+ } else {
214+ diagnostics = emitWithTypescript ( program , compilationTargets ) ;
237215 }
216+
238217 if ( diagnostics . length > 0 ) {
239218 console . error ( bazelDiagnostics . format ( bazelOpts . target , diagnostics ) ) ;
219+ debug ( 'compilation failed at' , new Error ( ) . stack ! ) ;
240220 return false ;
241221 }
242222
243223 cache . printStats ( ) ;
244224 return true ;
245225}
246226
227+ function emitWithTypescript (
228+ program : ts . Program , compilationTargets : ts . SourceFile [ ] ) : ts . Diagnostic [ ] {
229+ const diagnostics : ts . Diagnostic [ ] = [ ] ;
230+ for ( const sf of compilationTargets ) {
231+ const result = program . emit ( sf ) ;
232+ diagnostics . push ( ...result . diagnostics ) ;
233+ }
234+ return diagnostics ;
235+ }
236+
237+ function emitWithTsickle (
238+ program : ts . Program , compilerHost : CompilerHost ,
239+ compilationTargets : ts . SourceFile [ ] , options : ts . CompilerOptions ,
240+ bazelOpts : BazelOptions ) : ts . Diagnostic [ ] {
241+ const emitResults : tsickle . EmitResult [ ] = [ ] ;
242+ const diagnostics : ts . Diagnostic [ ] = [ ] ;
243+ // The 'tsickle' import above is only used in type positions, so it won't
244+ // result in a runtime dependency on tsickle.
245+ // If the user requests the tsickle emit, then we dynamically require it
246+ // here for use at runtime.
247+ let optTsickle : typeof tsickle ;
248+ try {
249+ // tslint:disable-next-line:no-require-imports
250+ optTsickle = require ( 'tsickle' ) ;
251+ } catch ( e ) {
252+ if ( e . code !== 'MODULE_NOT_FOUND' ) {
253+ throw e ;
254+ }
255+ throw new Error (
256+ 'When setting bazelOpts { tsickle: true }, ' +
257+ 'you must also add a devDependency on the tsickle npm package' ) ;
258+ }
259+ perfTrace . wrap ( 'emit' , ( ) => {
260+ for ( const sf of compilationTargets ) {
261+ perfTrace . wrap ( `emit ${ sf . fileName } ` , ( ) => {
262+ emitResults . push ( optTsickle . emitWithTsickle (
263+ program , compilerHost , compilerHost , options , sf ) ) ;
264+ } ) ;
265+ }
266+ } ) ;
267+ const emitResult = optTsickle . mergeEmitResults ( emitResults ) ;
268+ diagnostics . push ( ...emitResult . diagnostics ) ;
269+
270+ // If tsickle reported diagnostics, don't produce externs or manifest outputs.
271+ if ( diagnostics . length > 0 ) {
272+ return diagnostics ;
273+ }
274+
275+ let externs = '/** @externs */\n' +
276+ '// generating externs was disabled using generate_externs=False\n' ;
277+ if ( bazelOpts . tsickleGenerateExterns ) {
278+ externs =
279+ optTsickle . getGeneratedExterns ( emitResult . externs , options . rootDir ! ) ;
280+ }
281+
282+ if ( bazelOpts . tsickleExternsPath ) {
283+ // Note: when tsickleExternsPath is provided, we always write a file as a
284+ // marker that compilation succeeded, even if it's empty (just containing an
285+ // @externs ).
286+ fs . writeFileSync ( bazelOpts . tsickleExternsPath , externs ) ;
287+
288+ // When generating externs, generate an externs file for each of the input
289+ // .d.ts files.
290+ if ( bazelOpts . tsickleGenerateExterns &&
291+ compilerHost . provideExternalModuleDtsNamespace ) {
292+ for ( const extern of compilationTargets ) {
293+ if ( ! extern . isDeclarationFile ) continue ;
294+ const outputBaseDir = options . outDir ! ;
295+ const relativeOutputPath =
296+ compilerHost . relativeOutputPath ( extern . fileName ) ;
297+ mkdirp ( outputBaseDir , path . dirname ( relativeOutputPath ) ) ;
298+ const outputPath = path . join ( outputBaseDir , relativeOutputPath ) ;
299+ const moduleName = compilerHost . pathToModuleName ( '' , extern . fileName ) ;
300+ fs . writeFileSync (
301+ outputPath ,
302+ `goog.module('${ moduleName } ');\n` +
303+ `// Export an empty object of unknown type to allow imports.\n` +
304+ `// TODO: use typeof once available\n` +
305+ `exports = /** @type {?} */ ({});\n` ) ;
306+ }
307+ }
308+ }
309+
310+ if ( bazelOpts . manifest ) {
311+ perfTrace . wrap ( 'manifest' , ( ) => {
312+ const manifest =
313+ constructManifest ( emitResult . modulesManifest , compilerHost ) ;
314+ fs . writeFileSync ( bazelOpts . manifest , manifest ) ;
315+ } ) ;
316+ }
317+
318+ return diagnostics ;
319+ }
320+
321+ /**
322+ * Creates directories subdir (a slash separated relative path) starting from
323+ * base.
324+ */
325+ function mkdirp ( base : string , subdir : string ) {
326+ const steps = subdir . split ( path . sep ) ;
327+ let current = base ;
328+ for ( let i = 0 ; i < steps . length ; i ++ ) {
329+ current = path . join ( current , steps [ i ] ) ;
330+ if ( ! fs . existsSync ( current ) ) fs . mkdirSync ( current ) ;
331+ }
332+ }
333+
247334
248335if ( require . main === module ) {
249336 // Do not call process.exit(), as that terminates the binary before
0 commit comments