denoland / deno

A modern runtime for JavaScript and TypeScript.
https://deno.com
MIT License
94.58k stars 5.25k forks source link

Absolute imports do not work correctly #25691

Open happyhunter7 opened 2 weeks ago

happyhunter7 commented 2 weeks ago

Version: Deno 1.46.3

I'm getting this error , when importing a file using dynamic import

TypeError: Relative import path "react" not prefixed with / or ./ or ../

How to reproduce: let's say I have folder A which is a deno project and has a deno.json file and then I have project B which also have a deno.json file

Now in project A in some file I'm using a dynamic import I'm trying to import a file from project B and that file in project B has (import react from 'react') inside

Now I have in both projects in deno.json in imports section this "react": "npm:react@18.3.5" But even that doesn't help and I get the error that it is a relative import and react needs to be prefixed

But why is that if both projects have the dependency in the imports?

Is that I'm using a dynamic import with a absolte path in it? like that

const a = await import(absolutePathToFile)

And the in that absolutePathToFile in that file I have (import react)

lucacasonato commented 2 weeks ago

I can not reproduce the issue that you are describing, however I have some ideas that may be able to help you. Firstly, this what I am trying:

// a/deno.json
{
  "imports": {
    "react": "npm:react"
  }
}
// a/main.ts
await import("../b/mod.ts");
// b/mod.ts
import react from "react";
console.log(react);

Then when I run deno run -A a/main.ts, I get:

{
  Children: { ... },
  ...
  useTransition: [Function: useTransition],
  version: "18.3.1"
}

So this is working. However, if you move the deno.json into b/ rather than in a/, then it stops working:

error: Uncaught (in promise) TypeError: Relative import path "react" not prefixed with / or ./ or ../
    at file:///tmp/test/b/mod.ts:1:19
await import("../b/mod.ts");
^
    at async file:///tmp/test/a/main.ts:1:1

This happens because Deno resolves the config file only from the entrypoint, not from all files in your graph. What you want is a workspace. This is a collection of multiple packages, that all have their own import maps. And example how this could look in your example:

// deno.json
{
  "workspace": ["./a", "./b"]
}
// a/deno.json
{}
// b/deno.json
{
  "imports": {
    "react": "npm:react"
  }
}

Do let me know if this helps. If your issue is resolved, please close the issue.

happyhunter7 commented 2 weeks ago

Thanks @lucacasonato So I can't use a workspace because I'm building a library that needs to import user files from their project, so workspace is not an option here.

I resolved it by using the old trick with deps.ts, the only cons is that users also will have to use deps.ts in their project if they'll want to use my library

But in a ideal way I would expect it to work similar to how node resolves the dependencies if you import file B for example then node is doing a inverse Recursive walk and check for a node_modules in parent folders and imports from there, in this case it should check for a deno.json

Because at the moment to build a library similar to Next.js for example is super hard in deno, because your library should be able to import user dependencies not from your library, otherwise users will not be able to add other imports than the ones you have in your library

I'll try to create a repo that reproduces the issue, Thanks

lucacasonato commented 2 weeks ago

Yes, please create a reproduction.

happyhunter7 commented 2 weeks ago

@lucacasonato here is a repo and in the Readme I added the steps to reproduce https://github.com/happyhunter7/deno-imports-bug

Also as I said making this a workspace is not a posibility) even if it look like a workspace Think of A as a shareable public lib which needs to be installed on user machine and then be able to import files from the user project

cc @lucacasonato

lucacasonato commented 2 weeks ago

I don't understand. Are you saying your user would be running in b/, which then imports from a/, which then imports from b/ again? Or is the entrypoint actually a/?

The latter is not a supported use case for config file auto discovery - you have to then pass the config file from b/ manually using --config=b/deno.json. The former will just work.

happyhunter7 commented 2 weeks ago

No as you can see in the readme user just changes directory to B but the entry point is A , in B it just runs a command from A) Is a weird use case, but it works in node, because I'm migrating a node project to deno and thats a bloker

Btw were you able to reproduce using the repo I shared?

happyhunter7 commented 2 weeks ago

And I cant pass a config because this is a dynamic import in the code as you can see in the repo I shared

Also I cant pass a config because if B has a import in config but A not and uses another lib then that again would not work

As you mentioned in node this is autodiscovery, thats what I kinda need

happyhunter7 commented 2 weeks ago

And no is not a circular dependency as you can see in the example there is no circula deps, the code just uses a diferent CWD If it would be a circular thing then the code would not even work and throw another error

lucacasonato commented 2 weeks ago

If the user wants to run code in a/ with the config file in b/ the user will have to manually specify the --config flag.

If they do not do this we will discover the config file based on the entrypoint (in a/), and thus use the config file in a/.

happyhunter7 commented 2 weeks ago

That I think is not correct behavior Because if A has a dependency in imports and needs it and B not or reverse, this strategy is not going to work Which means you can't build in Deno tools that loads user code Well you can but with the only limitation that your library doesn't depend on any external library and user pass --config

Which also means that there is no concept of peerDependencies and also you may run in problems like 2 different versions of react being used in same project basically if you are required to have react in both projects

happyhunter7 commented 2 weeks ago

The problem I described above gets resolved only by using the deps.ts strategy But now you have to insure that react version users use should be the exact as your library use Otherwise a lot of popular libraries throws error when encounters 2 different versions being used And again when you build a developer tool you can't force user to install a dependency of a version they may not want

And that leads us to the fact that in Deno there is no concept of peerDependencies)

happyhunter7 commented 2 weeks ago

BTW is there a plan to support recursive dependency resolve? which is kinda what can fix this issue same as Node does?