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('/// ');
+ });
+ });
});