You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`@vitest/eslint-plugin`cannot be executed with tsx and jiti internally
116
+
`@vitest/eslint-plugin`fails with Invalid URL error when ESLint configs are loaded through `configureEslintPlugin()`. The issue occurs because tsx intercepts nested imports from files loaded by jiti, transforming `@vitest/eslint-plugin` before jiti can handle it.
123
117
124
118
**Description** (required)
125
-
The Vitest ESLint plugin is used for linting Vitest test files in our codebase. When running ESLint with tsx module loading (as used in `nx code-pushup`), the plugin fails due to URL parsing issues in its CommonJS distribution. When tsx transforms `@vitest/eslint-plugin`, `import.meta.url`becomes `'about:blank'`, causing `new URL('index.cjs', 'about:blank')`to fail with Invalid URL error.
119
+
The ESLint plugin (`configureEslintPlugin()`) loads ESLint configuration files using `loadConfigByPath()`. When ESLint configs (like `eslint.config.js`) import `@code-pushup/eslint-config/vitest.js`, which in turn imports `@vitest/eslint-plugin`, tsx transforms the CommonJS distribution. This causes `import.meta.url`to become `'about:blank'`, leading to `new URL('index.cjs', 'about:blank')`failing with Invalid URL error.
[packages/utils/src/lib/import-module.ts](packages/utils/src/lib/import-module.ts#L19-L23) - `JITI_NATIVE_MODULES` list that includes `@vitest/eslint-plugin`
161
+
**Status** (required)
162
+
Resolved - Fixed by temporarily removing `--import tsx` from `NODE_OPTIONS` when loading ESLint configs, allowing jiti to handle all module loading without tsx interference.
[packages/plugin-eslint/src/lib/meta/versions/flat.ts](packages/plugin-eslint/src/lib/meta/versions/flat.ts#L64-L73) - `loadConfigByPath` function that delegates to child process loading
159
166
160
-
[packages/plugin-eslint/src/lib/meta/versions/flat.ts](packages/plugin-eslint/src/lib/meta/versions/flat.ts#L62-L69) - `loadConfigByPath` function that loads ESLint config files
167
+
[packages/plugin-eslint/src/lib/meta/versions/flat.ts](packages/plugin-eslint/src/lib/meta/versions/flat.ts#L75-L210) - `loadConfigInChildProcess` function that spawns a child process without tsx
// Remove --import tsx if present, preserving other options
177
+
const nodeOptions =
178
+
originalNodeOptions
179
+
?.split(/\s+/)
180
+
.filter(opt=> {
181
+
if (opt.includes('--import')) {
182
+
return!opt.includes('tsx');
183
+
}
184
+
returntrue;
185
+
})
186
+
.join('') ||undefined;
187
+
188
+
// Temporarily set modified NODE_OPTIONS if it changed
189
+
if (nodeOptions!==originalNodeOptions) {
190
+
if (nodeOptions) {
191
+
process.env.NODE_OPTIONS=nodeOptions;
192
+
} else {
193
+
deleteprocess.env.NODE_OPTIONS;
194
+
}
195
+
}
196
+
197
+
try {
198
+
// Load config - jiti will handle imports without tsx interference
199
+
const mod =awaitimportModule<FlatConfig| { default:FlatConfig }>({
200
+
filepath: configPath,
201
+
});
202
+
return'default'inmod?mod.default:mod;
203
+
} finally {
204
+
// Always restore original NODE_OPTIONS
205
+
if (originalNodeOptions!==undefined) {
206
+
process.env.NODE_OPTIONS=originalNodeOptions;
207
+
} else {
208
+
deleteprocess.env.NODE_OPTIONS;
209
+
}
210
+
}
171
211
}
172
212
```
173
213
174
-
**Preliminary fix** (optional)
175
-
[tools/jiti/vitest/fix-vitest.ts](tools/jiti/vitest/fix-vitest.ts) - Fix implementation using jiti's `importModule` instead of dynamic `import()` to ensure `@vitest/eslint-plugin` is loaded natively without transformation.
214
+
[code-pushup.config.ts](code-pushup.config.ts#L20) - `configureEslintPlugin()` call that triggers ESLint config loading
215
+
216
+
[eslint.config.js](eslint.config.js#L8) - ESLint config that imports `@code-pushup/eslint-config/vitest.js`
217
+
218
+
[packages/utils/src/lib/import-module.ts](packages/utils/src/lib/import-module.ts#L19-L23) - `JITI_NATIVE_MODULES` list that includes `@vitest/eslint-plugin` and `@code-pushup/eslint-config`
219
+
220
+
**Solution** (required)
221
+
The fix loads ESLint configs in a child process without tsx to prevent tsx from intercepting nested imports. This allows jiti to handle all module loading and respect its `nativeModules` configuration.
222
+
223
+
**How it works:**
224
+
225
+
1. Create a temporary loader script that uses jiti to load the ESLint config
226
+
2. Spawn a child Node.js process with `NODE_OPTIONS` that excludes `--import tsx`
227
+
3. The child process loads the config using jiti, which handles all imports without tsx interference
228
+
4. Serialize the config (handling circular references) and write it to a temporary file
229
+
5. Read the serialized config from the parent process and return it
230
+
6. Clean up temporary files
231
+
232
+
**Why this works:**
233
+
234
+
- Child processes don't inherit already-registered loaders from the parent process
235
+
- By removing `--import tsx` from the child's `NODE_OPTIONS`, tsx is never registered as a loader
236
+
- jiti can then handle all module loading and respect its `nativeModules` configuration
237
+
-`@vitest/eslint-plugin` is loaded natively without transformation, preserving `import.meta.url`
238
+
- The parent process continues to use tsx normally for other operations
**Expected result:** The original `TypeError: Invalid URL` error with `@vitest/eslint-plugin` should be resolved. The command should no longer fail with the tsx transformation error.
248
+
249
+
**Note on optional dependencies:** If your ESLint config references optional peer dependencies (like `eslint-plugin-jsx-a11y`) that aren't installed, the child process may fail with a "Cannot find module" error. This is expected behavior and separate from the original JITI/tsx issue. To resolve:
250
+
251
+
1. Install the missing optional dependency: `npm install eslint-plugin-jsx-a11y`
252
+
2. Or remove the reference to the optional plugin from your ESLint config
253
+
254
+
The reproduction script should also run without the original Invalid URL error:
- The child process approach requires all ESLint config dependencies to be installed. Optional peer dependencies that are referenced in the config must be installed, even if they're marked as optional in package.json.
263
+
- This is a limitation of loading ESLint configs programmatically - ESLint itself may handle missing optional dependencies more gracefully during actual linting, but config parsing requires all referenced modules to be available.
264
+
- When a missing optional dependency is encountered, the error message now provides clear guidance on which package needs to be installed and how to install it.
265
+
266
+
**Error handling:**
267
+
The implementation includes improved error messages for missing ESLint dependencies. When a module like `eslint-plugin-jsx-a11y` is missing, the error will clearly indicate:
268
+
269
+
- Which dependency is missing
270
+
- That it's referenced in the ESLint config but not installed
271
+
- How to fix it (e.g., `npm install eslint-plugin-jsx-a11y`)
176
272
177
-
[packages/plugin-eslint/src/lib/meta/versions/flat.ts](packages/plugin-eslint/src/lib/meta/versions/flat.ts#L62-L69) - Updated `loadConfigByPath` to use `importModule` instead of dynamic import.
273
+
This makes it easier to diagnose and resolve missing optional dependency issues.
0 commit comments