angular-cn/packages/compiler-cli/test/ngtsc/incremental_spec.ts

808 lines
28 KiB
TypeScript
Raw Normal View History

/**
* @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
*/
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
import {loadStandardTestFiles} from '../helpers/src/mock_file_loading';
import {NgtscTestEnvironment} from './env';
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
const testFiles = loadStandardTestFiles();
runInEachFileSystem(() => {
describe('ngtsc incremental compilation', () => {
let env!: NgtscTestEnvironment;
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
beforeEach(() => {
env = NgtscTestEnvironment.setup(testFiles);
env.enableMultipleCompilations();
env.tsconfig();
});
it('should not crash if CLI does not provide getModifiedResourceFiles()', () => {
env.write('component1.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp', templateUrl: './component1.template.html'})
export class Cmp1 {}
`);
env.write('component1.template.html', 'cmp1');
env.driveMain();
// Simulate a change to `component1.html`
env.flushWrittenFileTracking();
env.invalidateCachedFile('component1.html');
env.simulateLegacyCLICompilerHost();
env.driveMain();
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
it('should skip unchanged services', () => {
env.write('service.ts', `
import {Injectable} from '@angular/core';
@Injectable()
export class Service {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('test.ts', `
import {Component} from '@angular/core';
import {Service} from './service';
@Component({selector: 'cmp', template: 'cmp'})
export class Cmp {
constructor(service: Service) {}
}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.driveMain();
env.flushWrittenFileTracking();
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
// Pretend a change was made to test.ts.
env.invalidateCachedFile('test.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
// The changed file should be recompiled, but not the service.
expect(written).toContain('/test.js');
expect(written).not.toContain('/service.js');
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
it('should rebuild components that have changed', () => {
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
env.tsconfig({strictTemplates: true});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('component1.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp', template: 'cmp'})
export class Cmp1 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('component2.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp2', template: 'cmp'})
export class Cmp2 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.driveMain();
// Pretend a change was made to Cmp1
env.flushWrittenFileTracking();
env.invalidateCachedFile('component1.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).toContain('/component1.js');
expect(written).not.toContain('/component2.js');
});
it('should rebuild components whose templates have changed', () => {
env.write('component1.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp', templateUrl: './component1.template.html'})
export class Cmp1 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('component1.template.html', 'cmp1');
env.write('component2.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp2', templateUrl: './component2.template.html'})
export class Cmp2 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('component2.template.html', 'cmp2');
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.driveMain();
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
// Make a change to Cmp1 template
env.flushWrittenFileTracking();
env.write('component1.template.html', 'changed');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).toContain('/component1.js');
expect(written).not.toContain('/component2.js');
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
it('should rebuild components whose partial-evaluation dependencies have changed', () => {
env.write('component1.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp', template: 'cmp'})
export class Cmp1 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('component2.ts', `
import {Component} from '@angular/core';
import {SELECTOR} from './constants';
@Component({selector: SELECTOR, template: 'cmp'})
export class Cmp2 {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('constants.ts', `
export const SELECTOR = 'cmp';
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.driveMain();
// Pretend a change was made to SELECTOR
env.flushWrittenFileTracking();
env.invalidateCachedFile('constants.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).toContain('/constants.js');
expect(written).not.toContain('/component1.js');
expect(written).toContain('/component2.js');
});
it('should rebuild components whose imported dependencies have changed', () => {
setupFooBarProgram(env);
// Pretend a change was made to BarDir.
env.invalidateCachedFile('bar_directive.ts');
env.driveMain();
let written = env.getFilesWrittenSinceLastFlush();
expect(written).toContain('/bar_directive.js');
expect(written).toContain('/bar_component.js');
expect(written).toContain('/bar_module.js');
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 19:03:17 -05:00
expect(written).toContain('/foo_component.js');
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
expect(written).not.toContain('/foo_pipe.js');
expect(written).not.toContain('/foo_module.js');
});
// https://github.com/angular/angular/issues/32416
it('should rebuild full NgModule scope when a dependency of a declaration has changed', () => {
env.write('component1.ts', `
import {Component} from '@angular/core';
import {SELECTOR} from './dep';
@Component({selector: SELECTOR, template: 'cmp'})
export class Cmp1 {}
`);
env.write('component2.ts', `
import {Component} from '@angular/core';
@Component({selector: 'cmp2', template: 'cmp2'})
export class Cmp2 {}
`);
env.write('dep.ts', `
export const SELECTOR = 'cmp';
`);
env.write('directive.ts', `
import {Directive} from '@angular/core';
@Directive({selector: 'dir'})
export class Dir {}
`);
env.write('pipe.ts', `
import {Pipe} from '@angular/core';
@Pipe({name: 'myPipe'})
export class MyPipe {}
`);
env.write('module.ts', `
import {NgModule} from '@angular/core';
import {Cmp1} from './component1';
import {Cmp2} from './component2';
import {Dir} from './directive';
import {MyPipe} from './pipe';
@NgModule({declarations: [Cmp1, Cmp2, Dir, MyPipe]})
export class Mod {}
`);
env.driveMain();
// Pretend a change was made to 'dep'. Since this may affect the NgModule scope, like it does
// here if the selector is updated, all components in the module scope need to be recompiled.
env.flushWrittenFileTracking();
env.invalidateCachedFile('dep.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/directive.js');
expect(written).not.toContain('/pipe.js');
expect(written).toContain('/component1.js');
expect(written).toContain('/component2.js');
expect(written).toContain('/dep.js');
expect(written).toContain('/module.js');
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
it('should rebuild components where their NgModule declared dependencies have changed', () => {
setupFooBarProgram(env);
// Pretend a change was made to FooPipe.
env.invalidateCachedFile('foo_pipe.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/bar_directive.js');
expect(written).not.toContain('/bar_component.js');
expect(written).not.toContain('/bar_module.js');
expect(written).toContain('/foo_component.js');
expect(written).toContain('/foo_pipe.js');
expect(written).toContain('/foo_module.js');
});
it('should rebuild components where their NgModule has changed', () => {
setupFooBarProgram(env);
// Pretend a change was made to FooPipe.
env.invalidateCachedFile('foo_module.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/bar_directive.js');
expect(written).not.toContain('/bar_component.js');
expect(written).not.toContain('/bar_module.js');
expect(written).toContain('/foo_component.js');
expect(written).toContain('/foo_pipe.js');
expect(written).toContain('/foo_module.js');
});
it('should rebuild a component if one of its deep NgModule dependencies changes', () => {
// This test constructs a chain of NgModules:
// - EntryModule imports MiddleAModule
// - MiddleAModule exports MiddleBModule
// - MiddleBModule exports DirModule
// The last link (MiddleBModule exports DirModule) is not present initially, but is added
// during a recompilation.
//
// Since the dependency from EntryModule on the contents of MiddleBModule is not "direct"
// (meaning MiddleBModule is not discovered during analysis of EntryModule), this test is
// verifying that NgModule dependency tracking correctly accounts for this situation.
env.write('entry.ts', `
import {Component, NgModule} from '@angular/core';
import {MiddleAModule} from './middle-a';
@Component({
selector: 'test-cmp',
template: '<div dir>',
})
export class TestCmp {}
@NgModule({
declarations: [TestCmp],
imports: [MiddleAModule],
})
export class EntryModule {}
`);
env.write('middle-a.ts', `
import {NgModule} from '@angular/core';
import {MiddleBModule} from './middle-b';
@NgModule({
exports: [MiddleBModule],
})
export class MiddleAModule {}
`);
env.write('middle-b.ts', `
import {NgModule} from '@angular/core';
@NgModule({})
export class MiddleBModule {}
`);
env.write('dir_module.ts', `
import {NgModule} from '@angular/core';
import {Dir} from './dir';
@NgModule({
declarations: [Dir],
exports: [Dir],
})
export class DirModule {}
`);
env.write('dir.ts', `
import {Directive} from '@angular/core';
@Directive({
selector: '[dir]',
})
export class Dir {}
`);
env.driveMain();
expect(env.getContents('entry.js')).not.toContain('Dir');
env.write('middle-b.ts', `
import {NgModule} from '@angular/core';
import {DirModule} from './dir_module';
@NgModule({
exports: [DirModule],
})
export class MiddleBModule {}
`);
env.driveMain();
expect(env.getContents('entry.js')).toContain('Dir');
});
it('should rebuild a component if removed from an NgModule', () => {
// This test consists of a component with a dependency (the directive DepDir) provided via an
// NgModule. Initially this configuration is built, then the component is removed from its
// module (which removes DepDir from the component's scope) and a rebuild is performed.
// The compiler should re-emit the component without DepDir in its scope.
//
// This is a tricky scenario due to the backwards dependency arrow from a component to its
// module.
env.write('dep.ts', `
import {Directive, NgModule} from '@angular/core';
@Directive({selector: '[dep]'})
export class DepDir {}
@NgModule({
declarations: [DepDir],
exports: [DepDir],
})
export class DepModule {}
`);
env.write('cmp.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: '<div dep></div>',
})
export class Cmp {}
`);
env.write('module.ts', `
import {NgModule} from '@angular/core';
import {Cmp} from './cmp';
import {DepModule} from './dep';
@NgModule({
declarations: [Cmp],
imports: [DepModule],
})
export class Module {}
`);
env.driveMain();
env.flushWrittenFileTracking();
// Remove the component from the module and recompile.
env.write('module.ts', `
import {NgModule} from '@angular/core';
import {DepModule} from './dep';
@NgModule({
declarations: [],
imports: [DepModule],
})
export class Module {}
`);
env.driveMain();
// After removing the component from the module, it should have been re-emitted without DepDir
// in its scope.
expect(env.getFilesWrittenSinceLastFlush()).toContain('/cmp.js');
expect(env.getContents('cmp.js')).not.toContain('DepDir');
});
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 19:03:17 -05:00
it('should rebuild only a Component (but with the correct CompilationScope) and its module if its template has changed',
() => {
setupFooBarProgram(env);
// Make a change to the template of BarComponent.
env.write('bar_component.html', '<div bar>changed</div>');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).not.toContain('/bar_directive.js');
expect(written).toContain('/bar_component.js');
perf(ivy): reuse prior analysis work during incremental builds (#34288) Previously, the compiler performed an incremental build by analyzing and resolving all classes in the program (even unchanged ones) and then using the dependency graph information to determine which .js files were stale and needed to be re-emitted. This algorithm produced "correct" rebuilds, but the cost of re-analyzing the entire program turned out to be higher than anticipated, especially for component-heavy compilations. To achieve performant rebuilds, it is necessary to reuse previous analysis results if possible. Doing this safely requires knowing when prior work is viable and when it is stale and needs to be re-done. The new algorithm implemented by this commit is such: 1) Each incremental build starts with knowledge of the last known good dependency graph and analysis results from the last successful build, plus of course information about the set of files changed. 2) The previous dependency graph's information is used to determine the set of source files which have "logically" changed. A source file is considered logically changed if it or any of its dependencies have physically changed (on disk) since the last successful compilation. Any logically unchanged dependencies have their dependency information copied over to the new dependency graph. 3) During the `TraitCompiler`'s loop to consider all source files in the program, if a source file is logically unchanged then its previous analyses are "adopted" (and their 'register' steps are run). If the file is logically changed, then it is re-analyzed as usual. 4) Then, incremental build proceeds as before, with the new dependency graph being used to determine the set of files which require re-emitting. This analysis reuse avoids template parsing operations in many circumstances and significantly reduces the time it takes ngtsc to rebuild a large application. Future work will increase performance even more, by tackling a variety of other opportunities to reuse or avoid work. PR Close #34288
2019-12-05 19:03:17 -05:00
// /bar_module.js should also be re-emitted, because remote scoping of BarComponent might
// have been affected.
expect(written).toContain('/bar_module.js');
expect(written).not.toContain('/foo_component.js');
expect(written).not.toContain('/foo_pipe.js');
expect(written).not.toContain('/foo_module.js');
// Ensure that the used directives are included in the component's generated template.
expect(env.getContents('/built/bar_component.js')).toMatch(/directives:\s*\[.+\.BarDir\]/);
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
it('should rebuild everything if a typings file changes', () => {
setupFooBarProgram(env);
// Pretend a change was made to a typings file.
env.invalidateCachedFile('foo_selector.d.ts');
env.driveMain();
const written = env.getFilesWrittenSinceLastFlush();
expect(written).toContain('/bar_directive.js');
expect(written).toContain('/bar_component.js');
expect(written).toContain('/bar_module.js');
expect(written).toContain('/foo_component.js');
expect(written).toContain('/foo_pipe.js');
expect(written).toContain('/foo_module.js');
});
it('should compile incrementally with template type-checking turned on', () => {
env.tsconfig({ivyTemplateTypeCheck: true});
env.write('main.ts', 'export class Foo {}');
env.driveMain();
env.invalidateCachedFile('main.ts');
env.driveMain();
// If program reuse were configured incorrectly (as was responsible for
// https://github.com/angular/angular/issues/30079), this would have crashed.
});
// https://github.com/angular/angular/issues/38979
it('should retain ambient types provided by auto-discovered @types', () => {
// This test verifies that ambient types declared in node_modules/@types are still available
// in incremental compilations. In the below code, the usage of `require` should be valid
// in the original program and the incremental program.
env.tsconfig({fullTemplateTypeCheck: true});
env.write('node_modules/@types/node/index.d.ts', 'declare var require: any;');
env.write('main.ts', `
import {Component} from '@angular/core';
require('path');
@Component({template: ''})
export class MyComponent {}
`);
env.driveMain();
env.invalidateCachedFile('main.ts');
const diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
// https://github.com/angular/angular/pull/26036
it('should handle redirected source files', () => {
env.tsconfig({fullTemplateTypeCheck: true});
// This file structure has an identical version of "a" under the root node_modules and inside
// of "b". Because their package.json file indicates it is the exact same version of "a",
// TypeScript will transform the source file of "node_modules/b/node_modules/a/index.d.ts"
// into a redirect to "node_modules/a/index.d.ts". During incremental compilations, we must
// assure not to reintroduce "node_modules/b/node_modules/a/index.d.ts" as its redirected
// source file, but instead use its original file.
env.write('node_modules/a/index.js', `export class ServiceA {}`);
env.write('node_modules/a/index.d.ts', `export declare class ServiceA {}`);
env.write('node_modules/a/package.json', `{"name": "a", "version": "1.0"}`);
env.write('node_modules/b/node_modules/a/index.js', `export class ServiceA {}`);
env.write('node_modules/b/node_modules/a/index.d.ts', `export declare class ServiceA {}`);
env.write('node_modules/b/node_modules/a/package.json', `{"name": "a", "version": "1.0"}`);
env.write('node_modules/b/index.js', `export {ServiceA as ServiceB} from 'a';`);
env.write('node_modules/b/index.d.ts', `export {ServiceA as ServiceB} from 'a';`);
env.write('test.ts', `
import {ServiceA} from 'a';
import {ServiceB} from 'b';
`);
env.driveMain();
env.flushWrittenFileTracking();
// Pretend a change was made to test.ts. If redirect sources were introduced into the new
// program, this would fail due to an assertion failure in TS.
env.invalidateCachedFile('test.ts');
env.driveMain();
});
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
describe('template type-checking', () => {
beforeEach(() => {
env.tsconfig({strictTemplates: true});
});
it('should repeat type errors across rebuilds, even if nothing has changed', () => {
// This test verifies that when a project is rebuilt multiple times with no changes, all
// template diagnostics are produced each time. Different types of errors are checked:
// - a general type error
// - an unmatched binding
// - a DOM schema error
env.write('component.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'test-cmp',
template: \`
{{notAProperty}}
<not-a-component></not-a-component>
<div [notMatched]="1"></div>
\`,
})
export class TestCmp {}
`);
let diags = env.driveDiagnostics();
// Should get a diagnostic for each line in the template.
expect(diags.length).toBe(3);
// Now rebuild without any changes, and verify they're still produced.
diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
// If it's worth testing, it's worth overtesting.
//
// Actually, the above only tests the transition from "initial" to "incremental"
// compilation. The next build verifies that an "incremental to incremental" transition
// preserves the diagnostics as well.
diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
});
it('should pick up errors caused by changing an unrelated interface', () => {
// The premise of this test is that `iface.ts` declares an interface which is used to type
// a property of a component. The interface is then changed in a subsequent compilation in
// a way that introduces a type error in the template. The test verifies the resulting
// diagnostic is produced.
env.write('iface.ts', `
export interface SomeType {
field: string;
}
`);
env.write('component.ts', `
import {Component} from '@angular/core';
import {SomeType} from './iface';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@Component({
selector: 'test-cmp',
template: '{{ doSomething(value.field) }}',
})
export class TestCmp {
value!: SomeType;
// Takes a string value only.
doSomething(param: string): string {
return param;
}
}
`);
expect(env.driveDiagnostics().length).toBe(0);
env.flushWrittenFileTracking();
// Change the interface.
env.write('iface.ts', `
export interface SomeType {
field: number;
}
`);
expect(env.driveDiagnostics().length).toBe(1);
});
it('should recompile when a remote change happens to a scope', () => {
// The premise of this test is that the component Cmp has a template error (a binding to an
// unknown property). Cmp is in ModuleA, which imports ModuleB, which declares Dir that has
// the property. Because ModuleB doesn't export Dir, it's not visible to Cmp - hence the
// error.
// In the test, during the incremental rebuild Dir is exported from ModuleB. This is a
// change to the scope of ModuleA made by changing ModuleB (hence, a "remote change"). The
// test verifies that incremental template type-checking.
env.write('cmp.ts', `
import {Component} from '@angular/core';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@Component({
selector: 'test-cmp',
template: '<div dir [someInput]="1"></div>',
})
export class Cmp {}
`);
env.write('module-a.ts', `
import {NgModule} from '@angular/core';
import {Cmp} from './cmp';
import {ModuleB} from './module-b';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@NgModule({
declarations: [Cmp],
imports: [ModuleB],
})
export class ModuleA {}
`);
// Declare ModuleB and a directive Dir, but ModuleB does not yet export Dir.
env.write('module-b.ts', `
import {NgModule} from '@angular/core';
import {Dir} from './dir';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@NgModule({
declarations: [Dir],
})
export class ModuleB {}
`);
env.write('dir.ts', `
import {Directive, Input} from '@angular/core';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@Directive({selector: '[dir]'})
export class Dir {
@Input() someInput!: any;
}
`);
let diags = env.driveDiagnostics();
// Should get a diagnostic about [dir] not being a valid binding.
expect(diags.length).toBe(1);
// As a precautionary check, run the build a second time with no changes, to ensure the
// diagnostic is repeated.
diags = env.driveDiagnostics();
// Should get a diagnostic about [dir] not being a valid binding.
expect(diags.length).toBe(1);
// Modify ModuleB to now export Dir.
env.write('module-b.ts', `
import {NgModule} from '@angular/core';
import {Dir} from './dir';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@NgModule({
declarations: [Dir],
exports: [Dir],
})
export class ModuleB {}
`);
diags = env.driveDiagnostics();
// Diagnostic should be gone, as the error has been fixed.
expect(diags.length).toBe(0);
});
describe('inline operations', () => {
it('should still pick up on errors from inlined type check blocks', () => {
// In certain cases the template type-checker has to inline type checking blocks into user
// code, instead of placing it in a parallel template type-checking file. In these cases
// incremental checking cannot be used, and the type-checking code must be regenerated on
// each build. This test verifies that the above mechanism works properly, by performing
// type-checking on an unexported class (not exporting the class forces the inline
// checking de-optimization).
env.write('cmp.ts', `
import {Component} from '@angular/core';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@Component({
selector: 'test-cmp',
template: '{{test}}',
})
class Cmp {}
`);
// On the first compilation, an error should be produced.
let diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
// Next, two more builds are run, one with no changes made to the file, and the other with
// changes made that should remove the error.
// The error should still be present when rebuilding.
diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
// Now, correct the error by adding the 'test' property to the component.
env.write('cmp.ts', `
import {Component} from '@angular/core';
perf(compiler-cli): perform template type-checking incrementally (#36211) This optimization builds on a lot of prior work to finally make type- checking of templates incremental. Incrementality requires two main components: - the ability to reuse work from a prior compilation. - the ability to know when changes in the current program invalidate that prior work. Prior to this commit, on every type-checking pass the compiler would generate new .ngtypecheck files for each original input file in the program. 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked. 3. (Build #2 main program): throw away old .ngtypecheck files and generate new empty ones. 4. (Build #2 type-check program): same as step 2. With this commit, the `IncrementalDriver` now tracks template type-checking _metadata_ for each input file. The metadata contains information about source mappings for generated type-checking code, as well as some diagnostics which were discovered at type-check analysis time. The actual type-checking code is stored in the TypeScript AST for type-checking files, which is now re-used between programs as follows: 1. (Build #1 main program): empty .ngtypecheck files generated for each original input file. 2. (Build #1 type-check program): .ngtypecheck contents overridden for those which have corresponding components that need type-checked, and the metadata registered in the `IncrementalDriver`. 3. (Build #2 main program): The `TypeCheckShimGenerator` now reuses _all_ .ngtypecheck `ts.SourceFile` shims from build #1's type-check program in the construction of build #2's main program. Some of the contents of these files might be stale (if a component's template changed, for example), but wholesale reuse here prevents unnecessary changes in the contents of the program at this point and makes TypeScript's job a lot easier. 4. (Build #2 type-check program): For those input files which have not "logically changed" (meaning components within are semantically the same as they were before), the compiler will re-use the type-check file metadata from build #1, and _not_ generate a new .ngtypecheck shim. For components which have logically changed or where the previous .ngtypecheck contents cannot otherwise be reused, code generation happens as before. PR Close #36211
2020-03-19 14:29:58 -04:00
@Component({
selector: 'test-cmp',
template: '{{test}}',
})
class Cmp {
test!: string;
}
`);
env.driveMain();
// The error should be gone.
diags = env.driveDiagnostics();
expect(diags.length).toBe(0);
});
it('should still pick up on errors caused by inlined type constructors', () => {
// In certain cases the template type-checker cannot generate a type constructor for a
// directive within the template type-checking file which requires it, but must inline the
// type constructor within its original source file. In these cases, template type
// checking cannot be performed incrementally. This test verifies that such cases still
// work correctly, by repeatedly performing diagnostics on a component which depends on a
// directive with an inlined type constructor.
env.write('dir.ts', `
import {Directive, Input} from '@angular/core';
export interface Keys {
alpha: string;
beta: string;
}
@Directive({
selector: '[dir]'
})
export class Dir<T extends keyof Keys> {
// The use of 'keyof' in the generic bound causes a deopt to an inline type
// constructor.
@Input() dir: T;
}
`);
env.write('cmp.ts', `
import {Component, NgModule} from '@angular/core';
import {Dir} from './dir';
@Component({
selector: 'test-cmp',
template: '<div dir="gamma"></div>',
})
export class Cmp {}
@NgModule({
declarations: [Cmp, Dir],
})
export class Module {}
`);
let diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText).toContain('"alpha" | "beta"');
// On a rebuild, the same errors should be present.
diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(diags[0].messageText).toContain('"alpha" | "beta"');
});
});
});
});
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
function setupFooBarProgram(env: NgtscTestEnvironment) {
env.write('foo_component.ts', `
import {Component} from '@angular/core';
import {fooSelector} from './foo_selector';
@Component({selector: fooSelector, template: 'foo'})
export class FooCmp {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('foo_pipe.ts', `
import {Pipe} from '@angular/core';
@Pipe({name: 'foo'})
export class FooPipe {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('foo_module.ts', `
import {NgModule} from '@angular/core';
import {FooCmp} from './foo_component';
import {FooPipe} from './foo_pipe';
import {BarModule} from './bar_module';
@NgModule({
declarations: [FooCmp, FooPipe],
imports: [BarModule],
})
export class FooModule {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('bar_component.ts', `
import {Component} from '@angular/core';
@Component({selector: 'bar', templateUrl: './bar_component.html'})
export class BarCmp {}
`);
env.write('bar_component.html', '<div bar></div>');
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('bar_directive.ts', `
import {Directive} from '@angular/core';
@Directive({selector: '[bar]'})
export class BarDir {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('bar_module.ts', `
import {NgModule} from '@angular/core';
import {BarCmp} from './bar_component';
import {BarDir} from './bar_directive';
@NgModule({
declarations: [BarCmp, BarDir],
exports: [BarCmp],
})
export class BarModule {}
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.write('foo_selector.d.ts', `
export const fooSelector = 'foo';
`);
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 15:22:32 -04:00
env.driveMain();
env.flushWrittenFileTracking();
}
});