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

Simple tilde resolution not working for me #2813

Closed
Kurren123 opened this issue Mar 18, 2019 · 34 comments
Closed

Simple tilde resolution not working for me #2813

Kurren123 opened this issue Mar 18, 2019 · 34 comments

Comments

@Kurren123
Copy link

Kurren123 commented Mar 18, 2019

🐛 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
@lustoykov
Copy link
Contributor

lustoykov commented Mar 19, 2019

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

case '~':
// Tilde path. Resolve relative to nearest node_modules directory,
// or the project root - whichever comes first.
while (
dir !== this.options.rootDir &&
path.basename(path.dirname(dir)) !== 'node_modules'
) {
dir = path.dirname(dir);
if (dir === path.dirname(dir)) {
dir = this.options.rootDir;
break;
}
}
return path.join(dir, filename.slice(1));

@mischnic
Copy link
Member

~/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

@lustoykov
Copy link
Contributor

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.

case '~':
// Tilde path. Resolve relative to nearest node_modules directory,
// or the project root - whichever comes first.
while (
dir !== this.options.rootDir &&
path.basename(path.dirname(dir)) !== 'node_modules'
) {
dir = path.dirname(dir);

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).

@bbugh
Copy link

bbugh commented Mar 28, 2019

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 ...

@pierre-charpentier
Copy link

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...

@tad-lispy
Copy link

tad-lispy commented Apr 9, 2019

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:

rootDir: getRootDir(this.entryFiles),

and this:

function getRootDir(files) {
let cur = null;
for (let file of files) {
let parsed = path.parse(file);
if (!cur) {
cur = parsed;
} else if (parsed.root !== cur.root) {
// bail out. there is no common root.
// this can happen on windows, e.g. C:\foo\bar vs. D:\foo\bar
return process.cwd();
} else {
// find the common path parts.
let curParts = cur.dir.split(path.sep);
let newParts = parsed.dir.split(path.sep);
let len = Math.min(curParts.length, newParts.length);
let i = 0;
while (i < len && curParts[i] === newParts[i]) {
i++;
}
cur.dir = i > 1 ? curParts.slice(0, i).join(path.sep) : cur.root;
}
}
return cur ? cur.dir : process.cwd();
}

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: #2857 (comment) . 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.

@devniel
Copy link

devniel commented Apr 16, 2019

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

@devniel
Copy link

devniel commented Apr 16, 2019

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.

@YassienW
Copy link

YassienW commented Jun 5, 2019

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

@vladinator1000
Copy link

vladinator1000 commented Jun 16, 2019

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

@alexerhardt
Copy link

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";

@mjgerace
Copy link

mjgerace commented Jan 9, 2020

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.

@kahurangitama
Copy link

/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)

@YassienW
Copy link

YassienW commented Jan 10, 2020

I don't think this is getting fixed since all the focus is on Parcel 2.
#2813 (comment)

@mjgerace
Copy link

@YassienW this is broken in Parcel 2

@DeMoorJasper
Copy link
Member

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

@mjgerace
Copy link

mjgerace commented Jan 13, 2020

@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.

@DeMoorJasper
Copy link
Member

Pretty sure #3141 fixed that

@mjgerace
Copy link

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

@Cristy94
Copy link

Cristy94 commented Mar 20, 2020

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.

@TimMensch
Copy link

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.

@mischnic
Copy link
Member

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

@TimMensch
Copy link

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.

@mischnic
Copy link
Member

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)

@devongovett
Copy link
Member

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.

@TimMensch
Copy link

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?

@mischnic
Copy link
Member

mischnic commented May 12, 2020

@mischnic
Copy link
Member

Merging this into #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.

@NicolasReibnitz
Copy link

For me the probem were assets. I managed to resolve the issue by adding a symlink to the static folder.

Take this (simplified) structure for example:

├── src
│   └── project_1
│       └── en
│           └── index.html
└── static
    └── media
        └── video
            └── video.mp4

In index.html I had the option to do this:

<source src="../../../static/media/videos/video.mp4" type="video/mp4" />

which I didn't like much. So I added a symlink to the static folder inside the en folder (which holds the index.html file).
Like so:

$ ln -sfn ../../../static static

Now the structure is:

├── src
│   └── project_1
│       └── en
│           ├── index.html
│           └── static -> ../../../static
└── static
    └── media
        └── video
            └── video.mp4

and I can use:

<source src="~static/media/videos/video.mp4" type="video/mp4" />

Not ideal but a lot closer to the intended use of the ~. At least the way I initially understood it.

Note: the tilde isn't really necessary of course, but it reminds me of the special status of the symlinked static folder.

@mischnic
Copy link
Member

mischnic commented Apr 9, 2021

An alternative to that symlink is using alias.

Like the local-module entry here: https://v2.parceljs.org/features/module-resolution#aliases

In your case, "alias": {"static": "./static"} should work (in a package.json next to src)

@NicolasReibnitz
Copy link

Oh! Gotta try that! That's of course a lot more elegant.

Thanks!

@warrenday
Copy link

Was this issue resolved? If code is within ./src I am now stuck needing to add the word src to all imports.

import { Layout } from "~/src/components/Layout";

It works but is kind of ugly.

@TimMensch
Copy link

Needing to add ./src was pretty much the nature of the bug/missing feature.

It should take the root from the TypeScript config paths field. Not sure if they finally fixed this; due to this and a few other problems I had with Parcel I gave up and switched to vite: https://vitejs.dev/

With Vite it all works if you import the vite-tsconfig-paths plugin. Vite is also crazy faster than Parcel because it uses esbuild. But some TypeScript features aren't supported. YMMV.

@warrenday
Copy link

Thanks I'll check out vite!

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