HTML bundler v4 makes ESLint overflow with circular references
Current behaviour
After upgrading to v4, ESLint fails with a RangeError: Maximum call stack size exceeded whenever it loads a Webpack config that includes this plugin. This is when using the eslint-plugin-import's Webpack resolver.
Expected behaviour
This plugin should not introduce circular structure that break tools attempting to serialize/hash the Webpack config. ESLint (and its major plugins) should be able to consume the config without crashing.
Reproduction
High level reproduction steps:
- Create a Webpack config including this plugin.
- Install and configure ESLint with
eslint-plugin-import. - Put
import/resolverto the Webpack config including this plugin:
import webpackConfig from './webpack.config.js';
export default {
...
{
files: [
'**/*.{js,jsx}',
],
ignores: ['./node_modules/**/*'],
settings: {
'import/resolver': {
webpack: {
config: webpackConfig,
},
},
},
},
};
- Run ESLint and witness the process overflow with
Maximum call stack size exceeded inside eslint-module-utils/hash.js.
I created a small repository reproducing this issue. Simply:
- Run
npm run dev-serverto verify that this plugin is building properly. - Run
npm run lintto replicate the range error
Environment
- OS: Windows 11
- Node.js: v20, v22, v24
- Webpack: v5.101.3
- Plugin: v4.21.1
Additional context
Removing this plugin from the Webpack config resolves any linting errors. This means that in my other projects, I can simply delete the plugin from the config file at runtime to avoid the issue. However, I should not have to do such a workaround.
Appreciation for the useful project
- [ ] After the problem is resolved, do not forget to give a star ⭐
I'm not affiliated with this project, but this sounds like a bug in ESLint.
@jakezatecky
Thanks for the issue, I will research the problem and try to find a solution. Probably ESlint can't handle some tricky code in the plugin.
@jakezatecky
The plugin itself works fine. The problem is in your eslint.config.js setup.
Problem 1
"Maximum call stack size exceeded" issue
Solution Provide the path to Webpack config file instead of imported object.
Problem 2
ESLint: Unable to resolve path to module 'html-bundler-webpack-plugin'. (import/no-unresolved)
Solution
Use native Node resolution for webpack.config.js file to avoid self-referencing Webpack resolver.
Replace your config with this one, and it will work:
import js from '@eslint/js';
import importPlugin from 'eslint-plugin-import';
import globals from 'globals';
- import webpackConfig from './webpack.config.js';
export default [
js.configs.recommended,
importPlugin.flatConfigs.recommended,
{
files: [
'**/*.{js,jsx}',
],
ignores: ['./node_modules/**/*'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: globals.browser,
},
settings: {
// Account for webpack.resolve.module imports
'import/resolver': {
webpack: {
- config: webpackConfig,
+ config: 'webpack.config.js',
},
},
},
},
+ {
+ files: ['webpack.config.js'],
+ settings: {
+ 'import/resolver': { node: true }, // <= add Node resolution for webpack.config.js
+ },
+ },
];
Now, the npm run lint cmd works fine.
Thank you for the quick reply. Sorry that it took this long to respond.
Problem 2 was entirely on me for not adequately recreating the minimal test case conditions. We normally specify node: { extensions: ['.js'] } for import/resolver in our other projects. I apologize.
Problem 1
For Problem 1, setting the config to your suggestion does not appear to resolve the issue on my end. It results in a different issue, due to the use of require to resolve webpack.config.js (instead of ESM's import). The following would appear for every file linted:
1:1 error Resolve error: Error [ERR_REQUIRE_ESM]: require() of ES Module
Even if the suggestion worked, pointing to the Webpack config file would limit our use of more dynamic configurations. This is not a major issue, but sometimes we might like to dynamically manipulate Webpack depending on the environment.
At the moment, we can sidestep Problem 1 entirely simply by remove this plugin from the final Webpack config object, since it is not really needed for ESLint:
import webpackConfig from './webpack.config.js';
...
delete webpackConfig.plugins;
Of course, this is not exactly clean or ideal.
@jakezatecky
This is not an issue in the plugin, but a limitation in eslint-plugin-import's Webpack resolver. The circular reference problem occurs because the resolver performs a deep recursive hash of the entire Webpack config object, including plugin instances, which leads to a stack overflow. Since this is outside the plugin's control, unfortunately it can't be fixed here.
Therefore, I will close this issue.