TLDR: When "module" is anything aside from "commonjs" you need to explicitly specifiy a "moduleResolution" value of "node" for this to work. Setting "commonjs"for"module" does so implicitly.

As this is by no means obvious, I strongly encourage explicitly specifying "moduleResolution" whenever specifying "module".

I strongly recommend that you always specify the output module format explicitly.

Additionally, despite the names of certain options, "target" and "module" are independent and orthogonal. They mean very different things and must not be confused.

{
  "compilerOptions": {
    "outDir": "./dist/",
    "module": "esnext",
    "moduleResolution": "node",
    "target": "esnext", // this isn't relevant
    "allowJs": true,
    "sourceMap": true
  }
}

"commonjs" is an output module format. ESNext dynamic import(...) statements are a transpiled into the output module format just like other module syntax such as ES2015 import and export statements.

When you specify --module esnext, you are telling TypeScript not to transpile any module syntax at all. That's the point of --module it specifies the output module format, not the source module format.

Answer from Aluan Haddad on Stack Overflow
🌐
Tsmean
tsmean.com › articles › learn-typescript › typescript-module-compiler-option
Module Compiler Option in TypeScript - tsmean
June 20, 2017 - Generally speaking, ESNext is the way forward. With a big BUT. ECMA came a bit late to the party, that's why other module systems arose, but now that they've defined an official standard for modules, all systems are trying to move in this direction.
🌐
Reddit
reddit.com › r/node › common js vs es modules and why?
r/node on Reddit: Common JS vs ES Modules and why?
December 9, 2022 -

Hey, I'm working on a small side project and learning node and just can't wrap my head around what's happening here. Please forgive if this has already been answered for someone else. I searched around and the answers don't quite do it for me.

My situation is this. I've installed create-react-app, graphql, and apollo client/server. I'm working on getting the server set up and through that have added a few lines to the server file that use an import statement.

The guide that I was following to set up the server however uses Common JS. This confuses me.

I don't understand why one is used over the either. I'm getting an error now when I init the server file that says "Cannot use import statement outside of a module". I understand that this means that the import statement isn't valid in my current file but I don't know why it's invalid in this file but perfectly fine in my react typescript files.

Can someone please explain to me why/when I should be using import vs. require, and why it only works in certain files and folders?

Thank you in advance!

Top answer
1 of 1
33
CommonJS is old, but Node tries to hold on to backwards compatibility as much as it can, but forward compatibility is a whole 'nother story. The major difference is, import is an asynchronous means of including modules, where as require is synchronous. CommonJS does not support async in the root of a module, import itself does exist in CommonJS, just not in the root level of a module (file). If you create an async function, you can all and await import from inside of it. ESM will handle the root level async imports for you, and provide a whole realm of future features going forward. ESM can consume and use CommonJS modules, but CommonJS cannot consume ESM modules, it's a one way street. A module either has to explicitly output both an ESM and a CommonJS output, and be configured in such a way that it exposes both in a resolvable fashion. In some cases people use a simple wrapper to achieve this, but it does not come without it's pains. This is mostly only an issue if you're building something with the intention of publishing it to npm where it will be consumed by a wider audience. Because whether or not you support both impacts who can consume your API. The reason you get this error: Cannot use import statement outside of a module Is because the backend project where you're using import is currently set to be CommonJS. You should add: { ... type: "module" ... } to your package.json in your backend project. If you'd like to see a TypeScript project configured to support both commonjs and ESM, without relying on any additional dependencies to do so, you can see my csrf-csrf project here, which has a package.json and a tsconfig, and some scripts, configured to output and publish a module to npm which natively supports both.
Discussions

Commonjs module system instead of ESNext
I noticed in the source code that v1.3.1 and later tsconfig with had the following option: "module": "ESNext" More on github.com
🌐 github.com
1
January 5, 2021
node modules - Typescript: esnext compiler option destroys es6 import from external lib - Stack Overflow
When setting ether the module or target property of the compiler options to esnext (id like to use import("example") statements), es6 import statements stop working for npm installed librarys (local More on stackoverflow.com
🌐 stackoverflow.com
"esnext" breaks import/export syntax from commonjs? (tsconfig.json)
Broken in what way? Do you get an error message? When you switched module to esnext did you do anything with moduleResolution? I typically have module set to esnext and moduleResolution set to 'node'. More on reddit.com
🌐 r/node
2
4
July 14, 2021
Docs: Difference between esnext, es6, es2015 module targets
Search Terms esnext, es6, es2015, module, tsconfig, tsconfig.json, difference, document Suggestion It would be great if this can be documented. On the face of it, the output for all three seems to ... More on github.com
🌐 github.com
19
May 12, 2018
🌐
DEV Community
dev.to › ruben_alapont › navigating-nodejs-module-systems-commonjs-vs-es-modules-with-typescript-2lak
Navigating Node.js Module Systems: CommonJS vs. ES Modules with TypeScript - DEV Community
September 23, 2023 - If you're in the process of transitioning an existing Node.js project from CommonJS to ES Modules, consider the following steps: Update Node.js: Ensure you're using a Node.js version that supports ES Modules. Refactor Imports and Exports: Replace require with import and module.exports/exports with export statements throughout your code. Update File Extensions: Modify your module file extensions to .mjs for ES Modules. Update TypeScript Configuration: Set "module": "ESNext" in your tsconfig.json to target ESM.
🌐
TypeStrong
typestrong.org › ts-node › docs › imports
CommonJS vs native ECMAScript modules | ts-node
Transforming to CommonJS is typically simpler and more widely supported because it is older. You must remove "type": "module" from package.json and set "module": "CommonJS" in tsconfig.json.
🌐
GitHub
github.com › react-hook-form › resolvers › issues › 109
Commonjs module system instead of ESNext · Issue #109 · react-hook-form/resolvers
January 5, 2021 - I noticed in the source code that v1.3.1 and later tsconfig with had the following option: "module": "ESNext" Whereas v1.3.0 has: "module": "commonjs" Downgrading to v1.3.0 fixes the import issue. I suggest changing the tsconfig.json. In case it was intentional, I'd like to know the rationale behind it.
Author   yashmahalwal
🌐
Michele Nasti
michelenasti.com › 2019 › 06 › 24 › typescript-why-so-complicated.html
Typescript: why so complicated?! (A list of my preferred options)
June 24, 2019 - CommonJS is a spec that is very similar to what NodeJS has implemented (require/exports). UMD stands for Universal Module Definition and tries to deliver a module that works with every possible module definition defined before.
Find elsewhere
🌐
LogRocket
blog.logrocket.com › home › commonjs vs. es modules in node.js
CommonJS vs. ES modules in Node.js - LogRocket Blog
June 20, 2024 - CommonJS was primarily intended for server-side development with Node.js. It implemented synchronous loading using require and module.exports. Asynchronous Module Definition (AMD), on the other hand, concentrates on browser environments with asynchronous loading using define and require, which improved page load time and responsiveness.
🌐
TypeScript
typescriptlang.org › tsconfig › module
TypeScript: TSConfig Option: module
You very likely want "nodenext" ... or esnext for code that will be bundled. Changing module affects moduleResolution which also has a reference page. ... In addition to the base functionality of ES2015/ES6, ES2020 adds support for dynamic imports, and import.meta while ES2022 further adds support for top level await. The node16, node18, node20, and nodenext modes integrate with Node’s native ECMAScript Module support. The emitted JavaScript uses either CommonJS or ES2020 ...
🌐
Andrewbran
blog.andrewbran.ch › is-nodenext-right-for-libraries-that-dont-target-node-js
Is `nodenext` right for libraries that don’t target Node.js? ∙ Andrew Branch
So at face value, the real question posed in Matt’s tweet is whether there are differences in emit between module: esnext and module: nodenext that might cause bundlers to trip over nodenext code. The most important thing to understand about module: nodenext is it doesn’t just emit ESM; it emits whatever format it has to in order for Node.js not to crash; each output filename is inherently either ESM or CommonJS according to Node.js’s rules and tsc chooses its emit format for each file based on those rules.
🌐
Webdevtutor
webdevtutor.net › blog › typescript-commonjs-vs-esnext
TypeScript CommonJS vs ESNext: Understanding the Differences
Static vs Dynamic: ESNext allows for static analysis of dependencies at compile-time, whereas CommonJS resolves dependencies dynamically at runtime.
🌐
LinkedIn
linkedin.com › posts › mbaneshi_commonjs-vs-esnext-generally-speaking-esnext-activity-7058365599667609600-s-eq
Mehdi Baneshi on LinkedIn: CommonJS vs ESNext Generally speaking, ESNext is the way forward. With a…
April 30, 2023 - So all in all this can be summarized as: Using TypeScript, node.js and the TypeScript compiler option "module": "esnext" together is next to impossible. So if you're building code that should be ran with node.js, in 2020, you should still choose "module": "commonjs".
🌐
Reddit
reddit.com › r/node › "esnext" breaks import/export syntax from commonjs? (tsconfig.json)
r/node on Reddit: "esnext" breaks import/export syntax from commonjs? (tsconfig.json)
July 14, 2021 -

I'm (simply) trying to use top-level exports... I did have my module set to commonjs and target to es6 but you need to set module to esnext to get top-levl await.

The issue is that setting modules to esnext has broken import/export.

I can't find any simple breakdown of the differnce between esnext modules and commonjs. Can anyone tell me why import/export would break like this?

Or a simple tsconfig.json example that will work for top-level await

All I want is a simple config.ts file where I can export env variables, but I need to get the variables from aws secret manager

🌐
GitHub
github.com › microsoft › TypeScript › issues › 24082
Docs: Difference between esnext, es6, es2015 module targets · Issue #24082 · microsoft/TypeScript
May 12, 2018 - esnext, es6, es2015, module, tsconfig, tsconfig.json, difference, document · It would be great if this can be documented. On the face of it, the output for all three seems to be identical. Related: It would also be nice to document why targeting module: "none" when your code is a module (ie. uses ES2015 imports/exports) generates CommonJS-compatible code instead of exporting to globals as "none" seems to imply.
Published   May 12, 2018
Author   bcherny
🌐
GitHub
github.com › Microsoft › TypeScript › issues › 16820
Dynamic import requires module: 'esnext' in Webpack · Issue #16820 · microsoft/TypeScript
June 29, 2017 - Hi guys, I just switched to using the new dynamic import in my webpack app but I was surprised that all the code was bundled together. The problem was that I was using module : "commonjs", instead of module: "esnext"" in my tsconfig.json...
Author   olmobrutall
🌐
Reddit
reddit.com › r/node › annoyed by the schism between commonjs and esm
r/node on Reddit: Annoyed by the schism between CommonJS and ESM
May 7, 2022 -

New to Node, and was fortunate to have the choice between module types. I preferred ESM syntax, did a bit of research into the differences between the two and it seemed the posts I were reading were suggesting ESM over CJS.

So that's cool but I've been running into weird issues where some dependencies need CommonJS, so I can't set "type": "module" in package.json

There are probably third party libraries that will aid me in these pains, but I'm up to my ears in third party libraries and thinking that I have to add yet another one for a fairly simple app is a bit cringey.

Unfortunately my desire to use ESM modules have resulted in quite a few hours of frustration as a newbie.

Sidenote: I was excited to learn Typescript because it seemed cool, but then I read a few articles that say turning a dynamically typed language to a static language is stupid. Except they went into much more depth into why it is stupid, and not gonna lie, it sounded convincing. But again, I'm just a newb. The JS universe is kinda quirky, eh.

🌐
Hoeser
hoeser.dev › blog › 2023-02-21-cjs-vs-esm
Snapstromegon - CommonJS vs. ESM
CommonJS is an established way of building modules in JavaScript. ESM on the other hand is a newer standard way of writing modules. Both have their ups and downs and I want to discuss them here.
🌐
TypeScript
typescriptlang.org › docs › handbook › modules › guides › choosing-compiler-options.html
TypeScript: Documentation - Modules - Choosing Compiler Options
If you’re using a third-party emitter to emit ESM outputs, ensure that you set "type": "module" in your package.json so TypeScript checks your code as ESM, which uses a stricter module resolution algorithm in Node.js than CommonJS does. As an example, let’s look at what would happen if a library were to compile with "moduleResolution": "bundler": ... Assuming ./utils.ts (or ./utils/index.ts) exists, a bundler would be fine with this code, so "moduleResolution": "bundler" doesn’t complain. Compiled with "module": "esnext", the output JavaScript for this export statement will look exactly the same as the input.