132

I require a module that was installed via npm. I want to access a .js file subordinate to that module (so I can subclass a Constructor method in it). I can't (well, don't want to) modify the module's code, so don't have a place to extract its __dirname.

I am aware of the following question, but it is about getting the path of a module that one has code control over (hence, __dirname is the solution): In Node.js how can I tell the path of `this` module?

~~~

Even better would be to get the module's loaded module info

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Zhami
  • 19,033
  • 14
  • 48
  • 47

9 Answers9

158

If I correctly understand your question, you should use require.resolve():

Use the internal require() machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.

Example: var pathToModule = require.resolve('module');

Linus Thiel
  • 38,647
  • 9
  • 109
  • 104
130

require.resolve() is a partial answer. The accepted answer may work for many node modules, but won't work for all of them.

require.resolve("moduleName") doesn't give you the directory where the module is installed; it gives you the location of the file defined in the main attribute in the module's package.json.

That might be moduleName/index.js or it could be moduleName/lib/moduleName.js. In the latter case, path.dirname(require.resolve("moduleName")) will return a directory you may not want or expect: node_modules/moduleName/lib

The correct way to get the complete path to a specific module is by resolving the filename:

let readmePath = require.resolve("moduleName/README.md");

If you just want the directory for the module (maybe you're going to make a lot of path.join() calls), then resolve the package.json — which must always be in the root of the project — and pass to path.dirname():

let packagePath = path.dirname(require.resolve("moduleName/package.json"));
Jason
  • 3,021
  • 1
  • 23
  • 25
  • 2
    very clever answer, by detecting the `package.json` file. Shouldn't you use `path.join('moduleName', 'package.json')` for being Windows compatible? – João Pimentel Ferreira Oct 21 '18 at 20:01
  • 8
    @JoãoPimentelFerreira `require.resolve` is platform agnostic, just like `require` so it is not needed to use `path.join` – Gopikrishna S Nov 16 '18 at 19:05
  • 3
    Don't forget to add `const path = require('path');` before using `path.dirname`. – GOTO 0 May 08 '19 at 10:31
  • 4
    I wish this answer was fully true! I can successfully resolve something like `require.resolve('@scope/module')` which gives me something like `/path/to/@scope/module/dist/index.js`, however if I try to run `require.resolve('@scope/module/package.json')` it throws a `MODULE_NOT_FOUND` error. I'm in Node 14.4.0, and the module I am trying to resolve has `"type": "module"` in its package.json with an `exports` field that does not include `package.json`. Not sure if that has anything to do with it... – trusktr Aug 16 '20 at 18:13
  • 4
    I found the problem: when a module has `type: module`, apparently `package.json` has to be explicitly exposed in the `exports` field. I thought that Node's new ESM feature didn't not block `require` from resolving paths like usual, but apparent it does. – trusktr Aug 16 '20 at 18:22
  • 1
    @trusktr we have scoping in APIs because we want to reserve options on what to expose, alter, or remove in the future. Node seems to be missing some basic metadata functionality around for pulling package metadata, which really should be a feature IMO. The only other suggestion I have is to look at the pkginfo module, which assumes that the main is a child of the module folder and traverses up until it finds it. https://github.com/indexzero/node-pkginfo/blob/master/lib/pkginfo.js#L89 You're probably right about resolve-package-path, rather than rolling your own. – Jason Oct 25 '21 at 18:37
  • 1
    Does not work for monorepos, `package.json` is not "always be in the root of the project" – Blunderchips Aug 24 '23 at 06:06
  • Ultimately this is all about trying to peak at files in other projects that the projects did not predict you wanting to peak at. There are going to be diminishing returns and at some point you'll have to start scanning the filesystem based off of the resolve path. Probably using the resolve-package-path tool mentioned by trusktr – Jason Aug 24 '23 at 23:14
  • @Blunderchips the problem with your suggested edit is that the question is 'how to find the path of the module', not 'how to find the path to a file in the module'. In my case for instance I'm hunting for a config directory (for node-config), no two of which are guaranteed to contain the same filename. You can't resolve a path, only files – Jason Aug 24 '23 at 23:23
  • "which must always be in the root of the project" is simply incorrect. – Blunderchips Aug 25 '23 at 08:55
  • Except you didn't delete that part, you deleted all of the code for the solution – Jason Aug 25 '23 at 16:38
5

Jason's answer was the best answer, until Node.js ESM and the exports field came out.

Now that Node supports packages with an exports field that by default will prevent files like package.json from being resolvable unless the package author explicitly decides to expose them, the trick in Jason's answer will fail for packages that do not explicitly expose package.json.

There is a package called resolve-package-path that does the trick.

Here's how to use it:

const resolvePkg = require('resolve-package-path')

console.log(resolvePkg('@some/package'))

which will output something like

/path/to/@some/package/package.json

regardless of what the package's exports field contains.

trusktr
  • 44,284
  • 53
  • 191
  • 263
  • I suspect that once an author is consciously exporting part of the contents of the module, you are on even shakier ground, because now the author has formally defined their public interface. That would, I think, tend to result in more aggressive refactoring of things that are not explicitly exported, would it not? – Jason Aug 17 '20 at 21:01
  • 1
    @Jason That's true for the source files, but package.json files aren't going away. I don't see any reason those should be hidden from import. – trusktr Aug 19 '20 at 22:16
  • 1
    If you're specifically looking for package.json instead of the other files, then yes, but that's only one flavor of the question at hand. Really I think we should all be pushing for the module object to contain the contents of the package.json file so we don't have to do stupid stuff like this anymore. – Jason Oct 25 '21 at 18:39
3

FYI, require.resolve returns the module identifier according to CommonJS. In node.js this is the filename. In webpack this is a number.

In webpack situation, here is my solution to find out the module path:

const pathToModule = require.resolve('module/to/require');
console.log('pathToModule is', pathToModule); // a number, eg. 8
console.log('__webpack_modules__[pathToModule] is', __webpack_modules__[pathToModule]);

Then from __webpack_modules__[pathToModule] I got information like this:

(function(module, exports, __webpack_require__) {

    eval("module.exports = (__webpack_require__(6))(85);\n\n//////////////////\n// 
    WEBPACK FOOTER\n// delegated ./node_modules/echarts/lib/echarts.js from dll-reference vendor_da75d351571a5de37e2e\n// module id = 8\n// module chunks = 0\n\n//# sourceURL=webpack:///delegated_./node_modules/echarts/lib/echarts.js_from_dll-reference_vendor_da75d351571a5de37e2e?");

    /***/
})

Turned out I required old scripts from previous dll build file(for faster build speed), so that my updated module file didn't work as I expected. Finally I rebuilt my dll file and solved my problem.

Ref: Using require.resolve to get resolved file path (node)

Alessia
  • 899
  • 10
  • 16
2

Here is a solution that returns the module directory in a platform agnostic way. This does not use any 3rd party libraries and successfully locates ESM modules with "type": "module" and modules installed via npm link..

NOTE: If a particular module is a symlink to another location (eg. npm link) you will need use fs.realpath to get the location of the target directory:

const moduleDir = getModuleDir('some-npm-module');
const theRealPath = fs.realpathSync(moduleDir);

ESM

import fs from 'fs';
import path from 'path';
import { createRequire } from 'module';

/**
 * Get's the file path to a module folder.
 * @param {string} moduleEntry 
 * @param {string} fromFile 
 */
const getModuleDir = (moduleEntry) => {
    const packageName = moduleEntry.includes('/') 
        ? moduleEntry.startsWith('@') 
            ? moduleEntry.split('/').slice(0, 2).join('/') 
            : moduleEntry.split('/')[0]
        : moduleEntry;
    const require = createRequire(import.meta.url);
    const lookupPaths = require.resolve.paths(moduleEntry).map((p) => path.join(p, packageName));
    return lookupPaths.find((p) => fs.existsSync(p)); 
};

CommonJS

const fs = require('fs');
const path = require('path');
const { createRequire } = require('module');

/**
 * Get's the file path to a module's folder.
 * @param {string} moduleEntry 
 * @param {string} fromFile 
 */
const getModuleDir = (moduleEntry, relativeToFile = __filename) => {
    const packageName = moduleEntry.includes('/') 
        ? moduleEntry.startsWith('@') 
            ? moduleEntry.split('/').slice(0, 2).join('/') 
            : moduleEntry.split('/')[0]
        : moduleEntry;
    const require = createRequire(relativeToFile);
    const lookupPaths = require.resolve.paths(moduleEntry).map((p) => path.join(p, packageName));
    return lookupPaths.find((p) => fs.existsSync(p)); 
}; 
Ryan Wheale
  • 26,022
  • 8
  • 76
  • 96
  • This may not work for linked dependencies. – brillout Jul 21 '22 at 13:04
  • In fact, the whole beauty of this solution is that it uses the internal mechanisms of `require.resolve` and `import.meta.resolve` to find the actual file path to a module. There is nothing special or hackish going on, and it works with ESM modules, and it doesn't require 3rd party modules... unlike every other solution offered in this thread (as of this writing). – Ryan Wheale Jul 21 '22 at 17:56
  • 1
    You can't assume `entry` to contain `moduleName`. I.e. the linked dependency may not contain `moduleName` in its filesystem path, which is usually the case for scoped npm package names e.g. `@brillout/json-s`. – brillout Jul 22 '22 at 08:03
  • 3
    @brillout - you are absolutely correct. Thanks for pointing out my oversight. I've updated my answer to reflect this. I still find this useful as it's a 4 line function that satisfies 90% of use cases. – Ryan Wheale Jul 23 '22 at 05:32
  • 1
    Thanks for the reply & udpate. I removed my down-vote. One more thing: I wonder if this works with Yarn PnP? – brillout Jul 23 '22 at 09:21
  • 1
    I don't have much experience with PnP, but based on [this page (near the bottom)](https://yarnpkg.com/features/pnp), it sounds like they patch node's `fs` and `require.resolve` to load files directly from the zip archive... so my guess would be that my code should still work - but that is not tested and I'm not in a position to learn and test. – Ryan Wheale Jul 27 '22 at 02:04
  • Also, this won't work if the dependency has no `main` entry. (I.e. you must know an entry.) – brillout Dec 01 '22 at 16:58
  • @brillout - thanks again for the comment. I've updated the answer to indicate that a valid entry point must be used. I also updated the code to 1) detemine the package name from a nested entry point and 2) use `createRequire` so that it works in both CJS and ESM environments without any flags. – Ryan Wheale Dec 01 '22 at 22:48
  • @brillout - I have further refined this method to now work with symlinks (`npm link`, et al), scoped module names, nested module entry points, and global packages. Let me know if you can poke any more holes in it. – Ryan Wheale Dec 02 '22 at 01:45
  • Neat, although this solution still needs the npm package to have a `package.json#main` entry (or `package.json#exports["./"]`). – brillout Dec 02 '22 at 10:51
  • 1
    I've made some updates since my last comment. By using `require.resolve.paths`, I'm able to locate modules no matter the main/exports value in package.json. This also works for link'd modules (npm link, pnpm, etc). Users can pass a module name or a nested entry point, and we will discover the module's location by its package name. – Ryan Wheale Dec 02 '22 at 19:16
  • Ok, I see. Neat idea to use `require.resolve.paths(request)`. One problem though: unlike `require.resolve(request, options)` it doesn't accept `options.paths` so it only works if you want to resolve the npm package starting from `__dirname` which makes this solution not work in certain situations. – brillout Dec 04 '22 at 10:37
1

I hope I correctly understand your needs: to get entry point file of some module. Let's say you want to get entry point of jugglingdb module:

node
> require('module')._resolveFilename('jugglingdb')
'/usr/local/lib/node_modules/jugglingdb/index.js'

As you can see this is not "official" way to get this kind of information about module, so behavior of this function may change from version to version. I've found it in node source: https://github.com/joyent/node/blob/master/lib/module.js#L280

Anatoliy
  • 29,485
  • 5
  • 46
  • 45
1

According to @anatoliy solution, On MacOS X I have found the lookup paths doing

require('module')._resolveLookupPaths('myModule')

so I get the resolved lookup paths

[ 'myModule',
  [ '/Users/admin/.node_modules',
    '/Users/admin/.node_libraries',
    '/usr/local/lib/node' ] ]

whereas the

require('module')._resolveFilename('myModule')

will not resolve the module I was looking for anyways, in fact the crazy thing is that the _load will not resolve the module:

> require('module')._load('myModule')
Error: Cannot find module 'myModule'
    at Function.Module._resolveFilename (module.js:440:15)
    at Function.Module._load (module.js:388:25)
    at repl:1:19
    at sigintHandlersWrap (vm.js:32:31)
    at sigintHandlersWrap (vm.js:96:12)
    at ContextifyScript.Script.runInContext (vm.js:31:12)
    at REPLServer.defaultEval (repl.js:308:29)
    at bound (domain.js:280:14)
    at REPLServer.runBound [as eval] (domain.js:293:12)
    at REPLServer.<anonymous> (repl.js:489:10)

while the require will:

> require('myModule')

but I don't have this module in

myProject/node_modules/
myProject/node_modules/@scope/
/usr/local/lib/node_modules/
/usr/local/lib/node_modules/@scope
/usr/local/lib/node_modules/npm/node_modules/
/usr/local/lib/node_modules/npm/node_modules/@scope
$HOME/.npm/
$HOME/.npm/@scope/

so where is this module???

First I had to do a $ sudo /usr/libexec/locate.updatedb Then after some coffee I did locate myModule or better locate myModule/someFile.js

et voilà, it comes out that it was in a parent folder of my project i.e. outside my project root folder:

$pwd
/Users/admin/Projects/Node/myProject
$ ls ../../node_modules/myModule/

so you cannot avoid to rm -rf ../../node_modules/myModule/ and a fresh npm install.

I can argue that no one instructed npm to scan my computer in search for modules elsewhere than my project root folder where it was supposed to run or in the default modules search path.

loretoparisi
  • 15,724
  • 11
  • 102
  • 146
1

This is maybe what you're looking for, check:

require.main.filename

IvanM
  • 2,913
  • 2
  • 30
  • 30
0

This code works for me:

First get the module main file path:

const mainDir = require.resolve(moduleName);

which output: D:\app\node_modules\tinycolor2\cjs\tinycolor.js

Then, get the package dir:

const realDir = mainDir.substring(0,mainDir.indexOf(moduleName)+moduleName.length);

Output: D:\app\node_modules\tinycolor2