-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Description
Which package?
Almost all of them, @formatjs/intl in this case specifically.
Describe the bug
I've stumbled upon a problem caused by the lack of exports map and improper ESM packaging by FormatJS packages.
I use import.meta.resolve (import-meta-resolve, actually, but it closely follows the Node.js sources) to find the path to an isolated @formatjs/intl dependency (in pnpm repository without shameful hoisting, I assume this is also a thing in regular npm now too).
Due to lack of exports map in any of the @formatjs/ (and intl-messageformat) packages are always imported in their CJS form by Node.js, only bundlers pick up the non-standard module property (and that also depends on the configuration).
So what I did in Nuxt is add auto-import from <whatever import.meta.resolve resolves @formatjs/intl to> for defineMessages, which caused any use of defineMessages to auto-import it from node_modules/.../@formatjs/intl/index.js (CJS module). It's should've probably been fine*, since Nuxt has some transpiling for require calls, but then it all crashes because ESM build of tslib (since Nuxt is ESM-first framework, it tries to maximise use of ESM) does not have default export which CJS require would expect.
* I say probably fine, but it probably wouldn't really be fine: I haven't checked, but I might assume that tree-shaking is completely broken, and if you import @formatjs anywhere in your .vue files, then it will most probably bring ESM build of @formatjs/intl, because unlike import.meta.resolve, an import by package name will be resolved by the bundler, which will take the non-standard module property into account during the build.
To Reproduce
Codesandbox URL
Unfortunately Codesandbox does not work well for me, so I created two StackBlitz sandboxes instead:
-
First probes some of the FormatJS packages for ESM support, as well as uses Rollup with ‘conventional’ build configuration to produce an ESM bundle, which you will not be able to run because it will spill out CJS code in there. https://stackblitz.com/edit/node-rzudtr?file=package.json&view=editor
-
Second is the Nuxt build that reproduces the probem that I stumbled upon. https://stackblitz.com/edit/nuxt-starter-dggqdk?file=modules/intl.ts&view=editor
Reproducible Steps/Repo
Steps to reproduce the behavior:
- Do
import * as formatJSIntl from "@formatjs/intl"
Expected behavior
package.json would declare proper conditional exports, allowing Node.js to properly determine where the ESM files are located, and import the ESM build. import.meta.resolve would resolve to an ESM module.
Actual behaviour
Due to lack of conditional exports, Node.js resolves to importing the CJS module specified in main field.
Screenshots
× Not applicable.
Environment
Node.js v18.14.0.
Additional context
Relevant issues:
- Can’t use
@formatjs/fast-memoizein isomorphic code #3999 caused by the same problem, but has expectation vs reality problem where ESM and CJS modules differ in implementation! - Add
exportsmap or"type": "module"#3878 previously closed because it had no real reproduction and deemed ‘non-issue’.
Workaround
For now you can import and resolve @formatjs/intl/lib/index.js instead.
Personal and irrelevant message of appreciation
Thank you for all the hard work on maintaining these packages! ❤️ I have been working on my package vintl for one of the projects I really appreciate. VIntl tries to bring first-class support for Intl in Vue with all the cool jazz like locale changing and such, and it is fully powered by your packages! So big probs for making it all possible. I am currently making a Nuxt module for it, and that's where the issue is coming from actually :D Hopefully I'll be able to finish it all one day