126 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			126 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @license
							 | 
						||
| 
								 | 
							
								 * Copyright Google Inc. All Rights Reserved.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Use of this source code is governed by an MIT-style license that can be
							 | 
						||
| 
								 | 
							
								 * found in the LICENSE file at https://angular.io/license
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import * as ts from 'typescript';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {FileSystem, getFileSystem} from '../../../src/ngtsc/file_system';
							 | 
						||
| 
								 | 
							
								import {MockFileSystemPosix} from '../../../src/ngtsc/file_system/testing';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {loadStandardTestFiles} from '../../../test/helpers';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type NodeModulesDef = {
							 | 
						||
| 
								 | 
							
								  [name: string]: Package
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export type Package = {
							 | 
						||
| 
								 | 
							
								  [path: string]: string;
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Compile one or more testing packages into the top-level `FileSystem`.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Instead of writing ESM5 code by hand, and manually describing the Angular Package Format
							 | 
						||
| 
								 | 
							
								 * structure of that code in a mock NPM package, `genNodeModules` allows for the generation of one
							 | 
						||
| 
								 | 
							
								 * or more NPM packages from TypeScript source code. Each named NPM package in `def` is
							 | 
						||
| 
								 | 
							
								 * independently transpiled with `compileNodeModuleToFs` and written into `node_modules` in the
							 | 
						||
| 
								 | 
							
								 * top-level filesystem, ready for use in testing ngcc.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								export function genNodeModules(def: NodeModulesDef): void {
							 | 
						||
| 
								 | 
							
								  const fs = getFileSystem();
							 | 
						||
| 
								 | 
							
								  for (const pkgName of Object.keys(def)) {
							 | 
						||
| 
								 | 
							
								    compileNodeModuleToFs(fs, pkgName, def[pkgName]);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Takes the TypeScript project defined in the `Package` structure, compiles it to ESM5, and sets it
							 | 
						||
| 
								 | 
							
								 * up as a package in `node_modules` in `fs`.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * TODO(alxhub): over time, expand this to other bundle formats and make it more faithful to the
							 | 
						||
| 
								 | 
							
								 * shape of real NPM packages.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								function compileNodeModuleToFs(fs: FileSystem, pkgName: string, pkg: Package): void {
							 | 
						||
| 
								 | 
							
								  const compileFs = new MockFileSystemPosix(true);
							 | 
						||
| 
								 | 
							
								  compileFs.init(loadStandardTestFiles({fakeCore: false}));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const options: ts.CompilerOptions = {
							 | 
						||
| 
								 | 
							
								    declaration: true,
							 | 
						||
| 
								 | 
							
								    module: ts.ModuleKind.ESNext,
							 | 
						||
| 
								 | 
							
								    target: ts.ScriptTarget.ES5,
							 | 
						||
| 
								 | 
							
								    lib: [],
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const rootNames = Object.keys(pkg);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for (const fileName of rootNames) {
							 | 
						||
| 
								 | 
							
								    compileFs.writeFile(compileFs.resolve(fileName), pkg[fileName]);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  const host = new MockCompilerHost(compileFs);
							 | 
						||
| 
								 | 
							
								  const program = ts.createProgram({host, rootNames, options});
							 | 
						||
| 
								 | 
							
								  program.emit();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // Copy over the JS and .d.ts files, and add a .metadata.json for each .d.ts file.
							 | 
						||
| 
								 | 
							
								  for (const inFileTs of rootNames) {
							 | 
						||
| 
								 | 
							
								    const inFileBase = inFileTs.replace(/\.ts$/, '');
							 | 
						||
| 
								 | 
							
								    fs.writeFile(
							 | 
						||
| 
								 | 
							
								        fs.resolve(`/node_modules/${pkgName}/${inFileBase}.d.ts`),
							 | 
						||
| 
								 | 
							
								        compileFs.readFile(compileFs.resolve(`${inFileBase}.d.ts`)));
							 | 
						||
| 
								 | 
							
								    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',
							 | 
						||
| 
								 | 
							
								    main: './index.js',
							 | 
						||
| 
								 | 
							
								    typings: './index.d.ts',
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  fs.writeFile(
							 | 
						||
| 
								 | 
							
								      fs.resolve(`/node_modules/${pkgName}/package.json`), JSON.stringify(pkgJson, null, 2));
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * 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;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |