Is it possible to declare a dependency which tracks the version of a transitive dependency? - npm

I've run into a situation where I need to access a transitive dependency in a stable way. In other words, I'd like to declare a dependency whose version is "whatever library Foo is already using".
Specifically, I'm setting up an Eleventy site and want to use markdown-it-anchor with it. Both libraries involve slugifying text, for which markdown-it-anchor allows you to specify a custom function. To keep everything consistent, I want to tell markdown-it-anchor to use the same slugifying function as Eleventy. Eleventy doesn't export its slugifying function, but it's just using #sindresorhus/slugify, so I can import that directly.
The problem came in when I added a direct dependency on #sindresorhus/slugify — I added a dependency with a splat version, on the assumption that npm would simply resolve it to the version of #sindresorhus/slugify which was already present in node_modules/. Instead, it resolved to the latest version. I tried playing around with editing package.json and even package-lock.json manually, but npm is very firm about installing Eleventy's version of #sindresorhus/slugify where I can't reach it and not installing the same version for my own use unless I duplicate the version specifier in my package.json.
What I want is to be able to freely update Eleventy in the future and have reasonable confidence that markdown-it-anchor will continue to be passed the correct version of #sindresorhus/slugify without having to manually verify each time that Eleventy hasn't bumped their dependency. Is there any way to accomplish that?

Well, I kind of got this to work. I say "kind of" because I'm depending on the splat version ("*") of my transitive dependency, which is pretty fragile. Getting it to work at all was pretty ugly, too, so there are multiple ways in which this isn't a "proper" solution.
Opened up package-lock.json and looked at the transitive dependency's dependencies (and transitive dependencies). Fortunately, for #sindresorhus/slugify, this isn't too bad.
Rearranged node_modules/ to move the transitive dependency to the top level (where my package can find it), and all of it's dependencies to where it can find them, without introducing new version conflicts. Again, in my case, this wasn't too bad.
Edited both package-lock.json and node_modules/.package-lock.json to reflect the moved packages' new locations.
Ran npm ci both to verify that I hadn't made any terrible mistakes and to make sure package-lock.json and node_modules/.package-lock.json were formatted the way it liked. (The only change it made was to reorder the packages to keep their directories sorted.)
Manually added a dependency on the (now formerly) transitive dependency with a splat version.
Ran npm install and verified that it didn't actually install or rearrange anything.
After all that, #sindresorhus/slugify works as expected when used directly from my site's build. There are a couple of serious caveats, though:
I'm not sure what npm's behavior will be if/when Eleventy updates its dependency on #sindresorhus/slugify. It may well simply update the latter where it's already located, in which everything will be fine. Otherwise, it probably won't.
I'm also not sure what npm's behavior will be if/when #sindresorhus/slugify gets added as a dependency anywhere else. It may well leave the existing version where it is and install new, conflicting versions under the …/node_modules/ folder of whatever packages require them, in which case everything will be fine. Otherwise, it probably won't.
In other words, I discovered a way to put a fair amount of effort into creating a situation which seems to work, but may not actually do what I originally wanted. 😅

Related

Can I have a fallback for peerDependencies?

In my package.json I want to declare a peerDependency and provide a fallback, if the user of my package does not have this peerDependency installed.
I have stumbled upon this repeatedly and I cannot seem to find a good solution. Let's assume, I am the author of a library that consists of two npm packages, where one packages depends on the other:
package1
package2 => has dependency to package1
In order to achieve the fallback behavior I tried adding package1 as a dependency as well as a peerDependency to package2.
Unfortunately it seems that this completely ignores the peerDependency, because peerDependencies do not enforce any behavior, they just yield a warning.
I am considering to remove the dependency, just leaving the peerDependency, which will force the user to install both packages, when they want to use package1, which I would like to avoid.
Is there any clean way to achieve the mentioned behavior?
After reading quite a bit about this, there is no clean way to achieve this (as expected quite frankly).
One blog post, that I found useful was this one. In my case to increase compatibility I will most likely resort to just using a peer dependency and sacrifice a bit of usability.

Forcing installation order using zef

Lately, installing LWP::Simple requires the prior installation of IO::Socket::SSL, as is shown in this Travis log. However, there does not seem to be a way of forcing zef to install them in that particular order. The only way I can think of is to list it before in the depends section of META6.JSON, but that does not seem to work.
The only slightly related solution I have found is this one, but that does not provide a solution, rather reports an (old and already fixed) bug.
Also, dependencies in the different phases (build, for instance) all seem to be blended together and installed in, I guess, dependence first order.
So, other than listing IO::Socket::SSL as a dependency in LWP::Simple, or forcing installation via another direct command before, is there any other way to fix this?
The module author does not get a say towards dependency installation order. A naive solution of doing them in order would not be parallelization friendly.
As to the actual problem of the failing tests -- how is this not a bug in LWP::Simple? The tests clearly fail due to missing IO::Socket::SSL, so either IO::Socket::SSL should be added to its test-depends, or its test should be fixed to not point at a url that forwards to https (before the skip-all test for IO::Socket::SSL is done 4 lines below).

npm: dependencies vs devDependencies with bundled dependencies

Using the search I already found some great answers to similar questions, but still I am not sure if I understood it correctly.
From these answers I learned that dependencies are required to run the application while devDependencies are only required while developing (like unit tests).
But how about this: My application depends on jQuery, but during a build step (with the help of my devDependencies), everything is bundled into one file. In this case, should I list jQuery as dependency or as devDependency?
To make my point more clear take a module like this:
define(['jquery'], function($) {
// use jQuery in this module
})
Later on, this module will be compiled into somehing like application.build.js which then contains this module and the jQuery dependency.
Since the end result is the same, there doesn't seem to be a definite rule here, but I found a couple of discussions on the matter:
If you're building an application
https://github.com/webpack/webpack/issues/520
A browser app built by [insert build tool/bundler] has no runtime node dependencies, and thus all frontend dependencies should be listed as devDependencies. The dependencies vs devDependencies naming convention stems historically from node being a server side package manager (...) It is as far as I can tell harmless to list frontend dependencies under dependencies, but it is wrong.
(...) as a general recommendation for everyone, move everything into devDependencies until it is actually needed under dependencies.
If you're building a library:
https://github.com/inuitcss/inuitcss/issues/225
In many frontend projects, all code served to the browser is compiled, there are no runtime dependencies. This would mean that there are no dependencies, only devDependencies – all dependencies are included in the build done during development.
One could also argue that dependencies are required for development as well, so it would be okay to list everything unter dependencies.
I think the fact that we have the optional distinction indicates a reasonable way to use them. It makes sense (to me) that the dependencies designation would represent the 'minimum viable' code to use and as an indicator of what's nonessential for something to work.
As I see it, anything that goes on to become part of the production code is a dependency.
Epilogue
Personally, I agree more with the last quote. It makes sense that dependencies tells us what the application code needs to run, and devDependencies what the developer needs to build/deploy/whatever the application/library.
One caveat though is that if someone npm installs your library to use the bundle as a module in their own application, they will download a lot of dependencies they don't actually need.

When to use shrinkwrap, npm-lockdown, or npm-seal

I'm coming from a background much more familiar with composer. I'm getting gulp (etc) going for the build processes and learning node and how to use npm as I go.
It's very odd (again, coming from a composer background) that a composer.lock-like manifest is not included by default. Having said that, I've been reading documentation on [shrinkwrap], [npm-lockdown], and [npm-seal]. ...and the more documentation I read, the more confused I become as to which I should be choosing (everyone thinks their way is the best way). One of the issues I notice is that npm-seal hasn't changed in 4 years and npm-lockdown in 8 months -- this all leads me to wonder if this because it's not needed with the newest version of npm...
What are the benefits / drawbacks of each?
In what cases would I use one over another in Project A, but use a different one in Project B?
How will each impact our development workflow?
PS: Brownie points if you include the most basic implementation example for each. ;)
npm shrinkwrap is the most standard way how to lock your dependencies. And yes, npm install does not create it by default which is a pity and it is something that npm creators definitely should change.
npm-lockdown is trying to do the same things as npm shrinkwrap, there are two minor points in which npm-lockdown is better: it handles optional dependencies better and it validates checksums of the package:
https://www.npmjs.com/package/lockdown#related-tools
Both these features seem not so relevant for me; I'm quite happy with npm shrinkwrap: For example, npmjs guarantees that once you upload certain package at certain version, it stays immutable - so checking sha checksums is not so hot (I've never encountered an error caused by this).
seal is meant to be used together with npm shrinkwrap. It adds the 'checksum checking' aspect. It looks abandoned and quite raw.
Good question - I'm going to skip everything but shrinkwrap because it is the de-facto way to do this, per NPM's docs.
Long story short the npm-shrinkwrap.json file is akin to your lock files you are used to in every other package manager, though NPM allows different versions of the same package to play nice together by isolation - literally scoping and copying different entire versions to node_modules at different levels of the tree. If two projects that are parent-child to each other use the exact same version, NPM will copy the version to only the parent and the child will traverse up the tree to find the package.
Best practice is simply to update package.json for your direct dependencies, run npm install, verify that things are working while developing, then run npm shrinkwrap when you are just about to commit and push. NOTE: make sure to rm npm-shrinkwrap.json before running npm install during active development - if your direct dependencies have changed, you want package.json to be used, and not the lock! Also include node_modules in your .gitignore or equivalent in your source control system. Then, when you are deploying and getting to run the project, run npm install like normal. If npm finds an npm-shrinkwrap.json file, it will use that to recursively pull all locked modules, and it will ignore package.json in both your project and all dependent projects.
You might find shrinkpack useful – it checks in the tarballs which npm install downloads and bundles them into your repository, before finally rewriting npm-shrinkwrap.json to point at that local bundle instead.
This way, your project is totally locked down, completely available offline, and much quicker to install.

Ivy dependency with changing="true" always downloads artifacts, even not changed

According to Ivy documentation dependency with changing="true" means the module can change even if the revision is the same. This is useful especially for integration.
Now, I expect the system is smart enough so it does not download artifacts every time.
It can compare, for example, "publication" timestamps in ivy.xml and download (and cache) only if necessary.
But this is not the case if the dependency has rev="latest.integration". I see it downloads artifacts every time and I'm sure they were not changed. If I change "rev" to some exact revision, then it works as expected.
Is this expected behavior and do I have any chance to make it work with "latest.integration"?
I use Ivy 2.2.0.
Obscure problem. Your report does appear to contradict a strict interpretation of how the changing module functionality works.
I'd suggest raising a ticket on the ivy JIRA. Not an issue that can be solved here.