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

Implements PnP support #217

Merged
merged 12 commits into from
Jul 19, 2024
Merged

Implements PnP support #217

merged 12 commits into from
Jul 19, 2024

Conversation

arcanis
Copy link
Contributor

@arcanis arcanis commented Jul 9, 2024

(note: each commit contains a particular semantic change, so you can review them one by one if you prefer)

This PR implements support for PnP. I opened it in draft since I'm sure some feedback rounds will be required, but it should already be working fine (added some tests to verify that).

There are two main changes:

  • During the node_modules resolution we first check whether a PnP manifest is attached to the resolver. If it is we query the PnP resolver to locate the package directory instead of performing the typical node_modules traversal. This directory is then used in oxc's subsequent resolution passes (exports, extensions, etc).

  • Since Yarn stores the packages inside a zipped cache (details here), oxc needs to know how to look into them. The pnp crate provides some utilities to make this work simpler, which I used here. It adds some boilerplate, I'm open to ideas to clean it up (but since it's in fairly low amount code in absolute measures, perhaps it's ok?).

There's one known limitation: PnP technically supports interlacing completely different dependency trees. This only happens when a project with a yarn.lock tries to import files from another project with a different yarn.lock, which is very rare (it usually only ever come up when using some specific scenarios of yarn dlx). I didn't implement it here, the manifest is instead passed as option (but perhaps we should instead locate the manifest by crawling the filesystem?).

Fixes #53

@Boshen Boshen self-assigned this Jul 9, 2024
@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

I'll start experimenting with this feature locally by initiating a yarn pnp project.

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

Things left for me to do:

@arcanis
Copy link
Contributor Author

arcanis commented Jul 9, 2024

The tests should work, I may have broken something before push; checking it now (my intent is that the presence or not of the PnP manifest is the feature flag - it should have 0 impact on people that aren't using PnP)

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

The latest changes is working on my end:

With a new project:

{
  "name": "test-yarn",
  "packageManager": "yarn@4.3.1",
  "dependencies": {
    "lodash": "^4.17.21"
  }
}

Update the options in examples/resolver.rs:

    let options = ResolveOptions {
        pnp_manifest: pnp::load_pnp_manifest("/Users/boshen/github/test-yarn/.pnp.cjs").ok(),
        ..ResolveOptions::default()
    };
cargo run --example resolver -- /Users/boshen/github/test-yarn lodash

prints

path: "/Users/boshen/github/test-yarn"
specifier: lodash
Resolved: "/Users/boshen/.yarn/berry/cache/lodash-npm-4.17.21-6382451519-10c0.zip/node_modules/lodash/lodash.js"

Copy link

codspeed-hq bot commented Jul 9, 2024

CodSpeed Performance Report

Merging #217 will not alter performance

Comparing arcanis:mael/pnp (ca3030c) with main (b9d0443)

Summary

✅ 3 untouched benchmarks

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

my intent is that the presence or not of the PnP manifest is the feature flag - it should have 0 impact on people that aren't using PnP

I understood that the option pnp_manifest is the runtime feature flag.

Unfortunately Cargo will pull in and compile all the unused dependencies. We need to a feature flag to avoid these extra dependencies at compile time.

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

Thank you arcanis, this integration is really smooth.

I'll take care of the rest. Will ping you or create an issue in pnp-rs if any problem comes up.

@arcanis
Copy link
Contributor Author

arcanis commented Jul 9, 2024

That's unfortunate :( Would it help if we had fewer dependencies?

My concern is that every tool using oxc-resolver would in theory benefit from having PnP support; if they have to do something to get it to work, they may forget doing it (the necessity of manually passing the pnp_manifest option is already adding this friction, but I was thinking it could be removed by crawling the filesystem to locate the manifest).

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

My concern is that every tool using oxc-resolver

The tool will decide whether they want to enable pnp.

All the Rust bundlers will enable pnp. Simple tools that only require simple resolution will not enable it.

the necessity of manually passing the pnp_manifest option is already adding this friction, but I was thinking it could be removed by crawling the filesystem to locate the manifest

I'll take a look at how enhanced-resolve and parcel handles this problem.

@arcanis
Copy link
Contributor Author

arcanis commented Jul 9, 2024

enhanced-resolve and parcel handles this problem.

Enhanced-resolve checks whether the process is running within the PnP runtime, so no configuration is needed. Parcel doesn't support PnP yet (I only prototyped it a year ago, but didn't open the PR).

@Boshen
Copy link
Member

Boshen commented Jul 9, 2024

Thank you for the context, I have gathered the following information:

For bundlers with napi integration, they can get the manifest path in node.js and pass it over.

So the question is, are we able to get the manifest path from require('pnpapi') or similar APIs provided by the runtime?

For tools without a node.js runtime, they'll have to provide the path explicitly as a compromise.

@arcanis
Copy link
Contributor Author

arcanis commented Jul 9, 2024

So the question is, are we able to get the manifest path from require('pnpapi') or similar APIs provided by the runtime?

Yes, there are two ways:

  • Calling require.resolve('pnpapi') will return the path to the current .pnp.cjs file
  • Calling require('module').findPnpApi?.(cwd) will crawl the filesystem starting from cwd (which can be __dirname, process.cwd(), ...)

@BasixKOR
Copy link

It would be worth looking into what esbuild does for detecting PnP manifest since using pnpapi would result in oxc-resolver loading up a Node.js instance every time the Resolver instance initializes.

https://github.com/evanw/esbuild/blob/360d47230813e67d0312ad754cad2b6ee09b151b/internal/resolver/resolver.go#L514

@Boshen Boshen marked this pull request as ready for review July 19, 2024 11:41
@Boshen
Copy link
Member

Boshen commented Jul 19, 2024

I'm going to merge this and continue working on the main branch.

Thank you arcanis for the hard work.

@Boshen Boshen merged commit 5310589 into oxc-project:main Jul 19, 2024
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Yarn PnP
3 participants