i am trying to understand npm version declaration ...
# javascript
n
i am trying to understand npm version declaration am i right to assume that i see people use
^1.20
instead of
1.2.0
because without
^
it would pin it and transitive dependencies cannot raise it ? is there any case where you would want to have a npm version without
^
prefixed ? context: working on adding support for npm to #gradle-refresh-versions
b
In npm world, ppl prefer version ranges over explicit version to make it easier to upgrade via "npm upgrade" actual resolved versions are still usually pinned via lockfiles.
Imho, in kjs case there's little use for version ranges since we cannot commit lockfile, so explicit versions should be enforced. Then upgrading is done via gradle plugins anyways (like refreshVersions), so no harm no foul.
n
in kjs is there tasks to look into what the actual resolved depencies are ? and i recall reading that if thee is a version conflict both version will be downloaded.. but i guess only the newest will be used ?
is there a way to make sure that npm warns me when there is a conflict so i can change the version in the
versions.properties
? such a thing would be a good thing to but in the readme or docs
r
using version declaration without
^
doesn't really "pin" the version of the library - it often puts two versions of the same library in the bundle file
there is
build/js/yarn.lock
file with all info about resolved versions
n
okay but it cannot put both versions in the output.. right ? so at runtime one or the other is used and if they are binary incompatible they would mess things up ?
r
it can
and it's often a problem, because it causes problems for some libs (eg. jquery)
n
so.. would prefixing versions with
^
when there is no other version range specified be sensible ?
r
if there are conflicts between declared versions yarn creates additional
node_modules
folders (local to some libraries - so we could have
build/js/node_modules/somelib/node_modules/someconflictinglib
I always use
^
because from my experience there is less chance of a problem if some lib will be forced to use a higher version of other library, than if there are multiple versions in one bundle (it gives very hard to track errors)
n
i see.. in my test project yarn lock i see
Copy code
loader-utils@^1.2.3, loader-utils@^1.4.0:
and
Copy code
loader-utils@^2.0.0:
which have a different
json5
dependency
r
still
^
will work only with the same major releases - you can still have two versions with different major number.
n
well thati s something we cannot fix automatically but doing that implicitely would take away the ability to pin a older version.. which i guess isn't a thing anyways ?
b
Guys, we're talking about two entirely different kind of dependencies
n
are we? elaborate please ?
b
Top-level: as declared by the user Transitive: declared in package.json in the resulting dependency tree
RefreshVersions should only manage top-level dependencies and ignore teansitive ones (in a same way you ignore transitive jar dependencies)
n
yes, BUT in jvm land transitive dependencies automatically pick one of them.. usually the newest i just wanted to make sure that this behaviour is the same when i do not use version ranges in
npm("...")
but maybe i want to.. just to potentially reduce the size of the node_modules folder a bit
r
I'm just giving my opinion, why dependencies declared by the user should always use
^
b
Now if there's a conflict between top-level version and transitive version(s) of a dependency, npm will download both, but not in the same place. Top-level version will end up in node_modules root and will be available for all other modules, whereas transitive version will be installed in node_modules of the parent module (one which requested it) and will override the "global" version for that module only
n
so version conflicts between 2 dependenciesi s something that we cannot predict or fix anyways.. it has to be checked manually by the user and pick the correct versions ?
b
So if the top-level dependencies and versions are unchanged, the build is fully reproduceable (assuming those modules didn't re-publish the same version with different dependencies, since unlike mavenCentral npm allows that)
No, conflicts are isolated by npm. No user input needed.
And when it does break, usually the only fix is to bump top-level version anyways...
So my suggestion would be to ignore the existence if transitive dependencies in refreshVersions
Only validate that the user didn't declare the same top-level dependency twice
n
okay.. that is the current behaviour.. we allow you to define them.. but version sorting and such works better without
^
prefixing it
b
I'd very much be in favour of enforcing explicit versions.
This would also better match versions on jar dependencies in gradle
r
If the user declares fixed version without
^
(where he has control) and any transitive dependency will also declare different fixed version (where he doesn't have control) - he will end up with two versions in the same bundle - and a great chance to have issues.
b
Ah, @Robert Jaros is right.
n
i guess once people want to publish kotlinjs to npm again.. they should think about using version ranges again to reduce breakages for their users...
b
Maybe make ^ implicit then?
And expose some Gradle prop to disable that behaviour
r
Even if such two version will work fine without issues - the bundle will be larger. I've experienced all these problems myself 🙂
n
i am thinking this is probably something we do not want to change globally.. but i guess we can add ranges if they are not declared.. unless the user turns it off.. then they have to explicitely add
^
where they need it
👍 2
the MOST annoying this right now is that we cannot overload
npm(dependency: String)
with a extension function even though its current implementation only throws a Exception forcing the user to write
npm("leaflet", "_")
if they just removed the function it would have crashed buildscripts the same
b
What's wrong with npm("module", "_")? Same as with jar deps
n
with jar deps we have no choice.. here it feels like we missed it by JB explicitly keeping a useless function migration would look so much nicer if i could tell people:
ans now delete all those version constants
hmm this should handle the version ranges..
Copy code
val version = if (FeatureFlag.NPM_IMPLICIT_RANGE.isEnabled){
    when {
        versionFromProperties.startsWith('^') -> versionFromProperties
        versionFromProperties.startsWith('~') -> versionFromProperties
        versionFromProperties.startsWith('*') -> versionFromProperties
        else -> "^$versionFromProperties"
    }
} else {
    versionFromProperties
}
or is there other syntax for version ranges where i don;t need to add a
^
(or that would make it illegal) ?
n
oh.. those things are legal too.. great
so if the first character is one of
^~*><=
then it is a range ?
sorting
>=2.0.0 <3.1.4
will be a mess.. would i sort it as the lower bound ? then refreshVersion shows anything newer than that ?
but for that i need to parse those as well ...
r
2.0.0 - 3.1.4
this is also a range and adding
^
will be illegal
n
i caught that in the docs already
the
=
operator will look terrible in a properties file
Copy code
version.npm.leaflet==1.7.1
does anyone have a set of dependencies that would cause a version conflict .. that also work with
generateExternals
so that its easy to write a test for that ?