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
This commit is contained in:
parent
cd449021c1
commit
7b55ba58b9
|
@ -17,7 +17,7 @@
|
||||||
"build": "yarn ~~build",
|
"build": "yarn ~~build",
|
||||||
"prebuild-local": "yarn setup-local",
|
"prebuild-local": "yarn setup-local",
|
||||||
"build-local": "yarn ~~build",
|
"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",
|
"build-with-ivy": "node scripts/build-with-ivy",
|
||||||
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js cafa558cf",
|
"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",
|
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
|
||||||
|
|
|
@ -72,7 +72,7 @@ class ExampleBoilerPlate {
|
||||||
// the module typings if we specified an "es2015" format. This means that
|
// 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
|
// we also need to build with "fesm2015" in order to get updated typings
|
||||||
// which are needed for compilation.
|
// 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 => {
|
exampleFolders.forEach(exampleFolder => {
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
import * as path from 'canonical-path';
|
import * as path from 'canonical-path';
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../src/ngtsc/path';
|
||||||
|
|
||||||
import {mainNgcc} from './src/main';
|
import {mainNgcc} from './src/main';
|
||||||
import {EntryPointJsonProperty} from './src/packages/entry_point';
|
import {EntryPointJsonProperty} from './src/packages/entry_point';
|
||||||
|
|
||||||
|
@ -19,7 +21,7 @@ if (require.main === module) {
|
||||||
yargs
|
yargs
|
||||||
.option('s', {
|
.option('s', {
|
||||||
alias: 'source',
|
alias: 'source',
|
||||||
describe: 'A path to the root folder to compile.',
|
describe: 'A path to the `node_modules` folder to compile.',
|
||||||
default: './node_modules'
|
default: './node_modules'
|
||||||
})
|
})
|
||||||
.option('f', {alias: 'formats', hidden: true, array: true})
|
.option('f', {alias: 'formats', hidden: true, array: true})
|
||||||
|
@ -34,8 +36,7 @@ if (require.main === module) {
|
||||||
})
|
})
|
||||||
.option('t', {
|
.option('t', {
|
||||||
alias: 'target',
|
alias: 'target',
|
||||||
describe: 'A path to a root folder where the compiled files will be written.',
|
describe: 'A path to a single entry-point to compile (plus its dependencies).',
|
||||||
defaultDescription: 'The `source` folder.'
|
|
||||||
})
|
})
|
||||||
.help()
|
.help()
|
||||||
.parse(args);
|
.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.');
|
'The formats option (-f/--formats) has been removed. Consider the properties option (-p/--properties) instead.');
|
||||||
process.exit(1);
|
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 propertiesToConsider: EntryPointJsonProperty[] = options['p'];
|
||||||
const baseTargetPath: string = options['t'];
|
const targetEntryPointPath =
|
||||||
|
options['t'] ? AbsoluteFsPath.from(path.resolve(options['t'])) : undefined;
|
||||||
try {
|
try {
|
||||||
mainNgcc({baseSourcePath, propertiesToConsider, baseTargetPath});
|
mainNgcc({baseSourcePath, propertiesToConsider, targetEntryPointPath});
|
||||||
process.exitCode = 0;
|
process.exitCode = 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e.stack || e.message);
|
console.error(e.stack || e.message);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../src/ngtsc/path';
|
||||||
import {checkMarker, writeMarker} from './packages/build_marker';
|
import {checkMarker, writeMarker} from './packages/build_marker';
|
||||||
import {DependencyHost} from './packages/dependency_host';
|
import {DependencyHost} from './packages/dependency_host';
|
||||||
import {DependencyResolver} from './packages/dependency_resolver';
|
import {DependencyResolver} from './packages/dependency_resolver';
|
||||||
|
@ -19,14 +20,12 @@ import {Transformer} from './packages/transformer';
|
||||||
*/
|
*/
|
||||||
export interface NgccOptions {
|
export interface NgccOptions {
|
||||||
/** The path to the node_modules folder that contains the packages to compile. */
|
/** The path to the node_modules folder that contains the packages to compile. */
|
||||||
baseSourcePath: string;
|
baseSourcePath: AbsoluteFsPath;
|
||||||
/** The path to the node_modules folder where modified files should be written. */
|
|
||||||
baseTargetPath?: string;
|
|
||||||
/**
|
/**
|
||||||
* The path, relative to `baseSourcePath` of the primary package to be compiled.
|
* The path, relative to `baseSourcePath` of the primary package to be compiled.
|
||||||
* All its dependencies will need to be compiled too.
|
* 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.
|
* 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.
|
* Each of properties contain a path to particular bundle format for a given entry-point.
|
||||||
|
@ -34,7 +33,7 @@ export interface NgccOptions {
|
||||||
propertiesToConsider?: EntryPointJsonProperty[];
|
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).
|
* 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.
|
* @param options The options telling ngcc what to compile and how.
|
||||||
*/
|
*/
|
||||||
export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targetEntryPointPath,
|
export function mainNgcc({baseSourcePath, targetEntryPointPath, propertiesToConsider}: NgccOptions):
|
||||||
propertiesToConsider}: NgccOptions): void {
|
void {
|
||||||
const transformer = new Transformer(baseSourcePath, baseTargetPath);
|
const transformer = new Transformer(baseSourcePath, baseSourcePath);
|
||||||
const host = new DependencyHost();
|
const host = new DependencyHost();
|
||||||
const resolver = new DependencyResolver(host);
|
const resolver = new DependencyResolver(host);
|
||||||
const finder = new EntryPointFinder(resolver);
|
const finder = new EntryPointFinder(resolver);
|
||||||
|
@ -57,51 +56,50 @@ export function mainNgcc({baseSourcePath, baseTargetPath = baseSourcePath, targe
|
||||||
// Are we compiling the Angular core?
|
// Are we compiling the Angular core?
|
||||||
const isCore = entryPoint.name === '@angular/core';
|
const isCore = entryPoint.name === '@angular/core';
|
||||||
|
|
||||||
let dtsTransformFormat: EntryPointFormat|undefined;
|
|
||||||
|
|
||||||
const propertiesToCompile =
|
const propertiesToCompile =
|
||||||
propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[];
|
propertiesToConsider || Object.keys(entryPoint.packageJson) as EntryPointJsonProperty[];
|
||||||
const compiledFormats = new Set<EntryPointFormat>();
|
const compiledFormats = new Set<string>();
|
||||||
|
|
||||||
for (let i = 0; i < propertiesToCompile.length; i++) {
|
for (let i = 0; i < propertiesToCompile.length; i++) {
|
||||||
const property = propertiesToCompile[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.
|
// 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)) {
|
if (checkMarker(entryPoint, property)) {
|
||||||
const bundle =
|
compiledFormats.add(formatPath);
|
||||||
makeEntryPointBundle(entryPoint, isCore, format, format === dtsTransformFormat);
|
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) {
|
if (bundle) {
|
||||||
|
console.warn(`Compiling ${entryPoint.name} : ${property} as ${format}`);
|
||||||
transformer.transform(entryPoint, isCore, bundle);
|
transformer.transform(entryPoint, isCore, bundle);
|
||||||
|
compiledFormats.add(formatPath);
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Skipping ${entryPoint.name} : ${format} (no entry point file for this format).`);
|
`Skipping ${entryPoint.name} : ${format} (no valid entry point file for this format).`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
|
console.warn(`Skipping ${entryPoint.name} : ${property} (already compiled).`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Write the built-with-ngcc marker.
|
// Either this format was just compiled or its underlying format was compiled because of a
|
||||||
|
// previous property.
|
||||||
|
if (compiledFormats.has(formatPath)) {
|
||||||
writeMarker(entryPoint, property);
|
writeMarker(entryPoint, property);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!dtsTransformFormat) {
|
if (compiledFormats.size === 0) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`);
|
`Failed to compile any formats for entry-point at (${entryPoint.path}). Tried ${propertiesToCompile}.`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export {NGCC_VERSION} from './packages/build_marker';
|
|
|
@ -10,6 +10,8 @@ import * as path from 'canonical-path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper functions for computing dependencies.
|
* 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.
|
* 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 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 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
|
* @param deepImports A set that will have the import paths that exist but cannot be mapped to
|
||||||
* entry-points, i.e. deep-imports.
|
* entry-points, i.e. deep-imports.
|
||||||
|
@ -25,11 +28,16 @@ export class DependencyHost {
|
||||||
* circular dependency loop.
|
* circular dependency loop.
|
||||||
*/
|
*/
|
||||||
computeDependencies(
|
computeDependencies(
|
||||||
from: string, resolved: Set<string>, missing: Set<string>, deepImports: Set<string>,
|
from: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath> = new Set(),
|
||||||
internal: Set<string> = new Set()): void {
|
missing: Set<PathSegment> = new Set(), deepImports: Set<PathSegment> = new Set(),
|
||||||
|
internal: Set<AbsoluteFsPath> = new Set()): {
|
||||||
|
dependencies: Set<AbsoluteFsPath>,
|
||||||
|
missing: Set<PathSegment>,
|
||||||
|
deepImports: Set<PathSegment>
|
||||||
|
} {
|
||||||
const fromContents = fs.readFileSync(from, 'utf8');
|
const fromContents = fs.readFileSync(from, 'utf8');
|
||||||
if (!this.hasImportOrReexportStatements(fromContents)) {
|
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.
|
// 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
|
// Grab the id of the module that is being imported
|
||||||
.map(stmt => stmt.moduleSpecifier.text)
|
.map(stmt => stmt.moduleSpecifier.text)
|
||||||
// Resolve this module id into an absolute path
|
// Resolve this module id into an absolute path
|
||||||
.forEach(importPath => {
|
.forEach((importPath: PathSegment) => {
|
||||||
if (importPath.startsWith('.')) {
|
if (importPath.startsWith('.')) {
|
||||||
// This is an internal import so follow it
|
// This is an internal import so follow it
|
||||||
const internalDependency = this.resolveInternal(from, importPath);
|
const internalDependency = this.resolveInternal(from, importPath);
|
||||||
|
@ -49,12 +57,12 @@ export class DependencyHost {
|
||||||
if (!internal.has(internalDependency)) {
|
if (!internal.has(internalDependency)) {
|
||||||
internal.add(internalDependency);
|
internal.add(internalDependency);
|
||||||
this.computeDependencies(
|
this.computeDependencies(
|
||||||
internalDependency, resolved, missing, deepImports, internal);
|
internalDependency, dependencies, missing, deepImports, internal);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath);
|
const resolvedEntryPoint = this.tryResolveEntryPoint(from, importPath);
|
||||||
if (resolvedEntryPoint !== null) {
|
if (resolvedEntryPoint !== null) {
|
||||||
resolved.add(resolvedEntryPoint);
|
dependencies.add(resolvedEntryPoint);
|
||||||
} else {
|
} else {
|
||||||
// If the import could not be resolved as entry point, it either does not exist
|
// If the import could not be resolved as entry point, it either does not exist
|
||||||
// at all or is a deep import.
|
// 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
|
* @param to the module specifier of the internal dependency to resolve
|
||||||
* @returns the resolved path to the import.
|
* @returns the resolved path to the import.
|
||||||
*/
|
*/
|
||||||
resolveInternal(from: string, to: string): string {
|
resolveInternal(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath {
|
||||||
const fromDirectory = path.dirname(from);
|
const fromDirectory = path.dirname(from);
|
||||||
// `fromDirectory` is absolute so we don't need to worry about telling `require.resolve`
|
// `fromDirectory` is absolute so we don't need to worry about telling `require.resolve`
|
||||||
// about it - unlike `tryResolve` below.
|
// about it by adding it to a `paths` parameter - unlike `tryResolve` below.
|
||||||
return require.resolve(path.resolve(fromDirectory, to));
|
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
|
* @returns the resolved path to the entry point directory of the import or null
|
||||||
* if it cannot be resolved.
|
* if it cannot be resolved.
|
||||||
*/
|
*/
|
||||||
tryResolveEntryPoint(from: string, to: string): string|null {
|
tryResolveEntryPoint(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null {
|
||||||
const entryPoint = this.tryResolve(from, `${to}/package.json`);
|
const entryPoint = this.tryResolve(from, `${to}/package.json` as PathSegment);
|
||||||
return entryPoint && path.dirname(entryPoint);
|
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
|
* @returns an absolute path to the entry-point of the dependency or null if it could not be
|
||||||
* resolved.
|
* resolved.
|
||||||
*/
|
*/
|
||||||
tryResolve(from: string, to: string): string|null {
|
tryResolve(from: AbsoluteFsPath, to: PathSegment): AbsoluteFsPath|null {
|
||||||
try {
|
try {
|
||||||
return require.resolve(to, {paths: [from]});
|
return AbsoluteFsPath.from(require.resolve(to, {paths: [from]}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,12 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {resolve} from 'canonical-path';
|
||||||
import {DepGraph} from 'dependency-graph';
|
import {DepGraph} from 'dependency-graph';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
import {DependencyHost} from './dependency_host';
|
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
|
// Now add the dependencies between them
|
||||||
entryPoints.forEach(entryPoint => {
|
entryPoints.forEach(entryPoint => {
|
||||||
const dependencies = new Set<string>();
|
|
||||||
const missing = new Set<string>();
|
|
||||||
const deepImports = new Set<string>();
|
|
||||||
const entryPointPath = getEntryPointPath(entryPoint);
|
const entryPointPath = getEntryPointPath(entryPoint);
|
||||||
this.host.computeDependencies(entryPointPath, dependencies, missing, deepImports);
|
const {dependencies, missing, deepImports} = this.host.computeDependencies(entryPointPath);
|
||||||
|
|
||||||
if (missing.size > 0) {
|
if (missing.size > 0) {
|
||||||
// This entry point has dependencies that are missing
|
// This entry point has dependencies that are missing
|
||||||
|
@ -152,12 +152,16 @@ export class DependencyResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEntryPointPath(entryPoint: EntryPoint): string {
|
function getEntryPointPath(entryPoint: EntryPoint): AbsoluteFsPath {
|
||||||
const entryPointPath =
|
const properties = Object.keys(entryPoint.packageJson);
|
||||||
entryPoint.fesm2015 || entryPoint.fesm5 || entryPoint.esm2015 || entryPoint.esm5;
|
for (let i = 0; i < properties.length; i++) {
|
||||||
if (!entryPointPath) {
|
const property = properties[i] as EntryPointJsonProperty;
|
||||||
throw new Error(
|
const format = getEntryPointFormat(property);
|
||||||
`There is no format with import statements in '${entryPoint.path}' entry-point.`);
|
|
||||||
|
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.`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,40 +8,30 @@
|
||||||
|
|
||||||
import * as path from 'canonical-path';
|
import * as path from 'canonical-path';
|
||||||
import * as fs from 'fs';
|
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.
|
* 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
|
* An object containing information about an entry-point, including paths
|
||||||
* to each of the possible entry-point formats.
|
* 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`). */
|
/** The name of the package (e.g. `@angular/core`). */
|
||||||
name: string;
|
name: string;
|
||||||
/** The parsed package.json file for this entry-point. */
|
/** The parsed package.json file for this entry-point. */
|
||||||
packageJson: EntryPointPackageJson;
|
packageJson: EntryPointPackageJson;
|
||||||
/** The path to the package that contains this entry-point. */
|
/** The path to the package that contains this entry-point. */
|
||||||
package: string;
|
package: AbsoluteFsPath;
|
||||||
/** The path to this entry point. */
|
/** The path to this entry point. */
|
||||||
path: string;
|
path: AbsoluteFsPath;
|
||||||
/** The path to a typings (.d.ts) file for this entry-point. */
|
/** The path to a typings (.d.ts) file for this entry-point. */
|
||||||
typings: string;
|
typings: AbsoluteFsPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PackageJsonFormatProperties {
|
interface PackageJsonFormatProperties {
|
||||||
|
@ -73,7 +63,8 @@ export type EntryPointJsonProperty = keyof(PackageJsonFormatProperties);
|
||||||
* @param entryPointPath the absolute path to the potential entry-point.
|
* @param entryPointPath the absolute path to the potential entry-point.
|
||||||
* @returns An entry-point if it is valid, `null` otherwise.
|
* @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');
|
const packageJsonPath = path.resolve(entryPointPath, 'package.json');
|
||||||
if (!fs.existsSync(packageJsonPath)) {
|
if (!fs.existsSync(packageJsonPath)) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -98,48 +89,31 @@ export function getEntryPointInfo(packagePath: string, entryPointPath: string):
|
||||||
return null;
|
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 = {
|
const entryPointInfo: EntryPoint = {
|
||||||
name: entryPointPackageJson.name,
|
name: entryPointPackageJson.name,
|
||||||
packageJson: entryPointPackageJson,
|
packageJson: entryPointPackageJson,
|
||||||
package: packagePath,
|
package: packagePath,
|
||||||
path: entryPointPath,
|
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;
|
return entryPointInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a package.json property into an entry-point format.
|
* 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.
|
* @param property The property to convert to a format.
|
||||||
* @returns An entry-point format or `undefined` if none match the given property.
|
* @returns An entry-point format or `undefined` if none match the given property.
|
||||||
*/
|
*/
|
||||||
export function getEntryPointFormat(
|
export function getEntryPointFormat(property: string): EntryPointFormat|undefined {
|
||||||
entryPointProperties: EntryPointPackageJson, property: string): EntryPointFormat|undefined {
|
|
||||||
switch (property) {
|
switch (property) {
|
||||||
case 'fesm2015':
|
case 'fesm2015':
|
||||||
return 'fesm2015';
|
return 'esm2015';
|
||||||
case 'fesm5':
|
case 'fesm5':
|
||||||
return 'fesm5';
|
return 'esm5';
|
||||||
case 'es2015':
|
case 'es2015':
|
||||||
return !entryPointProperties.fesm2015 ? 'fesm2015' : 'esm2015';
|
return 'esm2015';
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
return 'esm2015';
|
return 'esm2015';
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
|
@ -147,7 +121,7 @@ export function getEntryPointFormat(
|
||||||
case 'main':
|
case 'main':
|
||||||
return 'umd';
|
return 'umd';
|
||||||
case 'module':
|
case 'module':
|
||||||
return !entryPointProperties.fesm5 ? 'fesm5' : 'esm5';
|
return 'esm5';
|
||||||
default:
|
default:
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
* 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
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
import {resolve} from 'canonical-path';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
|
|
||||||
import {BundleProgram, makeBundleProgram} from './bundle_program';
|
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 {
|
export interface EntryPointBundle {
|
||||||
format: EntryPointFormat;
|
format: EntryPointFormat;
|
||||||
isFlat: boolean;
|
isFlatCore: boolean;
|
||||||
rootDirs: AbsoluteFsPath[];
|
rootDirs: AbsoluteFsPath[];
|
||||||
src: BundleProgram;
|
src: BundleProgram;
|
||||||
dts: BundleProgram|null;
|
dts: BundleProgram|null;
|
||||||
|
@ -28,34 +28,33 @@ export interface EntryPointBundle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an object that describes a formatted bundle for an entry-point.
|
* Get an object that describes a formatted bundle for an entry-point.
|
||||||
* @param entryPoint The entry-point that contains the bundle.
|
* @param entryPointPath The path to the entry-point that contains the bundle.
|
||||||
* @param format The format of the bundle.
|
* @param formatPath The path to the source files for this bundle.
|
||||||
* @param transformDts True if processing this bundle should also process its `.d.ts` files.
|
* @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(
|
export function makeEntryPointBundle(
|
||||||
entryPoint: EntryPoint, isCore: boolean, format: EntryPointFormat,
|
entryPointPath: string, formatPath: string, typingsPath: string, isCore: boolean,
|
||||||
transformDts: boolean): EntryPointBundle|null {
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the TS program and necessary helpers.
|
// Create the TS program and necessary helpers.
|
||||||
const options: ts.CompilerOptions = {
|
const options: ts.CompilerOptions = {
|
||||||
allowJs: true,
|
allowJs: true,
|
||||||
maxNodeModuleJsDepth: Infinity,
|
maxNodeModuleJsDepth: Infinity,
|
||||||
rootDir: entryPoint.path,
|
rootDir: entryPointPath,
|
||||||
};
|
};
|
||||||
const host = ts.createCompilerHost(options);
|
const host = ts.createCompilerHost(options);
|
||||||
const rootDirs = [AbsoluteFsPath.from(entryPoint.path)];
|
const rootDirs = [AbsoluteFsPath.from(entryPointPath)];
|
||||||
|
|
||||||
// Create the bundle programs, as necessary.
|
// 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 ?
|
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;
|
null;
|
||||||
const isFlat = src.r3SymbolsFile === null;
|
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
||||||
|
|
||||||
return {format, rootDirs, isFlat, src, dts};
|
return {format, rootDirs, isFlatCore, src, dts};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import * as path from 'canonical-path';
|
import * as path from 'canonical-path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
import {DependencyResolver, SortedEntryPointsInfo} from './dependency_resolver';
|
import {DependencyResolver, SortedEntryPointsInfo} from './dependency_resolver';
|
||||||
import {EntryPoint, getEntryPointInfo} from './entry_point';
|
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.
|
* 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.
|
* @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);
|
const unsortedEntryPoints = walkDirectoryForEntryPoints(sourceDirectory);
|
||||||
targetEntryPointPath =
|
|
||||||
targetEntryPointPath && path.resolve(sourceDirectory, targetEntryPointPath);
|
|
||||||
const targetEntryPoint = targetEntryPointPath ?
|
const targetEntryPoint = targetEntryPointPath ?
|
||||||
unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) :
|
unsortedEntryPoints.find(entryPoint => entryPoint.path === targetEntryPointPath) :
|
||||||
undefined;
|
undefined;
|
||||||
|
@ -34,7 +34,7 @@ export class EntryPointFinder {
|
||||||
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
|
* The function will recurse into directories that start with `@...`, e.g. `@angular/...`.
|
||||||
* @param sourceDirectory An absolute path to the root directory where searching begins.
|
* @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[] = [];
|
const entryPoints: EntryPoint[] = [];
|
||||||
fs.readdirSync(sourceDirectory)
|
fs.readdirSync(sourceDirectory)
|
||||||
// Not interested in hidden files
|
// Not interested in hidden files
|
||||||
|
@ -49,14 +49,15 @@ function walkDirectoryForEntryPoints(sourceDirectory: string): EntryPoint[] {
|
||||||
.forEach(p => {
|
.forEach(p => {
|
||||||
// Either the directory is a potential package or a namespace containing packages (e.g
|
// Either the directory is a potential package or a namespace containing packages (e.g
|
||||||
// `@angular`).
|
// `@angular`).
|
||||||
const packagePath = path.join(sourceDirectory, p);
|
const packagePath = AbsoluteFsPath.from(path.join(sourceDirectory, p));
|
||||||
if (p.startsWith('@')) {
|
if (p.startsWith('@')) {
|
||||||
entryPoints.push(...walkDirectoryForEntryPoints(packagePath));
|
entryPoints.push(...walkDirectoryForEntryPoints(packagePath));
|
||||||
} else {
|
} else {
|
||||||
entryPoints.push(...getEntryPointsForPackage(packagePath));
|
entryPoints.push(...getEntryPointsForPackage(packagePath));
|
||||||
|
|
||||||
// Also check for any nested node_modules in this package
|
// 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)) {
|
if (fs.existsSync(nestedNodeModulesPath)) {
|
||||||
entryPoints.push(...walkDirectoryForEntryPoints(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
|
* @param packagePath The absolute path to an npm package that may contain entry points
|
||||||
* @returns An array of entry points that were discovered.
|
* @returns An array of entry points that were discovered.
|
||||||
*/
|
*/
|
||||||
function getEntryPointsForPackage(packagePath: string): EntryPoint[] {
|
function getEntryPointsForPackage(packagePath: AbsoluteFsPath): EntryPoint[] {
|
||||||
const entryPoints: EntryPoint[] = [];
|
const entryPoints: EntryPoint[] = [];
|
||||||
|
|
||||||
// Try to get an entry point from the top level package directory
|
// 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 dir the directory to recursively walk.
|
||||||
* @param fn the function to apply to each directory.
|
* @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
|
return fs
|
||||||
.readdirSync(dir)
|
.readdirSync(dir)
|
||||||
// Not interested in hidden files
|
// 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));
|
const stat = fs.lstatSync(path.resolve(dir, p));
|
||||||
return stat.isDirectory() && !stat.isSymbolicLink();
|
return stat.isDirectory() && !stat.isSymbolicLink();
|
||||||
})
|
})
|
||||||
.forEach(subdir => {
|
.forEach(subDir => {
|
||||||
subdir = path.resolve(dir, subdir);
|
const resolvedSubDir = AbsoluteFsPath.from(path.resolve(dir, subDir));
|
||||||
fn(subdir);
|
fn(resolvedSubDir);
|
||||||
walkDirectory(subdir, fn);
|
walkDirectory(resolvedSubDir, fn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,8 +56,6 @@ export class Transformer {
|
||||||
* @param bundle the bundle to transform.
|
* @param bundle the bundle to transform.
|
||||||
*/
|
*/
|
||||||
transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void {
|
transform(entryPoint: EntryPoint, isCore: boolean, bundle: EntryPointBundle): void {
|
||||||
console.warn(`Compiling ${entryPoint.name} - ${bundle.format}`);
|
|
||||||
|
|
||||||
const reflectionHost = this.getHost(isCore, bundle);
|
const reflectionHost = this.getHost(isCore, bundle);
|
||||||
|
|
||||||
// Parse and analyze the files.
|
// Parse and analyze the files.
|
||||||
|
@ -78,10 +76,8 @@ export class Transformer {
|
||||||
const typeChecker = bundle.src.program.getTypeChecker();
|
const typeChecker = bundle.src.program.getTypeChecker();
|
||||||
switch (bundle.format) {
|
switch (bundle.format) {
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
case 'fesm2015':
|
|
||||||
return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
|
return new Esm2015ReflectionHost(isCore, typeChecker, bundle.dts);
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
case 'fesm5':
|
|
||||||
return new Esm5ReflectionHost(isCore, typeChecker, bundle.dts);
|
return new Esm5ReflectionHost(isCore, typeChecker, bundle.dts);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Reflection host for "${bundle.format}" not yet implemented.`);
|
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 {
|
getRenderer(host: NgccReflectionHost, isCore: boolean, bundle: EntryPointBundle): Renderer {
|
||||||
switch (bundle.format) {
|
switch (bundle.format) {
|
||||||
case 'esm2015':
|
case 'esm2015':
|
||||||
case 'fesm2015':
|
|
||||||
return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath);
|
return new EsmRenderer(host, isCore, bundle, this.sourcePath, this.targetPath);
|
||||||
case 'esm5':
|
case 'esm5':
|
||||||
case 'fesm5':
|
|
||||||
return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath);
|
return new Esm5Renderer(host, isCore, bundle, this.sourcePath, this.targetPath);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
|
throw new Error(`Renderer for "${bundle.format}" not yet implemented.`);
|
||||||
|
|
|
@ -137,7 +137,8 @@ export abstract class Renderer {
|
||||||
|
|
||||||
if (compiledFile) {
|
if (compiledFile) {
|
||||||
const importManager = new ImportManager(
|
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
|
// TODO: remove constructor param metadata and property decorators (we need info from the
|
||||||
// handlers to do this)
|
// handlers to do this)
|
||||||
|
|
|
@ -45,8 +45,10 @@ ts_library(
|
||||||
),
|
),
|
||||||
deps = [
|
deps = [
|
||||||
"//packages/compiler-cli/ngcc",
|
"//packages/compiler-cli/ngcc",
|
||||||
|
"//packages/compiler-cli/src/ngtsc/path",
|
||||||
"//packages/compiler-cli/test:test_utils",
|
"//packages/compiler-cli/test:test_utils",
|
||||||
"@npm//@types/mock-fs",
|
"@npm//@types/mock-fs",
|
||||||
|
"@npm//rxjs",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,7 +62,6 @@ jasmine_node_test(
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
":integration_lib",
|
":integration_lib",
|
||||||
"//packages/common",
|
|
||||||
"//tools/testing:node_no_angular",
|
"//tools/testing:node_no_angular",
|
||||||
"@npm//canonical-path",
|
"@npm//canonical-path",
|
||||||
"@npm//convert-source-map",
|
"@npm//convert-source-map",
|
||||||
|
|
|
@ -15,8 +15,6 @@ import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
||||||
|
|
||||||
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
|
export {getDeclaration} from '../../../src/ngtsc/testing/in_memory_typescript';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param format The format of the bundle.
|
* @param format The format of the bundle.
|
||||||
|
@ -28,8 +26,8 @@ export function makeTestEntryPointBundle(
|
||||||
dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle {
|
dtsFiles?: {name: string, contents: string, isRoot?: boolean}[]): EntryPointBundle {
|
||||||
const src = makeTestBundleProgram(files);
|
const src = makeTestBundleProgram(files);
|
||||||
const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null;
|
const dts = dtsFiles ? makeTestBundleProgram(dtsFiles) : null;
|
||||||
const isFlat = src.r3SymbolsFile === null;
|
const isFlatCore = src.r3SymbolsFile === null;
|
||||||
return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlat};
|
return {format, rootDirs: [AbsoluteFsPath.fromUnchecked('/')], src, dts, isFlatCore};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,33 +13,29 @@ const Module = require('module');
|
||||||
|
|
||||||
import {mainNgcc} from '../../src/main';
|
import {mainNgcc} from '../../src/main';
|
||||||
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers';
|
import {getAngularPackagesFromRunfiles, resolveNpmTreeArtifact} from '../../../test/runfile_helpers';
|
||||||
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
|
|
||||||
|
const NODE_MODULES = AbsoluteFsPath.from('/node_modules');
|
||||||
|
|
||||||
describe('ngcc main()', () => {
|
describe('ngcc main()', () => {
|
||||||
beforeEach(createMockFileSystem);
|
beforeEach(createMockFileSystem);
|
||||||
afterEach(restoreRealFileSystem);
|
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', () => {
|
it('should run ngcc without errors for esm2015', () => {
|
||||||
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm2015']}))
|
expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm2015']}))
|
||||||
.not.toThrow();
|
.not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run ngcc without errors for esm5', () => {
|
it('should run ngcc without errors for esm5', () => {
|
||||||
expect(() => mainNgcc({baseSourcePath: '/node_modules', propertiesToConsider: ['esm5']}))
|
expect(() => mainNgcc({baseSourcePath: NODE_MODULES, propertiesToConsider: ['esm5']}))
|
||||||
.not.toThrow();
|
.not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only compile the given package entry-point (and its dependencies)', () => {
|
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({
|
expect(loadPackage('@angular/common/http').__modified_by_ngcc__).toEqual({
|
||||||
module: '0.0.0-PLACEHOLDER',
|
module: '0.0.0-PLACEHOLDER',
|
||||||
|
@ -69,7 +65,7 @@ describe('ngcc main()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only build the format properties specified for each entry-point', () => {
|
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({
|
expect(loadPackage('@angular/core').__modified_by_ngcc__).toEqual({
|
||||||
esm5: '0.0.0-PLACEHOLDER',
|
esm5: '0.0.0-PLACEHOLDER',
|
||||||
|
@ -95,6 +91,7 @@ function createMockFileSystem() {
|
||||||
mockFs({
|
mockFs({
|
||||||
'/node_modules/@angular': loadAngularPackages(),
|
'/node_modules/@angular': loadAngularPackages(),
|
||||||
'/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')),
|
'/node_modules/rxjs': loadDirectory(resolveNpmTreeArtifact('rxjs', 'index.js')),
|
||||||
|
'/node_modules/tslib': loadDirectory(resolveNpmTreeArtifact('tslib', 'tslib.js')),
|
||||||
});
|
});
|
||||||
spyOn(Module, '_resolveFilename').and.callFake(mockResolve);
|
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'));
|
return JSON.parse(readFileSync(`/node_modules/${packageName}/package.json`, 'utf8'));
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@
|
||||||
import {readFileSync, writeFileSync} from 'fs';
|
import {readFileSync, writeFileSync} from 'fs';
|
||||||
import * as mockFs from 'mock-fs';
|
import * as mockFs from 'mock-fs';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
import {checkMarker, writeMarker} from '../../src/packages/build_marker';
|
import {checkMarker, writeMarker} from '../../src/packages/build_marker';
|
||||||
import {EntryPoint} from '../../src/packages/entry_point';
|
import {EntryPoint} from '../../src/packages/entry_point';
|
||||||
|
|
||||||
|
@ -94,11 +95,12 @@ function restoreRealFileSystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createEntryPoint(path: string): EntryPoint {
|
function createEntryPoint(path: string): EntryPoint {
|
||||||
|
const absolutePath = AbsoluteFsPath.from(path);
|
||||||
return {
|
return {
|
||||||
name: 'some-package',
|
name: 'some-package',
|
||||||
path,
|
path: absolutePath,
|
||||||
package: path,
|
package: absolutePath,
|
||||||
typings: '',
|
typings: AbsoluteFsPath.from('/typings'),
|
||||||
packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8'))
|
packageJson: JSON.parse(readFileSync(path + '/package.json', 'utf8'))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
import * as path from 'canonical-path';
|
import * as path from 'canonical-path';
|
||||||
import * as mockFs from 'mock-fs';
|
import * as mockFs from 'mock-fs';
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath, PathSegment} from '../../../src/ngtsc/path';
|
||||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
import {DependencyHost} from '../../src/packages/dependency_host';
|
||||||
const Module = require('module');
|
const Module = require('module');
|
||||||
|
|
||||||
|
@ -16,6 +18,8 @@ interface DepMap {
|
||||||
[path: string]: {resolved: string[], missing: string[]};
|
[path: string]: {resolved: string[], missing: string[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _ = AbsoluteFsPath.from;
|
||||||
|
|
||||||
describe('DependencyHost', () => {
|
describe('DependencyHost', () => {
|
||||||
let host: DependencyHost;
|
let host: DependencyHost;
|
||||||
beforeEach(() => host = new 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',
|
it('should not generate a TS AST if the source does not contain any imports or re-exports',
|
||||||
() => {
|
() => {
|
||||||
spyOn(ts, 'createSourceFile');
|
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();
|
expect(ts.createSourceFile).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,7 +42,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = 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.size).toBe(2);
|
||||||
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
||||||
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
||||||
|
@ -49,7 +54,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = 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.size).toBe(2);
|
||||||
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
||||||
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
||||||
|
@ -64,7 +69,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = 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.size).toBe(1);
|
||||||
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
||||||
expect(missing.size).toBe(1);
|
expect(missing.size).toBe(1);
|
||||||
|
@ -84,7 +89,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = 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(resolved.size).toBe(0);
|
||||||
expect(missing.size).toBe(0);
|
expect(missing.size).toBe(0);
|
||||||
expect(deepImports.size).toBe(1);
|
expect(deepImports.size).toBe(1);
|
||||||
|
@ -101,7 +106,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = new Set();
|
const deepImports = new Set();
|
||||||
host.computeDependencies('/internal/outer.js', resolved, missing, deepImports);
|
host.computeDependencies(_('/internal/outer.js'), resolved, missing, deepImports);
|
||||||
expect(getDependenciesSpy)
|
expect(getDependenciesSpy)
|
||||||
.toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports);
|
.toHaveBeenCalledWith('/internal/outer.js', resolved, missing, deepImports);
|
||||||
expect(getDependenciesSpy)
|
expect(getDependenciesSpy)
|
||||||
|
@ -121,7 +126,7 @@ describe('DependencyHost', () => {
|
||||||
const resolved = new Set();
|
const resolved = new Set();
|
||||||
const missing = new Set();
|
const missing = new Set();
|
||||||
const deepImports = 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.size).toBe(2);
|
||||||
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/x')).toBe(true);
|
||||||
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
expect(resolved.has('RESOLVED/path/to/y')).toBe(true);
|
||||||
|
@ -144,14 +149,15 @@ describe('DependencyHost', () => {
|
||||||
|
|
||||||
describe('resolveInternal', () => {
|
describe('resolveInternal', () => {
|
||||||
it('should resolve the dependency via `Module._resolveFilename`', () => {
|
it('should resolve the dependency via `Module._resolveFilename`', () => {
|
||||||
spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH');
|
spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
|
||||||
const result = host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE');
|
const result = host.resolveInternal(
|
||||||
expect(result).toEqual('RESOLVED_PATH');
|
_('/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', () => {
|
it('should first resolve the `to` on top of the `from` directory', () => {
|
||||||
const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('RESOLVED_PATH');
|
const resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
|
||||||
host.resolveInternal('/SOURCE/PATH/FILE', '../TARGET/PATH/FILE');
|
host.resolveInternal(_('/SOURCE/PATH/FILE'), PathSegment.fromFsPath('../TARGET/PATH/FILE'));
|
||||||
expect(resolveSpy)
|
expect(resolveSpy)
|
||||||
.toHaveBeenCalledWith('/SOURCE/TARGET/PATH/FILE', jasmine.any(Object), false, undefined);
|
.toHaveBeenCalledWith('/SOURCE/TARGET/PATH/FILE', jasmine.any(Object), false, undefined);
|
||||||
});
|
});
|
||||||
|
@ -159,37 +165,39 @@ describe('DependencyHost', () => {
|
||||||
|
|
||||||
describe('tryResolveExternal', () => {
|
describe('tryResolveExternal', () => {
|
||||||
it('should call `tryResolve`, appending `package.json` to the target path', () => {
|
it('should call `tryResolve`, appending `package.json` to the target path', () => {
|
||||||
const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED');
|
const tryResolveSpy = spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED');
|
||||||
host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH');
|
host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'));
|
||||||
expect(tryResolveSpy).toHaveBeenCalledWith('SOURCE_PATH', 'TARGET_PATH/package.json');
|
expect(tryResolveSpy).toHaveBeenCalledWith('/SOURCE_PATH', 'TARGET_PATH/package.json');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the directory containing the result from `tryResolve', () => {
|
it('should return the directory containing the result from `tryResolve', () => {
|
||||||
spyOn(host, 'tryResolve').and.returnValue('PATH/TO/RESOLVED');
|
spyOn(host, 'tryResolve').and.returnValue('/PATH/TO/RESOLVED');
|
||||||
expect(host.tryResolveEntryPoint('SOURCE_PATH', 'TARGET_PATH')).toEqual('PATH/TO');
|
expect(host.tryResolveEntryPoint(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH')))
|
||||||
|
.toEqual(_('/PATH/TO'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if `tryResolve` returns null', () => {
|
it('should return null if `tryResolve` returns null', () => {
|
||||||
spyOn(host, 'tryResolve').and.returnValue(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()', () => {
|
describe('tryResolve()', () => {
|
||||||
it('should resolve the dependency via `Module._resolveFilename`, passing the `from` path to the `paths` option',
|
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 resolveSpy = spyOn(Module, '_resolveFilename').and.returnValue('/RESOLVED_PATH');
|
||||||
const result = host.tryResolve('SOURCE_PATH', 'TARGET_PATH');
|
const result = host.tryResolve(_('/SOURCE_PATH'), PathSegment.fromFsPath('TARGET_PATH'));
|
||||||
expect(resolveSpy).toHaveBeenCalledWith('TARGET_PATH', jasmine.any(Object), false, {
|
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', () => {
|
it('should return null if `Module._resolveFilename` throws an error', () => {
|
||||||
const resolveSpy =
|
const resolveSpy =
|
||||||
spyOn(Module, '_resolveFilename').and.throwError(`Cannot find module 'TARGET_PATH'`);
|
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);
|
expect(result).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,10 +6,15 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {DependencyHost} from '../../src/packages/dependency_host';
|
||||||
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver';
|
import {DependencyResolver, SortedEntryPointsInfo} from '../../src/packages/dependency_resolver';
|
||||||
import {EntryPoint} from '../../src/packages/entry_point';
|
import {EntryPoint} from '../../src/packages/entry_point';
|
||||||
|
|
||||||
|
const _ = AbsoluteFsPath.from;
|
||||||
|
|
||||||
describe('DependencyResolver', () => {
|
describe('DependencyResolver', () => {
|
||||||
let host: DependencyHost;
|
let host: DependencyHost;
|
||||||
let resolver: DependencyResolver;
|
let resolver: DependencyResolver;
|
||||||
|
@ -18,18 +23,18 @@ describe('DependencyResolver', () => {
|
||||||
resolver = new DependencyResolver(host);
|
resolver = new DependencyResolver(host);
|
||||||
});
|
});
|
||||||
describe('sortEntryPointsByDependency()', () => {
|
describe('sortEntryPointsByDependency()', () => {
|
||||||
const first = { path: 'first', fesm2015: 'first/index.ts' } as EntryPoint;
|
const first = { path: _('/first'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
|
||||||
const second = { path: 'second', esm2015: 'second/index.ts' } as EntryPoint;
|
const second = { path: _('/second'), packageJson: {esm2015: 'sub/index.ts'} } as EntryPoint;
|
||||||
const third = { path: 'third', fesm2015: 'third/index.ts' } as EntryPoint;
|
const third = { path: _('/third'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
|
||||||
const fourth = { path: 'fourth', esm2015: 'fourth/index.ts' } as EntryPoint;
|
const fourth = { path: _('/fourth'), packageJson: {esm2015: 'sub2/index.ts'} } as EntryPoint;
|
||||||
const fifth = { path: 'fifth', fesm2015: 'fifth/index.ts' } as EntryPoint;
|
const fifth = { path: _('/fifth'), packageJson: {esm5: 'index.ts'} } as EntryPoint;
|
||||||
|
|
||||||
const dependencies = {
|
const dependencies = {
|
||||||
'first/index.ts': {resolved: ['second', 'third', 'ignored-1'], missing: []},
|
[_('/first/index.ts')]: {resolved: [second.path, third.path, '/ignored-1'], missing: []},
|
||||||
'second/index.ts': {resolved: ['third', 'fifth'], missing: []},
|
[_('/second/sub/index.ts')]: {resolved: [third.path, fifth.path], missing: []},
|
||||||
'third/index.ts': {resolved: ['fourth', 'ignored-2'], missing: []},
|
[_('/third/index.ts')]: {resolved: [fourth.path, '/ignored-2'], missing: []},
|
||||||
'fourth/index.ts': {resolved: ['fifth'], missing: []},
|
[_('/fourth/sub2/index.ts')]: {resolved: [fifth.path], missing: []},
|
||||||
'fifth/index.ts': {resolved: [], missing: []},
|
[_('/fifth/index.ts')]: {resolved: [], missing: []},
|
||||||
};
|
};
|
||||||
|
|
||||||
it('should order the entry points by their dependency on each other', () => {
|
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', () => {
|
it('should remove entry-points that have missing direct dependencies', () => {
|
||||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||||
'first/index.ts': {resolved: [], missing: ['missing']},
|
[_('/first/index.ts')]: {resolved: [], missing: ['/missing']},
|
||||||
'second/index.ts': {resolved: [], missing: []},
|
[_('/second/sub/index.ts')]: {resolved: [], missing: []},
|
||||||
}));
|
}));
|
||||||
const result = resolver.sortEntryPointsByDependency([first, second]);
|
const result = resolver.sortEntryPointsByDependency([first, second]);
|
||||||
expect(result.entryPoints).toEqual([second]);
|
expect(result.entryPoints).toEqual([second]);
|
||||||
expect(result.invalidEntryPoints).toEqual([
|
expect(result.invalidEntryPoints).toEqual([
|
||||||
{entryPoint: first, missingDependencies: ['missing']},
|
{entryPoint: first, missingDependencies: ['/missing']},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove entry points that depended upon an invalid entry-point', () => {
|
it('should remove entry points that depended upon an invalid entry-point', () => {
|
||||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||||
'first/index.ts': {resolved: ['second'], missing: []},
|
[_('/first/index.ts')]: {resolved: [second.path], missing: []},
|
||||||
'second/index.ts': {resolved: [], missing: ['missing']},
|
[_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']},
|
||||||
'third/index.ts': {resolved: [], missing: []},
|
[_('/third/index.ts')]: {resolved: [], missing: []},
|
||||||
}));
|
}));
|
||||||
// Note that we will process `first` before `second`, which has the missing dependency.
|
// Note that we will process `first` before `second`, which has the missing dependency.
|
||||||
const result = resolver.sortEntryPointsByDependency([first, second, third]);
|
const result = resolver.sortEntryPointsByDependency([first, second, third]);
|
||||||
expect(result.entryPoints).toEqual([third]);
|
expect(result.entryPoints).toEqual([third]);
|
||||||
expect(result.invalidEntryPoints).toEqual([
|
expect(result.invalidEntryPoints).toEqual([
|
||||||
{entryPoint: second, missingDependencies: ['missing']},
|
{entryPoint: second, missingDependencies: ['/missing']},
|
||||||
{entryPoint: first, missingDependencies: ['missing']},
|
{entryPoint: first, missingDependencies: ['/missing']},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove entry points that will depend upon an invalid entry-point', () => {
|
it('should remove entry points that will depend upon an invalid entry-point', () => {
|
||||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies({
|
||||||
'first/index.ts': {resolved: ['second'], missing: []},
|
[_('/first/index.ts')]: {resolved: [second.path], missing: []},
|
||||||
'second/index.ts': {resolved: [], missing: ['missing']},
|
[_('/second/sub/index.ts')]: {resolved: [], missing: ['/missing']},
|
||||||
'third/index.ts': {resolved: [], missing: []},
|
[_('/third/index.ts')]: {resolved: [], missing: []},
|
||||||
}));
|
}));
|
||||||
// Note that we will process `first` after `second`, which has the missing dependency.
|
// Note that we will process `first` after `second`, which has the missing dependency.
|
||||||
const result = resolver.sortEntryPointsByDependency([second, first, third]);
|
const result = resolver.sortEntryPointsByDependency([second, first, third]);
|
||||||
expect(result.entryPoints).toEqual([third]);
|
expect(result.entryPoints).toEqual([third]);
|
||||||
expect(result.invalidEntryPoints).toEqual([
|
expect(result.invalidEntryPoints).toEqual([
|
||||||
{entryPoint: second, missingDependencies: ['missing']},
|
{entryPoint: second, missingDependencies: ['/missing']},
|
||||||
{entryPoint: first, missingDependencies: ['second']},
|
{entryPoint: first, missingDependencies: [second.path]},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should error if the entry point does not have either the fesm2015 nor esm2015 formats',
|
it('should error if the entry point does not have either the esm5 nor esm2015 formats', () => {
|
||||||
() => {
|
expect(() => resolver.sortEntryPointsByDependency([
|
||||||
expect(() => resolver.sortEntryPointsByDependency([{ path: 'first' } as EntryPoint]))
|
{ path: '/first', packageJson: {} } as EntryPoint
|
||||||
.toThrowError(`There is no format with import statements in 'first' entry-point.`);
|
])).toThrowError(`There is no format with import statements in '/first' entry-point.`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should capture any dependencies that were ignored', () => {
|
it('should capture any dependencies that were ignored', () => {
|
||||||
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
|
spyOn(host, 'computeDependencies').and.callFake(createFakeComputeDependencies(dependencies));
|
||||||
const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]);
|
const result = resolver.sortEntryPointsByDependency([fifth, first, fourth, second, third]);
|
||||||
expect(result.ignoredDependencies).toEqual([
|
expect(result.ignoredDependencies).toEqual([
|
||||||
{entryPoint: first, dependencyPath: 'ignored-1'},
|
{entryPoint: first, dependencyPath: '/ignored-1'},
|
||||||
{entryPoint: third, dependencyPath: 'ignored-2'},
|
{entryPoint: third, dependencyPath: '/ignored-2'},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,10 +121,14 @@ describe('DependencyResolver', () => {
|
||||||
[path: string]: {resolved: string[], missing: string[]};
|
[path: string]: {resolved: string[], missing: string[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFakeComputeDependencies(dependencies: DepMap) {
|
function createFakeComputeDependencies(deps: DepMap) {
|
||||||
return (entryPoint: string, resolved: Set<string>, missing: Set<string>) => {
|
return (entryPoint: string) => {
|
||||||
dependencies[entryPoint].resolved.forEach(dep => resolved.add(dep));
|
const dependencies = new Set();
|
||||||
dependencies[entryPoint].missing.forEach(dep => missing.add(dep));
|
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};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,11 +7,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as mockFs from 'mock-fs';
|
import * as mockFs from 'mock-fs';
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '../../../src/ngtsc/path';
|
||||||
import {DependencyHost} from '../../src/packages/dependency_host';
|
import {DependencyHost} from '../../src/packages/dependency_host';
|
||||||
import {DependencyResolver} from '../../src/packages/dependency_resolver';
|
import {DependencyResolver} from '../../src/packages/dependency_resolver';
|
||||||
import {EntryPoint} from '../../src/packages/entry_point';
|
import {EntryPoint} from '../../src/packages/entry_point';
|
||||||
import {EntryPointFinder} from '../../src/packages/entry_point_finder';
|
import {EntryPointFinder} from '../../src/packages/entry_point_finder';
|
||||||
|
|
||||||
|
const _ = AbsoluteFsPath.from;
|
||||||
|
|
||||||
describe('findEntryPoints()', () => {
|
describe('findEntryPoints()', () => {
|
||||||
let resolver: DependencyResolver;
|
let resolver: DependencyResolver;
|
||||||
let finder: EntryPointFinder;
|
let finder: EntryPointFinder;
|
||||||
|
@ -26,56 +30,56 @@ describe('findEntryPoints()', () => {
|
||||||
afterEach(restoreRealFileSystem);
|
afterEach(restoreRealFileSystem);
|
||||||
|
|
||||||
it('should find sub-entry-points within a package', () => {
|
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]);
|
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
|
||||||
expect(entryPointPaths).toEqual([
|
expect(entryPointPaths).toEqual([
|
||||||
['/sub_entry_points/common', '/sub_entry_points/common'],
|
[_('/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')],
|
||||||
['/sub_entry_points/common', '/sub_entry_points/common/http/testing'],
|
[_('/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/testing')],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should find packages inside a namespace', () => {
|
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]);
|
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
|
||||||
expect(entryPointPaths).toEqual([
|
expect(entryPointPaths).toEqual([
|
||||||
['/namespaced/@angular/common', '/namespaced/@angular/common'],
|
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common')],
|
||||||
['/namespaced/@angular/common', '/namespaced/@angular/common/http'],
|
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http')],
|
||||||
['/namespaced/@angular/common', '/namespaced/@angular/common/http/testing'],
|
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/http/testing')],
|
||||||
['/namespaced/@angular/common', '/namespaced/@angular/common/testing'],
|
[_('/namespaced/@angular/common'), _('/namespaced/@angular/common/testing')],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if there are no packages', () => {
|
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([]);
|
expect(entryPoints).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an empty array if there are no valid entry-points', () => {
|
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([]);
|
expect(entryPoints).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore folders starting with .', () => {
|
it('should ignore folders starting with .', () => {
|
||||||
const {entryPoints} = finder.findEntryPoints('/dotted_folders');
|
const {entryPoints} = finder.findEntryPoints(_('/dotted_folders'));
|
||||||
expect(entryPoints).toEqual([]);
|
expect(entryPoints).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore folders that are symlinked', () => {
|
it('should ignore folders that are symlinked', () => {
|
||||||
const {entryPoints} = finder.findEntryPoints('/symlinked_folders');
|
const {entryPoints} = finder.findEntryPoints(_('/symlinked_folders'));
|
||||||
expect(entryPoints).toEqual([]);
|
expect(entryPoints).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle nested node_modules folders', () => {
|
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]);
|
const entryPointPaths = entryPoints.map(x => [x.package, x.path]);
|
||||||
expect(entryPointPaths).toEqual([
|
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
|
// 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'),
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,84 +6,76 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {AbsoluteFsPath} from '@angular/compiler-cli/src/ngtsc/path';
|
||||||
import {readFileSync} from 'fs';
|
import {readFileSync} from 'fs';
|
||||||
import * as mockFs from 'mock-fs';
|
import * as mockFs from 'mock-fs';
|
||||||
|
|
||||||
import {getEntryPointInfo} from '../../src/packages/entry_point';
|
import {getEntryPointInfo} from '../../src/packages/entry_point';
|
||||||
|
|
||||||
describe('getEntryPointInfo()', () => {
|
describe('getEntryPointInfo()', () => {
|
||||||
beforeEach(createMockFileSystem);
|
beforeEach(createMockFileSystem);
|
||||||
afterEach(restoreRealFileSystem);
|
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',
|
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({
|
expect(entryPoint).toEqual({
|
||||||
name: 'some-package/valid_entry_point',
|
name: 'some-package/valid_entry_point',
|
||||||
package: '/some_package',
|
package: SOME_PACKAGE,
|
||||||
path: '/some_package/valid_entry_point',
|
path: _('/some_package/valid_entry_point'),
|
||||||
typings: `/some_package/valid_entry_point/valid_entry_point.d.ts`,
|
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`,
|
|
||||||
packageJson: loadPackageJson('/some_package/valid_entry_point'),
|
packageJson: loadPackageJson('/some_package/valid_entry_point'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there is no package.json at the entry-point path', () => {
|
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);
|
expect(entryPoint).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there is no typings or types field in the package.json', () => {
|
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);
|
expect(entryPoint).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there is no esm2015 nor fesm2015 field in the package.json', () => {
|
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);
|
expect(entryPoint).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if there is no metadata.json file next to the typing file', () => {
|
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);
|
expect(entryPoint).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work if the typings field is named `types', () => {
|
it('should work if the typings field is named `types', () => {
|
||||||
const entryPoint =
|
const entryPoint =
|
||||||
getEntryPointInfo('/some_package', '/some_package/types_rather_than_typings');
|
getEntryPointInfo(SOME_PACKAGE, _('/some_package/types_rather_than_typings'));
|
||||||
expect(entryPoint).toEqual({
|
expect(entryPoint).toEqual({
|
||||||
name: 'some-package/types_rather_than_typings',
|
name: 'some-package/types_rather_than_typings',
|
||||||
package: '/some_package',
|
package: SOME_PACKAGE,
|
||||||
path: '/some_package/types_rather_than_typings',
|
path: _('/some_package/types_rather_than_typings'),
|
||||||
typings: `/some_package/types_rather_than_typings/types_rather_than_typings.d.ts`,
|
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`,
|
|
||||||
packageJson: loadPackageJson('/some_package/types_rather_than_typings'),
|
packageJson: loadPackageJson('/some_package/types_rather_than_typings'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work with Angular Material style package.json', () => {
|
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({
|
expect(entryPoint).toEqual({
|
||||||
name: 'some_package/material_style',
|
name: 'some_package/material_style',
|
||||||
package: '/some_package',
|
package: SOME_PACKAGE,
|
||||||
path: '/some_package/material_style',
|
path: _('/some_package/material_style'),
|
||||||
typings: `/some_package/material_style/material_style.d.ts`,
|
typings: _(`/some_package/material_style/material_style.d.ts`),
|
||||||
fesm2015: `/some_package/material_style/esm2015/material_style.js`,
|
packageJson: JSON.parse(readFileSync('/some_package/material_style/package.json', 'utf8')),
|
||||||
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'),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null if the package.json is not valid JSON', () => {
|
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);
|
expect(entryPoint).toBe(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue