5

In the past, I used app-module-path whenever I wanted to have relative paths in my Node.js apps. If I use ES Modules through the .mjs format, how do I have the same functionality where a certain directory path becomes relative?

In an alternative way, would I be able to assign an alias to a directory instead so all relative paths are relative to that alias much like how ./ is an alias for a path being relative to the current directory.

Kevin Ghadyani
  • 6,829
  • 6
  • 44
  • 62
  • *I used app-module-path whenever I wanted to have relative paths in my Node.js apps* - how did you use that? It's unclear what you're asking. ES and CJS modules resolve in a similar way. – Estus Flask Jul 21 '18 at 09:48
  • They resolve in a similar way, but as soon as I import app-module-path using `import` instead of `require`, I get cryptic errors in the terminal. – Kevin Ghadyani Jul 21 '18 at 10:15
  • 1
    If you have a specific problem, please explain it and provide https://stackoverflow.com/help/mcve . An answer to vague question won't necessarily help to resolve it. – Estus Flask Jul 21 '18 at 10:17
  • I want a way to not have to use `../` anywhere in my Node.js project. `app-module-path` allows me to do this by calling `require('app-module-path/register')` in the root directory. That way I can reference all folders in the root such as `require('some-directory/a-file')` from anywhere in the project even though that file is in the root directory. There don't seem to be any solutions to do this with the import syntax. Webpack has directory aliases so I figure there's gotta be a Node.js way. – Kevin Ghadyani Jul 21 '18 at 23:37
  • *app-module-path allows me to do this by calling require('app-module-path/register') in the root directory* - how exactly? You started from that statement in the question without elaborating what this means in your case. – Estus Flask Jul 21 '18 at 23:55
  • 1
    https://www.npmjs.com/package/module-alias <- looks like this library module-alias works with ES Modules and does something similar. – Kevin Ghadyani Jul 23 '18 at 03:34
  • Thanks, that's a good example. Sorry for being thorough, needed to be sure we're on the same page. – Estus Flask Jul 23 '18 at 09:47

3 Answers3

2

It's possible to give aliases to certain paths for CommonJS modules that are loaded with require by monkey-patching built-in module module.

ES modules provide a way to change module loading behaviour by specifying custom ES module loader, as explained in this related answer.

This way root source path (which will be specified relatively to loader location) can be mapped to some alias (@ is conventional for front-end projects):

custom-loader.mjs

import path from 'path';

const ROOT_PATH = new URL(path.dirname(import.meta.url) + '/src').pathname;

export function resolve(specifier, parentModuleURL, defaultResolver) {
    specifier = specifier.replace(/^@/, ROOT_PATH);
    return defaultResolver(specifier, parentModuleURL);
}

Which is used like:

node --experimental-modules --loader ./custom-loader.mjs ./app.mjs

Notice that this provides a behaviour that isn't natural for ES modules, this may affect how this code is treated by other tools like IDEs.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
2

A more 2019 version, working for ES Modules.

The easiest way I've found so far is to use the experimental feature --loader.

I use something like that to be able to require import { SOME_CONSTANT } from '@config' or import { createConnection } from '@server/connection'.

The loader code:

import path from 'path'
import fs from 'fs'

export function resolve (specifier, parentModuleURL, defaultResolver) {
  specifier = specifier.replace(/^@/, path.resolve('.') + '/src/')
  specifier = fs.existsSync(specifier) && fs.lstatSync(specifier).isDirectory() ? `${specifier}/index` : specifier
  specifier += '.js'
  return defaultResolver(specifier, parentModuleURL)
}

Then node --experimental-modules --loader ./moduleResolver.js ./myScriptWithoutExtension

NOTE: you don't need to use .mjs extension if you specify a "type": "module" in the nearest package.json. You can stay extensionless.
NOTE2: you can replace the src string with where you usually put your code, you could even have a process.env.NODE_ENV based resolution.
NOTE3: If you give a directory to @, it will expect to find an index.js file.
NOTE4: You can use whateveer you want for aliasing, just replace the regex

user6465431354
  • 143
  • 1
  • 12
  • The `path.resolve('.')` in this solution is going to be whatever directory you run your script from. I found using `import.meta.url` as in the accepted solution was safer. I found it was necessary to add the ".js" extension as this solution does, however. – David Hull May 25 '23 at 20:59
2

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. For more information, please refer to this post:

In the codes below, #root is the project root.

(Please kindly upvote this answer and this post if they help you. Thanks!)

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 the file path references. 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 with the reserved node_modules directory

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

howard_9
  • 413
  • 3
  • 15