I see.
I’ve experienced that removing the file cache fixed some issues.
rm -r node_modules/.cache
Your Webpack seems a bit older than mine. What if you upgrade to the latest one?
This says that removing the browser
key from package.json fixed the ‘module has no exports’ error.
Here are suggestions by Gemini 2.5 Pro. Please ask your AI for further.
The issue you’re facing is a classic case of a configuration mismatch between how Webpack bundles modules and how a separate type-checking process resolves them.
The problem stems from the interaction between fork-ts-checker-webpack-plugin
, TypeScript path aliases (~/*
), and the modern moduleResolution: "Bundler"
setting in the tsconfig.json
.
Here’s the breakdown and the solution you can provide.
The Root Cause
When you add ForkTsCheckerWebpackPlugin
, you effectively split one job into two separate processes:
- Transpilation:
ts-loader
(now with transpileOnly: true
) quickly converts TypeScript to JavaScript for Webpack. It uses tsconfig-paths-webpack-plugin
to correctly resolve your ~/*
aliases during the bundling process.
- Type Checking:
ForkTsCheckerWebpackPlugin
runs TypeScript in a separate process to check for type errors. This process reads your tsconfig.json
directly, but it does not use tsconfig-paths-webpack-plugin
.
The conflict arises from the "moduleResolution": "Bundler"
setting. This modern setting tells TypeScript to defer to the bundler’s (Webpack’s) logic for resolving paths. While this works great for the transpilation step, the separate type-checking process doesn’t have the full context of the Webpack bundler, so it fails to correctly resolve the ~/*
path alias. This leads to the misleading "export not found (module has no exports)"
warning, which in this case really means “I couldn’t find the module at this path.”
The Solution
The most reliable solution is to change the module resolution strategy in tsconfig.json
to one that the standalone TypeScript process can handle without relying on a bundler.
Change this line in tsconfig.json
:
JSON// "moduleResolution": "Bundler", "moduleResolution": "node",
By changing "moduleResolution"
from "Bundler"
to "node"
, you are using TypeScript’s classic and highly compatible Node.js resolution algorithm. Both Webpack (with tsconfig-paths-webpack-plugin
) and the separate fork-ts-checker-webpack-plugin
process understand this algorithm perfectly, resolving the path aliases correctly in both contexts.
Updating Your Article
To prevent this issue for other readers, I recommend adding a small note to your article.
Suggested addition:
A Note on TypeScript Configuration
If you use TypeScript path aliases in your tsconfig.json
(e.g., "paths": { "~/*": ["./*"] }
), you may encounter "export not found"
warnings after adding ForkTsCheckerWebpackPlugin
.
This is often caused by the "moduleResolution": "Bundler"
setting. To resolve this, change it to "node"
in your tsconfig.json
:
"compilerOptions": {
"moduleResolution": "node"
}
}
This ensures that both Webpack and the separate type-checker can correctly resolve your module paths.
Sticking with moduleResolution: "Bundler"
is a common requirement, especially in modern setups. The issue can still be resolved by making the path alias configuration more explicit for both Webpack and the TypeScript checker.
Here are the alternative solutions.
Solution 1: Synchronize Webpack’s resolve.alias
(Recommended)
This is the most robust solution. Instead of relying on a plugin to translate tsconfig.json
paths for Webpack, you define the alias explicitly in both your webpack.config.js
and tsconfig.json
. This creates two clear, independent “sources of truth” that happen to align—one for the bundler and one for TypeScript’s tools (like the VS Code language server and the fork-ts-checker-webpack-plugin
).
1. Keep the paths
in tsconfig.json
This is crucial for your IDE and for fork-ts-checker-webpack-plugin
. This part doesn’t change.
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"~/*": ["./*"]
}
// ... other options
}
}
2. Add resolve.alias
to webpack.config.js
This tells Webpack directly how to handle the ~
alias. You will also need to import Node’s path
module at the top of your webpack config.
// webpack.config.js
const path = require('path'); // <-- Make sure you have this
// ... other imports
module.exports = ({ production }, { analyze, hmr, port, host }) => ({
// ...
resolve: {
extensions: ['.ts', '.js'],
modules: [path.resolve(__dirname, 'src'), 'node_modules'],
alias: {
'~': path.resolve(__dirname, 'src') // <-- Add this alias
}
},
plugins: [
// You no longer need TsconfigPathsPlugin if this is your only alias!
// new TsconfigPathsPlugin(), // <-- You can likely remove this
new ForkTsCheckerWebpackPlugin({
// ...
})
]
// ...
});
By defining the alias in both places, Webpack has its own clear rule, and the fork-ts-checker
has its own clear rule from the tsconfig.json
. Since they both point to the same location (src
), the inconsistency is resolved.
Solution 2: Adjust TypeScript Module Settings
The moduleResolution: "Bundler"
setting works in tandem with other flags that control module handling. While the user has esModuleInterop
enabled, another flag could help reduce ambiguity for the type checker.
Try adding verbatimModuleSyntax: true
to tsconfig.json
This newer TypeScript setting is the successor to importsNotUsedAsValues
. It enforces a stricter, more explicit style for import
statements, which can help static analysis tools like fork-ts-checker-webpack-plugin
better understand the dependency graph.
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "Bundler",
"verbatimModuleSyntax": true, // <-- Add this
"esModuleInterop": true
// ...
}
}
After adding this, you may need to fix some imports in your code, for example by explicitly adding import type { ... }
for types that are only used in type annotations. This cleanup often resolves underlying module ambiguity that trips up the checker.
Summary for the User
If you must keep "moduleResolution": "Bundler"
, the best approach is to synchronize your aliases by defining them in both webpack.config.js
’s resolve.alias
and tsconfig.json
’s paths
. This makes the configuration explicit for all tools in your chain and typically removes the need for tsconfig-paths-webpack-plugin
, simplifying your build.