Adopt eslint-plugin-import-x to fix resolver loading (/typescript + /legacy)#49
Merged
Conversation
786d0de to
1ed443a
Compare
1ed443a to
4ec52d7
Compare
Vanilla eslint-plugin-import loads resolvers by name relative to each linted
file, so the bundled eslint-import-resolver-typescript wasn't found when npm
didn't hoist it to the consumer top level: /typescript fell back to the
`typescript` compiler ("invalid interface" — 55 false errors in a pilot), and
/legacy's import/no-unresolved flagged valid package subpaths (e.g. the
package's own /legacy entry) the default node resolver can't read.
Switch to eslint-plugin-import-x, which supports `import-x/resolver-next` and
lets the config pass the resolver object directly (import it, don't resolve it
by name) — so resolution never depends on install layout.
- /typescript: native import-x — rules are `import-x/*`, resolver-next holds the
imported createTypeScriptImportResolver(). A fresh strict tier (not a drop-in),
so the new namespace is fine.
- /legacy: import-x registered under the `import` namespace so rule names stay
`import/*` and existing v2 consumer overrides keep working (preserves the
drop-in promise). node resolver stays primary with the exports-aware resolver
as a fallback, so resolution is unchanged for JS consumers.
Also tune /typescript's import rules for TS: turn off named/namespace/default/
no-named-as-default-member (the type checker enforces these more accurately and
they're the slow resolver-heavy rules; no-unresolved stays on), enforce separate
top-level `import type` (consistent-type-specifier-style), set no-duplicates
prefer-inline:false, and pass `alwaysTryTypes` so @types-only packages resolve.
Bump typescript-eslint to ^8.60.1 for import-x's @typescript-eslint/utils peer;
drop eslint-plugin-import.
4ec52d7 to
547dee2
Compare
kpal81xd
added a commit
that referenced
this pull request
Jun 3, 2026
Vanilla eslint-plugin-import loads resolvers by name relative to each linted
file, so the bundled eslint-import-resolver-typescript wasn't found when npm
didn't hoist it to the consumer top level: /typescript fell back to the
`typescript` compiler ("invalid interface" — 55 false errors in a pilot), and
/legacy's import/no-unresolved flagged valid package subpaths (e.g. the
package's own /legacy entry) the default node resolver can't read.
Switch to eslint-plugin-import-x, which supports `import-x/resolver-next` and
lets the config pass the resolver object directly (import it, don't resolve it
by name) — so resolution never depends on install layout.
- /typescript: native import-x — rules are `import-x/*`, resolver-next holds the
imported createTypeScriptImportResolver(). A fresh strict tier (not a drop-in),
so the new namespace is fine.
- /legacy: import-x registered under the `import` namespace so rule names stay
`import/*` and existing v2 consumer overrides keep working (preserves the
drop-in promise). node resolver stays primary with the exports-aware resolver
as a fallback, so resolution is unchanged for JS consumers.
Also tune /typescript's import rules for TS: turn off named/namespace/default/
no-named-as-default-member (the type checker enforces these more accurately and
they're the slow resolver-heavy rules; no-unresolved stays on), enforce separate
top-level `import type` (consistent-type-specifier-style), set no-duplicates
prefer-inline:false, and pass `alwaysTryTypes` so @types-only packages resolve.
Bump typescript-eslint to ^8.60.1 for import-x's @typescript-eslint/utils peer;
drop eslint-plugin-import.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave 1 of the v3 rollout (#48) surfaced two resolver bugs that hit consumers on a normal install.
Bug A —
/typescript: "invalid interface loaded as resolver"eslint-plugin-importloads resolvers by name, relative to each linted file. The bundledeslint-import-resolver-typescriptisn't always hoisted to the consumer's top level, so the lookup fails and falls back torequire('typescript')(the compiler) → "invalid interface". A pilot (observer) saw 55 falseimport/*errors.Bug B —
/legacy: valid package subpaths flagged/legacyenabledimport/no-unresolvedwith no resolver, so the default node resolver (noexports-map support) flagged valid subpath imports like@playcanvas/eslint-config/legacy— the migration's own import line.Fix — switch to
eslint-plugin-import-xVanilla
eslint-plugin-import(stale since mid-2025) only resolves resolvers by name from the linted file, which is the root of both bugs.eslint-plugin-import-x(the maintained fork) supportsimport-x/resolver-next, which lets the config pass the resolver object directly (import { createTypeScriptImportResolver }) — so resolution never depends on install hoisting. This is the setupeslint-import-resolver-typescript's own README blesses for flat config.Per-tier namespace, by design:
/typescript— native import-x: rules areimport-x/*,import-x/resolver-next: [createTypeScriptImportResolver({ alwaysTryTypes: true })]. A fresh strict tier (not a drop-in), so the new namespace is the clean choice; consumers that override import rules useimport-x/*./legacy— import-x under theimportnamespace so rule names stayimport/*and existing v2 consumer overrides keep working (preserves the drop-in promise for JS consumers like the engine).import-x/resolver-next: [createNodeResolver(), createTypeScriptImportResolver()]— node stays primary, exports-aware resolver only catches subpaths node misses.typescript-eslint→^8.60.1(import-x's@typescript-eslint/utils ^8.56peer); dropseslint-plugin-import.TypeScript import-rule tuning (
/typescriptonly)import-x/named,import-x/namespace,import-x/default,import-x/no-named-as-default-member→off. The type checker enforces these more accurately, and they're the slowest import rules — a real win on the large TS repos in later waves.import-x/no-unresolvedstays on (catches path typos / asset imports).@typescript-eslint/consistent-type-imports):import-x/consistent-type-specifier-style: ['error', 'prefer-top-level'],import-x/no-duplicates: ['warn', { prefer-inline: false }]— keep type-only imports as separate top-levelimport typestatements.alwaysTryTypes: trueon the resolver so@types-only packages resolve (matches/legacy).(
/legacykeepsnamed/namespace/defaulton — it lints JS+JSDoc, where the type checker isn't doing that work.)Validation
prettier --check, self-lint (dogfoods/typescript): green — resolves an exports subpath (eslint-config-prettier/flat) + thetypescript-eslintexports package, zeroimport-x/no-unresolved, zero "invalid interface".print-configconfirms the per-tier namespaces and rule states.publint: all good.beta.4. observer'simport/no-unresolved: offoverride becomesimport-x/no-unresolved: off(I'll handle it).Target a
3.0.0-beta.4cut from this so the held Wave 1 pilots can move off the buggy beta.3.