Node - How can a script be sure of the location of a dependency to call? [duplicate] - node.js

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

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');

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"));

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)

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.

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

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.

This is maybe what you're looking for, check:
require.main.filename

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));
};

Related

Equivalent for __dirname in TypeScript when using target/module=esnext

I need to calculate a pathname relative to the file-system location of the module. I am using the latest TypeScript on Node.js 12.x. For other reasons in tsconfig.json I have set
"target": "esnext",
"module": "esnext",
This triggers a mode that is strict to the Node.js support for ES6 Modules. In that mode the __dirname variable is not available because that global is not defined in the ESanything spec. What we're supposed to do instead is access the import.meta.url variable and extract the directory name.
For an example see the last answer on this question: Alternative for __dirname in node when using the --experimental-modules flag
But in the TypeScript DefinitelyTyped collection the ImportMeta class is not defined to include a url member. Therefore the code fails to compile:
tdn.ts:7:25 - error TS2339: Property 'url' does not exist on type 'ImportMeta'.
7 console.log(import.meta.url);
~~~
I wasn't able to find the definition of ImportMeta in the Definitely Typed repository. But it seems to be improperly defined.
UPDATE: In node_modules//typescript/lib/lib.es5.d.ts I found this:
/**
* The type of `import.meta`.
*
* If you need to declare that a given property exists on `import.meta`,
* this type may be augmented via interface merging.
*/
interface ImportMeta {
}
Ugh...
/UPDATE
In the Node.js 12.x documentation on the ES Modules page it clearly describes the shape of import.meta and that we're supposed to do something like:
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
'__dirname', '__filename' and 'require'...etc are NodeJS specific keywords, and by default typescript doesn't recognize them, although you need to know that the compiler compile the ts file to js file (by default) and it works fine, in order to clear the errors you can to run this on your terminal (or cmd on windows):
npm install --save #types/node
that would install nodejs types definitions and allow you to avoid this type of errors when writing your nodejs programs in typescript.
using import.meta and fileToURLupdate this works...
As per NODE.org, both CommonJs variables specially __dirname or __filename are not available in ES Modules.
We have to replicate those commonJs variables from via import.meta.url.
Source: https://nodejs.org/api/esm.html#esm_no_filename_or_dirname

How can I keep "require" working for dynamic expressions

I want to bundle a Node.js script, which somewhere calls require(expression). After bundling the script with webpack, require fails. This is a super simplified example:
// main.js
const x = require(process.argv[2])
console.log(x)
I would like to either have a "normal" require for this case or tell webpack to include a specific file which I know will be required in the future (after bundling). To stick with this example, I know the value of process.argv[2] ahead of bundling.
Note: The code doing the expression based require is a dependency, so I cannot tweak require code.
This is my webpack.config.js
module.exports = {
entry: './test.js',
output: {
filename: 'test.js'
},
target: 'node'
}
The require path is relative to the file it is used in. So you'll need to figure out the path from where require is executing to the file it's loading from the parameter. Then prepend the relative part to the parameter.

How does react avoid using require("../../Path/To/Module"), and just use require("Module")

As far as I've seen, npm modules can be require() without a path:
require("module") // --> npm module
and local modules are require() using a path:
require("./module") // --> local module, in this directory
require("../../path/to/module") // path to directory
In react.js, modules are required without a path. See here for example. I'm wondering how they achieve this.
Apparently it uses rewrite-modules Babel plugin with module-map module (see gulpfile.js.)
There's also this Babel plugin that you can use to achieve the same behavior.
If you're using Webpack, you can add path/to/modules into resolve.modulesDirectories array and it will work similarly to requiring from node_modules instead of using relative paths.
resolve: {
modulesDirectories: ['path/to/modules', 'node_modules'],
},
and then
var foo = require('foo');
// Instead of:
// var foo = require('/path/to/modules/foo');
// or
// var foo = require('../../foo');

How to have path alias in nodejs?

Let's say i have following codes:
var mod1 = require('../../../../ok/mod1');
var mod2 = require('../../../info/mod2');
It's not pretty coding like above, i am wondering if there is a way to configure the root resolver just like webpack-resolve-root in nodejs?
So far as i know, the NODE_PATH can be used to replace the root of node_modules, but that's not what i want. I'd like to have the resolver to resolve multiple folders in order.
Updated answer for 2021.
nodejs subpath imports have been added in: v14.6.0, v12.19.0
This allows for you to add the following to package.json
"imports": {
"#ok/*": "./some-path/ok/*"
"#info/*": "./some-other-path/info/*"
},
and in your .js
import mod1 from '#ok/mod1';
import mod2 from '#info/mod2';
There is an npm package called module-alias that may do what you are looking for.
The best way to approach this would be to use a global (config) container.
In most cases you will have a config file in your application. In this config you can add a property which will be an object containing all absolute paths to files/folders.
Because config files are used at the start of you application, you just do the following:
var config = require("./config.js");
//config = {... , path: {"someModule": "/absolute/path/to", "someModule2": "/absolute/path/to"...}}
global.CONFIG_CONTAINER = config
Later on in your application you can just use
var myModule = require(CONFIG_CONTAINER.path.someModule)
// + concat if you are looking for a file
In case you have some complex paths and you need a more dynamic system, you can always implement a function inside the config that will build paths for you. ( config.makePath = function(){...} )
That should take care of it in a nutshell.

How can I get the path of a module I have loaded via require that is *not* mine (i.e. in some node_module)

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
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');
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"));
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)
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.
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
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.
This is maybe what you're looking for, check:
require.main.filename
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));
};

Resources