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

🐛 Knip does not resolve package.json exports quite correctly #853

Open
6 tasks done
maastrich opened this issue Nov 25, 2024 · 8 comments
Open
6 tasks done

🐛 Knip does not resolve package.json exports quite correctly #853

maastrich opened this issue Nov 25, 2024 · 8 comments
Labels
bug Something isn't working

Comments

@maastrich
Copy link

maastrich commented Nov 25, 2024

Prerequisites

Reproduction url

maastrich/repro#2

Reproduction access

  • I've made sure the reproduction is publicly accessible

Description of the issue

First, thanks for the amazing tool, it's really impressive how quick and effective knip is 👏

When setting up exports using glob syntax in the package.json, typescript can resolve * to be either a path part or multiple path part:

"./*": "./src/public/*/index.ts" // can resolve to
   -> "./src/public/foo/bar/index.ts"
   -> "./src/public/baz/index.ts"

but knip doesn't

For the moment I've found a workaround where I add this in my package.json so knip can resolve le path
(leaving both as typescript resolve the first one and knip the second)

"./*": "./src/public/*/index.ts",
+ "./**": "./src/public/**/index.ts",
@maastrich maastrich added the bug Something isn't working label Nov 25, 2024
@webpro
Copy link
Collaborator

webpro commented Nov 26, 2024

Thanks for report. That's correct according to the Node.js docs and honestly I'm a bit baffled by it:

* maps expose nested subpaths as it is a string replacement syntax only.

All instances of * on the right hand side will then be replaced with this value, including if it contains any / separators.

From a Knip perspective, the issues I have with this:

  • a lot more files might become entry files and this isn't great in terms of finding unused exports (which aren't reported by default in entry files)
  • unsure about the performance penalty, Knip has to traverse the fs and find all matching files (a runtime does string replacement first and then starts resolution).
  • people who'd be baffled like me might unintentionally use * thinking it actually means * in glob pattern matching, with subtly unexpected results both in exposed exported paths as in Knip analysis.

What about this workaround, would this work:

"./*": "./src/public/**/index.ts",

Just curious, not saying it's a solution.

@maastrich
Copy link
Author

Unfortunately the workaround you proposed is not working with rollup (but typescript handles it)
I was more thinking about an option in the knip config as a "quick fix" saying

pkgJsonExport: "glob" | "node";

where glob is the default (to remain non-breaking) and would become node in a future major ?

@webpro
Copy link
Collaborator

webpro commented Nov 26, 2024

Just saying, the generic (and intended) solution to this is to add entry patterns to your knip config manually:

{
  "entry": ["./src/public/{a,b}/index.ts"]
}

At this point this feels better than rewriting * to globstar ** and marking "everything" as an entry file (the node option), even though it goes 100% against the "zero-config aim".

@maastrich
Copy link
Author

I'll have to try it, but I think that next major i there is one should handle exports as specified in node.js docs

@webpro
Copy link
Collaborator

webpro commented Nov 27, 2024

Understood, but this is a good example of static analysis versus runtime behavior and there are tradeoffs to be considered.

@acidoxee
Copy link

I can confirm Knip does seem to have issues when subpath exports are used. Unused files/exports detection on our side is quite wrong (by hundreds of items), and I haven't found a solution yet.

Configuring paths in Knip to emulate those subpath exports seems to yield worse results, and (if I understand correctly) adding all files in entry isn't a solution as we use a very broad "./*": "./src/*.ts" subpath export for some packages ("core" packages that any other package can import from). We've added some of the files in there in entry as they are the "main" files expected to be imported from other packages, but other files in there just serve as utils/types/other stuff for those main files. If they're not useful anymore, we'd like to know.

Do you think I'm misconfiguring/misusing the tool somehow? Or is that a fact that subpath exports are a known issue and must be worked on before going further? Thanks 🙏

@webpro
Copy link
Collaborator

webpro commented Jan 10, 2025

There's a known issue as described above. Also, feel free to create a reproduction I could look into.

@acidoxee
Copy link

Sorry for not providing a reproduction, I tried one here that best resembles my project setup with minimum complexity.

Workspace b is using only a subset of workspace a. In the current state, packages/a/src/utils/unused.ts should be flagged as an unused file/all of its exports should be flagged as unused, and in packages/a/src/utils/used.ts, only the aFoo variable is being used, so the aBar export/variable should be flagged as unused.

The repo uses TS project references and workspaces (here npm, although mine is Yarn, but I don't believe it makes a huge difference), so the dependency between workspaces is seen both through the project references' setup (packages/b/tsconfig.json contains a project reference to packages/a/tsconfig.json) and through the regular dependencies (packages/b/package.json has a "@internal/a": "*" dev dependency). You can make sure that everything works properly by running npm run b (I've made this script readily available in the root), it should simply display aFoo.

Both workspaces define a "exports": { "./*": "./src/*.ts" } subpath export, which basically makes every file available from the outside. So technically, I guess this warrants an entry: ['./src/**'] config in Knip as every file could be accessed, so that's what I've done here. But despite that, we still want to know whether some of those files (and the exported variables inside) are unused, hence adding includeEntryExports: true to Knip's config.

Despite all this, if you run npm run knip (which I've setup to just target the packages/a workspace), the current result is the following, which isn't what I expected:

Unused exports (3)
unusedVar  unknown  packages/a/src/utils/unused.ts:1:14
aFoo       unknown  packages/a/src/utils/used.ts:1:14  
aBar       unknown  packages/a/src/utils/used.ts:2:14

Do you reckon my usage makes sense for Knip to analyze? Am I misconfiguring or misunderstanding something? Can you confirm the current configuration is correct (or not) and would work if this current issue were to be tackled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants