Some tools (such as Language Server and ng-packagr) only care about the processed typings generated by ngcc. Forcing these tools to process the JavaScript files as well has two disadvantages: First, unnecessary work is being done, which is time consuming. But more importantly, it is not always possible to know how the final bundling tools will want the processed JavaScript to be configured. For example, the CLI would prefer the `--create-ivy-entry-points` option but this would break non-CLI build tooling. This commit adds a new option (`--typings-only` on the command line, and `typingsOnly` via programmatic API) that instructs ngcc to only render changes to the typings files for the entry-points that it finds, and not to write any JavaScript files. In order to process the typings, a JavaScript format will need to be analysed, but it will not be rendered to disk. When using this option, it is best to offer ngcc a wide range of possible JavaScript formats to choose from, and it will use the first format that it finds. Ideally you would configure it to try the `ES2015` FESM format first, since this will be the most performant. Fixes #40969 PR Close #40976
		
			
				
	
	
		
			423 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			423 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC 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 {absoluteFrom, getFileSystem} from '../../../src/ngtsc/file_system';
 | |
| import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
 | |
| import {loadTestFiles} from '../../../src/ngtsc/testing';
 | |
| import {DtsProcessing} from '../../src/execution/tasks/api';
 | |
| import {EntryPoint} from '../../src/packages/entry_point';
 | |
| import {makeEntryPointBundle} from '../../src/packages/entry_point_bundle';
 | |
| import {createModuleResolutionCache, SharedFileCache} from '../../src/packages/source_file_cache';
 | |
| 
 | |
| runInEachFileSystem(() => {
 | |
|   describe('entry point bundle', () => {
 | |
|     let _: typeof absoluteFrom;
 | |
|     beforeEach(() => {
 | |
|       _ = absoluteFrom;
 | |
|     });
 | |
| 
 | |
|     function setupMockFileSystem(): void {
 | |
|       loadTestFiles([
 | |
|         {
 | |
|           name: _('/node_modules/test/package.json'),
 | |
|           contents:
 | |
|               '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
 | |
|         },
 | |
|         {name: _('/node_modules/test/index.d.ts'), contents: 'export * from "./public_api";'},
 | |
|         {name: _('/node_modules/test/index.js'), contents: 'export * from "./public_api";'},
 | |
|         {name: _('/node_modules/test/index.metadata.json'), contents: '...'},
 | |
|         {
 | |
|           name: _('/node_modules/test/public_api.d.ts'),
 | |
|           contents: `
 | |
|         // Note: no import from './internal';
 | |
|         export * from "test/secondary";
 | |
|         export * from "./nested";
 | |
|         export declare class TestClass {};
 | |
|       `
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/public_api.js'),
 | |
|           contents: `
 | |
|         import {internal} from './internal';
 | |
|         export * from "test/secondary";
 | |
|         export * from "./nested";
 | |
|         export const TestClass = function() {};
 | |
|        `
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/root.d.ts'),
 | |
|           contents: `
 | |
|         import * from 'other';
 | |
|         export declare class RootClass {};
 | |
|       `
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/root.js'),
 | |
|           contents: `
 | |
|         import * from 'other';
 | |
|         export const RootClass = function() {};
 | |
|       `
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/internal.d.ts'),
 | |
|           contents: `export declare function internal(): void;`
 | |
|         },
 | |
|         {name: _('/node_modules/test/internal.js'), contents: `export function internal() {}`},
 | |
|         {name: _('/node_modules/test/nested/index.d.ts'), contents: 'export * from "../root";'},
 | |
|         {name: _('/node_modules/test/nested/index.js'), contents: 'export * from "../root";'},
 | |
|         {name: _('/node_modules/test/es2015/index.js'), contents: 'export * from "./public_api";'},
 | |
|         {
 | |
|           name: _('/node_modules/test/es2015/public_api.js'),
 | |
|           contents: 'export class TestClass {};'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/es2015/root.js'),
 | |
|           contents: `
 | |
|           import * from 'other';
 | |
|           export class RootClass {};
 | |
|         `
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/es2015/nested/index.js'),
 | |
|           contents: 'export * from "../root";'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/package.json'),
 | |
|           contents:
 | |
|               '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/index.d.ts'),
 | |
|           contents: 'export * from "./public_api";'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/index.js'),
 | |
|           contents: 'export * from "./public_api";'
 | |
|         },
 | |
|         {name: _('/node_modules/test/secondary/index.metadata.json'), contents: '...'},
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/public_api.d.ts'),
 | |
|           contents: 'export declare class SecondaryClass {};'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/public_api.js'),
 | |
|           contents: 'export class SecondaryClass {};'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/es2015/index.js'),
 | |
|           contents: 'export * from "./public_api";'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/secondary/es2015/public_api.js'),
 | |
|           contents: 'export class SecondaryClass {};'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/other/package.json'),
 | |
|           contents:
 | |
|               '{"module": "./index.js", "es2015": "./es2015/index.js", "typings": "./index.d.ts"}'
 | |
|         },
 | |
|         {name: _('/node_modules/other/index.d.ts'), contents: 'export * from "./public_api";'},
 | |
|         {name: _('/node_modules/other/index.js'), contents: 'export * from "./public_api";'},
 | |
|         {name: _('/node_modules/other/index.metadata.json'), contents: '...'},
 | |
|         {
 | |
|           name: _('/node_modules/other/public_api.d.ts'),
 | |
|           contents: 'export declare class OtherClass {};'
 | |
|         },
 | |
|         {name: _('/node_modules/other/public_api.js'), contents: 'export class OtherClass {};'},
 | |
|         {name: _('/node_modules/other/es2015/index.js'), contents: 'export * from "./public_api";'},
 | |
|         {
 | |
|           name: _('/node_modules/other/es2015/public_api.js'),
 | |
|           contents: 'export class OtherClass {};'
 | |
|         },
 | |
| 
 | |
|         // Mimic an AFP package with declaration files in a different tree than the sources
 | |
|         {name: _('/node_modules/internal/index.d.ts'), contents: 'export * from "./src/index";'},
 | |
|         {name: _('/node_modules/internal/src/index.d.ts'), contents: ''},
 | |
|         {
 | |
|           name: _('/node_modules/internal/src/internal.d.ts'),
 | |
|           contents: 'export declare class Internal {}'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/internal/esm2015/index.js'),
 | |
|           contents: 'export * from "./src/index";'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/internal/esm2015/src/index.js'),
 | |
|           contents: 'import {Internal} from "./internal";'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/internal/esm2015/src/internal.js'),
 | |
|           contents: 'export class Internal {}'
 | |
|         },
 | |
| 
 | |
|         // A package with a secondary entry-point that has source files in a different tree
 | |
|         {
 | |
|           name: _('/node_modules/primary/secondary/index.d.ts'),
 | |
|           contents: 'export declare function secondary();'
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/primary/esm2015/secondary/index.js'),
 | |
|           contents: 'export function secondary();'
 | |
|         },
 | |
|       ]);
 | |
|     }
 | |
| 
 | |
|     // https://github.com/angular/angular/issues/29939
 | |
|     it('should resolve JavaScript sources instead of declaration files if they are adjacent',
 | |
|        () => {
 | |
|          setupMockFileSystem();
 | |
|          const fs = getFileSystem();
 | |
|          const entryPoint: EntryPoint = {
 | |
|            name: 'test',
 | |
|            path: absoluteFrom('/node_modules/test'),
 | |
|            packageName: 'test',
 | |
|            packagePath: absoluteFrom('/node_modules/test'),
 | |
|            packageJson: {name: 'test'},
 | |
|            typings: absoluteFrom('/node_modules/test/index.d.ts'),
 | |
|            compiledByAngular: true,
 | |
|            ignoreMissingDependencies: false,
 | |
|            generateDeepReexports: false,
 | |
|          };
 | |
|          const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|          const esm5bundle = makeEntryPointBundle(
 | |
|              fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false,
 | |
|              'esm5', DtsProcessing.Yes);
 | |
| 
 | |
|          expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
 | |
|              .toEqual(jasmine.arrayWithExactContents([
 | |
|                // Modules from the entry-point itself should be source files
 | |
|                '/node_modules/test/index.js',
 | |
|                '/node_modules/test/public_api.js',
 | |
|                '/node_modules/test/nested/index.js',
 | |
|                '/node_modules/test/root.js',
 | |
|                '/node_modules/test/internal.js',
 | |
| 
 | |
|                // Modules from a secondary entry-point should be declaration files
 | |
|                '/node_modules/test/secondary/public_api.d.ts',
 | |
|                '/node_modules/test/secondary/index.d.ts',
 | |
| 
 | |
|                // Modules resolved from "other" should be declaration files
 | |
|                '/node_modules/other/public_api.d.ts',
 | |
|                '/node_modules/other/index.d.ts',
 | |
|              ].map(p => absoluteFrom(p).toString())));
 | |
| 
 | |
|          expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName))
 | |
|              .toEqual(jasmine.arrayWithExactContents([
 | |
|                // All modules in the dts program should be declaration files
 | |
|                '/node_modules/test/index.d.ts',
 | |
|                '/node_modules/test/public_api.d.ts',
 | |
|                '/node_modules/test/nested/index.d.ts',
 | |
|                '/node_modules/test/root.d.ts',
 | |
|                '/node_modules/test/secondary/public_api.d.ts',
 | |
|                '/node_modules/test/secondary/index.d.ts',
 | |
|                '/node_modules/other/public_api.d.ts',
 | |
|                '/node_modules/other/index.d.ts',
 | |
|              ].map(p => absoluteFrom(p).toString())));
 | |
|        });
 | |
| 
 | |
|     it('does not include .js files outside of the package when no .d.ts file is available', () => {
 | |
|       // Declare main "test" package with "entry" entry-point that imports all sorts of
 | |
|       // internal and external modules.
 | |
|       loadTestFiles([
 | |
|         {
 | |
|           name: _('/node_modules/test/entry/package.json'),
 | |
|           contents: `{"name": "test", "main": "./index.js", "typings": "./index.d.ts"}`,
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/entry/index.d.ts'),
 | |
|           contents: `
 | |
|               import 'external-js';
 | |
|               import 'external-ts';
 | |
|               import 'nested-js';
 | |
|               import './local';
 | |
|               import '../package';
 | |
|           `,
 | |
|         },
 | |
|         {
 | |
|           name: _('/node_modules/test/entry/index.js'),
 | |
|           contents: `
 | |
|               import 'external-js';
 | |
|               import 'external-ts';
 | |
|               import 'nested-js';
 | |
|               import './local';
 | |
|               import '../package';
 | |
|             `,
 | |
|         },
 | |
|         {name: _('/node_modules/test/entry/local.d.ts'), contents: `export {};`},
 | |
|         {name: _('/node_modules/test/entry/local.js'), contents: `export {};`},
 | |
|         {name: _('/node_modules/test/package.d.ts'), contents: `export {};`},
 | |
|         {name: _('/node_modules/test/package.js'), contents: `export {};`},
 | |
|       ]);
 | |
| 
 | |
|       // Declare "external-js" package outside of the "test" package without .d.ts files, should
 | |
|       // not be included in the program.
 | |
|       loadTestFiles([
 | |
|         {
 | |
|           name: _('/node_modules/external-js/package.json'),
 | |
|           contents: `{"name": "external-js", "main": "./index.js"}`,
 | |
|         },
 | |
|         {name: _('/node_modules/external-js/index.js'), contents: 'export {};'},
 | |
|       ]);
 | |
| 
 | |
|       // Same as "external-js" but located in a nested node_modules directory, which should also
 | |
|       // not be included in the program.
 | |
|       loadTestFiles([
 | |
|         {
 | |
|           name: _('/node_modules/test/node_modules/nested-js/package.json'),
 | |
|           contents: `{"name": "nested-js", "main": "./index.js"}`,
 | |
|         },
 | |
|         {name: _('/node_modules/test/node_modules/nested-js/index.js'), contents: 'export {}'},
 | |
|       ]);
 | |
| 
 | |
|       // Declare "external-ts" which does have .d.ts files, so the .d.ts should be
 | |
|       // loaded into the program.
 | |
|       loadTestFiles([
 | |
|         {
 | |
|           name: _('/node_modules/external-ts/package.json'),
 | |
|           contents: `{"name": "external-ts", "main": "./index.js", "typings": "./index.d.ts"}`,
 | |
|         },
 | |
|         {name: _('/node_modules/external-ts/index.d.ts'), contents: 'export {};'},
 | |
|         {name: _('/node_modules/external-ts/index.js'), contents: 'export {};'},
 | |
|       ]);
 | |
| 
 | |
|       const fs = getFileSystem();
 | |
|       const entryPoint: EntryPoint = {
 | |
|         name: 'test/entry',
 | |
|         path: absoluteFrom('/node_modules/test/entry'),
 | |
|         packageName: 'test',
 | |
|         packagePath: absoluteFrom('/node_modules/test'),
 | |
|         packageJson: {name: 'test/entry'},
 | |
|         typings: absoluteFrom('/node_modules/test/entry/index.d.ts'),
 | |
|         compiledByAngular: true,
 | |
|         ignoreMissingDependencies: false,
 | |
|         generateDeepReexports: false,
 | |
|       };
 | |
|       const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|       const esm5bundle = makeEntryPointBundle(
 | |
|           fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false,
 | |
|           'esm5', DtsProcessing.Yes,
 | |
|           /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
 | |
| 
 | |
|       expect(esm5bundle.src.program.getSourceFiles().map(sf => _(sf.fileName)))
 | |
|           .toEqual(jasmine.arrayWithExactContents([
 | |
|             _('/node_modules/test/entry/index.js'),
 | |
|             _('/node_modules/test/entry/local.js'),
 | |
|             _('/node_modules/test/package.js'),
 | |
|             _('/node_modules/external-ts/index.d.ts'),
 | |
|           ]));
 | |
|       expect(esm5bundle.dts!.program.getSourceFiles().map(sf => _(sf.fileName)))
 | |
|           .toEqual(jasmine.arrayWithExactContents([
 | |
|             _('/node_modules/test/entry/index.d.ts'),
 | |
|             _('/node_modules/test/entry/local.d.ts'),
 | |
|             _('/node_modules/test/package.d.ts'),
 | |
|             _('/node_modules/external-ts/index.d.ts'),
 | |
|           ]));
 | |
|     });
 | |
| 
 | |
|     describe(
 | |
|         'including equivalently named, internally imported, src files in the typings program',
 | |
|         () => {
 | |
|           it('does include internal .d.ts files if `mirrorDtsFromSrc` is true', () => {
 | |
|             setupMockFileSystem();
 | |
|             const fs = getFileSystem();
 | |
|             const entryPoint: EntryPoint = {
 | |
|               name: 'test',
 | |
|               path: absoluteFrom('/node_modules/test'),
 | |
|               packageName: 'test',
 | |
|               packagePath: absoluteFrom('/node_modules/test'),
 | |
|               packageJson: {name: 'test'},
 | |
|               typings: absoluteFrom('/node_modules/test/index.d.ts'),
 | |
|               compiledByAngular: true,
 | |
|               ignoreMissingDependencies: false,
 | |
|               generateDeepReexports: false,
 | |
|             };
 | |
|             const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|             const esm5bundle = makeEntryPointBundle(
 | |
|                 fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false,
 | |
|                 'esm5', DtsProcessing.Yes,
 | |
|                 /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
 | |
|             expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .toContain(absoluteFrom('/node_modules/test/internal.js'));
 | |
|             expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .toContain(absoluteFrom('/node_modules/test/internal.d.ts'));
 | |
|           });
 | |
| 
 | |
|           it('should work when the .d.ts files are in a different tree than the sources', () => {
 | |
|             setupMockFileSystem();
 | |
|             const fs = getFileSystem();
 | |
|             const entryPoint: EntryPoint = {
 | |
|               name: 'internal',
 | |
|               path: absoluteFrom('/node_modules/internal'),
 | |
|               packageName: 'internal',
 | |
|               packagePath: absoluteFrom('/node_modules/internal'),
 | |
|               packageJson: {name: 'internal'},
 | |
|               typings: absoluteFrom('/node_modules/internal/index.d.ts'),
 | |
|               compiledByAngular: true,
 | |
|               ignoreMissingDependencies: false,
 | |
|               generateDeepReexports: false,
 | |
|             };
 | |
|             const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|             const esm5bundle = makeEntryPointBundle(
 | |
|                 fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache,
 | |
|                 './esm2015/index.js', false, 'esm2015', DtsProcessing.Yes,
 | |
|                 /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
 | |
|             expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .toContain(absoluteFrom('/node_modules/internal/esm2015/src/internal.js'));
 | |
|             expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .toContain(absoluteFrom('/node_modules/internal/src/internal.d.ts'));
 | |
|           });
 | |
| 
 | |
|           it('ignores internal .d.ts files if `mirrorDtsFromSrc` is false', () => {
 | |
|             setupMockFileSystem();
 | |
|             const fs = getFileSystem();
 | |
|             const entryPoint: EntryPoint = {
 | |
|               name: 'test',
 | |
|               path: absoluteFrom('/node_modules/test'),
 | |
|               packageName: 'test',
 | |
|               packagePath: absoluteFrom('/node_modules/test'),
 | |
|               packageJson: {name: 'test'},
 | |
|               typings: absoluteFrom('/node_modules/test/index.d.ts'),
 | |
|               compiledByAngular: true,
 | |
|               ignoreMissingDependencies: false,
 | |
|               generateDeepReexports: false,
 | |
|             };
 | |
|             const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|             const esm5bundle = makeEntryPointBundle(
 | |
|                 fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false,
 | |
|                 'esm5', DtsProcessing.Yes,
 | |
|                 /* pathMappings */ undefined, /* mirrorDtsFromSrc */ false);
 | |
|             expect(esm5bundle.src.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .toContain(absoluteFrom('/node_modules/test/internal.js'));
 | |
|             expect(esm5bundle.dts!.program.getSourceFiles().map(sf => sf.fileName))
 | |
|                 .not.toContain(absoluteFrom('/node_modules/test/internal.d.ts'));
 | |
|           });
 | |
|         });
 | |
| 
 | |
|     it('should set the `rootDir` to the package path not the entry-point path', () => {
 | |
|       setupMockFileSystem();
 | |
|       const fs = getFileSystem();
 | |
|       const entryPoint: EntryPoint = {
 | |
|         name: 'secondary',
 | |
|         path: absoluteFrom('/node_modules/primary/secondary'),
 | |
|         packageName: 'primary',
 | |
|         packagePath: absoluteFrom('/node_modules/primary'),
 | |
|         packageJson: {name: 'secondary'},
 | |
|         typings: absoluteFrom('/node_modules/primary/secondary/index.d.ts'),
 | |
|         compiledByAngular: true,
 | |
|         ignoreMissingDependencies: false,
 | |
|         generateDeepReexports: false,
 | |
|       };
 | |
|       const moduleResolutionCache = createModuleResolutionCache(fs);
 | |
|       const bundle = makeEntryPointBundle(
 | |
|           fs, entryPoint, new SharedFileCache(fs), moduleResolutionCache, './index.js', false,
 | |
|           'esm2015', DtsProcessing.Yes,
 | |
|           /* pathMappings */ undefined, /* mirrorDtsFromSrc */ true);
 | |
|       expect(bundle.rootDirs).toEqual([absoluteFrom('/node_modules/primary')]);
 | |
|     });
 | |
|   });
 | |
| });
 |