Skip to content

ESM is hell, how do we escape hell? #1346

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

Closed
1 task done
djMax opened this issue Sep 16, 2023 · 12 comments · Fixed by #1360
Closed
1 task done

ESM is hell, how do we escape hell? #1346

djMax opened this issue Sep 16, 2023 · 12 comments · Fixed by #1360
Labels
bug Something isn't working openapi-fetch Relevant to the openapi-fetch library

Comments

@djMax
Copy link

djMax commented Sep 16, 2023

Description

I struggle to even come up with a description other than "something is wrong with openapi-fetch that causes it to be almost impossible to use in a typical Typescript project. I know this is never ending frustration for everyone, but I'm hoping perhaps someone can spot the problem. I'm using express, node20, and vitest.

Reproduction

Here's the TS config:

  "compilerOptions": {
    "lib": [
      "ES2022",
      "DOM"
    ],
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022",
    "declaration": true,
    "sourceMap": true,
    "outDir": "./build",
    "incremental": true,
    "isolatedModules": true,
    "strict": true,
    "baseUrl": "./src",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "paths": {
      "@/*": [
        "./*"
      ]
    }
  }

Expected result

Not this error:

The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("openapi-fetch")' call instead.
  To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field `"type": "module"` to '/my-project/package.json'.ts(1479)

(using dynamic import doesn't help either)

Checklist

@djMax djMax added bug Something isn't working openapi-fetch Relevant to the openapi-fetch library labels Sep 16, 2023
@drwpow
Copy link
Contributor

drwpow commented Sep 16, 2023

Are you using Node in ESM mode ("type": "module")? ESM is the new standard JS module that supersedes legacy CJS. It’s recommended to use ESM wherever possible. That could be what’s missing.

@djMax
Copy link
Author

djMax commented Sep 17, 2023

No, because my package is part of 50 others that would all have to move. It was impossible when I was using Jest, but I've switched to vitest which maybe would work better. But ESM just poops on everything it touches, unless it touches everything.

@drwpow
Copy link
Contributor

drwpow commented Sep 19, 2023

I mean, like it or hate it, ESM is the standard module system now, and CJS will phase out eventually. It’s not that ESM itself is the issue, but the end result of a fractured ecosystem of CJS <> ESM isn’t fun for anyone. Everything will migrate to ESM, but it will just take time.

In the meantime, I can ship a CJS build for openapi-fetch until people are able to migrate. But CJS isn’t/won’t ever be the first-class citizen for support because ESM is the format that now runs universally in browsers / Node / Deno / etc.

@djMax
Copy link
Author

djMax commented Sep 19, 2023

Yeah - I was thinking the same. I don't know why openapi-fetch seems to be the "thing that breaks" so often - I guess because you're trying to do it right and ship a flexible package...

@nicu-chiciuc
Copy link

I don't have a setup right now, but you might try to change the file extension to ".mts"

yourFile.ts -> yourFile.mts

then import this file using an await in some of your main functions (you probably won't be able to have a top-level await).

function main() {
     const openapiFetch = await import('openapi-fetch')
}

@drwpow
Copy link
Contributor

drwpow commented Sep 23, 2023

Hm so I forgot we added this, but we actually do ship a CJS build already. Modern versions of Node (14+) should just “work” and pick up on the right thing.

If not, it may be outdated build tooling, perhaps? Some old versions of webpack don’t follow the current spec (and honestly I haven’t used even webpack 5 so I can’t say what that does)

@marcomuser
Copy link
Contributor

marcomuser commented Sep 24, 2023

No, because my package is part of 50 others that would all have to move. It was impossible when I was using Jest, but I've switched to vitest which maybe would work better. But ESM just poops on everything it touches, unless it touches everything.

Are you using openapi-fetch in a Node.js Express server without bundler? So that TS is emitting the end-output? Your tsconfig states that your project's input files are ESM but since you do not have type: "module" in your package.json the output files will then be CJS (see TS Module Documentation). In TS 5.0, a new config flag verbatimModuleSyntax was introduced that requires imports/exports in the project's files to be written in the output format. I suppose if you enable that, ts would require you to write your imports/exports in CJS style, right? Could you maybe create a second package.json just for that single package which has type: "module" in order to enable ESM for input AND output?

@djMax
Copy link
Author

djMax commented Sep 24, 2023

Yes, that should be the case. Our service is NOT esm, thus not type: module, so we just want the CJS module to get selected. And it DOES sometimes, but not all the time. Like VSCode for example shows a pile of errors about type resolutions that don't happen at runtime.

@marcomuser
Copy link
Contributor

marcomuser commented Sep 25, 2023

Ok but then your config seems alright for what you want to achieve. One would think that ts would simply pick up the CJS build of openapi-fetch. According to the TypeScript Documentation, the package.json of openapi-fetch does seem to have two issues:

  • The "types" condition should always come first in "exports".
  • the CommonJS entrypoint and the ES module entrypoint each needs its own declaration file, even if the contents are the same between them.

This might potentially be the problem. Could you maybe edit the package.json of openapi-fetch in your node_modules accordingly and check if that would fix the problem?

@djMax
Copy link
Author

djMax commented Sep 29, 2023

Ok, so here's an example. I have updated openapi-fetch from 0.7.5 (patched to remove type: module) to 0.7.6 (also attempted to patch similarly but with no luck), and now it does this:

error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("openapi-fetch")' call instead.
  To convert this file to an ECMAScript module, change its file extension to '.mts', or add the field `"type": "module"` to '/my-project/package.json'.

I don't want to do either of these things, I just want it to import the cjs version. I have no idea why it won't. I can fix this by setting module to CommonJS and moduleResolution to Node in tsconfig, but this still seems like it should work as is.

@marcomuser
Copy link
Contributor

marcomuser commented Sep 30, 2023

Alright I had a coffee and created a tiny repo to reproduce your setup: https://github.com/marcomuser/cjsts/tree/upstream. Indeed, I ran into this exact same ts error you mentioned. In order to fix this, one has to adjust the openapi-fetch build step a tiny bit. The problem is that typescript looks for a index.d.cts file to describe the index.cjs CommonJS output. Currently, openapi-fetch outputs a single index.d.ts for both the index.cjs and index.js files. This works for ESM projects but not for CommonJS projects. I fixed this in my error reproduction repo by moving the CommonJS file in its own cjs subdirectory and duplicating plus renaming the index.d.ts file to index.d.cts. Finally, I changed the exports field in openapi-fetch's package.json to point to the correct location for both ESM and CJS. Check this commit: marcomuser/cjsts@ea1ae14. I tried this setup and it works for both CJS and ESM projects in typescript!

To fix this upstream, I think we need to adjust a) the package.json and b) make the build step output a cjs subdirectory and make sure that it includes both an index.cjs and an index.d.cts file.

@cbix
Copy link

cbix commented Dec 12, 2024

In my nextjs + typescript + jest usecase, I'd get this error:

Cannot find module 'swr-openapi' from [...]

and was able to fix it by setting
next.config.ts:

transpilePackages: ['swr-openapi'],

jest.config.ts: follow nextjs guide, then

moduleNameMapper: {
  '^swr-openapi$': '<rootDir>/node_modules/swr-openapi/dist/index.js',
},

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working openapi-fetch Relevant to the openapi-fetch library
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants