visx: ERR_REQUIRE_ESM in Next.js app
Import @visx/xycharts
in a Next.js app returns the following error:
Error: require() of ES Module ~/app/node_modules/d3-scale/src/index.js from
~/app/node_modules/@visx/scale/lib/scales/band.js not supported.
Instead change the require of index.js in ~/app/node_modules/@visx/scale/lib/scales/band.js
to a dynamic import() which is available in all CommonJS modules.
Used Node version 18.13.0
, pacakge.json
dependecies:
"dependencies": {
...
"@visx/xychart": "^3.0.0",
"next": "13.0.7",
"react": "18.2.0",
"react-dom": "18.2.0",
...
},
Tested importing @visx/xycharts
in a React app. Node version 18.13.0
, package.json
dependencies:
...
"@visx/xychart": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
...
No ERR_REQUIRE_ESM
error when importing in a React app. Any idea how this error could be solved for a Next.js app? Maybe a configuration in nextjs.config.js
?
About this issue
- Original URL
- State: closed
- Created a year ago
- Reactions: 29
- Comments: 44 (5 by maintainers)
Commits related to this issue
- downgrade visx packages due to https://github.com/airbnb/visx/issues/1637 — committed to MagneticWatermelon/grana by MagneticWatermelon a year ago
- chore: add visx and patch for esm workaround https://github.com/airbnb/visx/issues/1637#issuecomment-1538160630 — committed to marcelblijleven/beanstats by marcelblijleven a year ago
I’ll take a shot at fixing this over the next week or so
also looking for a solution for this. solved it temporarily by importing the component that uses @visx/xychart like so:
Sorry for the delay here. Trying to make the right decision and we are leaning toward creating transpiled versions of the
esm
-onlyd3-*
dependencies that we have (similar to victory chart’s vendor packages) so that we can continue to shipcsj
+esm
versions of allvisx
packages.We also put it on vercel’s radar https://twitter.com/timneutkens/status/1653849232908886019
No concrete timeline but hoping to do this as soon as we can. If someone from the community is super motivated to try it we’re happy to discuss and provide PR review.
Hey, as promised I had a look into this.
As @ljharb pointed out ESM adoption is quite the problem. The reason it doesn’t just work in Next.js is that the package seems to have been published at a time before Node.js had built-in ESM. Because of that it’s using
main
andmodule
to differentiate CommonJS vs ESM. However, Node.js ESM is much stricter than what we had with bundlers.E.g.
node -e "import('@visx/scale')"
andnode -e "require('@visx/scale')"
doesn’t run currently.Which is why this problem happens, the package is not compatible with Node.js’s stricter ESM and requires bundling in order to work.
My recommendation would be to leverage conditional
exports
: https://nodejs.org/api/packages.html#conditional-exports. That way Next.js would correctly handle the package usingimport()
instead ofrequire()
.Still this means the package has to change to be compatible with the rules like explicit extensions and such.
–
(not recommended)
I managed to get it working adding the follow:
Add this
next.config.js
:Change
node_modules/@visx/scale/package.json
to have"type": "module"
.Here’s a sandbox with the changes applied:
Thanks for the great thoughts, Jordan 🙏 I agree going
ESM
-only would be the least ideal. Here are my thoughts on the other optionsd3-*
are so fundamental to@visx/*
I don’t think this is feasible unfortunatelyd3
made theESM
-only decision quite a while ago and there have since been several security fixes I don’t think they’ll backwards-patch (the entire reason we upgraded to theseESM
-only packages and released a major version bump was to fix a security vulnerability). One possible option we could consider here is usingvictory-vendor/d3-*
packages. They faced a similar dllemma and decided to transpile & release their ownCJS
version ofd3-*
packages. This could be viable, my only concern is possible TS clashes and this wouldn’t work for any future lib we depend on that wentESM
-onlynext.js
for our demo site, so we could test the feasibility ourselves with this.I am very limited on bandwidth the next couple of weeks, so if anyone would like to explore #3 I’d be happy to review/collaborate on a branch.
alright I think this is fixed folks. try
3.2.0
and let me know if you have issues, see https://github.com/airbnb/visx/pull/1716 where we introduced@visx/vendor
for more details.3.2.0
works without special transpiling logic in ournext@11
demo app, the package contents look good, and the sandbox works. I didn’t do another major version bump sincecsj
was effectively broken in3.0.x
and3.1.x
anyway.for anyone stumbling into this issue, here is a step-by-step of what worked for me on next13 using the pages router:
visx
:next.config.js
:patch-package
. you can use this to make changes to dependencies and have those automatically applied after installation:postinstall
script to your package.json. the package manager will run this after every install and apply the changes you’ll make in the following steps.node_modules/@visx
:@visx/
submodule that gives you the ERR_REQUIRE_ESM error, replace all instances ofrequire("d3
withimport("d3
(notice the space in the beginning of these strings) in the submodule’s lib folder. I tried doing this everywhere within the @visx/ folder but that broke, so I decided to just do it selectively, everywhere I run into an actual issue For example:@visx/scale
lib/
subfolder with importspatch-package
for every single visx submodule. this will save the changes you made to apatches/
folder in your repository. you need to set the exclude flag to some dummy value, otherwisepatch-package
ignores the changes made inpackage.json
:Summary of the steps:
next.config.js
to transpile relevant packages@visx/*
submodules package.json by addingexports
require()
withimport()
I’m sure parts of this could be done smarter, but this worked for me so I thought I’d share
@williaster yes - esm-only dependencies are user-hostile, because they ensure that everything using them synchronously must also be native ESM, and thus you can’t ship CJS.
Your choices (in order of my recommendation) are:
import()
- this still creates potential problems for consumers who use bundlers, but hopefully minor onesThere’s simply no value in going ESM-only - native ESM in node still lacks most of the tooling and ecosystem support that CJS has - and I strongly suggest authoring in ESM but transpiling to CJS for the foreseeable future.
To help anyone that stumbles upon this with Remix, I configured my
remix.config.js
in the following way to have it work.Hopefully, it helps those users and gleans something for Next users.
I found good blog post regarding this Publish ESM and CJS in a single package
@williaster vitest should support esm modules natively, so is no need for transpiling.
I think that the issue is with invalid
package.json
. For package providing both commonjs and esm -modules there should beexports
-field.if I add following to
@visx/scale
-package file. Import from esm is working correctly:Hey @kyhorne there are two issues I see with your
package.json
@visx/*
, can you try^3.2.0
(some packages you use may only have3.0.0
versions, but those with3.2.0
versions published should be used)?d3-scale
andd3-array
in yourdependencies
which are ESM-only.next@13
might be able to handle that okay, but if you aren’t importing from them directly in your project, you might consider removing them.^considering another validation in the wild, I’m going to go ahead and close this. but post any issues you have/we can re-open if necessary.
Worked for me with next@13
also thank you very much @timneutkens for digging into this and giving some clarity on our esm/cjs module structure, will incorporate this as part of the fix. really appreciate your time! ❤️
@Jules-7 I think the problem comes from @visx/scale package not from d3-scale. Try to add @visx/scale to your
transpilePackages
.The problem is that
~/app/node_modules/@visx/scale/lib/scales/band.js
is from cjs and it’s trying to use d3 ES Module. There is ESM version of @visx/scale available in~/node_modules/@visx/scale/esm/scales/band.js
. When I made thatexports
change, vitest was correctly able to use ESM version.I think that client side code (e.g. React) is using ESM version of @visx/scale and therefore there is no error. Problem with next.js could be that when next.js is doing server rendering it’s using node which is using CJS version. This is of course all my speculation so take it with grain of salt 🧂.
Awesome work @williaster, thank you! 🚀
@williaster for back compat, yes, you’d keep a top-level “types” key alongside the “exports” “types” key.
The best strategy imo is for ESM files to always be
.mjs
, for CJS files to always be.js
, and to not set the “type” field at all.I don’t know for sure how TS works with
d.mjs
vsd.cjs
but it kind of seems like types would always stay as.d.ts
, since nothing uses them at runtime?Okay, so the problem was that I totally forgot to include the
"use client"
string at the top of the component that uses Visx utilities. Such a rookie mistake 🙈I’m on Next.js 13.2.0 within the App router and I was also able to fix this issue by setting
esmExternals
toloose
within the next.config.js.Found this fix within their docs here
Thanks @alexmalev , I have been looking for a solution for this as well, and your fix is the only thing I found that did the trick!
Hi @williaster, thank you for your message.
I tried the following in
next.config.js
fornext@13.1
to no avail:In
next@13.1
module transpilation is built-in. Still got the same error.Had to switch to
@visx/xychart v2.18.0
to get it working.