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

Improve ease of use for import maps #3585

Closed
dev-nicolaos opened this issue Jan 2, 2020 · 21 comments
Closed

Improve ease of use for import maps #3585

dev-nicolaos opened this issue Jan 2, 2020 · 21 comments
Labels
cli related to cli/ dir suggestion suggestions for new features (yet to be agreed)

Comments

@dev-nicolaos
Copy link
Contributor

Import maps are an awesome feature, but having to type --importmap=import_map.json every time you run a script that depends on them is painful. This type of problem is less of a concern in Node, since I can add as much complexity as I want to an npm script and give it a nice alias.

An example of a nicer experience would be to have an alias (maybe -i?) and a default (maybe import_map.json?) so that as a user I could just type deno -i my-script.ts

Also, if I'm importing a module that uses an import map to import other dependencies, how will it be able to handle the module resolution? Obviously I can't pass the import map flag to the remote module, and even if I could, I'd have to know the location of each of my dependancies import map. Are import maps only meant for use in non-consumable applications?

@nayeemrmn
Copy link
Collaborator

An example of a nicer experience would be to have an alias (maybe -i?) and a default (maybe import_map.json?) so that as a user I could just type deno -i my-script.ts

having to type --importmap=import_map.json every time you run a script that depends on them is painful

I think Deno's current CLI philosophy is to be explicit about files like this. Needing to write out the whole command is never really a problem because you could wrap it in a script, otherwise the permission flags or any other would be a huge annoyance.

@dev-nicolaos
Copy link
Contributor Author

Hmm, I get your point about being explicit with files, especially since this has to do with module resolution. A alias for the flag would still be nice.

Needing to write out the whole command is never really a problem because you could wrap it in a script

What do you mean by this? Could you point to an example? I don't think I've seen or read about this pattern in Deno.

@kevinkassimo
Copy link
Contributor

kevinkassimo commented Jan 3, 2020

IMO introducing such command is similar to introducing package.json: a "magically" resolved file with a fixed filename that is not specified in import-maps specs, which is kind of the things we are avoiding in Deno (similar to why we never implemented index.js or .js/.ts auto suffix)

You can leverage stuff like shell script or plainly anything that could spawn a new Deno process (it is possible for third party to develop tools that does such simplification) However generally I believe we are avoiding to let these things slip into the Deno CLI itself.

@dev-nicolaos
Copy link
Contributor Author

Sounds good, what about the remote modules question? Can library authors that expects their modules to be imported by an another application use import maps? Or is bundling a de-facto requirement in order to make it consumable?

@sholladay
Copy link

sholladay commented Jan 3, 2020

Not being able to use import maps in my reusable library modules has been annoying for me, too.

One idea I came up with is to create a tool that looks at the dependency tree of the main entry script and tries to find import maps based on the URLs of those files. So if you import https://deno.land/x/pogo/main.js, then it would try to load https://deno.land/x/pogo/import-map.json, for example. Then it would take any import maps that were found and try to combine them, outputting a file that is usable by the top-level application. This is something you would use at compile time, akin to deno fetch, or it could be a wrapper around deno run that uses the generated file automatically.

It feels a little hacky and there could be problems with this approach. Import maps could conflict, among other things. However, I think that could be solved by using scopes in the generated import map. At the very least, it would be a start towards something usable.

@ry
Copy link
Member

ry commented Jan 3, 2020

Maybe @domenic has some suggestions about how to use import maps in a reusable module?

@rsp
Copy link
Contributor

rsp commented Jan 7, 2020

@sholladay The only way to use import maps in libraries that I see (that wouldn't involve automatically trying to download any special files with magic names) would be either to substitute them in a build step and distribute files with fully qualified import URLs and no source maps (so your users and Deno doesn't have to know that you use import maps in development but then you need a build step) or to use something like what @kitsonk is proposing for the type definitions in this comment, e.g.:

  • X-Import-Map: ./import-map.json header
  • /// <reference map="./import-map.json" /> comment

But Deno (currently) can use only a single import map for everything, so merging multiple import maps into one could cause conflicts if multiple libraries used the same prefix.

I don't think it can be solved unless Deno either uses multiple import maps (without merging them) for individual files, or maybe Deno resolves the imports and substitutes them in the source code before passing the files to TSC.

My concern in that case would be about browser compatibility because I don't see anything like that in https://wicg.github.io/import-maps/ and it would mean having TS files with unresolvable imports that make sense only with some external meta data (which would be pretty much like package.json).

This is not an issue for Deno-specific things like importing std that I described in my comments to #3268 like here and here and in issue #3332 because Deno's std is unlikely to be imported by browsers and because it is specifically meant for importing the correct std version matching the runtime so there are no conflicts (multiple modules use the same version of the runtime for any given deno run invocation), but for other potentially portable code it may be a problem.

Maybe it has already been considered, I wonder what Domenic thinks about it.

@domenic
Copy link

domenic commented Jan 7, 2020

The vision for import maps themselves is that they apply at an application level. An application's import map will generally be composed by tooling that takes into account a view of the entire application, including any reusable modules that the application contains.

If reusable modules want to use bare specifiers, e.g import "foo", they are essentially signaling "application developer, please figure out what to map these to. I want something that obeys the contract of being named "foo"; you can use your import map to tell me exactly what that is."

In systems like npm, they can provide additional out-of-band signals (viz. package.json) to give more details on this request. I.e., a npm-published module that references "foo" is generally asking for the "foo" published to the npm registry. Additionally, the module will likely contain a nearby package.json file that communicates an appropriate version range for "foo", which can also be used by the application. Then, various tools use these hints (often via an intermediate layer like yarn.lock) to generate the import map.

On the web, so far what we've seen is people using the npm registry and package.json system as input into import-map creation. Deno may want to follow that system, or may want to create its own. One advantage of creating its own system, is that import maps allow "foo" to map to any URL, but the npm registry + package.json system only allows "foo" to map to the contents of tarballs hosted on npmjs.org URLs. The latter doesn't seem very much in the spirit of Deno, IMHO.

A sketch of such a system would be to reuse the import map file format as a sort of Deno-package.json, and then have a command like deno generate-import-map that recursively finds all the these pseudo-import maps for depended-upon modules, and then compiles them into a single application-level import map. I can try to outline this in more detail if folks are interested. But there are probably other rather different systems possible within the import map framework as well.

@kitsonk
Copy link
Contributor

kitsonk commented Jan 7, 2020

@domenic thanks for your input, and it is understandable. While your final solution is logical, there is this point:

A sketch of such a system would be to reuse the import map file format as a sort of Deno-package.json,

Where we have long had a goal of trying to avoid any sort of "project meta data" as that has been a monkey on the back of Node.js for a long long time and to take an approach where code is code. The paradigm being a web application, which doesn't have this sort of meta data, and the complete programme exists as code. Admittedly we have things now like "manifest.json" and the like which enable things like PWAs, but I argue that we should try to ensure we can fully describe the programme from code.

Personally I would rather us steer towards some solution where any sort of meta data resolution, like "merging" of import-maps be described in code, where Deno.mergeImportMap() or something would be used and there is no magical resolution of the source of that map, but something the programme itself would express.

@domenic
Copy link

domenic commented Jan 7, 2020

Yeah, that makes sense. I guess the logical thing there would be to add the version ranges (and registry URLs?) in the import specifiers, i.e. instead of "foo" with a nearby package.json saying "foo": "^1.2.3" you'd need to use import "foo^1.2.3". (Or import "foo^1.2.3 from myregistry.org"??)

@kitsonk
Copy link
Contributor

kitsonk commented Jan 8, 2020

Currently in Deno, we support arbitrary information in the import specifier, in the sense it is up to whomever is serving the resource to determine whatever meaning. It is just URLs. So in Deno you would just:

import * as foo from "https://myregistry.org/foo@^1.2.3/index.ts";

Or whatever semantics that is meaningful to the server providing the module. The same applies to import-maps as well.

The challenge at the moment is that unlike a browser which can specify an arbitrary amount of import-maps as part of the HTML via the <script type="importmap"> elements, Deno only supports providing a single import map on the command line, and that is where the concerns around usability are coming from. It requires the invoker of the workload to know that an import map is needed and what its content is, where as in the browser, the person who created the program indicates the map is needed and provides it.

@kitsonk
Copy link
Contributor

kitsonk commented Jan 23, 2020

I was just thinking about this. This problem is a lot like the type definitions problem, where we have two scenarios:

  • The consumer/importer of a module knows that modules need to be mapped differently.
  • The hoster of a module knows that modules need to be mapped differently.

In a browser, it is always the latter, and the hoster delivers that information in the form of <script type="importmap"> as part of the HTML payload. Currently in Deno we only cater to the former, in that whomever invokes the workload needs to have a full understanding of the remapping of modules, and there is no way for the hoster to express that intent.

The type definitions was the same thing, where we only solved the first problem, but I have a PR open to solve the second problem, and a similar solution could work for this too. We could do the following:

  • Support a directive within a module that points to an import map, which would need to be loaded and merged before other dependencies of that module are resolved. (Something like // @deno-importmap="./importmap.json" or /// <reference importmap="./importmap.json" />)
  • Support a custom header that points to an import map, which would need to be loaded and merged before other dependencies of that module are resolved. (X-Import-Map for example)

@flying-sheep
Copy link
Contributor

flying-sheep commented Jan 24, 2020

single-file scripts

I think on this level, this can be easily made convenient via deno install https://... --import-map=... or using a shebang like #!/usr/bin/env -S deno --import-map=...

hierarchical online modules needing source maps

If we go with the “hoster provides a map” we’re in pioneering/extending territory again. I like deno’s philosphy of not barreling down its own path while leaving the rest of the JS ecosystem behind, just to find that it didn’t follow all the way and took a few different turns along the way.

import security

Something of an aside, but it ties into everything we’re talking here: We need integrity checks for imports, to make sure our application is safe when a server is compromised. This could maybe be fixed by extending the import map proposal? It would also mean that you would want a local import map or when downloading it, we’d want to download the import map without its own hash to verify it hasn’t been tampered with.

@dev-nicolaos
Copy link
Contributor Author

dev-nicolaos commented Apr 2, 2020

Just running through some testing, it appears there's currently no support for import maps in several of the CLI's sub commands:deno install, deno info, and deno bundle. I haven't checked deno test as I don't have any tests in a deno project yet (just tested deno test and import maps work as expected).

We have issues for a couple of these which I'll link here for reference...

...but I can't find any issue for using import maps in deno info.

@wongjiahau
Copy link
Contributor

wongjiahau commented Jul 16, 2020

@bartlomieju @kitsonk should we include the --importmap flag for all the subcommands listed by @dev-nicolaos ? I can try to do it if there's no special reasons against it.

@bartlomieju
Copy link
Member

@wongjiahau import maps are supported for deno bundle, only deno install doesn't support it yet

@wongjiahau
Copy link
Contributor

Actually it's also not working for "deno info", can I open a PR to allow importmap options for "deno install" and "deno info"?

@stale
Copy link

stale bot commented Jan 6, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jan 6, 2021
@kitsonk kitsonk added cli related to cli/ dir suggestion suggestions for new features (yet to be agreed) labels Jan 7, 2021
@stale stale bot removed stale labels Jan 7, 2021
@ghost
Copy link

ghost commented Feb 6, 2021

It would be nice if import maps could:

  1. be served remotely via url: "https://application.site/import-map.json"
  2. be defined in code via an object passed to a deno function like Deno.importmap({...})

@bartlomieju
Copy link
Member

It would be nice if import maps could:

  1. be served remotely via url: "https://application.site/import-map.json"
  2. be defined in code via an object passed to a deno function like Deno.importmap({...})

The first point is addressed in #9519.

The second point is very unlikely to be implemented; though there is discussion about similar API for worker scope in the import map spec repository.

We're aligning import maps to spec as per #9461 and plan to follow it closely, so I'm going to close this issue as it suggest adding APIs that are not part of the spec.

@EricRabil
Copy link

If Deno is adding compatibility with Node, this feels pretty incompatible.

Imagine having multiple, if not dozens, of flags for import maps that you have to adjust whenever you are updating your modules. That's a lot of noise. For Deno-native projects, fine. But for existing Node libraries trying to create an easy interop with Deno, this is a pretty discouraging decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cli related to cli/ dir suggestion suggestions for new features (yet to be agreed)
Projects
None yet
Development

No branches or pull requests