microbundle icon indicating copy to clipboard operation
microbundle copied to clipboard

Possible issue with missing type declaration files for ESM exports

Open aholachek opened this issue 1 year ago • 15 comments

Hi, this is possibly user error, but a recent routine release of my lib react-flip-toolkit broke type declarations-- a user filed an issue here and when I view the analysis on this page I see that types are not found.

After digging around for a bit, it seems possible based on my reading of this explanation that we need to package an index.d.mts alongside the index.d.ts automatically generated by microbundle.

aholachek avatar Jul 06 '24 15:07 aholachek

a user filed an issue here and when I view the analysis on this page I see that types are not found.

This honestly seems like a TS bug as you don't seem to use "exports" at all. I don't know why TS wouldn't resolve then to the top-level "types" entry.

we need to package an index.d.mts alongside the index.d.ts automatically generated by microbundle.

We don't support this at the moment, see #1030

rschristian avatar Jul 06 '24 17:07 rschristian

Sorry for the lack of clarity in my question--I ended up removing exports in a commit this morning because that was the only way I could solve the problem.

Until today my exports field in my package.json looked like this.

aholachek avatar Jul 06 '24 17:07 aholachek

Also be honest I am very flummoxed about why this issue just popped up just now, since I haven't changed anything major about my microbundle config since I added the modern export a bit more than a year ago!

aholachek avatar Jul 06 '24 17:07 aholachek

Ah, that makes sense. Thanks.

Do you want to supports CJS through "exports"? If not, we can handle that:

"exports": {
  "types": "./lib/index.d.mts",
  "default": "./lib/index.modern.mjs"
}

Keep in mind that "types" needs to come first.

why this issue just popped up just now

This happens when someone sets "moduleResolution": "node16" in their tsconfig.json, it changes the resolution process.

rschristian avatar Jul 06 '24 17:07 rschristian

Oh wow gotcha, thanks. And thanks so much for the quick response here.

Do you want to supports CJS through "exports"?

I am not sure, I think I want to focus on just maintaining current functionality, which seems like does not support CJS, right? I think I need to spend more time learning about the different module options.

aholachek avatar Jul 06 '24 17:07 aholachek

I will try the solution you outlined and report back! I think I must have had them out of order before.

aholachek avatar Jul 06 '24 17:07 aholachek

I am not sure, I think I want to focus on just maintaining current functionality, which seems like does not support CJS, right?

Yep, this is ESM-only (through "exports", anyhow. You do still have a CJS entry via "main"):

"exports": "./lib/index.modern.mjs",

And this is then the correct TS representation:

"exports": {
  "types": "./lib/index.d.mts",
  "default": "./lib/index.modern.mjs"
}

As TS needs to know where to find the types for this.

Please note I made a typo in the comment above, I didn't see .mjs. You'll need to do a post-build copy to .d.mts as TS made a rather frustrating change.

rschristian avatar Jul 06 '24 17:07 rschristian

Following your instructions seemed to create another error in the are the types wrong website, you can see the new report here.

And here's the newly published package.json with your instructions integrated.

Thanks for your help so far, this is not super urgent because I can just stick with the removed modern build...

aholachek avatar Jul 06 '24 20:07 aholachek

Also I should say just for fun I tried "moduleResolution": "bundler", in my tsconfig.json, but that did not seem to help either.

aholachek avatar Jul 06 '24 20:07 aholachek

Oh shoot, try switching "default" over to "import" -- I believe "default" is meant to match the module type.

rschristian avatar Jul 06 '24 20:07 rschristian

I unfortunately still see the same errors as before on arethetypeswrong

aholachek avatar Jul 06 '24 22:07 aholachek

(Last guess, I promise, feel free to ignore until I can check myself): It might be the (somewhat wonky) need to use file extensions even in the .d.ts. Here's what exists now:

import * as utilities from './utilities';
import * as constants from './constants';
export { default as Flipper } from './Flipper';
export { default as getFlippedElementPositionsBeforeUpdate } from './flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate';
export * from './flip';
export { utilities, constants };
export { default as spring } from './Spring';

I think TS's ESM support requires that turn into this:

import * as utilities from './utilities.js';
import * as constants from './constants.js';
export { default as Flipper } from './Flipper.js';
export { default as getFlippedElementPositionsBeforeUpdate } from './flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate.js';
export * from './flip.js';
export { utilities, constants };
export { default as spring } from './Spring.js';

Even if some of those are .d.ts files, you'll need to use .js in the import specifier. It's a bit weird.

Sorry I haven't been able to get you a fix, I'll be able to clone & test locally in an hour or so.

rschristian avatar Jul 06 '24 22:07 rschristian

Alright, it's indeed that.

ESM requires full file paths (i.e., utilities/index.js instead of utilities) which TS mirrors with "moduleResolution": "node16", and TS, in their desire to be purely additive, has decided they will not correct nor alter paths in any way. Your types now have to act like "real ESM", if you will.

So what you need to do is "correct" your entry point to look like this:

// src/index.ts
import * as utilities from './utilities/index.js'
import * as constants from './constants.js'
export { default as Flipper } from './Flipper.js'
export { default as getFlippedElementPositionsBeforeUpdate } from './flip/getFlippedElementPositions/getFlippedElementPositionsBeforeUpdate/index.js'
export * from './flip/index.js'
export { utilities, constants }
export { default as spring } from './Spring/index.js'

And do the same across your entire project. Yes, this means you're referring to (say) ./utilities/index.ts with ./utilities/index.js in your source (and your built types will then also use .js to then refer to .d.ts files), which is a bit silly, but that's the way the TS team wants people to do things. See mega thread of confusion and questioning.

We do use TS itself to output types, which unfortunately means we can't correct these paths for you.

Sorry, I know this is a pain (to put it lightly).

rschristian avatar Jul 06 '24 22:07 rschristian

Thanks for digging into this and for linking the thread. I will look at your pr--it feels like such a hack that we have to add file extensions like that, I never would have figured this out in a million years. I really appreciate your time looking into this today.

aholachek avatar Jul 07 '24 02:07 aholachek

it feels like such a hack that we have to add file extensions like that

I 100% agree.

There's a couple things in newer TS versions that help somewhat, namely, allowing you to write .ts & .tsx in your import specifiers, which was previously disallowed, though TS itself still won't fix them on build (need other tooling to do that). I'd love to see a "please just handle this for me option" but doesn't seem like one's landed yet. There are some other build tools that attempt it, I can't speak for any of them, but it's very possible something other than Microbundle might address your circumstances better.

Sorry for the long list of things to check & that the solution, as far as I understand it, is a bit miserable.

rschristian avatar Jul 07 '24 03:07 rschristian