Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix require(ESM) for Node v22 #722

Closed
wants to merge 1 commit into from
Closed

Conversation

@sindresorhus
Copy link
Owner

This is not correct.

@mdorda
Copy link
Author

mdorda commented Jan 16, 2025

So what is the correct solution to make it work in Node 22? I tried to explain my reasons here: #696 (comment)

I will be happy to fix it, but I do not know what you consider to be correct.

@caldrjir
Copy link

@sindresorhus May I ask what the correct solution is?
I am facing the same problem and this seems to be ok for me

@mdorda
Copy link
Author

mdorda commented Jan 16, 2025

Maybe to use require? It works in Node 22 as well: #723

@Borewit
Copy link
Collaborator

Borewit commented Feb 8, 2025

@sindresorhus May I ask what the correct solution is? I am facing the same problem and this seems to be okay for me

I'll try to answer your question. file-type is a pure ECMAScript Module (ESM). There is nothing wrong with the package.json exports configuration that defines the file-type ESM entry points. In fact, the exports might be slightly more complex than that of other ESM modules because it defines two entry points:

  1. A default entry point
  2. A Node.js entry point, which adds a few Node.js–specific functions.
    Each entry point has its own typings corresponding to that entry point.

loading ESM module with Node require

If require in Node 22 claims to be able to load ESM modules, it should be able to load file-type. There is nothing missing in the exports for that use case.

loading ESM in CommonJS (CJS)

We recommend migrating your project to ESM. However, if for any reason you decide to stick with CommonJS, you can load ESM modules using a dynamic import. You might find that inconvenient, but as module maintainers we cannot build a module that is fully CJS-compliant without introducing other issues.

async fucntion run() {
  // Load file-type module in CommonJS (CJS) module
  const {fileTypeFromStream} = await import('file-type');
}

loading ESM module in TypeScript / CommonJS (CJS) (including NestJS)

There’s an extra complication here: the TypeScript compiler tends to transform dynamic imports into require calls, and since require doesn’t always load ESM modules correctly, this remains an issue.

Correct solutions would be:

  1. Node’s require implementation fixes its ESM support.
  2. The TypeScript compiler respects dynamic imports instead of converting them to require calls.

A workaround for these problems is to use the load-esm package:

import {loadEsm} from "load-esm";

type FileTypeModule =  typeof import('file-type');

async fucntion run() {
  // Load file-type module in TypeScript  / CommonJS (CJS) module
   const {fileTypeFromStream} = await loadEsm<FileTypeModule>('file-type');
}

This also works in Jest, I just added example for that here: #661 (comment)

@mdorda
Copy link
Author

mdorda commented Feb 8, 2025

We have already discussed it #723
I do not think there is anything else to add. True is, that this package is the only one we cannot update. We use all other pure ESM packages without any problem. Just saying.

@Borewit
Copy link
Collaborator

Borewit commented Feb 8, 2025

I did some more investigation, and I think there is something we can do to make this work. Node.js introduced a new condition introduced "module-sync", which seems to tackle this issue.

	"exports": {
		".": {
			"node": {
				"types": "./index.d.ts",
				"import": "./index.js",
				"module-sync": "./index.js"
			},
			"default": {
				"types": "./core.d.ts",
				"import": "./core.js",
				"module-sync": "./core.js"
			}
		},
		"./core": {
			"types": "./core.d.ts",
			"import": "./core.js",
			"module-sync": "./core.js"
		}
	}

@sindresorhus, any objection we reopen this PR, and use module-sync enabling Node.js to import file-type as an ESM module using require?

@sindresorhus
Copy link
Owner

"module-sync"
The format is expected to be ES modules that does not contain top-level await in its module graph

I don't want to guarantee that.

@Borewit
Copy link
Collaborator

Borewit commented Feb 9, 2025

"module-sync"
The format is expected to be ES modules that does not contain top-level await in its module graph

I don't want to guarantee that.

As far as I can see we do not have any top-level await in the module graph. Therefor I see no issue declaring that this is the case, and that file-type it totally fine to be loaded as an ES Module via require. I wish they made this obvious option possible years ago.

Note that most your ESM modules are exporting their entry points via default, where the assumption that these can be loaded via require/ESM, is already made by Node 22.

3/4 file-type dependencies (@tokenizer/inflate, strtok3, token-type) I maintain, and these are not using top-level await. Number 4 is one you own, uint8array-extras, which neither has any top level await, and is already implicit module-sync compliant via default.

I like to have this one in very much, as I build my open source code components to be easy accessible and easy to use on as many platforms as possible. I really see no harm in this change, and if we experience issues, we always change course.

@sindresorhus
Copy link
Owner

As far as I can see we do not have any top-level await in the module graph. Therefor I see no issue declaring that this is the case, and that file-type it totally fine to be loaded as an ES Module via require. I wish they made this obvious option possible years ago.

None now, but could be in the future. I don't want to be held back by require. Declaring this also means we are on the hook to deal with it if any dependency at any level starts using top-level await (which does not generally require a major version, btw) at any point in the future.

easy to use on as many platforms as possible

All modern platforms support ESM.


It seems like this is an issue with Nest.js and it's not something we should have to deal with: nestjs/nest#13319 (comment)

@Borewit
Copy link
Collaborator

Borewit commented Feb 9, 2025

There is always a risk that dependency breaks our code. And there are plenty of ways to deal with that.
But you cannot build software today on something what is planned to to be build to tomorrow.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND

It does harm not if we allow esm-require for the time being, while the world is still very slowly migrating to ESM. It it is totally up to use for how long and under which condition we support that. But for this moment I am committed to do so.

@imaksp
Copy link

imaksp commented Feb 12, 2025

Hi all, I believe this is good change by Node, and it will help so many older projects who can't migrate to full ESM easily. so please consider adding its support (as it seems top level await is not being used). Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants