From aa48810d80325d481b8a65f402388a69f10d71c1 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 5 Dec 2018 16:05:29 -0800 Subject: [PATCH] feat(ivy): generate flat module index files (#27497) Previously, ngtsc did not respect the angularCompilerOptions settings for generating flat module indices. This commit adds a FlatIndexGenerator which is used to implement those options. FW-738 #resolve PR Close #27497 --- packages/compiler-cli/src/ngtsc/program.ts | 31 +++++++- .../compiler-cli/src/ngtsc/shims/index.ts | 3 +- .../ngtsc/shims/src/flat_index_generator.ts | 70 +++++++++++++++++++ .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 25 +++++++ 4 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 packages/compiler-cli/src/ngtsc/shims/src/flat_index_generator.ts diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index 62c4e0309d..fe17878985 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -17,7 +17,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato import {BaseDefDecoratorHandler} from './annotations/src/base_def'; import {TypeScriptReflectionHost} from './metadata'; import {FileResourceLoader, HostResourceLoader} from './resource_loader'; -import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, SummaryGenerator, generatedFactoryTransform} from './shims'; +import {FactoryGenerator, FactoryInfo, FlatIndexGenerator, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, generatedFactoryTransform} from './shims'; import {ivySwitchTransform} from './switch'; import {IvyCompilation, ivyTransformFactory} from './transform'; import {TypeCheckContext, TypeCheckProgramHost} from './typecheck'; @@ -56,6 +56,8 @@ export class NgtscProgram implements api.Program { const shouldGenerateShims = options.allowEmptyCodegenFiles || false; this.host = host; let rootFiles = [...rootNames]; + + const generators: ShimGenerator[] = []; if (shouldGenerateShims) { // Summary generation. const summaryGenerator = SummaryGenerator.forRootFiles(rootNames); @@ -73,7 +75,32 @@ export class NgtscProgram implements api.Program { const factoryFileNames = Array.from(factoryFileMap.keys()); rootFiles.push(...factoryFileNames, ...summaryGenerator.getSummaryFileNames()); - this.host = new GeneratedShimsHostWrapper(host, [summaryGenerator, factoryGenerator]); + generators.push(summaryGenerator, factoryGenerator); + } + + if (options.flatModuleOutFile !== undefined) { + const flatModuleId = options.flatModuleId || null; + const flatIndexGenerator = + FlatIndexGenerator.forRootFiles(options.flatModuleOutFile, rootNames, flatModuleId); + if (flatIndexGenerator !== null) { + generators.push(flatIndexGenerator); + rootFiles.push(flatIndexGenerator.flatIndexPath); + } else { + // This error message talks specifically about having a single .ts file in "files". However + // the actual logic is a bit more permissive. If a single file exists, that will be taken, + // otherwise the highest level (shortest path) "index.ts" file will be used as the flat + // module entry point instead. If neither of these conditions apply, the error below is + // given. + // + // The user is not informed about the "index.ts" option as this behavior is deprecated - + // an explicit entrypoint should always be specified. + throw new Error( + 'Angular compiler option "flatModuleIndex" requires one and only one .ts file in the "files" field.'); + } + } + + if (generators.length > 0) { + this.host = new GeneratedShimsHostWrapper(host, generators); } this.tsProgram = diff --git a/packages/compiler-cli/src/ngtsc/shims/index.ts b/packages/compiler-cli/src/ngtsc/shims/index.ts index 3980a4362c..115b80d8f9 100644 --- a/packages/compiler-cli/src/ngtsc/shims/index.ts +++ b/packages/compiler-cli/src/ngtsc/shims/index.ts @@ -9,5 +9,6 @@ /// export {FactoryGenerator, FactoryInfo, generatedFactoryTransform} from './src/factory_generator'; -export {GeneratedShimsHostWrapper} from './src/host'; +export {FlatIndexGenerator} from './src/flat_index_generator'; +export {GeneratedShimsHostWrapper, ShimGenerator} from './src/host'; export {SummaryGenerator} from './src/summary_generator'; diff --git a/packages/compiler-cli/src/ngtsc/shims/src/flat_index_generator.ts b/packages/compiler-cli/src/ngtsc/shims/src/flat_index_generator.ts new file mode 100644 index 0000000000..f44296b836 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/shims/src/flat_index_generator.ts @@ -0,0 +1,70 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import * as path from 'path'; +import * as ts from 'typescript'; + +import {ShimGenerator} from './host'; +import {isNonDeclarationTsFile} from './util'; + +export class FlatIndexGenerator implements ShimGenerator { + readonly flatIndexPath: string; + + private constructor( + relativeFlatIndexPath: string, readonly entryPoint: string, + readonly moduleName: string|null) { + this.flatIndexPath = path.posix.join(path.posix.dirname(entryPoint), relativeFlatIndexPath) + .replace(/\.js$/, '') + + '.ts'; + } + + static forRootFiles(flatIndexPath: string, files: ReadonlyArray, moduleName: string|null): + FlatIndexGenerator|null { + // If there's only one .ts file in the program, it's the entry. Otherwise, look for the shortest + // (in terms of characters in the filename) file that ends in /index.ts. The second behavior is + // deprecated; users should always explicitly specify a single .ts entrypoint. + const tsFiles = files.filter(isNonDeclarationTsFile); + if (tsFiles.length === 1) { + return new FlatIndexGenerator(flatIndexPath, tsFiles[0], moduleName); + } else { + let indexFile: string|null = null; + for (const tsFile of tsFiles) { + if (tsFile.endsWith('/index.ts') && + (indexFile === null || tsFile.length <= indexFile.length)) { + indexFile = tsFile; + } + } + if (indexFile !== null) { + return new FlatIndexGenerator(flatIndexPath, indexFile, moduleName); + } else { + return null; + } + } + } + + recognize(fileName: string): boolean { return fileName === this.flatIndexPath; } + + generate(): ts.SourceFile { + const relativeEntryPoint = './' + + path.posix.relative(path.posix.dirname(this.flatIndexPath), this.entryPoint) + .replace(/\.tsx?$/, ''); + + const contents = `/** + * Generated bundle index. Do not edit. + */ + +export * from '${relativeEntryPoint}'; +`; + const genFile = ts.createSourceFile( + this.flatIndexPath, contents, ts.ScriptTarget.ES2015, true, ts.ScriptKind.TS); + if (this.moduleName !== null) { + genFile.moduleName = this.moduleName; + } + return genFile; + } +} diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index d9d944025c..94b1d6df9b 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -1132,4 +1132,29 @@ describe('ngtsc behavioral tests', () => { const jsContents = env.getContents('test.js'); expect(jsContents).toMatch(/directives: \[i1\.ExternalDir\]/); }); + + describe('flat module indices', () => { + it('should generate a basic flat module index', () => { + env.tsconfig({ + 'flatModuleOutFile': 'flat.js', + }); + env.write('test.ts', 'export const TEST = "this is a test";'); + + env.driveMain(); + const jsContents = env.getContents('flat.js'); + expect(jsContents).toContain('export * from \'./test\';'); + }); + + it('should generate a flat module with an id', () => { + env.tsconfig({ + 'flatModuleOutFile': 'flat.js', + 'flatModuleId': '@mymodule', + }); + env.write('test.ts', 'export const TEST = "this is a test";'); + + env.driveMain(); + const dtsContents = env.getContents('flat.d.ts'); + expect(dtsContents).toContain('/// '); + }); + }); });