parcel-bundler/parcel

`package.json#source` field is used for npm packages with pnpm

mischnic opened this issue ยท 11 comments

๐Ÿ› bug report

When using pnpm, the resolver tries to use the "source" field of a package from npm.

๐Ÿ˜ฏ Current Behavior

๐Ÿšจ Build failed.
@parcel/core: Failed to resolve 'compute-scroll-into-view' from './index.js'
/index.js:1:35
> 1 | import computeScrollIntoView from "compute-scroll-into-view";
>   |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^
  2 |
  3 | computeScrollIntoView();
@parcel/resolver-default: Could not load './src/index.ts' from module 'compute-scroll-into-view' found in package.json#source
/node_modules/compute-scroll-into-view/package.json:88:13
  87 |   },
> 88 |   "source": "src/index.ts",
>    |             ^^^^^^^^^^^^^^ './src/index.ts' does not exist, did you mean './dist/index.js'?'
  89 |   "umd:main": "umd/compute-scroll-into-view.min.js"
  90 | }

๐Ÿค” Expected Behavior

Ignore package.json#source for npm packages

๐Ÿ”ฆ Context

#5604 (comment)

๐Ÿ’ป Code Sample

import computeScrollIntoView from "compute-scroll-into-view";

computeScrollIntoView();
{
  "dependencies": {
    "@babel/core": "^7.12.13",
    "compute-scroll-into-view": "^1.0.16"
  }
}

Use pnpm install!

๐ŸŒ Your Environment

Software Version(s)
Parcel 31f431d
ngfk commented

@mischnic Did you find a workaround for this issue? I'm also trying to load compute-scroll-into-view in a pnpm + parcel2 project.

I don't think there is a workaround, unfortunately.

This is the problematic code

// If the package has a `source` field, check if it is behind a symlink.
// If so, we treat the module as source code rather than a pre-compiled module.
if (pkg.source) {
let realpath = await this.fs.realpath(file);
if (realpath === file) {
delete pkg.source;
}
}

Until pnpm, symlinks meant that you're using a monorepo and you're not importing an npm package but another monorepo package. The condition should probably be adjusted to make pnpm work.

ngfk commented

Maybe something like this? This is very specific to pnpm though.

if (realpath === file || realpath.includes('node_modules/.pnpm'))
  delete pkg.source;

Or maybe something like "if it's a symlink and the realpath is inside of the project root"

ngfk commented

Isn't everything in the project root? Unless you meant the node_modules of the project root, that could work.

if (realpath === file || realpath.startsWith(path.join(this.projectRoot, 'node_modules')))
  delete pkg.source;

Don't the pnpm symlinks point to a global folder like /Users/abc/.pnpm/....?

ngfk commented

You're right I had to Google around to check. But pnpm works with a content-addressable store /Users/abc/.pnpm-store, and a virtual store node_modules/.pnpm. After an install it mentions that packages are hard linked from the content-addressable store to the virtual store.

Packages are hard linked from the content-addressable store to the virtual store.
  Content-addressable store is at: /Users/ngfk/.pnpm-store/v3
  Virtual store is at:             node_modules/.pnpm
Progress: resolved 951, reused 950, downloaded 1, added 954, done

In my case (macOS) the realpath resolved to the location in the virtual store. Not sure if it's the same on a Windows machine. Here's an example of what I get in my project when I simply add a console.log(realpath) to the function.

console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/package/client/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/compute-scroll-into-view@1.0.16/node_modules/compute-scroll-into-view/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json
console: /Users/ngfk/Repositories/bce-module-manager/node_modules/.pnpm/@parcel/runtime-js@2.0.0-nightly.599/node_modules/@parcel/runtime-js/package.json

Yes, it's in /Users/abc/.pnpm-store/. However that can be changed using

npm config set store-dir /path/to/.pnpm-store

I think a .modules.yml file can also be stored in the node_modules to redirect you to a different location as well. I've read about that, but never used it.

I actually use Rush to manage my monorepo, and I have it using pnpm. The final destination for the dependencies is .../project-xyz/common/temp/node_modules/.pnpm/node_modules. But I think pnpm actually saves the dependencies in sibling folders corresponding to the repository names to which node_modules is linked.

.../project-xyz/common/temp/node_modules/.pnpm/node_modules (symlinks to the other two folders)
.../project-xyz/common/temp/node_modules/.pnpm/npm.fontawesome.com
.../project-xyz/common/temp/node_modules/.pnpm/registry.npmjs.org
ngfk commented

Is there something I can do to help get a fix for this issue? I'm still running into packages that parcel incorrectly tries to resolve using package.json#source.

For this package specifically, adding "compute-scroll-into-view": "compute-scroll-into-view/dist" under "alias" in package.json solved the issue.

Maybe something like this? This is very specific to pnpm though.

if (realpath === file || realpath.includes('node_modules/.pnpm'))
  delete pkg.source;

I made a small Proof-Of-Concept ParcelJS Resolver based on your solution + DefaultResolver, it seems to fix my issues by now:

https://github.com/guesant/parcel-resolver-pnpm/blob/c1caa7aea03bead33e43685fb8c8968984d533a1/src/PNPMResolver.ts#L34-L42


Usage:

pnpm i parcel-resolver-pnpm

Partial .parcelrc file:

{
  "resolvers": [
    "parcel-resolver-pnpm",
    "..."
  ]
}

Here is the project repository: https://github.com/guesant/parcel-resolver-pnpm

NPM | unpkg browse