Parcel: Simple tilde resolution not working for me

Created on 18 Mar 2019  ยท  28Comments  ยท  Source: parcel-bundler/parcel

๐Ÿ› bug report

Simple tilde paths as described under getting started don't seem to be resolving.

๐Ÿค” Expected Behavior

A repo of the error. My folder structure:

node_modules
  xhook
    dist
      xhook.js
folder1
  index.html
index.html
package.json

And both index.html files have:

<html>
  <body>
    <script src="~/node_modules/xhook/dist/xhook.js"></script>
  </body>
</html>

When I run parcel folder1/index.html it should build.

๐Ÿ˜ฏ Current Behavior

parcel index.html works fine but parcel folder1/index.html returns:

D:\dev\parcel-test\folder1\node_modules\xhook\dist\xhook.js: ENOENT: no such file or directory, open 'D:\dev\parcel-test\folder1\node_modules\xhook\dist\xhook.js'

It seems to be treating the tilde as a relative path rather than looking for the folder containing node_modules.

๐Ÿ’ป Code Sample

Repository of the bug.

๐ŸŒ Your Environment

| Software | Version(s) |
| ---------------- | ---------- |
| Parcel | 1.12.2 |
| Node | 11.12.0 |
| npm/Yarn | npm 6.7.0 |
| Operating System | Windows 10 |

Bug โœจ Parcel 2

Most helpful comment

The code does appear like it's set up how it's supposed to be, but ~ is also not working for me as described in the above issue report.

~ should resolve to the package root (closest node_modules), but it is resolving to the entry point, not the package location. As soon as I changed my entry point from ./index.html to ./src/index.html, my ~ references broke, and I had to change them to relative folders with ...

All 28 comments

What is the expected behaviour here? Do we want ~ to work same as in webpack? Means that a lookup is performed against node_modules to resolve the path? If yes, then the path should have been
<script src="~xhook/dist/xhook.js"></script>. However, even in this case, parcel does not work as expected, but I could try to fix that if the specification is to be compliant with other bundlers?

Think the relevant code is here

https://github.com/parcel-bundler/parcel/blob/c3921cb93368c3e0d4c24f2966931279e7fd1ae4/packages/core/parcel-bundler/src/Resolver.js#L128-L143

~/node_modules/... is actually correct: https://parceljs.org/module_resolution.html#~-tilde-paths

that if the specification is to be compliant with other bundlers?

That difference isn't ideal though

I've looked a bit more into it and looks like the behavior is expected?

parcel folder1/index.html would mean that this.options.rootDir would be path/to/folder1, therefore parcel tries to resolve the resource relative to path/to/folder1/ - the entry root.

https://github.com/parcel-bundler/parcel/blob/c3921cb93368c3e0d4c24f2966931279e7fd1ae4/packages/core/parcel-bundler/src/Resolver.js#L128-L135

entry root: the directory of the entrypoint specified to Parcel, or the shared root (common parent directory) when multiple entrypoints are specified.

On an unrelated note, line 133 seems suspicous to me that it could result in another issue (in a much more obscure case though where package root has priority, but I haven't verified this).

The code does appear like it's set up how it's supposed to be, but ~ is also not working for me as described in the above issue report.

~ should resolve to the package root (closest node_modules), but it is resolving to the entry point, not the package location. As soon as I changed my entry point from ./index.html to ./src/index.html, my ~ references broke, and I had to change them to relative folders with ...

Just to add to the above comments, I find myself building modules that are nested in several folders like myapp/path/to/my/module/module.js.

Now if I import ~/utils/anotherModule.js in my module, I would expect Parcel to resolve myapp/utils/anotherModule.js

But it resolves to myapp/path/to/my/module/~/utils/anotherModule.js and fails.

The doc states:

~/foo resolves foo relative to the nearest package root or, if not found, the entry root.

But the code states:

Tilde path. Resolve relative to nearest node_modules directory, or the project root - whichever comes first.

They are both very different behaviors. I wonder what is the use case for the latter...

I have the same issue on OSX and I'm a little bit confused as to how it should work. The way OP describes it was in line with my intuition, but the documentation and code comments are IMO unclear. Specifically the definition of package root here. What does it mean:

the directory of the nearest module root in node_modules

What exactly is _module root_ and what if my project is not _in node_modules_, e.g. it's an application in ${HOME}/my-app? And what is _project root_ mentioned in the code comment? This seems not to be defined anywhere. Looking at source code I found this:

https://github.com/parcel-bundler/parcel/blob/161cd27c96ac1e9ec682741af3ed03e60b06def8/packages/core/parcel-bundler/src/Bundler.js#L134

and this:

https://github.com/parcel-bundler/parcel/blob/161cd27c96ac1e9ec682741af3ed03e60b06def8/packages/core/parcel-bundler/src/utils/getRootDir.js#L3-L29

Does it really have to be that complicated? Couldn't tilde just resolve to the working directory or it's nearest ancestor that contains package.json file? Also, maybe there could be a CLI option --root-dir for special cases. I'm sorry if I am missing something. These are sincere qustions.

For reference, there is also this comment from @mischnic: https://github.com/parcel-bundler/parcel/issues/2857#issuecomment-478719089 . It explains the intended tilde resolution mechanism very clearly. The problem is that currently it does not work that way.

In the mean time I use the following workaround:

<link href = "/../node_modules/some-module/some-file.css" />

It seems to be working from anywhere inside src/, provided that entry is src/index.html.

Hello, just to mention that I also had to use ~/../node_modules/.. when having the entry file in src/index.html. I'm using parcel-bundler as middleware with express.

@import url('../node_modules/...'); also works.

node_modules
 carbon-components
src
  App.jsx
  App.scss <-- @import '~/../node_modules/carbon-components/css/carbon-components.css'
  index.html
server.js
package.json

That was for that simple structure, now in other complex project structure I'm struggling with importing some css in a scss.

node_modules
client
  node_modules
    carbon-components
  public
    index.html <-- <script src="../src/index.js"/>
  src
    index.js
    index.scss <-- @import '~/../node_modules/carbon-components/css/carbon-components.css'
server.js <-- const bundler = new Bundler("client/public/index.html"); // etc.
package.json

The entry file is located in /Users/devniel/dev/da/project/client/public/index.html

and the import is resolving to /Users/devniel/dev/da/project/client/public/Users/devniel/dev/da/project/client/node_modules/carbon-components/css/carbon-components.css in the Resolver.js ๐Ÿ˜•

I think that it should be /Users/devniel/dev/da/project/client/public/node_modules/carbon-components/css/carbon-components.css, the thing is that the shown error is in the parcel building is reporting this path as cannot resolve..., but inside it's using the mentioned above.

Same issue, ~ seems to be resolving to the entry root, rendering it pretty much useless.

Hey y'all, here's another reproduction repo for anyone who wants to quickly see this bug in action:
https://github.com/savovs/parcel-tilde-bug

In my case (MacOS) tilde resolves to... nothing really. It's just taken literally:

Cannot resolve module [...] at '/project-root/src/styles/~/normalize.css/normalize.css'รฑ~

Adding ~/node_modules/... does nothing.

I've resorted to using the full relative path:

@import "../../node_modules/normalize.css/normalize.css";

This is still broken. For some reason, parcel tries to resolve ~ in the entry root, not the project root. I have a normal package.json and node_modules in my root folder, and this is not a monorepo.

/Users/loyi/nm-frontend/src/components/App/App.tsx:48:39: Cannot resolve dependency '~/routes' at '/Users/loyi/nm-frontend/src/components/App/~/routes'
    at Resolver.resolve (/Users/loyi/nm-frontend/node_modules/parcel-bundler/src/Resolver.js:71:17)

I don't think this is getting fixed since all the focus is on Parcel 2.
https://github.com/parcel-bundler/parcel/issues/2813#event-2432647097

@YassienW this is broken in Parcel 2

@mjgerace pretty sure this works perfectly in Parcel 2. What exactly is broken about it?

@DeMoorJasper it resolves to entry-pont directory as root instead of the package.json (or node_modules) parent directory as root. I can upload an example project/provide more details when out of work, if you'd like.

Pretty sure #3141 fixed that

@DeMoorJasper I am on the absolute latest version, but I can see when I get home and give you more information.

Still not working for me in latest version (alpha 2.3). TS compiles fine, Visual Studio autocompletes fine, parcel says path doesn't exist.

tsconfig:

{
    "compilerOptions": {
        "baseUrl": ".",
        "paths": {
            "~/*": [
                "./src/*"
            ]
        },
        ....
    },
    "include": [
        "./src/**/*"
    ]
}

Folder structure:

root
 - package.json
 --src
 ---index.html

Command: parcel src/index.html or parcel ./src/index.html.

Error:
@parcel/resolver-default: Cannot find module '~/config/Config' from 'C:\project-root\src\components\sidebar'.

I also get Build failed. Error: Got unexpected undefined. sometimes when building, after I clear the cache this undefined error goes away.

I also have to note that in Parcel v1 this worked as expected.

Parcel 2 is broken with respect to TypeScript tilde paths.

In the Parcel docs it tells us to use ~ to refer to files relative to the src folder. See this tsconfig.json from the example:

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "~/*": ["./*"]
    },
    "typeRoots": ["node_modules/@types"]
  },
  "include": ["src/**/*"]
}

And this from the gist:

import { Reset } from '~/ui/shared/Reset'

No src path in the example -- and in Parcel 1 it worked.

Apparently Parcel 2 is using ~ as a relative path to the package.json folder? That profoundly breaks all TypeScript code which depends on the original example. The original approach is frankly a _more sane_ assumption, too; otherwise you're adding src redundantly to all imports.

The original approach is frankly a more sane assumption, too; otherwise you're adding src redundantly to all imports.

The problem with Parcel 1's ~ is that was never really was src but rather the nearest parent folder of all entries:

โ”œโ”€โ”€ package.json
โ””โ”€โ”€ src
    โ”œโ”€โ”€ index.html (entry)  with `<script src=./index.ts`
    โ””โ”€โ”€ index.ts

-> ~ = root/src
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ index.html (entry)  with `<script src=./src/index.ts`
โ””โ”€โ”€ src
    โ””โ”€โ”€ index.ts

-> ~ = root

Understood. But the _sane_ approach includes _let us define what the root folder should be_. Don't just pick one arbitrarily; let us have a say in it.

And let us pick src in the second example, even though index.html is outside of src. In fact, the _best_ case would be to let us pick an array of root folders, and just search for every ~ file sequentially in each root.

Don't break hundreds of lines of existing code because of some arbitrary new rule you'd like to add. Make the feature actually _useful_.

Even better: Respect the paths entry from the TypeScript file instead of relying on a hack.

Understood. But the sane approach includes let us define what the root folder should be. Don't just pick one arbitrarily; let us have a say in it.

Parcel 2 has support for custom resolver plugins, so even a non-core userland plugin could resolve ~ however it wants.
(Not that we don't want to support this, but we also have other priorities at the moment)

All of that is typescript specific. Parcel has supported tilde resolution for literally years, and it applies the same way across all file types. TS doesn't get to dictate how all of Parcel works.

As @mischnic said, Parcel 2 supports custom resolver plugins, and these can be chained. So we could easily support a @parcel/resolver-typescript package for better TS support, which could override the default resolver for TypeScript files.

A TypeScript resolver sounds perfect, so that would address my immediate request. Something that is linked from the TypeScript docs would be even better.

Any documentation on how a resolver should work?

Merging this into https://github.com/parcel-bundler/parcel/issues/202.


The original issue post and the most +1'ed comment say

~ should resolve to the package root (closest node_modules),

which is already the case in Parcel 2.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

termhn picture termhn  ยท  3Comments

philipodev picture philipodev  ยท  3Comments

medhatdawoud picture medhatdawoud  ยท  3Comments

humphd picture humphd  ยท  3Comments

davidnagli picture davidnagli  ยท  3Comments