The problem now is I find it hard to rectify the use of version ranges with the idea of having reproducible builds.
It is obviously impossible.
how it is ever possible to have reproducible builds with version ranges when using
You can't. What makes you think you can?
This doesn't even have anything to do with NPM. It is just basic logic. "Reproducible build" means all versions are exactly specified, "version range" means, at least one version is not exactly specified, ergo you cannot have reproducible builds with version ranges, and you cannot have version ranges with reproducible builds.
That's just a simple logical contradiction. Nothing to do with NPM or any other packaging system.
If you want reproducible builds, you need to give up on using version ranges. Nobody is forcing you to use version ranges, and nobody is forcing you to use reproducible builds. You can choose to use either one of them, or neither one of them. You can not choose to use both, because they logically contradict each other.
What you can do, however, is the following:
Most newer package management systems split dependency resolution and package build into two separate steps. RubyGems, NPM, Yarn, and others do this, for example.
In NPM, RubyGems, and Yarn, after the package management system has resolved the dependencies, it records the resolution it found. All three package management systems I mentioned call this a "lock file". The package build is then performed using the already resolved dependencies from the lock file.
So, you can use version ranges during dependency resolution, but then you can commit the lock file to the repository, and thus everybody who builds the package from the repository, will get the already resolved exact versions.
Again, that is your choice. You can distribute the lock file, but you don't have to.
Again, all of this is a choice. Some people like version ranges, some people don't. If you don't like 'em, don't use 'em. Some people like reproducible builds, some people don't. If you decide to have reproducible builds, then you can't have version ranges. Period.
I, personally, chose to re-resolve dependencies on every build for my own private packages.