import {join} from 'path'; import {writeFileSync, readFileSync} from 'fs'; import {sync as glob} from 'glob'; import {red} from 'chalk'; import {BuildPackage} from './build-package'; import {tsCompile} from './ts-compile'; /** Incrementing ID counter. */ let nextId = 0; /** Compiles the TypeScript sources of a primary or secondary entry point. */ export async function compileEntryPoint(buildPackage: BuildPackage, tsconfigName: string, secondaryEntryPoint = '', es5OutputPath?: string) { const entryPointPath = join(buildPackage.sourceDir, secondaryEntryPoint); const entryPointTsconfigPath = join(entryPointPath, tsconfigName); const ngcFlags = ['-p', entryPointTsconfigPath]; if (es5OutputPath) { ngcFlags.push('--outDir', es5OutputPath, '--target', 'ES5'); } return tsCompile('ngc', ngcFlags).catch(() => { const error = red(`Failed to compile ${secondaryEntryPoint} using ${entryPointTsconfigPath}`); console.error(error); return Promise.reject(error); }); } /** * Adds `importAs` property to all generated metadata.json files for a package's secondary * entry-points. This is necessary for unit tests so that the imports generated by using the * `providedIn` syntax are able to resolve symbols to the "expected" location (e.g. * "@angular/cdk/overlay" instead of a local relative path). Assumes that the package has already * been built. */ export function addImportAsToAllMetadata(buildPackage: BuildPackage) { for (const entryPoint of buildPackage.secondaryEntryPoints) { addImportAs(buildPackage.name, buildPackage.outputDir, entryPoint); } } /** Renames `ɵa`-style re-exports generated by Angular to be unique across compilation units. */ export function renamePrivateReExportsToBeUnique(buildPackage: BuildPackage, secondaryEntryPoint = '') { // When we compiled the typescript sources with ngc, we do entry-point individually. // If the root-level module re-exports multiple of these entry-points, the private-export // identifiers (e.g., `ɵa`) generated by ngc will collide. We work around this by suffixing // each of these identifiers with an ID specific to this entry point. We make this // replacement in the js, d.ts, and metadata output. if (buildPackage.exportsSecondaryEntryPointsAtRoot && secondaryEntryPoint) { const entryPointId = nextId++; const outputPath = join(buildPackage.outputDir, secondaryEntryPoint); const esm5OutputPath = join(buildPackage.esm5OutputDir, secondaryEntryPoint); addIdToGlob(outputPath, entryPointId); addIdToGlob(esm5OutputPath, entryPointId); } } /** Adds `importAs` property to generated all metadata.json files for an entry-point. */ function addImportAs(packageName: string, outputPath: string, secondaryEntryPoint: string): void { const path = join(outputPath, secondaryEntryPoint); glob(join(path, '**/*.+(metadata.json)')).forEach(metadataPath => { let metadata = JSON.parse(readFileSync(metadataPath, 'utf-8')); metadata[0]['importAs'] = `@angular/${packageName}/${secondaryEntryPoint}`; writeFileSync(metadataPath, JSON.stringify(metadata), 'utf-8'); }); } /** Updates exports in designated folder with identifier specified. */ function addIdToGlob(outputPath: string, entryPointId: number): void { glob(join(outputPath, '**/*.+(js|d.ts|metadata.json)')).forEach(filePath => { let fileContent = readFileSync(filePath, 'utf-8'); // We check for double ɵ to avoid mangling symbols like `ɵɵdefineInjectable`. fileContent = fileContent.replace(/ɵ(ɵ)?[a-z]+/g, (match, isDoubleTheta) => { return isDoubleTheta ? match : match + entryPointId; }); writeFileSync(filePath, fileContent, 'utf-8'); }); }