Reputation: 19153
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
Upvotes: 140
Views: 111646
Reputation: 3179
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 most straightforward 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 module — and pass to path.dirname()
:
let packagePath = path.dirname(require.resolve("moduleName/package.json"));
Of course as many other people have now mentioned, this does not work with ESM modules, which don't expose files unless they are explicitly exposed, and that includes the package.json file.
However, as of NodeJS 22, there is finally module.findPackageJSON()
Upvotes: 139
Reputation: 1075
While this does not directly get the folder, it will get you the entry point (whatever is written down under exports
, or main
(if exports
is missing))
You can use import.meta.resolve("foreignModule")
to get the entry point.
The returned object is a FileURL. So you can use fileURLToPath
from url
to convert it into a regular path.
npm init -y;
npm pkg set type="module";
npm i chalk;
// index.js
import { fileURLToPath } from "url";
const pathToModuleEntry = fileURLToPath(import.meta.resolve("chalk"));
console.log(pathToModuleEntry);
// your/path/to/dir/node_modules/chalk/source/index.js
For reference, an excerpt from chalk's package.json
:
{
...
"type": "module",
"main": "./source/index.js",
"exports": "./source/index.js",
"imports": {
...
},
"types": "./source/index.d.ts",
...
}
Upvotes: 1
Reputation: 1
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
Upvotes: 0
Reputation: 28460
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 usefs.realpath
to get the location of the target directory:const moduleDir = getModuleDir('some-npm-module'); const theRealPath = fs.realpathSync(moduleDir);
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));
};
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));
};
Upvotes: 7
Reputation: 45502
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.
Upvotes: 8
Reputation: 959
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)
Upvotes: 3
Reputation: 39261
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');
Upvotes: 164
Reputation: 16309
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.
Upvotes: 1
Reputation: 30103
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
Upvotes: 1