mathiasbynens/punycode.js

Naming collision when installing punycode via npm

Opened this issue · 12 comments

I'm using Node 9.7.1, and have punycode@2.1.0 installed (via $ npm i punycode -S), but when I try and log out the punycode.version variable, I always seem to get "2.0.0", so I think my machine is still using the default/bundled/deprecated version of https://nodejs.org/api/punycode.html

How can you install this module from npm and use it in Node 8+ without it using the built-in version of the punycode module? It looks like I can use the npm version if I do something like require("./node_modules/punycode"), but that feels wrong.

$ node -e "console.log(require('punycode').version)" # 2.0.0
$ node -e "console.log(require('./node_modules/punycode').version)" # 2.1.0
$ node --version # v9.7.1

$ node -e "console.log(require.resolve('punycode'))" # punycode
$ node -e "console.log(require.resolve('./node_modules/punycode'))" # /Users/pdehaan/dev/tmp/node_modules/punycode/punycode.js

Maybe package-lock.json or a npm-shrinkwarp.json file helps here.
Deeper read: https://docs.npmjs.com/files/package-locks
But really not sure if that helps..

IIRC there was a better way of preferring an npm-installed module over a Node-built-in module, although maybe I was dreaming. @zkat, do you know?

zkat commented

This is literally impossible and unlikely to ever change. It is the first alternative in Node's module lookup algorithm, and so there's.... basically no actual way to do this besides referencing the module by path.

I would be tremendously surprised if this -ever- worked -- you might consider using a @scope for it, or simply publishing it as punycode.js (which is available! Get it while it's hot!).

zkat commented

Of course, the other alternative is to make Node's built-in punycode module do a path-based lookup in a project's current dependencies, but it ain't pretty to do that, imo. I think it's important to continue enshrining node built-ins as special without getting into weird bypassy shenanigans like that? It's a case you can make with them, though, as I have no say in what their decision would be ;)

Thank you, @zkat!

I went with @zkat’s suggestion to publish a copy as punycode.js, so @pdehaan you can now use npm install punycode.js and require('punycode.js') as a workaround.

Also, for my own future reference and general interest, I randomly spotted this gem in the eslint-plugin-node node/no-deprecated-api docs while closing some tabs:

⚠️ Note that userland modules don't hide core modules. For example, require("punycode") still imports the deprecated core module even if you executed npm install punycode. Use require("punycode/") to import userland modules rather than core modules.

$ node -e "console.log(require('punycode').version)" # 2.0.0
$ node -e "console.log(require('punycode/').version)" # 2.1.0

@pdehaan cool hacks!

Ooh, that may have been the workaround I was trying to remember! I like require('punycode/') much better than having a separate package.

Not that this is a democracy, but I like the new package name approach much better. It's a lot more obvious what's happening, and you wouldn't have to try explaining the odd looking trailing slash in the require statement.
It always feels weird to me that you can publish npm modules which have the same name as core Node modules (a la "fs", "path", "os", "crypto", etc). Seems super confusing, and the number of times I've seen people include "path" in their package.json is scary.

@mathiasbynens Can punycode.js package be updated to 2.1.1 as well?

As I agree with @pdehaan as why require the use of a "hack" to be able to use the package.