Skip to content

Commit e03e3de

Browse files
committed
chore: add changeset
1 parent 1ba821d commit e03e3de

File tree

1 file changed

+184
-0
lines changed

1 file changed

+184
-0
lines changed

.changeset/orange-nails-attack.md

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
The PR implements the new resolver design proposed in https://github.com/un-ts/eslint-plugin-import-x/issues/40#issuecomment-2381444266
2+
3+
----
4+
5+
### For `eslint-plugin-import-x` users
6+
7+
Like the ESLint flat config allows you to use any js objects (e.g. import and require) as ESLint plugins, the new `eslint-plugin-import-x` resolver settings allow you to use any js objects as custom resolvers through the new setting `import-x/resolver-next`:
8+
9+
```js
10+
// eslint.config.js
11+
import { createTsResolver } from '#custom-resolver';
12+
const { createOxcResolver } = require('path/to/a/custom/resolver');
13+
14+
const nodeResolverObject = {
15+
interfaceVersion: 3,
16+
name: 'my-custom-eslint-import-resolver',
17+
resolve(modPath, sourcePath) {
18+
};
19+
};
20+
21+
module.exports = {
22+
settings: {
23+
// multiple resolvers
24+
'import-x/resolver-next': [
25+
nodeResolverObject,
26+
createTsResolver(enhancedResolverOptions),
27+
createOxcResolver(oxcOptions),
28+
],
29+
// single resolver:
30+
'import-x/resolver-next': [createOxcResolver(oxcOptions)]
31+
}
32+
}
33+
```
34+
35+
The new `import-x/resolver-next` no longer accepts strings as the resolver, thus will not be compatible with the ESLint legacy config (a.k.a. `.eslintrc`). Those who are still using the ESLint legacy config should stick with `import-x/resolver`.
36+
37+
In the next major version of `eslint-plugin-import-x` (v5), we will rename the currently existing `import-x/resolver` to `import-x/resolver-legacy` (which still allows the existing ESLint legacy config users to use their existing resolver settings), and `import-x/resolver-next` will become the new `import-x/resolver`. When ESLint v9 (the last ESLint version with ESLint legacy config support) reaches EOL in the future, we will remove `import-x/resolver-legacy`.
38+
39+
We have also made a few breaking changes to the new resolver API design, so you can't use existing custom resolvers directly with `import-x/resolver-next`:
40+
41+
```js
42+
// An example of the current `import-x/resolver` settings
43+
module.exports = {
44+
settings: {
45+
'import-x/resolver': {
46+
node: nodeResolverOpt
47+
webpack: webpackResolverOpt,
48+
'custom-resolver': customResolverOpt
49+
}
50+
}
51+
}
52+
53+
// When migrating to `import-x/resolver-next`, you CAN'T use legacy versions of resolvers directly:
54+
module.exports = {
55+
settings: {
56+
// THIS WON'T WORK, the resolver interface required for `import-x/resolver-next` is different.
57+
'import-x/resolver-next': [
58+
require('eslint-import-resolver-node'),
59+
require('eslint-import-resolver-webpack'),
60+
require('some-custom-resolver')
61+
];
62+
}
63+
}
64+
```
65+
66+
For easier migration, the PR also introduces a compat utility `importXResolverCompat` that you can use in your `eslint.config.js`:
67+
68+
```js
69+
// eslint.config.js
70+
import eslintPluginImportX, { importXResolverCompat } from 'eslint-plugin-import-x';
71+
// or
72+
const eslintPluginImportX = require('eslint-plugin-import-x');
73+
const { importXResolverCompat } = eslintPluginImportX;
74+
75+
module.exports = {
76+
settings: {
77+
// THIS WILL WORK as you have wrapped the previous version of resolvers with the `importXResolverCompat`
78+
'import-x/resolver-next': [
79+
importXResolverCompat(require('eslint-import-resolver-node'), nodeResolveOptions),
80+
importXResolverCompat(require('eslint-import-resolver-webpack'), webpackResolveOptions),
81+
importXResolverCompat(require('some-custom-resolver'), {})
82+
];
83+
}
84+
}
85+
```
86+
87+
### For custom import resolver developers
88+
89+
This is the new API design of the resolver interface:
90+
91+
```ts
92+
export interface NewResolver {
93+
interfaceVersion: 3,
94+
name?: string, // This will be included in the debug log
95+
resolve: (modulePath: string, sourceFile: string) => ResolvedResult
96+
}
97+
98+
// The `ResultNotFound` (returned when not resolved) is the same, no changes
99+
export interface ResultNotFound {
100+
found: false
101+
path?: undefined
102+
}
103+
104+
// The `ResultFound` (returned resolve result) is also the same, no changes
105+
export interface ResultFound {
106+
found: true
107+
path: string | null
108+
}
109+
110+
export type ResolvedResult = ResultNotFound | ResultFound
111+
```
112+
113+
You will be able to import `NewResolver` from `eslint-plugin-import-x/types`.
114+
115+
The most notable change is that `eslint-plugin-import-x` no longer passes the third argument (`options`) to the `resolve` function.
116+
117+
We encourage custom resolvers' authors to consume the options outside the actual `resolve` function implementation. You can export a factory function to accept the options, this factory function will then be called inside the `eslint.config.js` to get the actual resolver:
118+
119+
```js
120+
// custom-resolver.js
121+
exports.createCustomResolver = (options) => {
122+
// The options are consumed outside the `resolve` function.
123+
const resolverInstance = new ResolverFactory(options);
124+
125+
return {
126+
name: 'custom-resolver',
127+
interfaceVersion: 3,
128+
resolve(mod, source) {
129+
const found = resolverInstance.resolve(mod, {});
130+
131+
// Of course, you still have access to the `options` variable here inside
132+
// the `resolve` function. That's the power of JavaScript Closures~
133+
}
134+
}
135+
};
136+
137+
// eslint.config.js
138+
const { createCustomResolver } = require('custom-resolver')
139+
140+
module.exports = {
141+
settings: {
142+
'import-x/resolver-next': [
143+
createCustomResolver(options)
144+
];
145+
}
146+
}
147+
```
148+
149+
This allows you to create a reusable resolver instance to improve the performance. With the existing version of the resolver interface, because the options are passed to the `resolver` function, you will have to create a resolver instance every time the `resolve` function is called:
150+
151+
```js
152+
module.exports = {
153+
interfaceVersion: 2,
154+
resolve(mod, source) {
155+
// every time the `resolve` function is called, a new instance is created
156+
// This is very slow
157+
const resolverInstance = ResolverFactory.createResolver({});
158+
const found = resolverInstance.resolve(mod, {});
159+
}
160+
}
161+
```
162+
163+
With the factory function pattern, you can create a resolver instance beforehand:
164+
165+
```js
166+
exports.createCustomResolver = (options) => {
167+
// `enhance-resolve` allows you to create a reusable instance:
168+
const resolverInstance = ResolverFactory.createResolver({});
169+
const resolverInstance = enhanceResolve.create({});
170+
171+
// `oxc-resolver` also allows you to create a reusable instance:
172+
const resolverInstance = new ResolverFactory({});
173+
174+
return {
175+
name: 'custom-resolver',
176+
interfaceVersion: 3,
177+
resolve(mod, source) {
178+
// the same re-usable instance is shared across `resolve` invocations.
179+
// more performant
180+
const found = resolverInstance.resolve(mod, {});
181+
}
182+
}
183+
};
184+
```

0 commit comments

Comments
 (0)