Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Jonathon Snook
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with: Jonathon Snook@snookca )

Using require.resolve() To Calculate Module-Relative File Paths In Node.js

By Ben Nadel on

Over the weekend, I started looking into Express.js - the [seemingly] oldest and most popular framework for building web applications with Node.js. I've been using Node.js for a few years; but, I've never actually built a full-on web application. As such, I thought it would be fun to read up on it. And, while I was reading up on it, I came across something I had never seen before - a StrongLoop article that was using require.resolve() to calculate module-relative file paths. Historically, I've used the module-local variable, "__dirname", to calculate module-relative paths; so, I wanted to take a minute and look at how require.resolve() works.

In Node.js, the module-loader - require() - can use module-relative paths. So, for example, if you have a module in some arbitrary folder in the file system and that module needs to load one of its own libraries, you can safely refer to that library using a module-relative path:

require( "../lib/thinger" );

Outside of the module-loader, however, file paths often needs to be either absolute or relative to the root execution context of the node application. So, for example, when you read-in the contents of a file, you need to provide the fs.readFileSync() method with a non-local path. Historically, I've used the "__dirname" variable as a way to calculate that non-local path:

fs.readFileSync( path.join( __dirname, "local-file.txt" ) );

This produces a path that takes the absolute location of the module-directory and appends the given filename to it. This approach works fine; but, there is something very alluring about being able to more naturally calculate a module-relative file path. This is why the require.resolve() method caught my eye. require.resolve() uses the same machinery that require uses to load a module - which means it can use module-relative paths; but, rather than loading the module, it simply returns the resolved file path to said module. This module-relative cum absolute file path can then be used to, for example, read-in the content of said "module".

To see this in action, I've created a module that does nothing but locate and read-in the content of a file in the same directory. But, in order to ensure that we don't accidentally create root-relative paths, my app module loads my demo module from a sub-directory:

  • // We want to request a module in a sub-directory so that module-relative paths aren't
  • // accidentally ALSO RELATIVE to the root of the application.
  • require( "./sub/test" );

Then, from within that sub-directory demo module, I use __dirname and require.resolve() to generate module-relative paths:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  • var fileSystem = require( "fs" );
  • var path = require( "path" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Traditionally, I have used the "__dirname" as a way to calculate module-relative
  • // paths since "__dirname" provides the absolute path to the current module directory.
  • // That way, any relative-path appended to the "__dirname" generates a module-relative
  • // path to the given location.
  • console.log( chalk.red.bold( "Using __dirname:" ) );
  • console.log( path.join( __dirname, "data.txt" ) );
  • console.log( chalk.dim( fileSystem.readFileSync( path.join( __dirname, "data.txt" ) ) ) );
  •  
  • console.log( "" );
  •  
  • // The require.resolve() method uses the same mechanics for locating a module (ie, the
  • // thing you normally "require"); but, instead of loading it, .resolve() returns the
  • // calculated path to the given module. And, since require() can use module-relative
  • // paths, so can require.resolve().
  • console.log( chalk.red.bold( "Using require.resolve():" ) );
  • console.log( require.resolve( "./data.txt" ) );
  • console.log( chalk.dim( fileSystem.readFileSync( require.resolve( "./data.txt" ) ) ) );

As you can see, I'm just locating and reading-in a file using the two different approaches. And, when we run this demo from within the root node app, we get the following terminal output:


 
 
 

 
 Using require.resolve() to locate an existing file. 
 
 
 

As you can see, both approaches calculated the same absolute file path given the module-relative path. And, both approaches were able to read-in the contents of the text file.

At first, it might seem like this is a pure win on generating module-relative file paths. But, require.resolve() has some significant caveats. For starters, it's interacting with the file system. Because require.resolve() uses the same module-resolution algorithm that require() uses, it's actually locating the module. Which means that it's looking at the file system to make sure the module exists. This has processing overhead to it (presumably more than the __dirname approach). And, it also brings us to the second caveat: it only works with existing files. Meaning, you can't use require.resolve() to generate a module-relative path to which you want to save a new file.

To see what I mean, I'm going to update my demo module to include a superfluous call to resolve a module-relative file path for a non-existent file:

  • // Require the core node modules.
  • var chalk = require( "chalk" );
  • var fileSystem = require( "fs" );
  • var path = require( "path" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Executing a no-op call to resolve a module-local file path to a non-existent file
  • // in order to demonstrate that this is doing more than just calculating file paths.
  • require.resolve( "./my-new-file.txt" );
  •  
  • // ----------------------------------------------------------------------------------- //
  • // ----------------------------------------------------------------------------------- //
  •  
  • // Traditionally, I have used the "__dirname" as a way to calculate module-relative
  • // paths since "__dirname" provides the absolute path to the current module directory.
  • // That way, any relative-path appended to the "__dirname" generates a module-relative
  • // path to the given location.
  • console.log( chalk.red.bold( "Using __dirname:" ) );
  • console.log( path.join( __dirname, "data.txt" ) );
  • console.log( chalk.dim( fileSystem.readFileSync( path.join( __dirname, "data.txt" ) ) ) );
  •  
  • console.log( "" );
  •  
  • // The require.resolve() method uses the same mechanics for locating a module (ie, the
  • // thing you normally "require"); but, instead of loading it, .resolve() returns the
  • // calculated path to the given module. And, since require() can use module-relative
  • // paths, so can require.resolve().
  • console.log( chalk.red.bold( "Using require.resolve():" ) );
  • console.log( require.resolve( "./data.txt" ) );
  • console.log( chalk.dim( fileSystem.readFileSync( require.resolve( "./data.txt" ) ) ) );

As you can see, I'm trying to resolve the module-relative file path for the non-existent file, "my-new-file.txt". And, when we re-run this demo, we get the following terminal output:


 
 
 

 
 Using require.resolve() to locate a non-existing file. 
 
 
 

As you can see, the whole demo fails because we're trying to resolve the file-path for a non-existent file. The module-resolution algorithm comes bubbling to the surface.

Clearly, the require.resolve() method is doing far more than just generating file paths - it's actually interacting with the file system. As such, it has greater processing overhead and some limitations in the way it can be used. But, it also provides a more natural way of "calculating" module-relative paths. So, while I don't think I would use this approach for many situations, it could be nice for executing one-time reads of things like configuration files.



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.