Okay, this project looks really interesting so far. While watching the talk I just noticed one thing that might be a problem for some developers. That's just allowing imports with relative path's (besides URLs). There are a lot of questions on the internet on how one can import a file in TS/Webpack/whatever relative to the project root. With relative paths people will end up with things like import { foo } from '../../../../modules/featureA/foo.ts'
. Or was it just ../../../modules/...
? What if I move my files around? Yes, you have to update your imports anyway. But figuring out how many directories you have to go up is a very tedious task on its own.
In the talk, you/Ryan talked about browser compatibility. I haven't tried out ES modules in the browser just yet. But I'm pretty sure it also supports absolute paths. Absolute in that context means relative to the web root. It would be really handy if we were able to just write import { foo } from '/modules/featureA/foo.ts'
.
Of course, now there's the question: What is the root directory of a deno application? The only thing that makes sense to me right now is the folder that contains the entry file ‒ basically what process.cwd()
is in node. But I also think it's fine to place the entry file in the most top-level directory of the project, isn't it?
Or is there any problem about absolute paths I didn't think about so far? What do you think about that?
There are no absolute paths in a browser, those are called URLs which are supported. Absolute paths don't make sense in deno IMO because they would mean 100% non portable code, which makes no sense why you would generate that?
Paths are relative to the module, which means all the module needs to know is its own contained little world. Absolute paths invariably would leak local machine details and not be portable to things like a CI server, or anyone else's machine.
Indeed, in the browser when you import something starting with /
it is from the web root.
In my opinion, it makes sense for Deno to import from the project root.
Node imports from the OS root. Which makes the code non portable for browsers.
Or have a --base
flag like <base href>
in the browser.
Exactly, I'm talking about <script src="index.js">
vs. <script src="/index.js">
. Of course technically both is a relative path, but the starting point is different. I'm not talking about fs absolute path like /var/www/...
Then what are the proposed semantics of defining the base?
Then what are the proposed semantics of defining the base?
What about a flag?
deno --base=./some-path
EDIT: @nicolasparada I just realized you already said this
Or don't. Just the project root.
But if you like the idea, I think it should only allow paths inside the current project for security reasons.
Or don't. Just the project root.
What is the project root? There is no concept of a project with deno at the moment, and highly unlikely to be. I am still not sure what problem this would solve once you accept that even with a _base_ all paths are relative.
Now that you say it, you're right. I was confused with the browser and serving the content.
So in Deno one could do:
import { whatever } from '../../../some/os/path/file.js'
Right?
Just to write down an example:
Assume the entry file is located at (absolute path on an os level, just for clearification): /home/username/projects/my-project/main.ts
In that case the base would be /home/username/projects/my-project
.
Writing import { someFunc } from '/modules/module-a.ts'
would resolve to: /home/username/projects/my-project/modules/module-a.ts
.
If the entry file was located at /var/www/project-a/main.ts
, that line would resolve to: /var/www/project-a/modules/module-a.ts
, which makes it basically portable for my understanding.
There is no concept of a project with deno at the moment, and highly unlikely to be.
Even if there's no "project" from a Deno perspective, there will always be one from another perspective. You will still check-in a project folder into version control e. g.
I am still not sure what problem this would solve once you accept that even with a base all paths are relative.
What problem does it solve? Here are some other sources that describe the problem:
In another article I just read the term "relative path hell" which describes it very well.
I understand that Deno won't support aliases as it looks right now, but my proposal is a possible workaround or alternative.
EDIT: okay, I've found a way to describe the problem in a short way:
With import { func } from '/folder/file.ts'
you only have to know the location of the imported file.
With import { func } from '../folder/file.ts'
you have to know the locations of both the imported file and the importing file.
Hope that makes it more clear.
It's true that in JavaScript we don't enforce project structure (like Go and its main.go
and main()
function). In browser you just load the scripts you want. But on the server people do execute just one script that serves as entry point for a project.
cd /home/username/projects/project-name
deno main.ts
Now, imports starting with /
should point to /home/username/projects/project-name/
.
Deno aims to be secure and browser compatible...
I propose to don't allow imports from paths up /home/username/projects/project-name/
in this case.
For example, in that main.ts
, the following shouldn't be allowed.
import { something } from '../other-project/lib.ts'
cd /home/username/projects/project-name
deno some-module/main.ts
If you try to run that, now the "root" will be /home/username/projects/project-name/some-module/
and it will break if you wrote imports with the assumption root was /home/username/projects/project-name/
. This is where a --base
flag comes handy.
cd /home/username/projects/project-name
deno --base . some-module/main.ts
With
import { func } from '/folder/file.ts'
you only have to know the location of the imported file.With
import { func } from '../folder/file.ts'
you have to know the locations of both the imported file and > the importing file.Hope that makes it more clear.
In the first you still have to know where/how the base is defined and hope that is consistent across every environment it runs in and that doesn't change between invocations of deno.
In the first you still have to know where/how the base is defined...
While this is true, it is only one thing to remember. With relative paths you have to remember \
Oh, I just noticed another problem with relative paths. Let's take this import statement as an example:
~ts
import { func } from '../folder/file.ts'
~
Now imagine there are several file.ts
inside a folder
in different places. What file.ts
am I talking about? You can use this exact statement without any changes in different files across a project and every statement points to a different file on disk.
If you write import ... '/module-a/folder/file.ts'
and import ... '/module-b/folder/file.ts'
I know exactly what file.ts
I am referencing here without having to look up what file I'm currently looking at. This makes imports also more explicit.
...and hope that is consistent across every environment it runs in...
You check-in your project into a VCS, check-out on another machine and the base is still the same because your folder structure is the same as well. If your base changes for some reason I think you do something very wrong - like changing the project structure or moving just a subfolder of your project to another system. Why would you do that, if you're not explicitly working with something like submodules? (If that was even a reason. I don't know.)
...and that doesn't change between invocations of deno.
Why would it change there? In order for a change to happen here you have to change your project structure on purpose. This doesn't happen for no reason. And if you do that it would mess up relative paths as well, or actually even more easily.
About the security concerns @nicolasparada mentions: Deno is said to have read access to the entire file system. Limiting the files a Deno app can import doesn't seem to be a goal right now. But I feel that this is still a point that is worth thinking about... maybe? But I think that may be a different topic.
What if we allowed relative imports that do not back-reference? Like, only allow relative importing of this folder and what's contained within. Or why not both, just like accessing files in a shell. Except / is the root of your project.
I'm of the opinion that relative imports are better in general because it's much more explicit _what_ file you want to actually import, when compared to node-style imports. That's the core reason for deno's approach here, no?
While it is bothersome needing to update imports whenever moving a file, or having to figure out how many ..
are needed to get to the file you want, we don't have to do these things manually. That's not to say _everyone_ is using VSCode, but obviously a lot of people are, and it's certainly because of features like this. Plus, VSCode isn't the only editor to have them. Any editor using the typescript server can take advantage of them.
When met with the safer, simpler, more maintainable option (relative imports), relying on tooling to smooth out the DX seems like the next natural step forward.
@kizerkizer Yes, that would be great. (I don't know if I've been clear enough. I don't want to remove or replace the possibility to use relative paths. I just think they are not enough.)
@GSchrammel A predefined alias (that's what they are called in Webpack) could be a semantic alternative. The result is the same. Also the main question still applies: "What is the project scope? How is it defined?"
@kingdaro
it's much more explicit what file you want to actually import,...
Can you explain why? In my previous comment, I just explained the opposite.
when compared to node-style imports.
Node also only supports relative paths. Plus the module resolution thing, but that's an entire different topic. Somewhere I read also absolute paths are supported but couldn't find any information on that. I guess it's fs absolute which wouldn't be very helpful.
I'm also using VS Code and I do know about these features. But I don't know if it's a good idea to tell others to just use another editor if they don't like relative import paths. Especially those who use Atom or Vim (Is it possible to add these features there? I don't know).
When met with the safer, simpler, more maintainable option (relative imports), relying on tooling to smooth out the DX seems like the next natural step forward.
I have to admin, I do get this point. But on the other hand you could also say: "Tooling shouldn't be necessary to fix design flaws." The question is, which one applies here...
I like the idea of absolute paths and I think, when done right, they could make imports more readable, as described above. But, I think that implementing them in a way that they actually make sense is a little problematic, which would largely come down to external imports.
I like the idea of absolute paths being relative to the project root, which is where the entry point would always be located. However, the absolute paths for imports within external imports would have to use the imported file as their root.
So, doing deno ./myproject
would make ./myproject
the root of this project. Then, doing import { test } from "https://unpkg.com/[email protected]/testing.ts"
would make https://unpkg.com/[email protected]/
the importee's root.
However, what happens, if you want to import a sub resource from say https://unpkg.com/[email protected]/subfolder/somethingelse.ts
? Would https://unpkg.com/[email protected]/subfolder/
become the root? What if said resource relies on its root being https://unpkg.com/[email protected]/
with its absolute imports? How would Deno know what the intended root should be for external resources?
I don't see an obvious way to solve this problem, since Deno doesn't/won't have a package.json file or similar which could describe the project root. Unless, of course, the import statement would also support some sort of root flag -- which might make importing stuff more confusing...
Since on the web import { method } from '/module.ts'
is basically just a shorthand for import { method } from 'http://currentdomain.com/module.ts
, the question is whether we can find a single, unambigious way to resolve the same shorthand outside of the web. I don't think we can, since in this thread alone there are already a few competing resolutions: current 'project', current working directory, etc.
If project- or cwd-based resolution is desired however, we can make this explicit in the syntax, like so:
import { method } from 'project://module.ts'
import { method } from 'cwd://module.ts'
When accidentally used in the browser, this will throw a much clearer 'Invalid URL scheme' style error, instead of the likely 404 you'll get when using '/module.ts'
.
So, doing deno ./myproject would make ./myproject the root of this project. Then, doing import { test } from "https://unpkg.com/[email protected]/testing.ts" would make https://unpkg.com/[email protected]/ the importee's root.
Oh, wait. If you import a file via a URL this file is the only one that gets imported. There are no other files at that location that could be imported. Because of that, you should only import bundled files from external sources. But bundling is a planned feature of Deno. So I don't see a problem there. But importing external .ts
files that also import other files with a relative path might be a problem - unless Deno is able to resolve those references correctly. But as you said, this is not possible for absolute paths. But there are no details in the presentation on this topic.
But to be honest I'm also not really sold on the "package.json
-less structure" part. Every development environment has some kind of project concept. Node has npm, PHP has Composer, Java has Maven and Gradle, Rust has Cargo, .Net has NuGet, Go... seems to have a whole list of solutions. And all of them need to be configured in one or another way.
If Deno has an equivalent it could contain instructions on how to bundle your code (of course not too much). But the finished product could still be a single code file (is it supposed to be a .ts
file or something different?) that can be referenced by other projects and you don't end up with the same problem node_modules
has.
Also updating a library is another problem. If the URL contains the imported version and the URL is used all over your project you'll have a hard time updating the version numbers without missing a spot. Otherwise, you'll end up shipping your code with two or more versions of a dependency. (And relying on tooling could be more difficult here)
I just fear if Deno doesn't tackle these problems someone else will. Wasn't npm developed independently at first and got integrated with Node.js later on? Now we have npm, Yarn, bower, etc. Java has a similar problem (see above). Cargo has been part of Rust from the very beginning and everyone seems to be happy with it (= I don't know of any alternatives).
I just realized something: You could still use npm with Deno: init an npm project, install your dependencies, write a global importNpmModule
function and voila. My fear is: if Deno doesn't provide some project related tools, someone else will, and that can get out of control very quickly (see npm, see Maven and Gradle, see PHP's PSR-0 and PSR-4, ...).
@brechtcs For browsers the scope gets defined by the HTTP server. Via an apache or nginx config. (But when I started developing in Node, I've realized that a mapping between a URL path and a file system path is just a "feature" of the server. The URL path is technically just a string and you can do with it whatever you want.)
If this should be supported by Deno as well there of course also needs to be that kind of configuration. And there we are at a package.json
equivalent. I don't really see another way here. I don't think a flag is a good idea since you have to remember it and you can change it too easily.
cwd relative imports are actually not a bad idea. Use cases would be CLI tools written in Deno. But then again, you need to configure some things: What commands should be available in the terminal, what files should be executed? And you need a way to register them. (If that's a planned feature at all.)
@alinnert I don't necessarily disagree with you. My point was just that, since it's difficult to pin down the exact user expectation of what import '/module.ts
should mean on the server, it might be better to add an explicit protocol identifier to resolve it.
@brechtcs Yes, I understand. I just took your idea and developed it a little further. If we use protocols we just have to be careful not to introduce protocol clashes. Custom protocols are in heavy use recently. VSCode uses vscode:
for instance.
@alinnert
Can you explain why? In my previous comment, I just explained the opposite.
You mean this?
Oh, I just noticed another problem with relative paths. Let's take this import statement as an example:
Now imagine there are several file.ts inside a folder in different places. What file.ts am I talking about? You can use this exact statement without any changes in different files across a project and every statement points to a different file on disk.
We'll just have to agree to disagree on this one. If I have a file in a project referencing another file, there can only be one possible file to import in that specific context. If you mean that there could be multiple ../folder/file.ts
's between projects, I'll know what project I'm working in when I'm working on it, and I'll know what file that is, so I can't ever see that being a problem. "Go to definition" on the import itself will bring me to the file either way, no?
@kingdaro Technically this, but mentally I was inside one project. You know of course if you're inside a different project. It's difficult to come up with a more specific example...
~
my-project/
feature-a/
fetchData.ts
main.ts
feature-b/
fetchData.ts
main.ts
~
Both main.ts
contain import { foo } from './fetchData.ts'
. If you switch between files a lot (maybe because you're new and you're trying to figure out what this project does and how it works) you can easily lose track of what file you're currently looking at and what fetchData.ts
gets imported right now.
import { foo } from '/feature-a/fetchData.ts'
is more explicit because it explicitly says which fetchData.ts
it is importing right there.
I get what you mean, and I can agree that it's _clearer_ from a human readability standpoint, but from a technical standpoint, I don't think "explicit" is the most accurate word to use here.
If we look at things from the perspective of the module resolution algorithm:
./fetchData.ts
is explicit because it can only mean one thing and can only point to one file./feature-a/fetchData.ts
is ambiguous, and could mean: some file in my project, some third-party module, some file from the root of my file-system, and so on. On top of that, what if there's a feature-a
folder in another project? We'd run into the same human readability problem here, no? And even more trouble for the module resolver.Though this can be remedied with some sort of prefix system (as I think was mentioned before). Respectively, module:feature-a
, project:feature-a
, or absolute:feature-a
. After we figure out what "project" means, anyway.
While that could bring about the complexity in module resolution this project was most likely trying to avoid, it feels like a reasonable enough compromise.
EDIT: Actually, after some thought:
/deno-modules/some-module/index.ts
/src/whatever/feature.ts
And the project root could be configured using something like the --base
flag that was mentioned before, so... Maybe it could work ¯\_(ツ)_/¯
I'd still prefer to just use relatives, though.
Ah, now we're on the same level. I was missing the "technical standpoint" information bit.
I agree that /feature-a/fetchData.ts
could mean both "start from the project root" and "start from the fs root". But a module feature-a
would be resolved with import { ... } from 'feature-a/fetchData.ts'
. Without the leading slash. Just like Node does it. I don't see any connection to other modules in /foo/bar
. At least this kind of resolution would be new to me.
But I'm just asking for the pure existence of this feature, the semantics were just a first thought or idea. It doesn't really matter how it gets implemented. I'm fine with any solution: prefix, protocols, pre-defined alias...
I didn't read the whole detail of what you are talking about but on the web /...
means the root so I would just push for /
pointing to the cwd
folder.
There seem to be two major concerns:
Maybe some syntactic sugar can help make the most of both?
base <basepath> { // absolute or relative
from <relativepath> import <symbols>,
...
}
You could have multiple basepaths but group / deduplicate as appropiate to e.g.:
It’s time that real namespacing is introduced to the JS world.
namespace MyNamespace:
use My\Awesome\Lib;
Please for the love of god make this happen. Relative imports are another can of worms.
@Androbin I don't think that's the right solution here. Wasn't the goal of Deno to specifically avoid proprietary "cute" solutions and to be compatible with browsers? Browsers support absolute paths, so asking for support for them in Deno is reasonable in my opinion, unlike what you're suggesting.
Not only that, but something like ..6
doesn't really solve the problem that absolute paths are trying to solve. Sure, it would be easier to tell right away just how many steps up the path it goes, but that doesn't really help the programmer reason the location of the file. Instead of having to think about both the location of the importee and the importer and their relation to each other, you'll only have to think about the location of the importee in relation to the project, which most programmers already have an intuitive mental image of when thinking about what to import.
@l0gicgate this isn't the right place to discuss language syntax
It's clear that loading modules from relative root is something people want. If you don't implement something, you're basically going to leave implementation to others in the ecosystem. Who knows what crazy workaround is going to become standard?
Perhaps we go pythonish style?
import "mod.js";
--mdir /some/place
(/some/place/mod.js)Clearly we don't want to outright clone Pythons style of imports, it has plenty of its own problems & PYTHONPATH hell, but this seems a reasonable approach.
Security is, of course, an issue but as these are all local paths we can to some extent assume the local system isn't compromised
I imagine the original train of thought was that having a domain name / URL was basically the equivalent of a namespace + the benefit of clear ownership. That makes sense, but also comes wit ha bunch of extra baggage. Files can become unavailable, domains can change hands instantly compromising any re-download, etc. I'm going to suggest a few things, hopefully one of them is helpful, even if it's not actually used.
/
represents project root (like @yordis & @alinnert suggested), modules could appear as @moduleName/file.ts
. If paired with resolver middleware could later be swapped out for hosted solutions. /your-project
main.ts
utilities/
throttle.ts
debounce.ts
modules
rtc-swarm/
connect.ts
discover.ts
imports in main.ts
might looks like this
import throttle from '/utilities/throttle.ts';
import connect from '@rtc-swarm/connect.ts';
// directly pull from git
import throttle from 'git://js-utils/func/throttle.ts';
// get from IPFS distributed storage
import debounce from 'ipfs://js-utils/func/debounce.ts';
// grab from npm package
import throttle from 'npm://js-utils/func/throttle.ts'
I'm not suggesting these be supported out of the box, just that a custom resolver or resolver middleware could be used to make these possible. Suggestion no. 1 would be possible with custom resolvers.
I understand browser compatibility is broken with these without the use of a build tool. I would consider not worrying about browser compatibility and think more about overall usability. It's not like the browser has file IO and other server functionality anyways.
One of Deno's big design goals is browser compatibility, so I assume the goal was chosen to force Deno to choose browser-compatible usability features / tooling, or to force Deno contributors to contribute to Javascript/browser standards or tooling in order to advance Javascript for everyone including Deno.
There are proposals for standardizing custom Javascript Module Loaders. (That feature didn't make it into ES2015 partly because they wanted to re-do it to be more consistent with the Service Worker and Realms standards.) https://github.com/ModuleLoader/es-module-loader and https://github.com/whatwg/loader seem to be where the activity is.
If the Module Loader standard advanced and then was implemented in Deno, using it could possibly look like this: a project has a myLoader.ts
file that contains some calls to System.register() to customize how module loading works (it could implement Node-style loading from node_modules, reading URLs from a project-local cache, pulling things from npm automatically as needed, etc), and a myMain.ts
file that contains import statements that are intended to be resolved using the logic in myLoader.ts
. Then the project must have the loader executed first before any files that rely on it: deno myLoader.ts myMain.ts
. Maybe the filenames given after myLoader.ts could be resolved only after myLoader.ts was executed, so you could do this too depending on the logic in myLoader.ts: deno myLoader.ts my-npm-module@^3.2.1
That feature didn't make it into ES2015 partly because they wanted to re-do it to be more consistent with the Service Worker and Realms standards.
And partly because TC39 knew it was a can of worms and dodge the bullet by saying _we only worry about semantics_. They abdicated that mess to WHATWG, and Guy tried to press forward by trying to implement a spec in SystemJS, but that has largely fallen apart, because none of the browser implementors would come to a consensus. This is why three years later, no one agrees on how load semantically valid ES Modules in browsers.
If /
points to the cwd
directory, how would you determine the cwd
during editing a file, e.g. by the TS language server?
I have read this thread partially, but I think type safety should not be damaged by any choice.
PS. I will elaborate a bit, with relative paths and URLs the individual file is self contained, you can reason all of the imports just from the file. On the other hand, if /
means cwd
you have to figure out what is the cwd? It can change from depending where you start the program.
In my opinion restricting to relative paths and URLs, is a way to make individual file import list easy to reason about.
@Ciantic That's indeed a good point.
TypeScript still has tsconfig.json
's compilerOptions.paths
. Maybe that should be supported? Currently I can't see if that file is supported at all.
Oh wait... from the TypeScript documentation:
The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript project.
This could actually solve several problems mentioned in this issue including imports relative to the project root.
If tsconfig.json
is the root, how would you determine that if you import URL such as https://example.com/somedir/dir/file.ts
and it contains an import from /
? Should the user of file.ts
also recurse upwards url and look for tsconfig.json
?
With only relative paths and URLs, all files, where ever you open, can determine all it's imports and build a tree without any /
lookup recursion.
Currently I can't see if that file is supported at all.
Currently deno has hard coded compiler configuration. #51 covers being able to change that configuration. I don't want to speak for Ryan, but I suspect trying to avoid any meta configuration would be the most desirable option, though that will likely be unavoidable.
No matter what, there are issues at the moment with TypeScript and the language services as the module resolution that deno supports is incompatible with any module resolution logic that TypeScript supports (mainly in that TypeScript disallows the importing of foo.ts
as a module name, while foo.js
is allowed as well as the language service cannot resolve FQDN of modules either), but there is no value in championing any support in TypeScript until there is something to champion.
@kitsonk Thanks for the information. Well, from the current standpoint this issue is already covered by #51. Even paths
support is mentioned there. At first I just didn't think about solving this issue by using tsconfig.json
.
Should we close this issue?
There are so many replies here that it's hard to know what exactly has been discussed and agreed on.
I think it's worth noting how common a request this is in Node land. I wouldn't be surprised if they're still removing one or two issues a week asking for this.
Of course, things are different given how its module system works, but to me the greatest failure of the module resolution algorithm is that you can refer to any package in your include path except yourself. It makes sense to me that if you're in package packageA
, you should be able to import from packageA/somedir/somefile.ts
. There are more workarounds designed to circumvent this than stars in the sky.
The Deno invocation may change, the current working directory may be different, but does it matter where the project is located? Whether it's on a web server in https://domain.com/static/js/packageA
or on the filesystem at /home/me/packageA/
, they're going to have the same structure.
I'm undoubtedly just repeating what others have said though. Sorry about that. But if it's not going to happen I understand. ES module importing is a mess and adding opinionated solutions might not be a good plan going forward.
I would just like to add the importance of hotcode load and pushing them to clients in realtime asap. It's also another argument against using const ever.
@MasterJames How is this related to this issue? And depending on what you mean with "clients" I also don't get your argument. (HMR replaces entire modules which don't care about the way you declare variables.)
Worth mentioning package name maps https://github.com/domenic/package-name-maps are a thing
Just something I wanted people to be cognizant about. If it's not relative don't worry about it, sorry to complicate the issue.
likes url-loader pakage
There are no absolute paths in a browser, those are called URLs which are supported. Absolute paths don't make sense in deno IMO because they would mean 100% non portable code, which makes no sense why you would generate that?
Paths are relative to the module, which means all the module needs to know is its own contained little world. Absolute paths invariably would leak local machine details and not be portable to things like a CI server, or anyone else's machine.
Wouldn't portability just be a matter of transforming all the import paths in a project? Could it somehow be snuck into the default process of porting to browser-compatible JS?
Worth mentioning package name maps https://github.com/domenic/package-name-maps are a thing
+1 for this as it would solve not only the issue of relative-path-hell, but also dependency management (a.k.a not having urls scattered around your project). The only thing I would be curious about is where the map file is located.
Others in this thread have brought up the concern of importing third party modules that have their own mapping. Making the location of the config file implicit (e.g. the root of the package) becomes a nightmare quickly (e.g. having to walk up the directory tree looking for a map file; e.g. the node_modules problem).
What if this was handled like sourcemaps? Every file that wants to use the mapping needs an explicit annotation. e.g.
map.json
{
"imports": {
"test": "https://deno.land/x/testing/testing.ts"
}
}
module.js
//# importMapURL=./map.json
import { test } from 'test'
//...
some/nested/module
//# importMapURL=../../map.json
import { test } from 'test'
//...
It would be slightly inconvenient having to maintain the path to the map file, but I feel like this addresses the concerns mentioned so far in this thread and limits the logic deno has to worry about
@chrisdothtml this can simply be solved within the existing capabilities of ES Modules. Essentially your suggestion is something like what I have done with oak:
export { serve } from "https://deno.land/x/net/http.ts";
export { Status } from "https://deno.land/x/net/http_status.ts";
and then:
import { serve } from "./deps";
No need for anything specific in Deno. I don't see why something like this wouldn't be viable. It is a convention that I would hope would become standard within the Deno community.
@kitsonk does that scale to a project with something like 20+ dependencies though? That solution either
A. reduces all of a library's exports down to a single export:
export * as lodash from 'https://some/path/lodash'
which eliminates any possibility of importing one individual export; or
B. combines all of a library's exports with all your other dependency's exports in one single file:
export { get, map } from 'https://some/path/lodash'
So, in the context of a module using the deps, it would end up being very ambiguous:
// what is get?
import { get } from './deps.js'
Not to mention modules like https://deno.land/x/testing/testing.ts
or https://deno.land/x/dotenv/load.ts
which are self-initializing, so are just incompatible with the deps file format
@chrisdothtml in my opinion it is as scalable as an external meta data file.
export * as
is currently not valid ES syntax. See: https://github.com/tc39/proposal-export-ns-from
It would have to be something like this currently:
import * as lodashNS from "https://some/path/loadash";
export const http = lodash;
As far as B, that is currently solvable:
export { get as lodashGet } from "https://some/path/loadash";
Considering that Option A is generally equivalent to an external imports JSON file, again, we should try to avoid any external meta data requirements however possible. That is a foundational design goal of the project.
I can definitely appreciate keeping it simple; the speed of directly linking to third party modules in deno is very refreshing.
Most of my professional experience is on large teams that maintain many apps, so my head always goes to abstraction right away. I still believe a more official solution to this problem is inevitable, but it's probably best to not hard-wire any particular solution until the ecosystem matures more
The rational of import maps isn't that it's easier than a deps.ts
file - it's for web and browser compatibility and in order to get bare names.
Here is an overview of the current behaviour. The last one surprised me.
import↓ context → | /path/mod.ts | http://example.org/sub/mod.ts
------------ | ------------- | -------------
import './a.ts' | /path/a.ts | http://example.org/sub/a.ts
import '../a.ts' | /a.ts | http://example.org/a.ts
import '../../../../a.ts' | /a.ts | http://example.org/a.ts
import '/a.ts | /a.ts | /a.ts 🤷♂️
🤷♂️ is the only browser deviation.
JSPM also expects absolute paths to resolve like browsers do. Unless they change it.
@thgh We should be able to support absolute path resolution. It's a little odd for local development tho because deno won't know where the root will be in a file system...
How do browsers find http://example.org
? Is it simply window.location.origin
? What about looking for window.location.origin
or global.location.origin
for wherever the global context object is defined?
@jedahan Yes - let's do window.location.origin
- that's the proper solution.
Browsers don't use that. Items like AMD use XHR, and the internals of the browser know the current location when the URL is absolute. window.location.origin
won't work, because that is different based on what module you are calling it from.
Also module resolution doesn't happen in the runtime. It is Rust that needs to know that and it does, because the request contains the referencing URL. To support it, you just need to change the module resolution logic in Rust, but I still don't think it will do what people are expecting (Or be useful). Likely someone will need/want to configure the loader, and the WHATWG has repeatedly dodged making the ability to configure the ESM loader.
Let's just assume that on the file system, the "root" is the current working directory. If we resolve absolute paths and set window.location.origin
to a file://
URL pointing to cwd, I think this solves the problem of working locally.
@ry to be clear...
If you run deno foo.ts
then:
foo.ts
requesting module /bar.ts
will resolve to $cwd/bar.ts
foo.ts
requests https://example.com/bar.ts
and https://example.com/bar.ts
requests /baz.ts
it will resolve to https://example.com/baz.ts
window.location.origin
will be set to file://$cwd
globally no matter who loads what.My question is if you run deno https://example.com/foo.ts
the resolution can be the same, but what is window.location.origin
?
@kitsonk I agree with those itemized examples.
My question is if you run
deno https://example.com/foo.ts
the resolution can be the same, but what iswindow.location.origin
?
I guess in that case it should be set to https://example.com
like the browser would.
Cool... so to be clear:
window.location.origin
will be set to cwd if the arg module is local, and host if the arg module is remote.
window.location.origin
will be set to cwd if the arg module is local, and host if the arg module is remote.
s/arg module/main module/
What is the origin in this case:
deno https://unpkg.com/[email protected]/somefile.ts
If somefile.ts
has:
import { something } from "/otherfile.ts"
Will it use https://unpkg.com/otherfile.ts
or https://unpkg.com/[email protected]/otherfile.ts
?
If I understood correctly, if deno sets it to just host then the answer is first one. In case it sets it to the host and path, it's the second.
file://$cwd
If I write /Users/thomas/
in the Chrome address bar, then open console, I get:
I'm not sure which program is rendering the page, the page says Chromium.
Following the logic above, window.location.origin
should be:
deno https://unpkg.com/[email protected]/somefile.ts
=> https://unpkg.com
deno somefile.ts
=> file://
I think we've reached consensus here on behavior. I will now accept implementations for abs path resolution and window.location
. Let's remember to update the documentation when doing this.
(I believe @kitsonk is working on this as part of his continuing compiler refactors?)
@ry yup... probably easier taking care of it that way.
I forgot this... I will take a look at it. We have window.location
but we need the absolute module resolution logic.
I haven't read all the comments above so someone might have already mentioned this.
I think there is no need to be able to import relative to the "project" root, if this is defined as the entrypoint of your application.
What we should be able to do is import relative to the root of the current "module boundary" as I like to call it. In node.js this would have been relative to the root of the package you are in. With Deno, there is no concept of packages, so it would be necessary in code to be able to create a new isolated boundary. I think this is quite fundamental to module resolution in all languages / package managers.
However, I understand that this is likely not possible in Deno in order to achieve compatibility with browsers.
Most helpful comment
Just to write down an example:
Assume the entry file is located at (absolute path on an os level, just for clearification):
/home/username/projects/my-project/main.ts
In that case the base would be
/home/username/projects/my-project
.Writing
import { someFunc } from '/modules/module-a.ts'
would resolve to:/home/username/projects/my-project/modules/module-a.ts
.If the entry file was located at
/var/www/project-a/main.ts
, that line would resolve to:/var/www/project-a/modules/module-a.ts
, which makes it basically portable for my understanding.Even if there's no "project" from a Deno perspective, there will always be one from another perspective. You will still check-in a project folder into version control e. g.
What problem does it solve? Here are some other sources that describe the problem:
In another article I just read the term "relative path hell" which describes it very well.
I understand that Deno won't support aliases as it looks right now, but my proposal is a possible workaround or alternative.
EDIT: okay, I've found a way to describe the problem in a short way:
With
import { func } from '/folder/file.ts'
you only have to know the location of the imported file.With
import { func } from '../folder/file.ts'
you have to know the locations of both the imported file and the importing file.Hope that makes it more clear.