sindresorhus / file-type

Detect the file type of a file, stream, or data
MIT License
3.72k stars 354 forks source link

Can't access node functions when tsconfig moduleResolution is set to "bundler" #652

Closed alex-e-leon closed 2 months ago

alex-e-leon commented 3 months ago

I'm using tsup to bundle my node project into a single file, and have moduleResolution: "bundler" set in my tsconfig because a) it feels like the right choice as I'm using a bundler, and b) it allows me to ignore writing out file type extensions with esm.

Unfortunately it looks like file-type only exposes node functions like fileTypeFromFile when moduleResolution is set to "node". I get that this is done to add extra safety and prevent consumers from importing functions that don't work in the environment that they are running, and 99% of projects with moduleResolution: "bundler" are likely headed for the browser, but considering that bundling is valid for node projects as well, I wonder whether the exports should be relaxed for this?

Borewit commented 3 months ago

Unfortunately it looks like file-type only exposes node functions like fileTypeFromFile when moduleResolution is set to "node".

file-type exports Node.js specific functions, like fileTypeFromFile, to JavaScript engines of type "node".

The moduleResolution, in your project, determines the module resolution strategy, and does, or could potentially, impact if the ESM exports (field in package.json) is correctly interpreted. More info: TypeScript: Module Resolution

Looks like "moduleResolution" = "bundler" is desgined for Bundlers transpiling to CommonJs. Maybe give "node16" a try.

functions that don't work in the environment that they are running, and 99% of projects with moduleResolution: "bundler" are likely headed for the browser, but considering that bundling is valid for node projects as well

That would break browser projects, they are not only ending up with functions they cannot use, but more problematic are the Node.js API dependencies.

This is a problem your project, possibly your bundler, not in file-type.

alex-e-leon commented 3 months ago

Hey @Borewit unfortunately the issue is either a mismatch between nodes spec and typescripts spec, and has nothing to do with the bundler itself. I haven't actually checked how the bundler behaves in this case, because I get a typescript error which is preventing me from bundling at all.

Node says for exports "node" - matches for any Node.js environment. Can be a CommonJS or ES module file. In most cases explicitly calling out the Node.js platform is not necessary.

While typescript says for moduleResolution: 'bundler' for use with bundlers. Like node16 and nodenext, this mode supports package.json "imports" and "exports", but unlike the Node.js resolution modes, bundler never requires file extensions on relative paths in imports.

However in spite of those 2 specs agreeing that bundler should behave like node16/nodenext most use cases for moduleResolution: bundler typescript is not reading the node export, I believe because most use cases for moduleResolution: bundler are to bundle for the browser.

You can setup a quick example project to see that it is just this property in tsconfig which is preventing it from being imported.

In my case I've "fixed" the issue by switching from an ESM import to require, but I thought I'd raise it because commonJS isn't great and maybe there's a better way to expose the different functions in this situation, like exporting the "node" functions at a different path/name when moduleResolution is bundler.

Borewit commented 3 months ago

@alex-e-leon Can you try this branch? explicit-node-subpath-export, b75fc026c899eb6be1e192d96032ad7a10737460

npm install 'sindresorhus/file-type#b75fc026c899eb6be1e192d96032ad7a10737460'

Using subpath import file-type/node

I doubt it will solve your problem, as some of the sub-dependencies also rely on the node engine dependency.

sindresorhus commented 3 months ago

You need to open an issue on the TypeScript repo about this. This package is defined correctly. The problem is that moduleResolution: bundler assumes not node, so they need to add a way to override that. It doesn't make sense to do anything here.

alex-e-leon commented 3 months ago

Hey, sorry for taking a while to get to this. Like I said I've already worked around it so it took me a while to get the time to come back to this.

@Borewit - your branch almost works. as there's no exported node.js file it doesn't resolve, but if instead the exports look like:

"./node": {
    "types": "./index.d.ts",
    "import": "./index.js"
}

Then yes I can import successfully with import {fileTypeFromFile} from 'file-type/node';

@sindresorhus - you're right it's really an issue with typescript, but considering the current state of the landscape I figured you'd want to know about the issue and potentially include a work around until typescript + node figure it out. With a bit of tweaking @Borewit 's solution seems to work fine and I don't think leaves too big a footprint for a workaround. But it's not blocking me at all, so I'll leave it up to you to make the call.

Thanks both for being so responsive!

Borewit commented 2 months ago

Would you mind we add an explicit sub-path for node @sindresorhus ?

See: https://github.com/sindresorhus/file-type/tree/explicit-node-subpath-export

sindresorhus commented 2 months ago

I would prefer not to. If all packages just work around the problem, the TypeScript team has no incentive to prioritize fixing this.

Borewit commented 2 months ago

I'll now close this issue. If there are any additional details we may have overlooked, feel free to reopen it.