Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ngcc): correctly identify the package of targeted entry-points #36249

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -150,13 +150,35 @@ export class TargetedEntryPointFinder implements EntryPointFinder {
break;
}
}
// If we get here then none of the `basePaths` matched the `entryPointPath`, which
// is somewhat unexpected and means that this entry-point lives completely outside
// any of the `basePaths`.
// All we can do is assume that his entry-point is a primary entry-point to a package.
return entryPointPath;
}

// We couldn't find a `packagePath` using `basePaths` so try to find the nearest `node_modules`
// that contains the `entryPointPath`, if there is one, and use it as a `basePath`.
let packagePath = entryPointPath;
let scopedPackagePath = packagePath;
let containerPath = this.fs.dirname(packagePath);
while (!this.fs.isRoot(containerPath) && !containerPath.endsWith('node_modules')) {
scopedPackagePath = packagePath;
packagePath = containerPath;
containerPath = this.fs.dirname(containerPath);
}

if (this.fs.exists(join(packagePath, 'package.json'))) {
// The directory directly below `node_modules` is a package - use it
return packagePath;
} else if (
this.fs.basename(packagePath).startsWith('@') &&
this.fs.exists(join(scopedPackagePath, 'package.json'))) {
// The directory directly below the `node_modules` is a scope and the directory directly
// below that is a scoped package - use it
return scopedPackagePath;
} else {
// If we get here then none of the `basePaths` contained the `entryPointPath` and the
// `entryPointPath` contains no `node_modules` that contains a package or a scoped
// package. All we can do is assume that this entry-point is a primary entry-point to a
// package.
return entryPointPath;
}
}

/**
* Split the given `path` into path segments using an FS independent algorithm.
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-cli/ngcc/src/entry_point_finder/utils.ts
Expand Up @@ -30,7 +30,7 @@ import {PathMappings} from '../utils';
export function getBasePaths(
sourceDirectory: AbsoluteFsPath, pathMappings: PathMappings | undefined): AbsoluteFsPath[] {
const fs = getFileSystem();
let basePaths = [sourceDirectory];
const basePaths = [sourceDirectory];
if (pathMappings) {
const baseUrl = resolve(pathMappings.baseUrl);
Object.values(pathMappings.paths).forEach(paths => paths.forEach(path => {
Expand Down
Expand Up @@ -166,6 +166,81 @@ runInEachFileSystem(() => {
]);
});

it('should handle external node_modules folders (e.g. in a yarn workspace)', () => {
// Note that neither the basePath and targetPath contain each other
const basePath = _Abs('/nested_node_modules/packages/app/node_modules');
const targetPath = _Abs('/nested_node_modules/node_modules/package/entry-point');
loadTestFiles([
...createPackage(_Abs('/nested_node_modules/node_modules'), 'package'),
...createPackage(_Abs('/nested_node_modules/node_modules/package'), 'entry-point'),
]);
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, targetPath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(_Abs('/nested_node_modules'), entryPoints)).toEqual([
['node_modules/package', 'node_modules/package/entry-point'],
]);
});

it('should handle external node_modules folders (e.g. in a yarn workspace) for dependencies',
petebacondarwin marked this conversation as resolved.
Show resolved Hide resolved
() => {
// The application being compiled is at `/project/packages/app` so the basePath sent to
// ngcc is the `node_modules` below it
const basePath = _Abs('/project/packages/app/node_modules');
// `packages/app` depends upon lib1, which has a private dependency on lib2 in its
// own `node_modules` folder
const lib2 = createPackage(
_Abs('/project/node_modules/lib1/node_modules'), 'lib2', ['lib3/entry-point']);
// `lib2` depends upon `lib3/entry-point` which has been hoisted all the way up to the
// top level `node_modules`
const lib3 = createPackage(_Abs('/project/node_modules'), 'lib3');
const lib3EntryPoint = createPackage(_Abs('/project/node_modules/lib3'), 'entry-point');
loadTestFiles([...lib2, ...lib3, ...lib3EntryPoint]);
// The targetPath being processed is `lib2` and we expect it to find the correct
// entry-point info for the `lib3/entry-point` dependency.
const targetPath = _Abs('/project/node_modules/lib1/node_modules/lib2');
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, targetPath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(_Abs('/project/node_modules'), entryPoints)).toEqual([
['lib3', 'lib3/entry-point'],
['lib1/node_modules/lib2', 'lib1/node_modules/lib2'],
]);
});

it('should handle external node_modules folders (e.g. in a yarn workspace) for scoped dependencies',
() => {
// The application being compiled is at `/project/packages/app` so the basePath sent to
// ngcc is the `node_modules` below it
const basePath = _Abs('/project/packages/app/node_modules');
// `packages/app` depends upon lib1, which has a private dependency on lib2 in its
// own `node_modules` folder
const lib2 = createPackage(
_Abs('/project/node_modules/lib1/node_modules'), 'lib2',
['@scope/lib3/entry-point']);
// `lib2` depends upon `lib3/entry-point` which has been hoisted all the way up to the
// top level `node_modules`
const lib3 = createPackage(_Abs('/project/node_modules/@scope'), 'lib3');
const lib3EntryPoint = createPackage(
_Abs('/project/node_modules/@scope/lib3'), 'entry-point', ['lib4/entry-point']);
const lib4 =
createPackage(_Abs('/project/node_modules/@scope/lib3/node_modules'), 'lib4');
const lib4EntryPoint = createPackage(
_Abs('/project/node_modules/@scope/lib3/node_modules/lib4'), 'entry-point');
loadTestFiles([...lib2, ...lib3, ...lib3EntryPoint, ...lib4, ...lib4EntryPoint]);
// The targetPath being processed is `lib2` and we expect it to find the correct
// entry-point info for the `lib3/entry-point` dependency.
const targetPath = _Abs('/project/node_modules/lib1/node_modules/lib2');
const finder = new TargetedEntryPointFinder(
fs, config, logger, resolver, basePath, targetPath, undefined);
const {entryPoints} = finder.findEntryPoints();
expect(dumpEntryPointPaths(_Abs('/project/node_modules'), entryPoints)).toEqual([
['@scope/lib3/node_modules/lib4', '@scope/lib3/node_modules/lib4/entry-point'],
['@scope/lib3', '@scope/lib3/entry-point'],
['lib1/node_modules/lib2', 'lib1/node_modules/lib2'],
]);
});

it('should handle dependencies via pathMappings', () => {
const basePath = _Abs('/path_mapped/node_modules');
const targetPath = _Abs('/path_mapped/node_modules/test');
Expand Down