mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
test(ngcc): don't use the workspace typescript version to generate integration test fixtures (#44448)
Now that ViewEngine libraries can no longer be created, the latest TypeScript version that ngcc should be able to process is TypeScript 4.3, i.e. the version of TypeScript that was supported in Angular 12. However, ngcc's integration tests used the TypeScript version of the workspace to create the JavaScript files from TypeScript sources on demand. This introduces friction when upgrading the TypeScript version within the workspace, as changes to TypeScript's emit format may affect ngcc's ability to process it correctly. The on demand creation of JavaScript files was convenient for authoring tests, but it also helped to detect incompatibilities with newer versions of TypeScript. Now that ngcc no longer has to process newer versions of TypeScript, we want to pin the integration suite to use JavaScript code as if it were compiled using TypeScript 4.3, i.e. a version of TypeScript that is actually supported by ngcc. This commit updates the integration test to inline all the generated files directly in the tests, instead of compiling them on demand. This was done by temporarily installing TypeScript 4.3 and using it to create the `loadTestFiles` statement from the original TypeScript inputs. An alternative could have been to install TypeScript 4.3 as an actual dependency in the workspace and using that to continue compiling the integration suite on demand, but this brings some overhead in package installations (TypeScript is ~60MB) and the authoring aspect of ngcc integration test is expected to diminish, now that ngcc's support is no longer a moving target. PR Close #44448
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -6,224 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import ts from 'typescript';
|
||||
|
||||
import {AbsoluteFsPath, FileSystem, getFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {Folder, MockFileSystemPosix} from '../../../src/ngtsc/file_system/testing';
|
||||
|
||||
import {loadTestDirectory, loadTsLib, resolveNpmTreeArtifact} from '../../../src/ngtsc/testing';
|
||||
|
||||
export type PackageSources = {
|
||||
[path: string]: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Instead of writing packaged code by hand, and manually describing the layout of the package, this
|
||||
* function transpiles the TypeScript sources into a flat file structure using the ES5 format. In
|
||||
* this package layout, all compiled sources are at the root of the package, with `.d.ts` files next
|
||||
* to the `.js` files. Each `.js` also has a corresponding `.metadata.json` file alongside with it.
|
||||
*
|
||||
* All generated code is written into the `node_modules` in the top-level filesystem, ready for use
|
||||
* in testing ngcc.
|
||||
*
|
||||
* @param pkgName The name of the package to compile.
|
||||
* @param sources The TypeScript sources to compile.
|
||||
*/
|
||||
export function compileIntoFlatEs5Package(pkgName: string, sources: PackageSources): void {
|
||||
compileIntoFlatPackage(pkgName, sources, {
|
||||
target: ts.ScriptTarget.ES5,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
formatProperty: 'esm5',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of writing packaged code by hand, and manually describing the layout of the package,
|
||||
* this function transpiles the TypeScript sources into a flat file structure using the ES2015
|
||||
* format. In this package layout, all compiled sources are at the root of the package, with
|
||||
* `.d.ts` files next to the `.js` files. Each `.js` also has a corresponding `.metadata.json`
|
||||
* file alongside with it.
|
||||
*
|
||||
* All generated code is written into the `node_modules` in the top-level filesystem, ready for use
|
||||
* in testing ngcc.
|
||||
*
|
||||
* @param pkgName The name of the package to compile.
|
||||
* @param sources The TypeScript sources to compile.
|
||||
*/
|
||||
export function compileIntoFlatEs2015Package(pkgName: string, sources: PackageSources): void {
|
||||
compileIntoFlatPackage(pkgName, sources, {
|
||||
target: ts.ScriptTarget.ES2015,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
formatProperty: 'esm2015',
|
||||
});
|
||||
}
|
||||
|
||||
export interface FlatLayoutOptions {
|
||||
/**
|
||||
* The script target version to compile into.
|
||||
*/
|
||||
target: ts.ScriptTarget;
|
||||
|
||||
/**
|
||||
* The module kind to use in the compiled result.
|
||||
*/
|
||||
module: ts.ModuleKind;
|
||||
|
||||
/**
|
||||
* The name of the property in package.json that refers to the root source file.
|
||||
*/
|
||||
formatProperty: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of writing packaged code by hand, and manually describing the layout of the package, this
|
||||
* function transpiles the TypeScript sources into a flat file structure using a single format. In
|
||||
* this package layout, all compiled sources are at the root of the package, with `.d.ts` files next
|
||||
* to the `.js` files. Each `.js` also has a corresponding `.metadata.json` file alongside with it.
|
||||
*
|
||||
* All generated code is written into the `node_modules` in the top-level filesystem, ready for use
|
||||
* in testing ngcc.
|
||||
*
|
||||
* @param pkgName The name of the package to compile.
|
||||
* @param sources The TypeScript sources to compile.
|
||||
* @param options Allows for configuration of how the sources are compiled.
|
||||
*/
|
||||
function compileIntoFlatPackage(
|
||||
pkgName: string, sources: PackageSources, options: FlatLayoutOptions): void {
|
||||
const fs = getFileSystem();
|
||||
const {rootNames, compileFs} = setupCompileFs(sources);
|
||||
|
||||
const emit = (options: ts.CompilerOptions) => {
|
||||
const host = new MockCompilerHost(compileFs);
|
||||
const program = ts.createProgram({host, rootNames, options});
|
||||
program.emit();
|
||||
};
|
||||
|
||||
emit({
|
||||
declaration: true,
|
||||
emitDecoratorMetadata: true,
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
module: options.module,
|
||||
target: options.target,
|
||||
lib: [],
|
||||
});
|
||||
|
||||
// Copy over the JS and .d.ts files, and add a .metadata.json for each .d.ts file.
|
||||
for (const file of rootNames) {
|
||||
const inFileBase = file.replace(/\.ts$/, '');
|
||||
|
||||
const dtsContents = compileFs.readFile(compileFs.resolve(`/${inFileBase}.d.ts`));
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/${inFileBase}.d.ts`), dtsContents);
|
||||
|
||||
const jsContents = compileFs.readFile(compileFs.resolve(`/${inFileBase}.js`));
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/${inFileBase}.js`), jsContents);
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/${inFileBase}.metadata.json`), '{}');
|
||||
}
|
||||
|
||||
// Write the package.json
|
||||
const pkgJson: unknown = {
|
||||
name: pkgName,
|
||||
version: '0.0.1',
|
||||
[options.formatProperty]: './index.js',
|
||||
typings: './index.d.ts',
|
||||
};
|
||||
|
||||
fs.writeFile(
|
||||
fs.resolve(`/node_modules/${pkgName}/package.json`), JSON.stringify(pkgJson, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Instead of writing packaged code by hand, and manually describing the layout of the package, this
|
||||
* function transpiles the TypeScript sources into a package layout that of Angular Package Format.
|
||||
* Both esm2015 and esm5 bundles are present in this layout. The .d.ts files reside in the /src
|
||||
* directory and a public .d.ts file is present in the root, re-exporting /src/index.ts.
|
||||
*
|
||||
* Flat modules (fesm2015 and fesm5) and UMD bundles are not generated like they ought to be in APF.
|
||||
*
|
||||
* All generated code is written into the `node_modules` in the top-level filesystem, ready for use
|
||||
* in testing ngcc.
|
||||
*/
|
||||
export function compileIntoApf(
|
||||
pkgName: string, sources: PackageSources, extraCompilerOptions: ts.CompilerOptions = {}): void {
|
||||
const fs = getFileSystem();
|
||||
const {rootNames, compileFs} = setupCompileFs(sources);
|
||||
|
||||
const emit = (options: ts.CompilerOptions) => {
|
||||
const host = new MockCompilerHost(compileFs);
|
||||
const program =
|
||||
ts.createProgram({host, rootNames, options: {...extraCompilerOptions, ...options}});
|
||||
program.emit();
|
||||
};
|
||||
|
||||
// Compile esm2015 into /esm2015
|
||||
compileFs.ensureDir(compileFs.resolve('esm2015'));
|
||||
emit({
|
||||
declaration: true,
|
||||
emitDecoratorMetadata: true,
|
||||
outDir: './esm2015',
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
target: ts.ScriptTarget.ES2015,
|
||||
lib: [],
|
||||
});
|
||||
|
||||
fs.ensureDir(fs.resolve(`/node_modules/${pkgName}/src`));
|
||||
fs.ensureDir(fs.resolve(`/node_modules/${pkgName}/esm2015/src`));
|
||||
for (const file of rootNames) {
|
||||
const inFileBase = file.replace(/\.ts$/, '');
|
||||
|
||||
// Copy declaration file into /src tree
|
||||
const dtsContents = compileFs.readFile(compileFs.resolve(`/esm2015/${inFileBase}.d.ts`));
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/src/${inFileBase}.d.ts`), dtsContents);
|
||||
|
||||
// Copy compiled source file into /esm2015/src tree
|
||||
const jsContents = compileFs.readFile(compileFs.resolve(`/esm2015/${inFileBase}.js`));
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/esm2015/src/${inFileBase}.js`), jsContents);
|
||||
}
|
||||
fs.writeFile(
|
||||
fs.resolve(`/node_modules/${pkgName}/esm2015/index.js`), `export * from './src/index';`);
|
||||
|
||||
// Compile esm5 into /esm5
|
||||
compileFs.ensureDir(compileFs.resolve('esm5'));
|
||||
emit({
|
||||
declaration: false,
|
||||
emitDecoratorMetadata: true,
|
||||
outDir: './esm5',
|
||||
moduleResolution: ts.ModuleResolutionKind.NodeJs,
|
||||
module: ts.ModuleKind.ESNext,
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [],
|
||||
});
|
||||
|
||||
fs.ensureDir(fs.resolve(`/node_modules/${pkgName}/esm5/src`));
|
||||
for (const file of rootNames) {
|
||||
const inFileBase = file.replace(/\.ts$/, '');
|
||||
|
||||
// Copy compiled source file into esm5/src tree
|
||||
const jsContents = compileFs.readFile(compileFs.resolve(`/esm5/${inFileBase}.js`));
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/esm5/src/${inFileBase}.js`), jsContents);
|
||||
}
|
||||
fs.writeFile(
|
||||
fs.resolve(`/node_modules/${pkgName}/esm5/index.js`), `export * from './src/index';`);
|
||||
|
||||
// Write a main declaration and metadata file to the root
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/index.d.ts`), `export * from './src/index';`);
|
||||
fs.writeFile(fs.resolve(`/node_modules/${pkgName}/index.metadata.json`), '{}');
|
||||
|
||||
// Write the package.json
|
||||
const pkgJson: unknown = {
|
||||
name: pkgName,
|
||||
version: '0.0.1',
|
||||
esm5: './esm5/index.js',
|
||||
esm2015: './esm2015/index.js',
|
||||
module: './esm2015/index.js',
|
||||
typings: './index.d.ts',
|
||||
};
|
||||
|
||||
fs.writeFile(
|
||||
fs.resolve(`/node_modules/${pkgName}/package.json`), JSON.stringify(pkgJson, null, 2));
|
||||
}
|
||||
|
||||
export function loadNgccIntegrationTestFiles(): Folder {
|
||||
const tmpFs = new MockFileSystemPosix(true);
|
||||
const basePath = '/' as AbsoluteFsPath;
|
||||
@@ -240,68 +26,3 @@ export function loadNgccIntegrationTestFiles(): Folder {
|
||||
loadTestDirectory(tmpFs, resolveNpmTreeArtifact('rxjs'), tmpFs.resolve('/node_modules/rxjs'));
|
||||
return tmpFs.dump();
|
||||
}
|
||||
|
||||
const stdFiles = loadNgccIntegrationTestFiles();
|
||||
|
||||
/**
|
||||
* Prepares a mock filesystem that contains all provided source files, which can be used to emit
|
||||
* compiled code into.
|
||||
*/
|
||||
function setupCompileFs(sources: PackageSources): {rootNames: string[], compileFs: FileSystem} {
|
||||
const compileFs = new MockFileSystemPosix(true);
|
||||
compileFs.init(stdFiles);
|
||||
|
||||
const rootNames = Object.keys(sources);
|
||||
|
||||
for (const fileName of rootNames) {
|
||||
compileFs.writeFile(compileFs.resolve(fileName), sources[fileName]);
|
||||
}
|
||||
|
||||
return {rootNames, compileFs};
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple `ts.CompilerHost` that uses a `FileSystem` instead of the real FS.
|
||||
*
|
||||
* TODO(alxhub): convert this into a first class `FileSystemCompilerHost` and use it as the base for
|
||||
* the entire compiler.
|
||||
*/
|
||||
class MockCompilerHost implements ts.CompilerHost {
|
||||
constructor(private fs: FileSystem) {}
|
||||
getSourceFile(
|
||||
fileName: string, languageVersion: ts.ScriptTarget,
|
||||
onError?: ((message: string) => void)|undefined,
|
||||
shouldCreateNewSourceFile?: boolean|undefined): ts.SourceFile|undefined {
|
||||
return ts.createSourceFile(
|
||||
fileName, this.fs.readFile(this.fs.resolve(fileName)), languageVersion, true,
|
||||
ts.ScriptKind.TS);
|
||||
}
|
||||
|
||||
getDefaultLibFileName(options: ts.CompilerOptions): string {
|
||||
return ts.getDefaultLibFileName(options);
|
||||
}
|
||||
|
||||
writeFile(fileName: string, data: string): void {
|
||||
this.fs.writeFile(this.fs.resolve(fileName), data);
|
||||
}
|
||||
|
||||
getCurrentDirectory(): string {
|
||||
return this.fs.pwd();
|
||||
}
|
||||
getCanonicalFileName(fileName: string): string {
|
||||
return fileName;
|
||||
}
|
||||
useCaseSensitiveFileNames(): boolean {
|
||||
return true;
|
||||
}
|
||||
getNewLine(): string {
|
||||
return '\n';
|
||||
}
|
||||
fileExists(fileName: string): boolean {
|
||||
return this.fs.exists(this.fs.resolve(fileName));
|
||||
}
|
||||
readFile(fileName: string): string|undefined {
|
||||
const abs = this.fs.resolve(fileName);
|
||||
return this.fs.exists(abs) ? this.fs.readFile(abs) : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user