Import ES6 Modules with absolute paths in NodeJS without using Babel/Webpack
Asked Answered
H

4

20

I have a plain old NodeJS project (with Typescript) and I'm really struggling to find out how to do ES6 imports for local files without having to do "../../../foo/bar" all the time.

There are loads of similar questions but all seem to revolve around babel/Webpack which I'm not using.

If I do the following:

import foo from `/bar`

it looks for it in the root folder of my PC (e.g. c:/bar) and fails.

I have tried using a .env file with NODE_PATH set to various hings ("/", "." etc) but no luck. I have also tried setting "type: 'module'" in my package.json and my tsconfig.json file has {"baseUrl": "."}

So I think I've tried every answer I can find. Am I just doing them in the wrong combination or is the solution something different?

Heraclitean answered 27/11, 2020 at 15:53 Comment(1)
Take a look at my answer here: https://mcmap.net/q/88643/-how-to-import-javascript-files-in-nodejs-using-file-paths-from-project-root-instead-of-relative-file-pathsBybidder
N
9

Here are two tricks I've used for this, with Node.js and native ES modules.

  1. file: dependencies

If you want to access <project root>/bar from a sub package two levels down, adding this to the package.json dependencies:

    "@local/bar": "file:../../bar",

..makes bar available to the said subpackage as @local/bar.

While relative paths are still present, they are now all in the package.json files and the sources never need to know..

  1. Use dynamic import()

Pick the root folder's path to a constant and do this:

const foo = await import(`${rootPath}/bar`);
Nylanylghau answered 16/4, 2021 at 16:9 Comment(2)
Will dynamic importing result in that IDE lost some code intelligence?Rechaba
@Rechaba It might. I have successfully used alternative 1 now, for a while. It requires npm >= 7.7 but if that's okay, I recommend it. This reserves the dynamic import to cases that really need it.Nylanylghau
S
7

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 the reserved node_modules directory

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

Sufficiency answered 23/3, 2023 at 16:15 Comment(1)
Should specify that the hash (#) is required here, it won't work without it as per the spec.Brakesman
E
1

This was pretty simple to implement for me, using Typescript in VSCode.

In tsconfig.json, I added "baseUrl": "./", under compilerOptions

After a restart of VSCode, VSCode will automatically import using psuedo-absolute paths.

The paths won't begin with /, that still points to your drive root.

If the is below the current file, it will still use a ./relative/path, but no more ../../tree/traversing

Then I set dev in packages.json to

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
  "dev": "cross-env NODE_PATH=./ nodemon ./app.ts"
},

I use cross-env. You may use the set command and nodemon for automatic reloading of change files.

Setting NODE_PATH to ./ tells NodeJS to do what Visual Studio and TypeScript are doing.

I do have package.json in my src directory. You may not, and may need to change some pathing to adjust.

Elvia answered 12/9, 2022 at 18:56 Comment(0)
K
-1

I am not sure what you mean by "local files without having to do "../../../foo/bar""

Let me explain how Javascript handles imports. Here is a basic folder structure.

C:Users/NAME/Project/randomfolder/bar.js


- Project
  - Random Folder
    - foo.js
  - bar.js

Option 1 Probably the better option for 95% of what you will do

Let's say you are trying to import foo.js into bar.js we first get our current location of bar.js using a . so it would be

import foo from "./Random Folder/foo.js"

If you are going the other way the . is still used to say get current location in the folder structure but you pass a second . so .. then a / to go up a folder. So to import bar.js into foo.js it would look like this:

import bar from "../bar.js"

We are going up a folder then looking for the file we want.

Option 2

But if you know you folder structure is going to be very big and you will be importing always a few folders up or down why not make a variable and then use string literals.

  let folder = `../../../`
  import test from `${folder}foo`

You have a few options of how to handle what you want to do.

Option 3 For NodeJS and modules

If you are using NodeJS and want to get the path not relative, the use the app-root-path module. It lets you always get your project root and then dig into to files accordingly. This can be accomplished with string literals.

var appRoot = require('app-root-path');
import foo from appRoot + "/foo/bar/folders/bla/bla"

or

import foo from `${appRoot}/foo/bar/folders/bla/bla` <-- string literals

Kraemer answered 27/11, 2020 at 18:11 Comment(3)
Unfortunately both of these options still use relative urls, which means if I move my files to a different folder I have to update every single import. What I need is to be able to use the root of my project as the starting point (not the root of my hard drive), e.g. import foo from '/controllers/bar where the controllers folder is at the top level of my project. ' This would then work wherever I put my 'bar' file in the folder structure.Heraclitean
Since you are using NodeJS there is a package you can use called app-root-path: npmjs.com/package/app-root-path Then all you need to do is declare a variable for appRoot path and add it there.Will update my answerKraemer
How do you manage to make template literals work with import ? I get a SyntaxError: Unexpected template string when I try this syntax with Node 14.16.1. Interpolation works with the top-level await + dynamic import syntax but I find yours a lot more readable.Leupold

© 2022 - 2024 — McMap. All rights reserved.