Search code examples
javascriptnode.jswebstormrequirecommonjs

How to import JavaScript files in NodeJS using file paths from project root? (instead of relative file paths)


Question:

How to import JavaScript files in NodeJS using file paths from project root? (instead of relative file paths)

In other words, my team is trying to transition from Syntax Style A to Syntax Style B (shown below):

Syntax Style A

const source = require('../../../../../../source.js');

Syntax Style B

const source = require('src/source.js'); // "src" is a direct subfolder of the project root

I have reviewed a lot of posts on this topic, and none of them can satisfy my project's requirements...


Project Requirements:

  • Requirement #1: No external packages (such as Babel.js or Webpack), since disk space is limited. Also, we cannot install anything with "npm install".
  • Requirement #2: A JavaScript file should be able to import another JavaScript file using "project root" file paths, not relative file paths. (This will avoid long relative paths such as "../../../../../../../script.js", which can become unmanageable.)
  • Requirement #3: Right-clicking the imported class anywhere in the source code should bring up a context menu, with an option to "Go to Declaration" (which takes us to the imported file directly). Also, moving a JavaScript file to a different folder in the IDE should automatically update all references. We can use any IDE.
  • Requirement #4: To have a consistent way of importing scripts, we will be using NodeJS's "require" keyword to import (instead of JavaScript's "import" keyword).

Setup:

Below is my setup. Package.json was generated by WebStorm, and I haven't changed it.

Screenshot_of_Project_Structure

Project_Root
├─-─ src
│    └── a1
│       └── a2
│           └── a3
│               └── Worker.js
├──- subFolder1
│    └── SubWorker.js
├─ main.js
└─ package.json

main.js

const Worker = require('./src/a1/a2/a3/Worker.js');
Worker.doWork();

Worker.js

// If I replace the following with Project Root path, such as:
//    const SubWorker = require('src/subFolder1/SubWorker.js');
// The script will fail with "Error: Cannot find module './src/subFolder1/SubWorker.js'."

const SubWorker = require('../../../subFolder1/SubWorker.js');

module.exports =
class Worker { 

        static doWork() {
                console.log("doing work");
                SubWorker.doMoreWork();
        }
}

SubWorker.js

module.exports =
class SubWorker {

        static doMoreWork() {
                console.log("doing more work");
        }

};

Attempt #1: path.resolve

I tried the "path.resolve" solution, as suggested by this post:


Attempt #2: app-module-path

  • Importing using relative paths in Node.js with ES Modules
    • Using app-module-path solves all three requirements, but the program will not run (which is an implied requirement): ⛔Error: Cannot find module 'src/subFolder1/SubWorker.js'
    • Error was reproducible in both WebStorm 2022.3.2 and VS Code 1.76.2

Attempt #3: dynamic import()

Worker.js

const rootPath = 'src/subFolder1';
const SubWorker = require(`${rootPath}/SubWorker.js`);


Attempt #4: subpath imports


Expected Outcome:

Since NodeJs is one of the most important backend languages, I was expecting excellent IDE support for imports... Referencing a source file from Project Root should be a simple operation that is handled by the IDE (I shouldn't need to use Babel.js or Webpack). Am I doing something wrong here?

I don't know what I am doing wrong, since I have already followed standard practices:

  • I used latest versions of the most popular NodeJS IDEs (WebStorm 2022.3.2 and VS Code 1.76.2)
  • I used WebStorm's "Create a new NodeJS project" wizard to create the project
  • I used the latest NodeJS (v18.15.0). (on Windows 10, v21H2)

Thanks!


Related Posts:

The Stack Overflow posts below are relevant, but do not address the specific issue in this post. (Please kindly search by the title on Stack Overflow, since the spam filter limited the number of links in this post, thanks!)

  • Title: NodeJS - Relative File Paths
    • Failed the same way as my Attempt #1 above
  • Title: Project root based relative paths for NodeJS modules
    • Violated Requirement #4 (the desired solution should use "require" instead of "import")
  • Title: is it possible to get rid of the relative paths on imports?
    • The top answer uses Babel.js, which violates Requirement #1
  • Title: How to import a JavaScript file to another JavaScript file
    • Does not address the question of relative path vs "project root" path
  • Title: How to use absolute/dynamic paths in NodeJs
    • Violated Requirement #4 (the desired solution should use "require" instead of "import")
  • Title: how to use absolute paths instead of relative paths in React or Next.js?
    • We are not using React or Next.js here.
  • Title: JavaScript Import file by absolute path
    • Violated Requirement #4 (the desired solution should use "require" instead of "import")
  • Title: javascript file with absolute file path
    • The script here was launched from the client-side web browser. This post deals with nodeJS scripts that are launched from the server-side backend.


Solution

  • Using the "Imports" property

    As of March 2023, a good way to eliminate the NodeJS relative paths is to use the imports property in package.json. (In the codes below, #root is the project root.)


    For CommonJS-style JavaScripts:

    // package.json
    {
      "imports": {
        "#root/*.js": "./*.js"
      }
    }
    
    // main.js:
    const Source = require('#root/path/to/Source.js');
    
    // Source.js:
    module.exports = class Source {
      // ...
    }
    
    

    For ECMAScript-style JavaScripts:

    // package.json:
    {
      "type" : "module",
      "imports": {
        "#root/*.js": "./*.js"
      }
    }
    
    // main.js
    import { Source } from '#root/path/to/Source.js';
    
    // Source.js:
    export class Source {
      // ...
    }
    
    

    Advantages:

    • No need to "import" or "require" any additional packages (No Babel.js, No Webpack, No RequireJS). After installing NodeJS, this method works out of the box.

    • IDE linkages work as expected (Ctrl-click a class name to jump directly to the source file. Also, moving the source file (by drag and drop) will automatically update all references to the file. Tested on WebStorm 2022.3.2 and VS Code 1.76.2.)

    • Works with both .mjs (ECMAScript module system) and .cjs (CommonJS) file types. Please see this reference Post on .cjs and .mjs.

    • No need to modify the reserved node_modules directory

    • No need to set up any linux file links at the OS level

    Updates in April 2023: