Question

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import when attempting to start Nodejs App locally

I'm caught in a bit of a loop trying to deploy my app to Heroku. My import statements (e.g. import cors from 'cors') seem to prevent the app from launching in production, due to the "Cannot Load ES6 Modules in Common JS" error. Locally it runs just fine.

However, when I attempt to resolve the above error by adding "type": "module" to my package.json I get a whole new set of errors and the app will no longer run locally. I believe this error is due to the way I'm initializing sequelize and associated models but I am unsure. I'd like to resolve this error but need a hand with new syntax for the imports... I think.

Error, package.json and index.js include below.

Error Text

[nodemon] starting `babel-node src/index.js`
internal/process/esm_loader.js:74
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/Users/jeff/Clients/Bummer/Code/Server/src/models' is not supported resolving ES modules imported from /Users/jeff/Clients/Bummer/Code/Server/src/index.js
    at finalizeResolution (internal/modules/esm/resolve.js:272:17)
    at moduleResolve (internal/modules/esm/resolve.js:699:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:810:11)
    at Loader.resolve (internal/modules/esm/loader.js:85:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:229:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:51:40)
    at link (internal/modules/esm/module_job.js:50:36) {
  code: 'ERR_UNSUPPORTED_DIR_IMPORT',
  url: 'file:///Users/jeff/Clients/Bummer/Code/Server/src/models'
}
[nodemon] app crashed - waiting for file changes before starting...

Package.JSON

{
  "name": "bummer",
  "type": "module",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon --exec babel-node src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.9.6",
    "@babel/node": "^7.8.7",
    "@babel/preset-env": "^7.9.6",
    "nodemon": "^2.0.4",
    "sequelize-cli": "^6.2.0"
  },
  "dependencies": {
    "cookie-parser": "^1.4.5",
    "cors": "^2.8.5",
    "dotenv": "^8.2.0",
    "express": "^4.17.1",
    "pg": "^8.2.1",
    "querystring": "^0.2.0",
    "request": "^2.88.2",
    "sequelize": "^6.3.5",
    "sequelize-auto-migrations": "^1.0.3",
    "uuid": "^8.0.0"
  }
}

Index.js

import cors from 'cors';
import express from 'express';
import models, { sequelize } from './models';
// import routes from './routes';

//Initiaze Express
const app = express();
const routes = require('./routes');


//Helpers for Spotify oAuth
const cookieParser = require('cookie-parser')


// Include Middleware
app.use(express.static(__dirname + '/public'))
   .use(cors())
   .use(cookieParser())
   .use(express.json())
   .use(express.urlencoded({ extended: true }))
   require('dotenv').config()

   

// Include all Models
app.use((req, res, next) => {
  req.context = {
    models,
  };
  next();
});



// Load Routes from Router Index
app.use('/', routes);

sequelize.sync().then(() => {
  app.listen(process.env.PORT, () => {
    console.log(`Example app listening on port ${process.env.PORT}!`)
  });
});

Thoughts or pointers? Thank you!

 46  81517  46
1 Jan 1970

Solution

 75

Explicitly, point to your main file (usually index.js). E. g.

import ... from './models'          // ❌
import ... from './models/index.js' // ✅

Alternative way (see the below UPDATE for Node.js v19+ and the newer one for Node.js 20.8+):

Use --experimental-specifier-resolution=node flag. For example:

node --experimental-specifier-resolution=node main.js

See: https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm


UPDATE (Node.js v19+):

Node.js has removed the --experimental-specifier-resolution flag. Its functionality can now be achieved via custom loaders.

https://nodejs.org/en/blog/announcements/v19-release-announce/#custom-esm-resolution-adjustments

The simplest loader (that only appends .js extension if needed) is something like:

import {isBuiltin} from 'node:module'

// noinspection JSUnusedGlobalSymbols
export const resolve = (specifier, context, nextResolve) => // This function can be `async` too
  nextResolve(isBuiltin(specifier) || specifier.endsWith('.js') ? specifier : `${specifier}.js`, context)

Name it loader.js (or loader.mjs if you don't set yet "type": "module" in your package.json).

Then if you run your script (e.g. ./some-script.js) using this loader:

node --loader ./loader.js some-script.js

the imports within some-script.js (and the files imported in it) can omit .js extension.

See more complex examples: https://github.com/nodejs/loaders-test


UPDATE (Node.js v20.8+): register()/--import

Using register() function (from built-in node:module package) and then --import flag (instead of --loader).

There is a ts-loader example, in my other answer, using ts-node/esm (from ts-node library):

import {register} from 'node:module'
import {pathToFileURL} from 'node:url'

register('ts-node/esm', pathToFileURL('./'))

And then:

node --import ./ts-loader.js my-script.ts

That has some cons that I mentioned there (specially in imports inside your file/files).

Plus another perfect ts-loader solution using esbuild that you can see it there.

They're both originally for executing TypeScript files using Node.js. But they both work for the purpose of current question, too. It means you can execute your models/index.js (or models/index.ts), by:

node --import ./ts-loader.js models

or (using the second perfect solution):

node --import ./register-ts-loader.js models

Also, using it, you can execute js/ts files w/o extension + running ts files with js extension (as you can import ts files in another ts file, using js extension). With the second perfect solution, you can enjoy these features recursively for imports inside your file/files. Plus supporting jsx/tsx!

2021-08-02

Solution

 8

In Node.js, import statements are permitted only in ES modules. so, directory imports do not work in Node.js. Read Node.js Documentation.

2020-10-24