next.js
next.js copied to clipboard
fix: make typescript imports work with nodenext module resolution
What?
A fix for nodenext
module resolution.
https://www.typescriptlang.org/tsconfig#moduleResolution
Why?
- fixes https://github.com/vercel/next.js/issues/46078
How?
Adding exports
.
Looks like this is failing because it cannot resolve dist/compiled/*
paths.
I am not sure how to properly fix this.
"./dist/compiled/*": {
"require": "./dist/compiled/*"
},
This fails because it cannot find /dist/compiled/chalk
The file we want is ./index.js
. However, if we change export to:
"./dist/compiled/*": {
"require": "./dist/compiled/*/index.js"
},
then other dependencies start to fail:
dist/compiled/arg/index.js/index.js
etc.
Long story short, I tried patching them one by one...
"exports": {
".": {
"import": "./index.js",
"types": "./index.d.ts"
},
"./amp": {
"import": "./amp.js",
"types": "./amp.d.ts"
},
"./app": {
"import": "./app.js",
"types": "./app.d.ts"
},
"./babel": {
"import": "./babel.js",
"types": "./babel.d.ts"
},
"./cache": {
"import": "./cache.js",
"types": "./cache.d.ts"
},
"./client": {
"import": "./client.js",
"types": "./client.d.ts"
},
"./config": {
"import": "./config.js",
"types": "./config.d.ts"
},
"./dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWebpackPlugin": {
"require": "./dist/compiled/@next/react-refresh-utils/dist/ReactRefreshWebpackPlugin.js"
},
"./package.json": {
"require": "./package.json"
},
"./dist/compiled/@next/react-refresh-utils/dist/runtime": {
"require": "./dist/compiled/@next/react-refresh-utils/dist/runtime.js"
},
"./dist/pages/*": {
"require": "./dist/pages/*.js"
},
"./dist/compiled/devalue": {
"require": "./dist/compiled/devalue/devalue.umd.js"
},
"./dist/compiled/regenerator-runtime": {
"require": "./dist/compiled/regenerator-runtime/runtime.js"
},
"./dist/compiled/unistore": {
"require": "./dist/compiled/unistore/unistore.js"
},
"./dist/next-config-validate.js": {
"require": "./dist/next-config-validate.js"
},
"./dist/compiled/@babel/runtime/package.json": {
"require": "./dist/compiled/@babel/runtime/package.json"
},
"./dist/compiled/webpack/*": {
"require": "./dist/compiled/webpack/*.js"
},
"./dist/compiled/acorn": {
"require": "./dist/compiled/acorn/acorn.js"
},
"./dist/compiled/neo-async": {
"require": "./dist/compiled/neo-async/async.js"
},
"./dist/compiled/webpack/webpack": {
"require": "./dist/compiled/webpack/webpack.js"
},
"./dist/compiled/loader-runner": {
"require": "./dist/compiled/loader-runner/LoaderRunner.js"
},
"./dist/compiled/@edge-runtime/primitives/crypto": {
"require": "./dist/compiled/@edge-runtime/primitives/crypto.js"
},
"./dist/compiled/stacktrace-parser": {
"require": "./dist/compiled/stacktrace-parser/stack-trace-parser.cjs.js"
},
"./dist/compiled/@next/react-dev-overlay/dist/middleware": {
"require": "./dist/compiled/@next/react-dev-overlay/dist/middleware.js"
},
"./dist/compiled/@edge-runtime/primitives/abort-controller": {
"require": "./dist/compiled/@edge-runtime/primitives/abort-controller.js"
},
"./dist/compiled/watchpack": {
"require": "./dist/compiled/watchpack/watchpack.js"
},
"./dist/compiled/arg/index.js": {
"require": "./dist/compiled/arg/index.js"
},
"./dist/compiled/*": {
"require": "./dist/compiled/*/index.js"
},
"./dist/client/components/noop-head": {
"require": "./dist/client/components/noop-head.js"
},
"./dist/shared/lib/*": {
"require": "./dist/shared/lib/*.js"
},
"./dist/compiled/sass-loader": {
"require": "./dist/compiled/sass-loader/cjs.js"
},
"./dist/compiled/punycode": {
"require": "./dist/compiled/punycode/punycode.js"
},
"./dist/compiled/string_decoder": {
"require": "./dist/compiled/string_decoder/string_decoder.js"
},
"./dist/build/webpack/config/blocks/css": {
"require": "./dist/build/webpack/config/blocks/css/index.js"
},
"./font/google/target.css": {
"require": "./font/google/target.css"
},
"./font/local/target.css": {
"require": "./font/local/target.css"
},
"./dist/client/*": {
"require": "./dist/client/*.js"
},
"./dist/compiled/constants-browserify": {
"require": "./dist/compiled/constants-browserify/constants.json"
},
"./dist/compiled/os-browserify": {
"require": "./dist/compiled/os-browserify/browser.js"
},
"./dist/compiled/@next/font/*/loader": {
"require": "./dist/compiled/@next/font/*/loader.js"
},
"./dist/compiled/@next/react-refresh-utils/dist/loader": {
"require": "./dist/compiled/@next/react-refresh-utils/dist/loader.js"
},
"./dist/compiled/assert": {
"require": "./dist/compiled/assert/assert.js"
},
"./dynamic": {
"require": "./dynamic.js"
},
"./document": {
"import": "./document.js",
"types": "./document.d.ts"
},
"./error": {
"import": "./error.js",
"types": "./error.d.ts"
},
"./font": {
"import": "./font/index.js",
"types": "./font/index.d.ts"
},
"./font/google": {
"import": "./font/google.js",
"types": "./font/google.d.ts"
},
"./font/local": {
"import": "./font/local.js",
"types": "./font/local.d.ts"
},
"./head": {
"require": "./head.js",
"import": "./head.js",
"types": "./head.d.ts"
},
"./headers": {
"import": "./headers.js",
"types": "./headers.d.ts"
},
"./image": {
"import": "./image.js",
"types": "./image.d.ts"
},
"./link": {
"require": "./link.js",
"types": "./link.d.ts"
},
"./navigation": {
"import": "./navigation.js",
"types": "./navigation.d.ts"
},
"./router": {
"import": "./router.js",
"types": "./router.d.ts"
},
"./script": {
"import": "./script.js",
"types": "./script.d.ts"
},
"./server": {
"import": "./server.js",
"types": "./server.d.ts"
},
"./web-vitals": {
"import": "./web-vitals.js",
"types": "./web-vitals.d.ts"
}
},
Long story short, this approach is not going to work.
Pretty big problem as it makes next.js pretty much unusable with default TypeScript v5 settings.
Just in case, leaving this PR open to drive attention. Not expecting work to continue, as it seems that this approach will not work. Unless there is some underlying work that can be done with how the loaders function.
Why so complicated? Something as simple as this should work for the majority of the public API:
"exports": {
".": "./index.js",
"./*": "./*.js",
"./font/*": "./font/*/index.js",
"./dist/*": "./dist/*/index.js"
},
Why so complicated? Something as simple as this should work for the majority of the public API:
Unfortunately these wildcards are too aggressive,
- they will change
dist/compiled/arg/index.js
todist/compiled/arg/index.js/index.js
- they will change
next/link.js
tonext/link.js.js
We can fix these two issues with:
+ "./dist/*.js" : "./dist/*.js",
"./dist/*" : "./dist/*/index.js",
But we still fail to resolve dist/compiled/devalue
to dist/compiled/devalue/devalue.umd.js
, dist/compiled/regenerator-runtime
to dist/compiled/regenerator-runtime/runtime.js
, etc. Now it seems the issue here is that if we use "exports"
to resolve one name, we must use it to resolve ALL names. So we can't resolve some names through "exports"
and everything else through the default node resolution algorithm.
There are a lot of these packages: find . -name "package.json" -exec jq -r 'if .main != "index.js" then .main else empty end' {} + | less
It looks like "exports"
is not expressive enough to handle such vendored packages unfortunately.
This wont work, it is not nearly enough to make things work I am afraid. But good for driving discussion. See https://github.com/vercel/next.js/issues/46078#issuecomment-2134508560