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

NX Packages without default export are excluded from dependencies #29486

Open
1 of 4 tasks
JacobLey opened this issue Dec 30, 2024 · 3 comments
Open
1 of 4 tasks

NX Packages without default export are excluded from dependencies #29486

JacobLey opened this issue Dec 30, 2024 · 3 comments

Comments

@JacobLey
Copy link
Contributor

Current Behavior

When a project declares an exports field and does not include a "." export, it will be ignored as a dependency.

e.g.

// foo/project.json
{
    "name": "foo",
    "exports": {
        "./foo": "./foo.js"
    }
}

and

// bar/project.json
{
    "name": "bar",
    "dependencies": {
       "bar": "workspace:^"
    }
}

Nx will not respect the dependency that bar has on foo, despite the explicit declaration in package.json.

This is reflected in both the Web UI and graph output, as well as he dependencies field for project graph from @nx/devkit.

import { createProjectGraphAsync } from '@nx/devkit';

const projectGraph = await createProjectGraphAsync();

projectGraph.dependencies['bar']
// [{ source: 'bar', target: 'npm:foo', type: 'static' }]
// The `npm:` prefix indicates an external dependency, despite it being a local dependency

Expected Behavior

When projects depend on each otheir via package.json (pnpm's "workspace:^" specifier is used as an example, but is hardly the limit), Nx should respect that dependency when building the project graph.

That means that the project graph should explicitly call out the dependency as seen the web UI (or a JSON output), and the dependencies field for project graph from @nx/devkit should also declare the project as a local dependency.

e.g. for example bar's dependency on foo above

import { createProjectGraphAsync } from '@nx/devkit';

const projectGraph = await createProjectGraphAsync();

projectGraph.dependencies['bar']
// [{ source: 'bar', target: 'foo', type: 'static' }]

GitHub Repo

https://github.com/JacobLey/issue-recreator/tree/nx-exports-dependency

Steps to Reproduce

  1. Create a project ("foo") that includes an exports field in the package.json. Export any path other than "." (I suspect this issue would also occur if the exports object is empty)
  2. Create a second project ("bar") that depends on the first. This dependency can be via traditional dependencies field
  3. Run nx graph and inspect the apparent lack of dependency of foo from bar.

Nx Report

Node           : 22.12.0
OS             : linux-arm64
Native Target  : aarch64-linux
pnpm           : 9.15.2

Failure Logs

No explicit failure logs.

Just omission of project as dependency.

Any "failures" would manifest because of tasks being executed out of order and dependencies not being available

Package Manager Version

9.15.2

Operating System

  • macOS
  • Linux
  • Windows
  • Other (Please specify)

Additional Information

Context is also shared in the issue recreation, which helps demonstrate the fact that this dependency is only omitted when using exports that don't include the "." field.

Repeated here for simplicity:

The logic to declare a dependency as "local" as opposed to "external" is implemented in this line.

const localProject = targetProjectLocator.findDependencyInWorkspaceProjects(d);

The implementation logic for this method is fairly straightforward.
If the name of the project exists in the map, return in it

return this.packageEntryPointsToProjectMap[dep]?.name ?? null;

However it will also calculate this map (memoized), which exposes the bug:

if (!packageExports || typeof packageExports === 'string') {
    // no `exports` or it points to a file, which would be the equivalent of
    // an '.' export, in which case the package name is the entry point
    result[packageName] = project;
} else {
    for (const entryPoint of Object.keys(packageExports)) {
        result[join(packageName, entryPoint)] = project;
    }
}

result['foo'] will get written because join('foo', '.') returns foo.
result['foobar'] would also work because it lacks an exports field.

Howecer result['bar/bar'] is the result of bar because it only has an export for that path.
But the dependency is checked against 'bar', and therefore appears to be an external dependency.

@JacobLey
Copy link
Contributor Author

Image Image

Screenshots of Web UI helping show failure to detect dependency

@JacobLey
Copy link
Contributor Author

Issue originally discovered because a local plugin for my Nx repo suddenly stopped being picked up as a dependency, resulting in failures.

My workaround was to declare a ".": "https://github.com/nrwl/nx/issues/29486" export

@JacobLey
Copy link
Contributor Author

Issue arose between 20.2.2 -> 20.3.0 (can't reproduce on 20.2.2)

Most likely aligning with creation of file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant