From 7b55ba58b9cbed50ddcfb82ed56afc51c4fadb8a Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Wed, 20 Mar 2019 13:47:58 +0000 Subject: [PATCH] refactor(ivy): ngcc - remove flat-format and use AbsoluteFsPath (#29092) Now that we are using package.json properties to indicate which entry-point format to compile, it turns out that we don't really need to distinguish between flat and non-flat formats, unless we are compiling `@angular/core`. PR Close #29092 --- aio/package.json | 2 +- aio/tools/examples/example-boilerplate.js | 2 +- packages/compiler-cli/ngcc/main-ngcc.ts | 14 ++-- packages/compiler-cli/ngcc/src/main.ts | 78 +++++++++---------- .../ngcc/src/packages/dependency_host.ts | 39 ++++++---- .../ngcc/src/packages/dependency_resolver.ts | 28 ++++--- .../ngcc/src/packages/entry_point.ts | 54 ++++--------- .../ngcc/src/packages/entry_point_bundle.ts | 39 +++++----- .../ngcc/src/packages/entry_point_finder.ts | 25 +++--- .../ngcc/src/packages/transformer.ts | 6 -- .../ngcc/src/rendering/renderer.ts | 3 +- packages/compiler-cli/ngcc/test/BUILD.bazel | 3 +- .../compiler-cli/ngcc/test/helpers/utils.ts | 6 +- .../ngcc/test/integration/ngcc_spec.ts | 27 +++---- .../ngcc/test/packages/build_marker_spec.ts | 8 +- .../test/packages/dependency_host_spec.ts | 54 +++++++------ .../test/packages/dependency_resolver_spec.ts | 77 ++++++++++-------- .../test/packages/entry_point_finder_spec.ts | 40 +++++----- .../ngcc/test/packages/entry_point_spec.ts | 54 ++++++------- 19 files changed, 276 insertions(+), 283 deletions(-) diff --git a/aio/package.json b/aio/package.json index 7867e3b51f..d47c1373d5 100644 --- a/aio/package.json +++ b/aio/package.json @@ -17,7 +17,7 @@ "build": "yarn ~~build", "prebuild-local": "yarn setup-local", "build-local": "yarn ~~build", - "prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc", + "prebuild-with-ivy": "yarn setup-local && yarn ivy-ngcc --properties module es2015", "build-with-ivy": "node scripts/build-with-ivy", "extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js cafa558cf", "lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint", diff --git a/aio/tools/examples/example-boilerplate.js b/aio/tools/examples/example-boilerplate.js index 197ac603e1..6a7c1715a1 100644 --- a/aio/tools/examples/example-boilerplate.js +++ b/aio/tools/examples/example-boilerplate.js @@ -72,7 +72,7 @@ class ExampleBoilerPlate { // the module typings if we specified an "es2015" format. This means that // we also need to build with "fesm2015" in order to get updated typings // which are needed for compilation. - shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc`); + shelljs.exec(`yarn --cwd ${SHARED_PATH} ivy-ngcc --properties module es2015`); } exampleFolders.forEach(exampleFolder => { diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts index b00af6ef8e..1d6fffff70 100644 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ b/packages/compiler-cli/ngcc/main-ngcc.ts @@ -9,6 +9,8 @@ import * as path from 'canonical-path'; import * as yargs from 'yargs'; +import {AbsoluteFsPath} from '../src/ngtsc/path'; + import {mainNgcc} from './src/main'; import {EntryPointJsonProperty} from './src/packages/entry_point'; @@ -19,7 +21,7 @@ if (require.main === module) { yargs .option('s', { alias: 'source', - describe: 'A path to the root folder to compile.', + describe: 'A path to the `node_modules` folder to compile.', default: './node_modules' }) .option('f', {alias: 'formats', hidden: true, array: true}) @@ -34,8 +36,7 @@ if (require.main === module) { }) .option('t', { alias: 'target', - describe: 'A path to a root folder where the compiled files will be written.', - defaultDescription: 'The `source` folder.' + describe: 'A path to a single entry-point to compile (plus its dependencies).', }) .help() .parse(args); @@ -45,11 +46,12 @@ if (require.main === module) { 'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.'); process.exit(1); } - const baseSourcePath: string = path.resolve(options['s']); + const baseSourcePath = AbsoluteFsPath.from(path.resolve(options['s'] || './node_modules')); const propertiesToConsider: EntryPointJsonProperty[] = options['p']; - const baseTargetPath: string = options['t']; + const targetEntryPointPath = + options['t'] ? AbsoluteFsPath.from(path.resolve(options['t'])) : undefined; try { - mainNgcc({baseSourcePath, propertiesToConsider, baseTargetPath}); + mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath}); process.exitCode = 0; } catch (e) { console.error(e.stack || e.message); diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index e2e68e2691..9faf4f3c44 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ +import {AbsoluteFsPath} from '../../src/ngtsc/path'; import {checkMarker, writeMarker} from './packages/build_marker'; import {DependencyHost} from './packages/dependency_host'; import {DependencyResolver} from './packages/dependency_resolver'; @@ -19,14 +20,12 @@ import {Transformer} from './packages/transformer'; */ export interface NgccOptions { /** The path to the node_modules folder that contains the packages to compile. */ - baseSourcePath: string; - /** The path to the node_modules folder where modified files should be written. */ - baseTargetPath?: string; + baseSourcePath: AbsoluteFsPath; /** * The path, relative to `baseSourcePath` of the primary package to be compiled. * All its dependencies will need to be compiled too. */ - targetEntryPointPath?: string; + targetEntryPointPath?: AbsoluteFsPath; /** * Which entry-point properties in the package.json to consider when compiling. * Each of properties contain a path to particular bundle format for a given entry-point. @@ -34,7 +33,7 @@ export interface NgccOptions { propertiesToConsider?: EntryPointJsonProperty[]; } -const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'fesm5', 'fesm2015']; +const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015']; /** * This is the main entry-point into ngcc (aNGular Compatibility Compiler). @@ -44,9 +43,9 @@ const SUPPORTED_FORMATS: EntryPointFormat[] = ['esm5', 'esm2015', 'fesm5', 'fesm * * @param options The options telling ngcc what to compile and how. */ -export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targetEntryPointPath, - propertiesToConsider}: NgccOptions): void { - const transformer = new Transformer(baseSourcePath, baseTargetPath); +export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToConsider}: NgccOptions): + void { + const transformer = new Transformer(baseSourcePath, baseSourcePath); const host = new DependencyHost(); const resolver = new DependencyResolver(host); const finder = new EntryPointFinder(resolver); @@ -57,51 +56,50 @@ export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targe // Are we compiling the Angular core? const isCore = entryPoint.name === '@angular/core'; - let dtsTransformFormat: EntryPointFormat|undefined; - const propertiesToCompile = propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[]; - const compiledFormats = new Set(); + const compiledFormats = new Set(); for (let i = 0; i < propertiesToCompile.length; i++) { const property = propertiesToCompile[i]; - const format = getEntryPointFormat(entryPoint.packageJson, property); + const formatPath = entryPoint.packageJson[property]; + const format = getEntryPointFormat(property); // No format then this property is not supposed to be compiled. - if (!format || SUPPORTED_FORMATS.indexOf(format) === -1) continue; + if (!formatPath || !format || SUPPORTED_FORMATS.indexOf(format) === -1) continue; - // We don't want to compile a format more than once. - // This could happen if there are multiple properties that map to the same format... - // E.g. `fesm5` and `module` both can point to the flat ESM5 format. - if (!compiledFormats.has(format)) { - compiledFormats.add(format); - - // Use the first format found for typings transformation. - dtsTransformFormat = dtsTransformFormat || format; - - - if (checkMarker(entryPoint, property)) { - const bundle = - makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat); - if (bundle) { - transformer.transform(entryPoint, isCore, bundle); - } else { - console.warn( - `Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`); - } - } else { - console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`); - } + if (checkMarker(entryPoint, property)) { + compiledFormats.add(formatPath); + console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`); + continue; + } + + if (!compiledFormats.has(formatPath)) { + const bundle = makeEntryPointBundle( + entryPoint.path, formatPath, entryPoint.typings, isCore, format, + compiledFormats.size === 0); + if (bundle) { + console.warn(`Compiling ${entryPoint.name} : ${property} as ${format}`); + transformer.transform(entryPoint, isCore, bundle); + compiledFormats.add(formatPath); + } else { + console.warn( + `Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`); + } + } else { + console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`); + } + + // Either this format was just compiled or its underlying format was compiled because of a + // previous property. + if (compiledFormats.has(formatPath)) { + writeMarker(entryPoint, property); } - // Write the built-with-ngcc marker. - writeMarker(entryPoint, property); } - if (!dtsTransformFormat) { + if (compiledFormats.size === 0) { throw new Error( `Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`); } }); } - -export {NGCC_VERSION} from './packages/build_marker'; \ No newline at end of file diff --git a/packages/compiler-cli/ngcc/src/packages/dependency_host.ts b/packages/compiler-cli/ngcc/src/packages/dependency_host.ts index 5c26e34a8b..bd959ed568 100644 --- a/packages/compiler-cli/ngcc/src/packages/dependency_host.ts +++ b/packages/compiler-cli/ngcc/src/packages/dependency_host.ts @@ -10,6 +10,8 @@ import * as path from 'canonical-path'; import * as fs from 'fs'; import * as ts from 'typescript'; +import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path'; + /** * Helper functions for computing dependencies. */ @@ -17,7 +19,8 @@ export class DependencyHost { /** * Get a list of the resolved paths to all the dependencies of this entry point. * @param from An absolute path to the file whose dependencies we want to get. - * @param resolved A set that will have the absolute paths of resolved entry points added to it. + * @param dependencies A set that will have the absolute paths of resolved entry points added to + * it. * @param missing A set that will have the dependencies that could not be found added to it. * @param deepImports A set that will have the import paths that exist but cannot be mapped to * entry-points, i.e. deep-imports. @@ -25,11 +28,16 @@ export class DependencyHost { * circular dependency loop. */ computeDependencies( - from: string, resolved: Set, missing: Set, deepImports: Set, - internal: Set = new Set()): void { + from: AbsoluteFsPath, dependencies: Set = new Set(), + missing: Set = new Set(), deepImports: Set = new Set(), + internal: Set = new Set()): { + dependencies: Set, + missing: Set, + deepImports: Set + } { const fromContents = fs.readFileSync(from, 'utf8'); if (!this.hasImportOrReexportStatements(fromContents)) { - return; + return {dependencies, missing, deepImports}; } // Parse the source into a TypeScript AST and then walk it looking for imports and re-exports. @@ -41,7 +49,7 @@ export class DependencyHost { // Grab the id of the module that is being imported .map(stmt => stmt.moduleSpecifier.text) // Resolve this module id into an absolute path - .forEach(importPath => { + .forEach((importPath: PathSegment) => { if (importPath.startsWith('.')) { // This is an internal import so follow it const internalDependency = this.resolveInternal(from, importPath); @@ -49,12 +57,12 @@ export class DependencyHost { if (!internal.has(internalDependency)) { internal.add(internalDependency); this.computeDependencies( - internalDependency, resolved, missing, deepImports, internal); + internalDependency, dependencies, missing, deepImports, internal); } } else { const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath); if (resolvedEntryPoint !== null) { - resolved.add(resolvedEntryPoint); + dependencies.add(resolvedEntryPoint); } else { // If the import could not be resolved as entry point, it either does not exist // at all or is a deep import. @@ -67,6 +75,7 @@ export class DependencyHost { } } }); + return {dependencies, missing, deepImports}; } /** @@ -75,11 +84,11 @@ export class DependencyHost { * @param to the module specifier of the internal dependency to resolve * @returns the resolved path to the import. */ - resolveInternal(from: string, to: string): string { + resolveInternal(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath { const fromDirectory = path.dirname(from); // `fromDirectory` is absolute so we don't need to worry about telling `require.resolve` - // about it - unlike `tryResolve` below. - return require.resolve(path.resolve(fromDirectory, to)); + // about it by adding it to a `paths` parameter - unlike `tryResolve` below. + return AbsoluteFsPath.from(require.resolve(path.resolve(fromDirectory, to))); } /** @@ -99,9 +108,9 @@ export class DependencyHost { * @returns the resolved path to the entry point directory of the import or null * if it cannot be resolved. */ - tryResolveEntryPoint(from: string, to: string): string|null { - const entryPoint = this.tryResolve(from, `${to}/package.json`); - return entryPoint && path.dirname(entryPoint); + tryResolveEntryPoint(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null { + const entryPoint = this.tryResolve(from, `${to}/package.json` as PathSegment); + return entryPoint && AbsoluteFsPath.from(path.dirname(entryPoint)); } /** @@ -112,9 +121,9 @@ export class DependencyHost { * @returns an absolute path to the entry-point of the dependency or null if it could not be * resolved. */ - tryResolve(from: string, to: string): string|null { + tryResolve(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null { try { - return require.resolve(to, {paths: [from]}); + return AbsoluteFsPath.from(require.resolve(to, {paths: [from]})); } catch (e) { return null; } diff --git a/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts b/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts index a7a372d7ba..8d151cd68e 100644 --- a/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts +++ b/packages/compiler-cli/ngcc/src/packages/dependency_resolver.ts @@ -6,9 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ +import {resolve} from 'canonical-path'; import {DepGraph} from 'dependency-graph'; + +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyHost} from './dependency_host'; -import {EntryPoint} from './entry_point'; +import {EntryPoint, EntryPointJsonProperty, getEntryPointFormat} from './entry_point'; /** @@ -105,11 +108,8 @@ export class DependencyResolver { // Now add the dependencies between them entryPoints.forEach(entryPoint => { - const dependencies = new Set(); - const missing = new Set(); - const deepImports = new Set(); const entryPointPath = getEntryPointPath(entryPoint); - this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports); + const {dependencies, missing, deepImports} = this.host.computeDependencies(entryPointPath); if (missing.size > 0) { // This entry point has dependencies that are missing @@ -152,12 +152,16 @@ export class DependencyResolver { } } -function getEntryPointPath(entryPoint: EntryPoint): string { - const entryPointPath = - entryPoint.fesm2015 || entryPoint.fesm5 || entryPoint.esm2015 || entryPoint.esm5; - if (!entryPointPath) { - throw new Error( - `There is no format with import statements in '${entryPoint.path}' entry-point.`); +function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath { + const properties = Object.keys(entryPoint.packageJson); + for (let i = 0; i < properties.length; i++) { + const property = properties[i] as EntryPointJsonProperty; + const format = getEntryPointFormat(property); + + if (format === 'esm2015' || format === 'esm5') { + const formatPath = entryPoint.packageJson[property] !; + return AbsoluteFsPath.from(resolve(entryPoint.path, formatPath)); + } } - return entryPointPath; + throw new Error(`There is no format with import statements in '${entryPoint.path}' entry-point.`); } diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point.ts b/packages/compiler-cli/ngcc/src/packages/entry_point.ts index 12d26bf6f8..541b81f6e9 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point.ts @@ -8,40 +8,30 @@ import * as path from 'canonical-path'; import * as fs from 'fs'; -import {isDefined} from '../utils'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; -/** - * An object containing paths to the entry-points for each format. - */ -export interface EntryPointPaths { - esm5?: string; - fesm5?: string; - esm2015?: string; - fesm2015?: string; - umd?: string; -} /** * The possible values for the format of an entry-point. */ -export type EntryPointFormat = keyof(EntryPointPaths); +export type EntryPointFormat = 'esm5' | 'esm2015' | 'umd'; /** * An object containing information about an entry-point, including paths * to each of the possible entry-point formats. */ -export interface EntryPoint extends EntryPointPaths { +export interface EntryPoint { /** The name of the package (e.g. `@angular/core`). */ name: string; /** The parsed package.json file for this entry-point. */ packageJson: EntryPointPackageJson; /** The path to the package that contains this entry-point. */ - package: string; + package: AbsoluteFsPath; /** The path to this entry point. */ - path: string; + path: AbsoluteFsPath; /** The path to a typings (.d.ts) file for this entry-point. */ - typings: string; + typings: AbsoluteFsPath; } interface PackageJsonFormatProperties { @@ -73,7 +63,8 @@ export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties); * @param entryPointPath the absolute path to the potential entry-point. * @returns An entry-point if it is valid, `null` otherwise. */ -export function getEntryPointInfo(packagePath: string, entryPointPath: string): EntryPoint|null { +export function getEntryPointInfo( + packagePath: AbsoluteFsPath, entryPointPath: AbsoluteFsPath): EntryPoint|null { const packageJsonPath = path.resolve(entryPointPath, 'package.json'); if (!fs.existsSync(packageJsonPath)) { return null; @@ -98,48 +89,31 @@ export function getEntryPointInfo(packagePath: string, entryPointPath: string): return null; } - const formats = Object.keys(entryPointPackageJson) - .map((property: EntryPointJsonProperty) => { - const format = getEntryPointFormat(entryPointPackageJson, property); - return format ? {property, format} : undefined; - }) - .filter(isDefined); - const entryPointInfo: EntryPoint = { name: entryPointPackageJson.name, packageJson: entryPointPackageJson, package: packagePath, path: entryPointPath, - typings: path.resolve(entryPointPath, typings) + typings: AbsoluteFsPath.from(path.resolve(entryPointPath, typings)), }; - // Add the formats to the entry-point info object. - formats.forEach( - item => entryPointInfo[item.format] = - path.resolve(entryPointPath, entryPointPackageJson[item.property] !)); - return entryPointInfo; } /** * Convert a package.json property into an entry-point format. * - * The actual format is dependent not only on the property itself but also - * on what other properties exist in the package.json. - * - * @param entryPointProperties The package.json that contains the properties. * @param property The property to convert to a format. * @returns An entry-point format or `undefined` if none match the given property. */ -export function getEntryPointFormat( - entryPointProperties: EntryPointPackageJson, property: string): EntryPointFormat|undefined { +export function getEntryPointFormat(property: string): EntryPointFormat|undefined { switch (property) { case 'fesm2015': - return 'fesm2015'; + return 'esm2015'; case 'fesm5': - return 'fesm5'; + return 'esm5'; case 'es2015': - return !entryPointProperties.fesm2015 ? 'fesm2015' : 'esm2015'; + return 'esm2015'; case 'esm2015': return 'esm2015'; case 'esm5': @@ -147,7 +121,7 @@ export function getEntryPointFormat( case 'main': return 'umd'; case 'module': - return !entryPointProperties.fesm5 ? 'fesm5' : 'esm5'; + return 'esm5'; default: return undefined; } diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts index 0153376360..b04dd3e205 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_bundle.ts @@ -5,12 +5,12 @@ * 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 {resolve} from 'canonical-path'; import * as ts from 'typescript'; import {AbsoluteFsPath} from '../../../src/ngtsc/path'; - import {BundleProgram, makeBundleProgram} from './bundle_program'; -import {EntryPoint, EntryPointFormat} from './entry_point'; +import {EntryPointFormat} from './entry_point'; @@ -20,7 +20,7 @@ import {EntryPoint, EntryPointFormat} from './entry_point'; */ export interface EntryPointBundle { format: EntryPointFormat; - isFlat: boolean; + isFlatCore: boolean; rootDirs: AbsoluteFsPath[]; src: BundleProgram; dts: BundleProgram|null; @@ -28,34 +28,33 @@ export interface EntryPointBundle { /** * Get an object that describes a formatted bundle for an entry-point. - * @param entryPoint The entry-point that contains the bundle. - * @param format The format of the bundle. - * @param transformDts True if processing this bundle should also process its `.d.ts` files. + * @param entryPointPath The path to the entry-point that contains the bundle. + * @param formatPath The path to the source files for this bundle. + * @param typingsPath The path to the typings files if we should transform them with this bundle. + * @param isCore This entry point is the Angular core package. + * @param format The underlying format of the bundle. + * @param transformDts Whether to transform the typings along with this bundle. */ export function makeEntryPointBundle( - entryPoint: EntryPoint, isCore: boolean, format: EntryPointFormat, - transformDts: boolean): EntryPointBundle|null { - // Bail out if the entry-point does not have this format. - const path = entryPoint[format]; - if (!path) { - return null; - } - + entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean, + format: EntryPointFormat, transformDts: boolean): EntryPointBundle|null { // Create the TS program and necessary helpers. const options: ts.CompilerOptions = { allowJs: true, maxNodeModuleJsDepth: Infinity, - rootDir: entryPoint.path, + rootDir: entryPointPath, }; const host = ts.createCompilerHost(options); - const rootDirs = [AbsoluteFsPath.from(entryPoint.path)]; + const rootDirs = [AbsoluteFsPath.from(entryPointPath)]; // Create the bundle programs, as necessary. - const src = makeBundleProgram(isCore, path, 'r3_symbols.js', options, host); + const src = makeBundleProgram( + isCore, resolve(entryPointPath, formatPath), 'r3_symbols.js', options, host); const dts = transformDts ? - makeBundleProgram(isCore, entryPoint.typings, 'r3_symbols.d.ts', options, host) : + makeBundleProgram( + isCore, resolve(entryPointPath, typingsPath), 'r3_symbols.d.ts', options, host) : null; - const isFlat = src.r3SymbolsFile === null; + const isFlatCore = isCore && src.r3SymbolsFile === null; - return {format, rootDirs, isFlat, src, dts}; + return {format, rootDirs, isFlatCore, src, dts}; } diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts index cda95dbd91..89b47c15c2 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_finder.ts @@ -8,6 +8,7 @@ import * as path from 'canonical-path'; import * as fs from 'fs'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyResolver, SortedEntryPointsInfo} from './dependency_resolver'; import {EntryPoint, getEntryPointInfo} from './entry_point'; @@ -18,10 +19,9 @@ export class EntryPointFinder { * Search the given directory, and sub-directories, for Angular package entry points. * @param sourceDirectory An absolute path to the directory to search for entry points. */ - findEntryPoints(sourceDirectory: string, targetEntryPointPath?: string): SortedEntryPointsInfo { + findEntryPoints(sourceDirectory: AbsoluteFsPath, targetEntryPointPath?: AbsoluteFsPath): + SortedEntryPointsInfo { const unsortedEntryPoints = walkDirectoryForEntryPoints(sourceDirectory); - targetEntryPointPath = - targetEntryPointPath && path.resolve(sourceDirectory, targetEntryPointPath); const targetEntryPoint = targetEntryPointPath ? unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) : undefined; @@ -34,7 +34,7 @@ export class EntryPointFinder { * The function will recurse into directories that start with `@...`, e.g. `@angular/...`. * @param sourceDirectory An absolute path to the root directory where searching begins. */ -function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] { +function walkDirectoryForEntryPoints(sourceDirectory: AbsoluteFsPath): EntryPoint[] { const entryPoints: EntryPoint[] = []; fs.readdirSync(sourceDirectory) // Not interested in hidden files @@ -49,14 +49,15 @@ function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] { .forEach(p => { // Either the directory is a potential package or a namespace containing packages (e.g // `@angular`). - const packagePath = path.join(sourceDirectory, p); + const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p)); if (p.startsWith('@')) { entryPoints.push(...walkDirectoryForEntryPoints(packagePath)); } else { entryPoints.push(...getEntryPointsForPackage(packagePath)); // Also check for any nested node_modules in this package - const nestedNodeModulesPath = path.resolve(packagePath, 'node_modules'); + const nestedNodeModulesPath = + AbsoluteFsPath.from(path.resolve(packagePath, 'node_modules')); if (fs.existsSync(nestedNodeModulesPath)) { entryPoints.push(...walkDirectoryForEntryPoints(nestedNodeModulesPath)); } @@ -70,7 +71,7 @@ function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] { * @param packagePath The absolute path to an npm package that may contain entry points * @returns An array of entry points that were discovered. */ -function getEntryPointsForPackage(packagePath: string): EntryPoint[] { +function getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] { const entryPoints: EntryPoint[] = []; // Try to get an entry point from the top level package directory @@ -96,7 +97,7 @@ function getEntryPointsForPackage(packagePath: string): EntryPoint[] { * @param dir the directory to recursively walk. * @param fn the function to apply to each directory. */ -function walkDirectory(dir: string, fn: (dir: string) => void) { +function walkDirectory(dir: AbsoluteFsPath, fn: (dir: AbsoluteFsPath) => void) { return fs .readdirSync(dir) // Not interested in hidden files @@ -108,9 +109,9 @@ function walkDirectory(dir: string, fn: (dir: string) => void) { const stat = fs.lstatSync(path.resolve(dir, p)); return stat.isDirectory() && !stat.isSymbolicLink(); }) - .forEach(subdir => { - subdir = path.resolve(dir, subdir); - fn(subdir); - walkDirectory(subdir, fn); + .forEach(subDir => { + const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir)); + fn(resolvedSubDir); + walkDirectory(resolvedSubDir, fn); }); } diff --git a/packages/compiler-cli/ngcc/src/packages/transformer.ts b/packages/compiler-cli/ngcc/src/packages/transformer.ts index 83a8d8260e..587daa994c 100644 --- a/packages/compiler-cli/ngcc/src/packages/transformer.ts +++ b/packages/compiler-cli/ngcc/src/packages/transformer.ts @@ -56,8 +56,6 @@ export class Transformer { * @param bundle the bundle to transform. */ transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void { - console.warn(`Compiling ${entryPoint.name} - ${bundle.format}`); - const reflectionHost = this.getHost(isCore, bundle); // Parse and analyze the files. @@ -78,10 +76,8 @@ export class Transformer { const typeChecker = bundle.src.program.getTypeChecker(); switch (bundle.format) { case 'esm2015': - case 'fesm2015': return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts); case 'esm5': - case 'fesm5': return new Esm5ReflectionHost(isCore, typeChecker, bundle.dts); default: throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`); @@ -91,10 +87,8 @@ export class Transformer { getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer { switch (bundle.format) { case 'esm2015': - case 'fesm2015': return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath); case 'esm5': - case 'fesm5': return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath); default: throw new Error(`Renderer for "${bundle.format}" not yet implemented.`); diff --git a/packages/compiler-cli/ngcc/src/rendering/renderer.ts b/packages/compiler-cli/ngcc/src/rendering/renderer.ts index bc0ee48ca8..8a054abed9 100644 --- a/packages/compiler-cli/ngcc/src/rendering/renderer.ts +++ b/packages/compiler-cli/ngcc/src/rendering/renderer.ts @@ -137,7 +137,8 @@ export abstract class Renderer { if (compiledFile) { const importManager = new ImportManager( - this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlat), IMPORT_PREFIX); + this.getImportRewriter(this.bundle.src.r3SymbolsFile, this.bundle.isFlatCore), + IMPORT_PREFIX); // TODO: remove constructor param metadata and property decorators (we need info from the // handlers to do this) diff --git a/packages/compiler-cli/ngcc/test/BUILD.bazel b/packages/compiler-cli/ngcc/test/BUILD.bazel index 8e7f1efd6b..a8209d4029 100644 --- a/packages/compiler-cli/ngcc/test/BUILD.bazel +++ b/packages/compiler-cli/ngcc/test/BUILD.bazel @@ -45,8 +45,10 @@ ts_library( ), deps = [ "//packages/compiler-cli/ngcc", + "//packages/compiler-cli/src/ngtsc/path", "//packages/compiler-cli/test:test_utils", "@npm//@types/mock-fs", + "@npm//rxjs", ], ) @@ -60,7 +62,6 @@ jasmine_node_test( ], deps = [ ":integration_lib", - "//packages/common", "//tools/testing:node_no_angular", "@npm//canonical-path", "@npm//convert-source-map", diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts index 441fdb1d18..464b4af515 100644 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/ngcc/test/helpers/utils.ts @@ -15,8 +15,6 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle'; export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript'; - - /** * * @param format The format of the bundle. @@ -28,8 +26,8 @@ export function makeTestEntryPointBundle( dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle { const src = makeTestBundleProgram(files); const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null; - const isFlat = src.r3SymbolsFile === null; - return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlat}; + const isFlatCore = src.r3SymbolsFile === null; + return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlatCore}; } /** diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 11c062ae81..f4f5cf5d40 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -13,33 +13,29 @@ const Module = require('module'); import {mainNgcc} from '../../src/main'; import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; + +const NODE_MODULES = AbsoluteFsPath.from('/node_modules'); describe('ngcc main()', () => { beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); - it('should run ngcc without errors for fesm2015', () => { - expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm2015']})) - .not.toThrow(); - }); - - it('should run ngcc without errors for fesm5', () => { - expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['fesm5']})) - .not.toThrow(); - }); - it('should run ngcc without errors for esm2015', () => { - expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm2015']})) + expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm2015']})) .not.toThrow(); }); it('should run ngcc without errors for esm5', () => { - expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm5']})) + expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm5']})) .not.toThrow(); }); it('should only compile the given package entry-point (and its dependencies)', () => { - mainNgcc({baseSourcePath: '/node_modules', targetEntryPointPath: '@angular/common/http'}); + mainNgcc({ + baseSourcePath: NODE_MODULES, + targetEntryPointPath: AbsoluteFsPath.from(`${NODE_MODULES}/@angular/common/http`) + }); expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({ module: '0.0.0-PLACEHOLDER', @@ -69,7 +65,7 @@ describe('ngcc main()', () => { }); it('should only build the format properties specified for each entry-point', () => { - mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['main', 'esm5', 'module']}); + mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['main', 'esm5', 'module']}); expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({ esm5: '0.0.0-PLACEHOLDER', @@ -95,6 +91,7 @@ function createMockFileSystem() { mockFs({ '/node_modules/@angular': loadAngularPackages(), '/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')), + '/node_modules/tslib': loadDirectory(resolveNpmTreeArtifact('tslib', 'tslib.js')), }); spyOn(Module, '_resolveFilename').and.callFake(mockResolve); } @@ -163,6 +160,6 @@ function mockResolve(request: string): string|null { } } -function loadPackage(packageName: string) { +function loadPackage(packageName: string): any { return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8')); } \ No newline at end of file diff --git a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts index 623ad40b2c..3977b9e1e7 100644 --- a/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/build_marker_spec.ts @@ -9,6 +9,7 @@ import {readFileSync, writeFileSync} from 'fs'; import * as mockFs from 'mock-fs'; +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {checkMarker, writeMarker} from '../../src/packages/build_marker'; import {EntryPoint} from '../../src/packages/entry_point'; @@ -94,11 +95,12 @@ function restoreRealFileSystem() { } function createEntryPoint(path: string): EntryPoint { + const absolutePath = AbsoluteFsPath.from(path); return { name: 'some-package', - path, - package: path, - typings: '', + path: absolutePath, + package: absolutePath, + typings: AbsoluteFsPath.from('/typings'), packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8')) }; } diff --git a/packages/compiler-cli/ngcc/test/packages/dependency_host_spec.ts b/packages/compiler-cli/ngcc/test/packages/dependency_host_spec.ts index fc19dd07e4..0a6889bff3 100644 --- a/packages/compiler-cli/ngcc/test/packages/dependency_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/dependency_host_spec.ts @@ -9,6 +9,8 @@ import * as path from 'canonical-path'; import * as mockFs from 'mock-fs'; import * as ts from 'typescript'; + +import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path'; import {DependencyHost} from '../../src/packages/dependency_host'; const Module = require('module'); @@ -16,6 +18,8 @@ interface DepMap { [path: string]: {resolved: string[], missing: string[]}; } +const _ = AbsoluteFsPath.from; + describe('DependencyHost', () => { let host: DependencyHost; beforeEach(() => host = new DependencyHost()); @@ -27,7 +31,8 @@ describe('DependencyHost', () => { it('should not generate a TS AST if the source does not contain any imports or re-exports', () => { spyOn(ts, 'createSourceFile'); - host.computeDependencies('/no/imports/or/re-exports.js', new Set(), new Set(), new Set()); + host.computeDependencies( + _('/no/imports/or/re-exports.js'), new Set(), new Set(), new Set()); expect(ts.createSourceFile).not.toHaveBeenCalled(); }); @@ -37,7 +42,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/external/imports.js', resolved, missing, deepImports); + host.computeDependencies(_('/external/imports.js'), resolved, missing, deepImports); expect(resolved.size).toBe(2); expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true); @@ -49,7 +54,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/external/re-exports.js', resolved, missing, deepImports); + host.computeDependencies(_('/external/re-exports.js'), resolved, missing, deepImports); expect(resolved.size).toBe(2); expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true); @@ -64,7 +69,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/external/imports-missing.js', resolved, missing, deepImports); + host.computeDependencies(_('/external/imports-missing.js'), resolved, missing, deepImports); expect(resolved.size).toBe(1); expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(missing.size).toBe(1); @@ -84,7 +89,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/external/deep-import.js', resolved, missing, deepImports); + host.computeDependencies(_('/external/deep-import.js'), resolved, missing, deepImports); expect(resolved.size).toBe(0); expect(missing.size).toBe(0); expect(deepImports.size).toBe(1); @@ -101,7 +106,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/internal/outer.js', resolved, missing, deepImports); + host.computeDependencies(_('/internal/outer.js'), resolved, missing, deepImports); expect(getDependenciesSpy) .toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports); expect(getDependenciesSpy) @@ -121,7 +126,7 @@ describe('DependencyHost', () => { const resolved = new Set(); const missing = new Set(); const deepImports = new Set(); - host.computeDependencies('/internal/circular-a.js', resolved, missing, deepImports); + host.computeDependencies(_('/internal/circular-a.js'), resolved, missing, deepImports); expect(resolved.size).toBe(2); expect(resolved.has('RESOLVED/path/to/x')).toBe(true); expect(resolved.has('RESOLVED/path/to/y')).toBe(true); @@ -144,14 +149,15 @@ describe('DependencyHost', () => { describe('resolveInternal', () => { it('should resolve the dependency via `Module._resolveFilename`', () => { - spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); - const result = host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE'); - expect(result).toEqual('RESOLVED_PATH'); + spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH'); + const result = host.resolveInternal( + _('/SOURCE/PATH/FILE'), PathSegment.fromFsPath('../TARGET/PATH/FILE')); + expect(result).toEqual('/RESOLVED_PATH'); }); it('should first resolve the `to` on top of the `from` directory', () => { - const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); - host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE'); + const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH'); + host.resolveInternal(_('/SOURCE/PATH/FILE'), PathSegment.fromFsPath('../TARGET/PATH/FILE')); expect(resolveSpy) .toHaveBeenCalledWith('/SOURCE/TARGET/PATH/FILE', jasmine.any(Object), false, undefined); }); @@ -159,37 +165,39 @@ describe('DependencyHost', () => { describe('tryResolveExternal', () => { it('should call `tryResolve`, appending `package.json` to the target path', () => { - const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED'); - host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH'); - expect(tryResolveSpy).toHaveBeenCalledWith('SOURCE_PATH', 'TARGET_PATH/package.json'); + const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED'); + host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')); + expect(tryResolveSpy).toHaveBeenCalledWith('/SOURCE_PATH', 'TARGET_PATH/package.json'); }); it('should return the directory containing the result from `tryResolve', () => { - spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED'); - expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual('PATH/TO'); + spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED'); + expect(host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'))) + .toEqual(_('/PATH/TO')); }); it('should return null if `tryResolve` returns null', () => { spyOn(host, 'tryResolve').and.returnValue(null); - expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual(null); + expect(host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'))) + .toEqual(null); }); }); describe('tryResolve()', () => { it('should resolve the dependency via `Module._resolveFilename`, passing the `from` path to the `paths` option', () => { - const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH'); - const result = host.tryResolve('SOURCE_PATH', 'TARGET_PATH'); + const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH'); + const result = host.tryResolve(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')); expect(resolveSpy).toHaveBeenCalledWith('TARGET_PATH', jasmine.any(Object), false, { - paths: ['SOURCE_PATH'] + paths: ['/SOURCE_PATH'] }); - expect(result).toEqual('RESOLVED_PATH'); + expect(result).toEqual(_('/RESOLVED_PATH')); }); it('should return null if `Module._resolveFilename` throws an error', () => { const resolveSpy = spyOn(Module, '_resolveFilename').and.throwError(`Cannot find module 'TARGET_PATH'`); - const result = host.tryResolve('SOURCE_PATH', 'TARGET_PATH'); + const result = host.tryResolve(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')); expect(result).toBe(null); }); }); diff --git a/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts b/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts index ee01076dc4..e3deb28f9e 100644 --- a/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/dependency_resolver_spec.ts @@ -6,10 +6,15 @@ * found in the LICENSE file at https://angular.io/license */ +import {resolve} from 'canonical-path'; + +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyHost} from '../../src/packages/dependency_host'; import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver'; import {EntryPoint} from '../../src/packages/entry_point'; +const _ = AbsoluteFsPath.from; + describe('DependencyResolver', () => { let host: DependencyHost; let resolver: DependencyResolver; @@ -18,18 +23,18 @@ describe('DependencyResolver', () => { resolver = new DependencyResolver(host); }); describe('sortEntryPointsByDependency()', () => { - const first = { path: 'first', fesm2015: 'first/index.ts' } as EntryPoint; - const second = { path: 'second', esm2015: 'second/index.ts' } as EntryPoint; - const third = { path: 'third', fesm2015: 'third/index.ts' } as EntryPoint; - const fourth = { path: 'fourth', esm2015: 'fourth/index.ts' } as EntryPoint; - const fifth = { path: 'fifth', fesm2015: 'fifth/index.ts' } as EntryPoint; + const first = { path: _('/first'), packageJson: {esm5: 'index.ts'} } as EntryPoint; + const second = { path: _('/second'), packageJson: {esm2015: 'sub/index.ts'} } as EntryPoint; + const third = { path: _('/third'), packageJson: {esm5: 'index.ts'} } as EntryPoint; + const fourth = { path: _('/fourth'), packageJson: {esm2015: 'sub2/index.ts'} } as EntryPoint; + const fifth = { path: _('/fifth'), packageJson: {esm5: 'index.ts'} } as EntryPoint; const dependencies = { - 'first/index.ts': {resolved: ['second', 'third', 'ignored-1'], missing: []}, - 'second/index.ts': {resolved: ['third', 'fifth'], missing: []}, - 'third/index.ts': {resolved: ['fourth', 'ignored-2'], missing: []}, - 'fourth/index.ts': {resolved: ['fifth'], missing: []}, - 'fifth/index.ts': {resolved: [], missing: []}, + [_('/first/index.ts')]: {resolved: [second.path, third.path, '/ignored-1'], missing: []}, + [_('/second/sub/index.ts')]: {resolved: [third.path, fifth.path], missing: []}, + [_('/third/index.ts')]: {resolved: [fourth.path, '/ignored-2'], missing: []}, + [_('/fourth/sub2/index.ts')]: {resolved: [fifth.path], missing: []}, + [_('/fifth/index.ts')]: {resolved: [], missing: []}, }; it('should order the entry points by their dependency on each other', () => { @@ -40,58 +45,58 @@ describe('DependencyResolver', () => { it('should remove entry-points that have missing direct dependencies', () => { spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ - 'first/index.ts': {resolved: [], missing: ['missing']}, - 'second/index.ts': {resolved: [], missing: []}, + [_('/first/index.ts')]: {resolved: [], missing: ['/missing']}, + [_('/second/sub/index.ts')]: {resolved: [], missing: []}, })); const result = resolver.sortEntryPointsByDependency([first, second]); expect(result.entryPoints).toEqual([second]); expect(result.invalidEntryPoints).toEqual([ - {entryPoint: first, missingDependencies: ['missing']}, + {entryPoint: first, missingDependencies: ['/missing']}, ]); }); it('should remove entry points that depended upon an invalid entry-point', () => { spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ - 'first/index.ts': {resolved: ['second'], missing: []}, - 'second/index.ts': {resolved: [], missing: ['missing']}, - 'third/index.ts': {resolved: [], missing: []}, + [_('/first/index.ts')]: {resolved: [second.path], missing: []}, + [_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']}, + [_('/third/index.ts')]: {resolved: [], missing: []}, })); // Note that we will process `first` before `second`, which has the missing dependency. const result = resolver.sortEntryPointsByDependency([first, second, third]); expect(result.entryPoints).toEqual([third]); expect(result.invalidEntryPoints).toEqual([ - {entryPoint: second, missingDependencies: ['missing']}, - {entryPoint: first, missingDependencies: ['missing']}, + {entryPoint: second, missingDependencies: ['/missing']}, + {entryPoint: first, missingDependencies: ['/missing']}, ]); }); it('should remove entry points that will depend upon an invalid entry-point', () => { spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({ - 'first/index.ts': {resolved: ['second'], missing: []}, - 'second/index.ts': {resolved: [], missing: ['missing']}, - 'third/index.ts': {resolved: [], missing: []}, + [_('/first/index.ts')]: {resolved: [second.path], missing: []}, + [_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']}, + [_('/third/index.ts')]: {resolved: [], missing: []}, })); // Note that we will process `first` after `second`, which has the missing dependency. const result = resolver.sortEntryPointsByDependency([second, first, third]); expect(result.entryPoints).toEqual([third]); expect(result.invalidEntryPoints).toEqual([ - {entryPoint: second, missingDependencies: ['missing']}, - {entryPoint: first, missingDependencies: ['second']}, + {entryPoint: second, missingDependencies: ['/missing']}, + {entryPoint: first, missingDependencies: [second.path]}, ]); }); - it('should error if the entry point does not have either the fesm2015 nor esm2015 formats', - () => { - expect(() => resolver.sortEntryPointsByDependency([{ path: 'first' } as EntryPoint])) - .toThrowError(`There is no format with import statements in 'first' entry-point.`); - }); + it('should error if the entry point does not have either the esm5 nor esm2015 formats', () => { + expect(() => resolver.sortEntryPointsByDependency([ + { path: '/first', packageJson: {} } as EntryPoint + ])).toThrowError(`There is no format with import statements in '/first' entry-point.`); + }); it('should capture any dependencies that were ignored', () => { spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies)); const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]); expect(result.ignoredDependencies).toEqual([ - {entryPoint: first, dependencyPath: 'ignored-1'}, - {entryPoint: third, dependencyPath: 'ignored-2'}, + {entryPoint: first, dependencyPath: '/ignored-1'}, + {entryPoint: third, dependencyPath: '/ignored-2'}, ]); }); @@ -116,10 +121,14 @@ describe('DependencyResolver', () => { [path: string]: {resolved: string[], missing: string[]}; } - function createFakeComputeDependencies(dependencies: DepMap) { - return (entryPoint: string, resolved: Set, missing: Set) => { - dependencies[entryPoint].resolved.forEach(dep => resolved.add(dep)); - dependencies[entryPoint].missing.forEach(dep => missing.add(dep)); + function createFakeComputeDependencies(deps: DepMap) { + return (entryPoint: string) => { + const dependencies = new Set(); + const missing = new Set(); + const deepImports = new Set(); + deps[entryPoint].resolved.forEach(dep => dependencies.add(dep)); + deps[entryPoint].missing.forEach(dep => missing.add(dep)); + return {dependencies, missing, deepImports}; }; } }); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts index dbd2971015..f2c7f8f92f 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_finder_spec.ts @@ -7,11 +7,15 @@ */ import * as mockFs from 'mock-fs'; + +import {AbsoluteFsPath} from '../../../src/ngtsc/path'; import {DependencyHost} from '../../src/packages/dependency_host'; import {DependencyResolver} from '../../src/packages/dependency_resolver'; import {EntryPoint} from '../../src/packages/entry_point'; import {EntryPointFinder} from '../../src/packages/entry_point_finder'; +const _ = AbsoluteFsPath.from; + describe('findEntryPoints()', () => { let resolver: DependencyResolver; let finder: EntryPointFinder; @@ -26,56 +30,56 @@ describe('findEntryPoints()', () => { afterEach(restoreRealFileSystem); it('should find sub-entry-points within a package', () => { - const {entryPoints} = finder.findEntryPoints('/sub_entry_points'); + const {entryPoints} = finder.findEntryPoints(_('/sub_entry_points')); const entryPointPaths = entryPoints.map(x => [x.package, x.path]); expect(entryPointPaths).toEqual([ - ['/sub_entry_points/common', '/sub_entry_points/common'], - ['/sub_entry_points/common', '/sub_entry_points/common/http'], - ['/sub_entry_points/common', '/sub_entry_points/common/http/testing'], - ['/sub_entry_points/common', '/sub_entry_points/common/testing'], + [_('/sub_entry_points/common'), _('/sub_entry_points/common')], + [_('/sub_entry_points/common'), _('/sub_entry_points/common/http')], + [_('/sub_entry_points/common'), _('/sub_entry_points/common/http/testing')], + [_('/sub_entry_points/common'), _('/sub_entry_points/common/testing')], ]); }); it('should find packages inside a namespace', () => { - const {entryPoints} = finder.findEntryPoints('/namespaced'); + const {entryPoints} = finder.findEntryPoints(_('/namespaced')); const entryPointPaths = entryPoints.map(x => [x.package, x.path]); expect(entryPointPaths).toEqual([ - ['/namespaced/@angular/common', '/namespaced/@angular/common'], - ['/namespaced/@angular/common', '/namespaced/@angular/common/http'], - ['/namespaced/@angular/common', '/namespaced/@angular/common/http/testing'], - ['/namespaced/@angular/common', '/namespaced/@angular/common/testing'], + [_('/namespaced/@angular/common'), _('/namespaced/@angular/common')], + [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http')], + [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http/testing')], + [_('/namespaced/@angular/common'), _('/namespaced/@angular/common/testing')], ]); }); it('should return an empty array if there are no packages', () => { - const {entryPoints} = finder.findEntryPoints('/no_packages'); + const {entryPoints} = finder.findEntryPoints(_('/no_packages')); expect(entryPoints).toEqual([]); }); it('should return an empty array if there are no valid entry-points', () => { - const {entryPoints} = finder.findEntryPoints('/no_valid_entry_points'); + const {entryPoints} = finder.findEntryPoints(_('/no_valid_entry_points')); expect(entryPoints).toEqual([]); }); it('should ignore folders starting with .', () => { - const {entryPoints} = finder.findEntryPoints('/dotted_folders'); + const {entryPoints} = finder.findEntryPoints(_('/dotted_folders')); expect(entryPoints).toEqual([]); }); it('should ignore folders that are symlinked', () => { - const {entryPoints} = finder.findEntryPoints('/symlinked_folders'); + const {entryPoints} = finder.findEntryPoints(_('/symlinked_folders')); expect(entryPoints).toEqual([]); }); it('should handle nested node_modules folders', () => { - const {entryPoints} = finder.findEntryPoints('/nested_node_modules'); + const {entryPoints} = finder.findEntryPoints(_('/nested_node_modules')); const entryPointPaths = entryPoints.map(x => [x.package, x.path]); expect(entryPointPaths).toEqual([ - ['/nested_node_modules/outer', '/nested_node_modules/outer'], + [_('/nested_node_modules/outer'), _('/nested_node_modules/outer')], // Note that the inner entry point does not get included as part of the outer package [ - '/nested_node_modules/outer/node_modules/inner', - '/nested_node_modules/outer/node_modules/inner' + _('/nested_node_modules/outer/node_modules/inner'), + _('/nested_node_modules/outer/node_modules/inner'), ], ]); }); diff --git a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts index dffa1e8738..de60cb2cb4 100644 --- a/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts +++ b/packages/compiler-cli/ngcc/test/packages/entry_point_spec.ts @@ -6,84 +6,76 @@ * found in the LICENSE file at https://angular.io/license */ +import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path'; import {readFileSync} from 'fs'; import * as mockFs from 'mock-fs'; + import {getEntryPointInfo} from '../../src/packages/entry_point'; describe('getEntryPointInfo()', () => { beforeEach(createMockFileSystem); afterEach(restoreRealFileSystem); + const _ = AbsoluteFsPath.from; + const SOME_PACKAGE = _('/some_package'); + it('should return an object containing absolute paths to the formats of the specified entry-point', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/valid_entry_point'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/valid_entry_point')); expect(entryPoint).toEqual({ name: 'some-package/valid_entry_point', - package: '/some_package', - path: '/some_package/valid_entry_point', - typings: `/some_package/valid_entry_point/valid_entry_point.d.ts`, - fesm2015: `/some_package/valid_entry_point/fesm2015/valid_entry_point.js`, - esm2015: `/some_package/valid_entry_point/esm2015/valid_entry_point.js`, - fesm5: `/some_package/valid_entry_point/fesm2015/valid_entry_point.js`, - esm5: `/some_package/valid_entry_point/esm2015/valid_entry_point.js`, - umd: `/some_package/valid_entry_point/bundles/valid_entry_point.umd.js`, + package: SOME_PACKAGE, + path: _('/some_package/valid_entry_point'), + typings: _(`/some_package/valid_entry_point/valid_entry_point.d.ts`), packageJson: loadPackageJson('/some_package/valid_entry_point'), }); }); it('should return null if there is no package.json at the entry-point path', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_package_json'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_package_json')); expect(entryPoint).toBe(null); }); it('should return null if there is no typings or types field in the package.json', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_typings'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_typings')); expect(entryPoint).toBe(null); }); it('should return null if there is no esm2015 nor fesm2015 field in the package.json', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_esm2015'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_esm2015')); expect(entryPoint).toBe(null); }); it('should return null if there is no metadata.json file next to the typing file', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/missing_metadata.json'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/missing_metadata.json')); expect(entryPoint).toBe(null); }); it('should work if the typings field is named `types', () => { const entryPoint = - getEntryPointInfo('/some_package', '/some_package/types_rather_than_typings'); + getEntryPointInfo(SOME_PACKAGE, _('/some_package/types_rather_than_typings')); expect(entryPoint).toEqual({ name: 'some-package/types_rather_than_typings', - package: '/some_package', - path: '/some_package/types_rather_than_typings', - typings: `/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`, - fesm2015: `/some_package/types_rather_than_typings/fesm2015/types_rather_than_typings.js`, - esm2015: `/some_package/types_rather_than_typings/esm2015/types_rather_than_typings.js`, - fesm5: `/some_package/types_rather_than_typings/fesm2015/types_rather_than_typings.js`, - esm5: `/some_package/types_rather_than_typings/esm2015/types_rather_than_typings.js`, - umd: `/some_package/types_rather_than_typings/bundles/types_rather_than_typings.umd.js`, + package: SOME_PACKAGE, + path: _('/some_package/types_rather_than_typings'), + typings: _(`/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`), packageJson: loadPackageJson('/some_package/types_rather_than_typings'), }); }); it('should work with Angular Material style package.json', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/material_style'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/material_style')); expect(entryPoint).toEqual({ name: 'some_package/material_style', - package: '/some_package', - path: '/some_package/material_style', - typings: `/some_package/material_style/material_style.d.ts`, - fesm2015: `/some_package/material_style/esm2015/material_style.js`, - fesm5: `/some_package/material_style/esm5/material_style.es5.js`, - umd: `/some_package/material_style/bundles/material_style.umd.js`, - packageJson: loadPackageJson('/some_package/material_style'), + package: SOME_PACKAGE, + path: _('/some_package/material_style'), + typings: _(`/some_package/material_style/material_style.d.ts`), + packageJson: JSON.parse(readFileSync('/some_package/material_style/package.json', 'utf8')), }); }); it('should return null if the package.json is not valid JSON', () => { - const entryPoint = getEntryPointInfo('/some_package', '/some_package/unexpected_symbols'); + const entryPoint = getEntryPointInfo(SOME_PACKAGE, _('/some_package/unexpected_symbols')); expect(entryPoint).toBe(null); }); });