Skip to content

Commit 9c7a0bd

Browse files
committed
Update exports + rename customHOCs to extraHOCs
1 parent a1dad75 commit 9c7a0bd

7 files changed

Lines changed: 60 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
### Breaking changes
66

77
- Packages now ships as ESM and requires ESLint 9 + node 20
8-
- Validation of HOCs calls is now more strict, you may need to add some HOCs to the `customHOCs` option
8+
- Plugin is now exported under `<default>.plugin` instead of `<default>`. Named export have been removed.
99
- Configs are now functions that return the config object with passed options merged with the base options of that config
10+
- `customHOCs` was renamed to `extraHOCs`
11+
- Validation of HOCs calls is now more strict, you may need to add some HOCs to the `extraHOCs` option
1012

1113
Example:
1214

@@ -16,10 +18,32 @@ import reactRefresh from "eslint-plugin-react-refresh";
1618

1719
export default defineConfig(
1820
/* Main config */
19-
reactRefresh.configs.vite({ customHOCs: ["connect"] }),
21+
reactRefresh.configs.vite({ extraHOCs: ["someLibHOC"] }),
2022
);
2123
```
2224

25+
Example without config:
26+
27+
```js
28+
import { defineConfig } from "eslint/config";
29+
import reactRefresh from "eslint-plugin-react-refresh";
30+
31+
export default defineConfig({
32+
files: ["**/*.ts", "**/*.tsx"],
33+
plugins: {
34+
// other plugins
35+
"react-refresh": reactRefresh.plugin,
36+
},
37+
rules: {
38+
// other rules
39+
"react-refresh/only-export-components": [
40+
"warn",
41+
{ extraHOCs: ["someLibHOC"] },
42+
],
43+
},
44+
});
45+
```
46+
2347
### Why
2448

2549
This version follows a revamp of the internal logic to better make the difference between random call expressions like `export const Enum = Object.keys(Record)` and actual React HOC calls like `export const MemoComponent = memo(Component)`. (fixes [#93](https://github.com/ArnaudBarre/eslint-plugin-react-refresh/issues/93))
@@ -30,22 +54,19 @@ The rule now handles ternaries and patterns like `export default customHOC(props
3054
{
3155
"react-refresh/only-export-components": [
3256
"warn",
33-
{ "customHOCs": ["createRootRouteWithContext"] }
57+
{ "extraHOCs": ["createRootRouteWithContext"] }
3458
]
3559
}
3660
```
3761

3862
> [!NOTE]
39-
> Actually createRoute functions from TanStack Router are not React HOCs, they return route objects that [fake to be a memoized component](https://github.com/TanStack/router/blob/8628d0189412ccb8d3a01840aa18bac8295e18c8/packages/react-router/src/route.tsx#L263) but are not. When only doing `createRootRoute({ component: Foo })`, HMR will work fine, but as soon as you add a prop to the options that is not a React component, HMR will not work. I would recommend to avoid adding any TanStack function to `customHOCs` it you want to preserve good HMR in the long term. [Bluesky thread](https://bsky.app/profile/arnaud-barre.bsky.social/post/3ma5h5tf2sk2e).
63+
> Actually createRoute functions from TanStack Router are not React HOCs, they return route objects that [fake to be a memoized component](https://github.com/TanStack/router/blob/8628d0189412ccb8d3a01840aa18bac8295e18c8/packages/react-router/src/route.tsx#L263) but are not. When only doing `createRootRoute({ component: Foo })`, HMR will work fine, but as soon as you add a prop to the options that is not a React component, HMR will not work. I would recommend to avoid adding any TanStack function to `extraHOCs` it you want to preserve good HMR in the long term. [Bluesky thread](https://bsky.app/profile/arnaud-barre.bsky.social/post/3ma5h5tf2sk2e).
4064
41-
Because I'm not 100% sure this new logic doesn't introduce any false positive, this is done in a major-like version. This also give me the occasion to remove the hardcoded `connect` from the rule. If you are using `connect` from `react-redux`, you should now add it to `customHOCs` like this:
65+
Because I'm not 100% sure this new logic doesn't introduce any false positive, this is done in a major-like version. This also give me the occasion to remove the hardcoded `connect` from the rule. If you are using `connect` from `react-redux`, you should now add it to `extraHOCs` like this:
4266

4367
```json
4468
{
45-
"react-refresh/only-export-components": [
46-
"warn",
47-
{ "customHOCs": ["connect"] }
48-
]
69+
"react-refresh/only-export-components": ["warn", { "extraHOCs": ["connect"] }]
4970
}
5071
```
5172

@@ -122,7 +143,7 @@ export default observer(Foo);
122143
{
123144
"react-refresh/only-export-components": [
124145
"error",
125-
{ "customHOCs": ["observer"] }
146+
{ "extraHOCs": ["observer"] }
126147
]
127148
}
128149
```

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ import reactRefresh from "eslint-plugin-react-refresh";
7979
export default defineConfig({
8080
// in main config for TSX/JSX source files
8181
plugins: {
82-
"react-refresh": reactRefresh,
82+
"react-refresh": reactRefresh.plugin,
8383
},
8484
rules: {
8585
"react-refresh/only-export-components": "error",
@@ -141,29 +141,29 @@ These options are all present on `react-refresh/only-exports-components`.
141141

142142
```ts
143143
interface Options {
144-
customHOCs?: string[];
144+
extraHOCs?: string[];
145145
allowExportNames?: string[];
146146
allowConstantExport?: boolean;
147147
checkJS?: boolean;
148148
}
149149

150150
const defaultOptions: Options = {
151-
customHOCs: [],
151+
extraHOCs: [],
152152
allowExportNames: [],
153153
allowConstantExport: false,
154154
checkJS: false,
155155
};
156156
```
157157

158-
### customHOCs <small>(v0.4.15)</small>
158+
### extraHOCs <small>(v0.5.0)</small>
159159

160-
If you're exporting a component wrapped in a custom HOC, you can use this option to avoid false positives.
160+
If you're exporting components wrapped in non built-in React HOC (memo, forwardRef, lazy), you can use this option to avoid false positives.
161161

162162
```json
163163
{
164164
"react-refresh/only-export-components": [
165165
"error",
166-
{ "customHOCs": ["observer", "withAuth"] }
166+
{ "extraHOCs": ["observer", "withAuth"] }
167167
]
168168
}
169169
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-react-refresh",
3-
"version": "0.5.0-alpha.0",
3+
"version": "0.5.0-alpha.1",
44
"type": "module",
55
"license": "MIT",
66
"scripts": {

src/index.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { onlyExportComponents } from "./only-export-components.ts";
22
import type { OnlyExportComponentsOptions } from "./types.d.ts";
33

4-
export const rules = {
5-
"only-export-components": onlyExportComponents,
4+
const plugin = {
5+
rules: {
6+
"only-export-components": onlyExportComponents,
7+
},
68
};
79

8-
const plugin = { rules };
9-
1010
const buildConfig =
1111
({
1212
name,
@@ -26,7 +26,7 @@ const buildConfig =
2626
},
2727
});
2828

29-
export const configs = {
29+
const configs = {
3030
recommended: buildConfig({ name: "recommended", baseOptions: {} }),
3131
vite: buildConfig({
3232
name: "vite",
@@ -62,5 +62,4 @@ export const configs = {
6262
}),
6363
};
6464

65-
// Probably not needed, but keep for backwards compatibility
66-
export default { rules, configs };
65+
export default { plugin, configs };

src/only-export-components.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,27 @@ const valid: {
7070
{
7171
name: "styled components",
7272
code: "export const Foo = () => {}; export const Bar = styled.div`padding-bottom: 6px;`;",
73-
options: { customHOCs: ["styled"] },
73+
options: { extraHOCs: ["styled"] },
7474
},
7575
{
7676
name: "styled components",
7777
code: "export const Foo = () => {}; export const Flex = styled.div({ display: 'flex' });",
78-
options: { customHOCs: ["styled"] },
78+
options: { extraHOCs: ["styled"] },
7979
},
8080
{
8181
name: "Curried HOC with styled (object form)",
8282
code: "export const Foo = () => {}; export const Flex = styled('div')({display: 'flex'});",
83-
options: { customHOCs: ["styled"] },
83+
options: { extraHOCs: ["styled"] },
8484
},
8585
{
8686
name: "Curried HOC with styled (template literal form)",
8787
code: "export const Foo = () => {}; export const Flex = styled('div')`display: flex;`;",
88-
options: { customHOCs: ["styled"] },
88+
options: { extraHOCs: ["styled"] },
8989
},
9090
{
9191
name: "Curried HOC only first call",
9292
code: "export const Foo = () => {}; export const Flex = styled('div');",
93-
options: { customHOCs: ["styled"] },
93+
options: { extraHOCs: ["styled"] },
9494
},
9595
{
9696
name: "Direct export variable",
@@ -210,7 +210,7 @@ const valid: {
210210
{
211211
name: "Allow connect from react-redux",
212212
code: "const MyComponent = () => {}; export default connect(() => ({}))(MyComponent);",
213-
options: { customHOCs: ["connect"] },
213+
options: { extraHOCs: ["connect"] },
214214
},
215215
{
216216
name: "Two components, one of them with 'Context' in its name",
@@ -227,7 +227,7 @@ const valid: {
227227
{
228228
name: "Custom HOCs like mobx's observer",
229229
code: "const MyComponent = () => {}; export default observer(MyComponent);",
230-
options: { customHOCs: ["observer"] },
230+
options: { extraHOCs: ["observer"] },
231231
},
232232
{
233233
name: "Local constant with component casing and non component function",
@@ -257,7 +257,7 @@ const valid: {
257257
{
258258
name: "TanStack Router",
259259
code: "const RootComponent = () => {}; export const Route = createRootRoute()({ component: RootComponent });",
260-
options: { customHOCs: ["createRootRoute"] },
260+
options: { extraHOCs: ["createRootRoute"] },
261261
},
262262
{
263263
name: "Rename export",

src/only-export-components.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const onlyExportComponents: TSESLint.RuleModule<
2222
anonymousExport:
2323
"Fast refresh can't handle anonymous components. Add a name to your export.",
2424
localComponents:
25-
"Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `customHOCs` option.",
25+
"Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the `extraHOCs` option.",
2626
noExport:
2727
"Fast refresh only works when a file has exports. Move your component(s) to a separate file.",
2828
reactContext:
@@ -33,7 +33,7 @@ export const onlyExportComponents: TSESLint.RuleModule<
3333
{
3434
type: "object",
3535
properties: {
36-
customHOCs: { type: "array", items: { type: "string" } },
36+
extraHOCs: { type: "array", items: { type: "string" } },
3737
allowExportNames: { type: "array", items: { type: "string" } },
3838
allowConstantExport: { type: "boolean" },
3939
checkJS: { type: "boolean" },
@@ -45,7 +45,7 @@ export const onlyExportComponents: TSESLint.RuleModule<
4545
defaultOptions: [],
4646
create: (context) => {
4747
const {
48-
customHOCs = [],
48+
extraHOCs = [],
4949
allowExportNames,
5050
allowConstantExport = false,
5151
checkJS = false,
@@ -70,7 +70,7 @@ export const onlyExportComponents: TSESLint.RuleModule<
7070
? new Set(allowExportNames)
7171
: undefined;
7272

73-
const validHOCs = ["memo", "forwardRef", "lazy", ...customHOCs];
73+
const validHOCs = ["memo", "forwardRef", "lazy", ...extraHOCs];
7474
const getHocName = (
7575
node: TSESTree.CallExpression | TSESTree.TaggedTemplateExpression,
7676
): string | undefined => {

src/types.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
type Rules = { "only-export-components": any };
22

33
export type OnlyExportComponentsOptions = {
4-
customHOCs?: string[];
4+
extraHOCs?: string[];
55
allowExportNames?: string[];
66
allowConstantExport?: boolean;
77
checkJS?: boolean;
@@ -14,7 +14,9 @@ type Config = (options?: OnlyExportComponentsOptions) => {
1414
};
1515

1616
declare const _default: {
17-
rules: Rules;
17+
plugin: {
18+
rules: Rules;
19+
};
1820
configs: {
1921
recommended: Config;
2022
vite: Config;

0 commit comments

Comments
 (0)