-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Built-in support for semvered specifiers on the deno.land/x registry #17495
Comments
I think the disadvantages of the complexity introduced are greater than the advantages gained by implementing this. Since npm modules are designed with semver resolution in mind, having multiple versions of dependencies tends to cause problems. Therefore, I think that npm specifiers need semver support. If there's a use case for semver, it's when I want to bundle front-end code to reduce size instead of running it locally on Deno. In that case, I'm not running any code on Deno, so I don't think having built-in semver support would help. (Maybe a clever bundler that automatically resolves semver would be useful here.) What if another runtime starts implementing import specifiers like |
Ideological Concerns
Could you please remove
As @ayame113 noted, it should be completely OK for non-legacy code. Yes, you still have some exotic cases nowadays, but these are exceptions rather than the reason for such a controversial change.
Even if you have these problems somehow, inspectable deps tree was a part of the ideology, which is good. It's good for developer to know which deps does his project have.
Yeah, but the main idea of semver is that any version under the same major number should be compatible. If it's not, library author should deprecate incompatible version of his library and create a major release. I mean, specific
This doesn't feel normal, since it's not simple and not stable because of the possible changes of registry index, which will affect your dependency tree possibly. Many of the ideological concerns were stated by the community in the original roadmap issue. I personally think that @aapoalas was exceptionally strong in this comment. Technical AspectsThere is a common use-case when module from What if one ends up with having
This is going to increase the complexity of self-hosted CDN / registry. Moreover, it seems that self-hosted registry is going to be vendor-locked to Deno's implementation, which is not good. Nowadays we have a wide choice of full-featured CDN implementations, which is nice. Yes, you kinda could still use these even after, but at same time you could not because of the previous point.
It feels that
There is a common use-case when module from Maybe we could register |
I like this problem statement. Nice to have this. First a couple of arguments against it: 2: Users needing to know if a duplicated dependency will cause issues is unavoidable: Some dependencies will, some won't. Some of those dependencies will be used by other dependencies with a static version which may clash with other static versions defined by yet another dependency. Semver resolution doesn't make this issue go away, except if everyone uses semver and they define similarly loose semver ranges. Manual deduplication (or an automatic tool to help with this) will still be needed for any other case. 3: Most modules will probably only be tested to work on the specific version that the developer used locally when they were writing it. A module with a large support crew like Nest.js or React might actually have tested at least some alternative versions in the semver range but I doubt that's a very common occurrence. eg. Is Fresh tested against all Preact v10 series versions? Most likely not: We just trust that breaking changes do not occur within the same semver. That exact same trust can be used by an end-user to deduplicate locally. Now, it's definitely true that deduplication currently would be done manually using an import map. However, I do not see this to be a particularly horrible state of affairs in general. Import maps are a great and powerful tool, and they are a standard so it makes sense to use them. The Deno team proposal would effectively leave the world of import maps: Yes, internally it might still be an import map resolution or something that can be turned into an import map, but it would still be an internal resolution algorithm. You might as well argue that Node is using import maps since for any node_modules configuration it is possible to write an import map that resolves the same way. Alternative proposalI would instead propose that Deno would simply make generating this proposed internal import map easier. That is, provide a tool (either in Deno CLI or as a script in std) to both view the dependency tree information in a more human readable format (suppressing details in favour of readability), and to generate an import map from the dependency tree with flags controlling (automatic) deduplication. A rough sketch would be something like this (all the command names and flags here are horrible, don't mind them specifically): Human readable format for detecting duplicates
This would output the dependency tree (taking into account existing import map if detected) but dropping out all individual code files from the listing, as well as flattening the dependency tree: In this view we do not care if dependency X is imported by A or B or both, nor if it is X/mod.ts or X/deps.ts or both that we are importing. This would be a simplified list of which HTTP URLs we're importing code from with similar paths joined together so that EDIT: The intention here is to prove an easy form from which a user can manually detect duplication, eg:
This command could also optionally highlight (potential) duplicates with a different color or such. Generating import maps with automatic or semi-automatic deduplication
Same as above but now write a new import map and request automatic deduplication. Given that the /x/ registry does not force semver, nor does it force This command would generate an import map where eg. This tool could then be extended to control various parts of this deduplication and import map generation, such as:
Additionally, this tooling could be built to take into account registry-provided hints of existing import maps in dependencies (I outlined an idea of registries opting to provide a path to library import map through a custom header.), maybe also including loose semver definitions in this. A dependency could then opt to eg. do loose semver plain imports with an import map as: {
"imports": {
"library": "library@^3.0.0",
"library@^3.0.0": "https://deno.land/x/[email protected]/mod.ts"
}
} This would still mesh with a local import map that defined a similar loose semver range. |
One thing to consider as well: The Deno team's proposed solution uses an implied internal import map to handle the |
@aapoalas, I also tried the same (or quite similar) deduping idea here. The code is undocumented and quite unoptimized, but I just want to say that this idea seemed to be working well.
One'll probably want to have different bases or even URL patterns for different CDNs, since some CDNs produce different prefixes for the same assets sometimes. This should be configurable probably. But yes, the idea of controllable resolving to the highest already available version in dependency graph is what I'd like to see also. |
It used to be a value of Deno that I think the idea by @aapoalas is great! It does not care about the implementation details of the registry as long as there is semver in the URL. In addition, it will work well with how libraries are written today, it is easy to understand and maintain, and it seems to align pretty well with Deno's values of not doing any magic and following web standards. No more custom Deno is easy to learn. What I like especially about this suggestion is the fact that it can be used optionally, which makes it very approachable. When people read module code or application code, they don't have to know anything about what using |
Please try tani/lib.deno.dev (https://lib.deno.dev) for redirecting
|
There was a discussion maybe over a year ago, in which ry suggested that deno.land should transpile modules automatically to js, presumably to align Deno with browsers and make things simpler for those who write for both. This feature seems to be going the other direction, and frankly it's worrying as a long-time user. |
Credit where it's due, |
What happens for version resolution failures? ie:
As a plugin library author (i.e. depC is a library with a plugin mechanism and depA / depB register with depC), I want this to fail. I don't want this to resolve to two different versions of depC. As an app author I want this to fail understandably so I know the "plugins" that are failing resolution and can resolve it before the app can run. What would help me is knowing if there are upgrade or even downgrade paths for depA and depB that can resolve successfully. As a non-plugin library author, as long as these module uses are isolated I don't think I care how many copies are in an app. As a library author I want tooling that makes it easier to keep my library up to date and tested against the latest versions of dependencies. As the app author it's nice if the app doesn't duplicate dependencies. As an app author what I want is insight into my dependency tree and tooling for easy updates of dependencies. If the community and applications had the necessary tooling and a strong ingrained practice for staying "up-to-date" that would lead to natural de-duplication for cases where de-duplication isn't critical for execution (minimizing app size) and make resolution easier for cases where de-duplicaiton is critical (plugin libraries sharing transitive dependencies). |
I wouldn't consider this a "version resolution failure" as it's a valid scenario that should be allowed. This would not error and resolve depC >= 3 for depA & depC < 3 for depB similar to what npm does and many other package managers. The output of If someone really wanted they could use an import map to override one of the specifiers to point to a different version and remove the duplicate dependency if they find the code still works. |
I'm arguing as a module author an important constraint I need to express is of being a singleton (the plugin system use-case). I'm re-iterating it because the response doesn't acknowledge that use-case. If that use-case is not intended to be improved as part of semver that's fine to state. I'd probably argue the feature design of semver-aware modules is missing an important use-case that should be considered / captured in design discussion somewhere. The npm ecosystem had to do a bunch of twisting in the wind to express the singleton / plugin use-case out-of-band from semver as peerDependencies. Would be nice for deno to do it better / have recommendations of how module authors handle that better. |
this optimizes the server side bundle size but is it that much of a concern? |
I'm still not a fan of Is there any point to talk about this further? |
I'll just drop my thoughts here in a somewhat brief fashion. ProblemWe want to deduplicate dependencies. A imports B and C, B imports D, and C imports a slightly different version of D. Now D is largely or entirely duplicated, even though B and C could very well share the same dependency D if only they knew of each other. (Peer deps basically.) What the solution should offerIMO we should try to:
What the solution should avoidIMO we should avoid:
What I think we should doExample time. Let's say we have
Then what could happen is this:
That way, we resolved all deps and deduped them correctly. Comparison with
|
It may sound rude, but how about this:
|
I don't think it is possible to deduplicate dependencies purely server-side. Only the CLI knows which other modules it needs, so it will have to be responsible for resolving the module graph. But yes, we will still need an issue for the feature at https://github.com/denoland/dotland. |
In case you're now thinking “yeah so the duplicate dependency problem can be solved better without
I hope you dare to stay true to the philosophy of Deno, even though I understand that there's pressure to ease the migration from Node. Adding |
@ry I've seen the announcement for your talk at NodeCongress in Berlin. Did you decide to ignore all concerns about bare specifiers and protocol hell, and turn down the above suggestions? If so, why? In case you guys actually pull through with |
Why is Deno reaching for a custom import specifier over experimental/unstable extensions to import maps that provide composability & dependency deduplication? They're both going to be custom, but the latter has the potential to become standard web technology. "Web-standard APIs" are advertised on Deno's homepage, and it'd be disappointing to see Deno move further away from those efforts. |
@KnorpelSenf no, we've just been busy working on other things lately. Back in February, we internally discussed something similar using redirects where the CLI passes along its resolution state so the server can provide the correct redirect, but the CLI ignoring the redirect body is an interesting option. It would need to be slightly more complex to work and handle some edge cases, but I think it's something that could work. |
Awesome!
That would be very cool to see! Please LMK if I can be of any assistance here. Do you feel like elaborating on said edge cases? |
@KnorpelSenf we discussed modifying how https imports work in more detail last week. We can't do that because it's not standard with how https is fetched and would differ from how browsers load https imports. From my understanding, both of these statements should be the opposite "Does it follow web standards? ... ambiguous redirect: yes", "Does it invent yet another standard? ... ambiguous redirect: no", but I'm not super familiar in this area (discuss with Luca if you're interested in following up).
By needing to be more complex, I mean:
I can't remember the other points atm, but it seems like we can't do this anyway. |
Curious if a non-standard way to compose import maps was discussed: one where library authors provide suggested sources which will be picked if application developers don't override them. Cascading import maps? Fresh does something similar manually. |
@lilnasy we've discussed that in the past, but import maps don't have a way for module authors or anyone to express semver resolution. They're only a way to map specifiers from a single root config file. (On the note of composable import maps, I personally hope import maps are never composable. It would be complicated and it creates a package.json scenario where you need to consult a dependency's manifest file instead of just importing a script. I think dependencies should be consumed with their import maps already unfurled ahead of time (denoland/website_feedback#3) and everything can be figured out from the javascript files. Again, this is just my personal opinion) |
Not natively. However, a library author can always include the desired semver in the keyed bare specifier. import { colors } from 'std@^1/fmt/colors.ts" ...and This may come across as too much co-ordination, but what I want to avoid here is deno becoming the "odd" runtime that needs a lot of special treatment from third-party build tools. No one builds alone. |
I just stumbled upon HTTP 300. I don't know why I didn't suggest this initially. HTTP has a native way of telling clients that it should choose to redirect to one of several provided options. It's even possible to give a preferred redirect, which can be used to point out the most recent version! I'll leave the MDN link here: https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections#special_redirections
I see your point that not following the redirect for 301 would be non-standard. This isn't the case with 300, though. What I meant by inventing a standard for bare specifiers is that you'd effectively come up with a protocol called So no, it's not the opposite :) |
@lucacasonato perhaps you wanna join discussion at this point? |
Any extensions to HTTPS specifiers are non standard. Even this HTTP 300 approach would NOT work in browsers. Also, to make this actually work one would still need to teach the HTTPS loader what "packages" and "package versions" are. Imagine I import To ensure this does not happen, we somehow need to entangle all files imported from the same "package" to a single version constraint resolve. Without a non-standard extension this can not work. Pretending you are using HTTPS specifiers, but actually you have a layer of magic underneath that makes it behave differently from "standard" HTTPS specifiers is very bad. It confuses users. Deno always takes the approach of "explicit magic". We don't implicitly do some magic internally. If you want magic you explicitly ask (for |
|
So would Also |
There will be a way to generate an import map which maps these specifiers to their resolved location for environments that don't support it. That said, most websites will need to transpile typescript in remote modules and want to bundle anyway. |
I fail to see how using HTTP 300 in the specified way is an extension to the standard. Perhaps I'm reading the specs wrong: The server desires that the user agent engage in reactive negotiation to select the most appropriate representation(s) […]. The user agent MAY make a selection from that list automatically if it understands the provided media type. A specific format for automatic selection is not defined by this specification because HTTP tries to remain orthogonal to the definition of its content. — https://httpwg.org/specs/rfc9110.html#status.300 All of that sounds to me like we can safely redirect to multiple targets, and the Deno CLI is allowed to pick the one it likes best. We are free to implement this redirection as we like, and all of this is within the specs (as far as I understand it). How does it deviate here?
I am not so sure about that. If the two scripts somehow reference each other in any way, be is directly or transitively, then we will discover one through the other in the dependency graph resolution, so we can make the respective adjustments and resolve all files to It feels a lot like
The difference is that by using
Deno can run JS. |
It would be reassuring to know if there's interest in this feature, from either enterprise or independent developers. I am assuming you guys made this decision based on conversations that the community is not privy to. Without that context, this change comes across as bullheaded. |
Have you tried this? From my tests, browsers don't seem to support 300 redirects. Again, as mentioned by Luca, and to emphasize: doing this with https would be implicit magic, but
This is not true. As I mentioned above, specifiers can be mapped via import maps and these specifiers are optional to use. |
Sorry for the delay, I have tested it now. Status 300 works with Firefox, but not with Chrome, so there is some support. Either way, I am aware that 300 is seen very rarely in the wild. I guess I would personally prioritize following web specs over shipping a solution that is convenient for Deno to implement (obligatory link). I see why you guys have different incentives, so while I still disagree that |
There is an experimental preliminary PR for this here: #20517 (though it's not usable at this point) Some changes are that it is no longer |
I want to reply to the PR description, but I think it makes more sense to continue the discussion here. Quotes are taken from the PR description.
Sounds more pleasant.
It looks to me that the tooling of this kind is a must for
|
I think this shortcut cannot increase productivity… What about third party CDNs? Should they get their own specifiers? If not, why should Plz add something like |
This is no longer relevant with the release of https://jsr.io |
Extracted from #17475 and rewritten with added context
Problem
On the deno.land/x registry, it's difficult for module authors to publish a module and specify the version constraint of their dependencies. This often leads to module consumers ending up with duplicates of almost the same dependency (ex. 1.2.1 and 1.2.2 of the same module).
The recommended solution today is for module consumers to examine their dependency tree, then do the "module deduplication" themselves. This is hard for several reasons:
Proposed solution
To allow users and module authors to easily optionally depend on version constraints of dependencies, built-in support of semvered specifiers for the deno.land/x registry could be added in order to provide a recommended solution to this problem.
The deno CLI could analyze these semvered specifiers and based on them, create an internal implicit import map:
This would solve all the issues above out of the box and allow specifying more terse imports for the deno.land/x registry.
Other tools wishing to use code that has these specifiers, could run them via an import map that could be generated from the output of
deno info --json
.deno:
or something else?deno://crux.land/path_to_regex@^6.2
)?The text was updated successfully, but these errors were encountered: