diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c09c4b..552fbd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,23 @@ export default observer(Foo); Thanks @HorusGoul! +### Add recommended config and simple types (#67) + +You can now add the recommended config to your ESLint config like this: + +```js +import reactRefresh from "eslint-plugin-react-refresh"; + +export default [ + /* Main config */ + reactRefresh.configs.recommended, // Or reactRefresh.configs.vite for Vite users +]; +``` + +To follow ESLint recommandations, the rule is added with the `error` severity. + +Some simple types ensure that people typecheking their config won't need `@ts-expect-error` anymore. + ### Bump ESLint peer dependency to 8.40 This was actually done by mistake in the previous release when moving from a deprecated API to a new one. diff --git a/README.md b/README.md index 5256595..dad9549 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # eslint-plugin-react-refresh [![npm](https://img.shields.io/npm/v/eslint-plugin-react-refresh)](https://www.npmjs.com/package/eslint-plugin-react-refresh) -Validate that your components can safely be updated with fast refresh. +Validate that your components can safely be updated with Fast Refresh. ## Explainer -"Fast refresh", also known as "hot reloading", is a feature in many modern bundlers. +"Fast Refresh", also known as "hot reloading", is a feature in many modern bundlers. If you update some React component(s) on disk, then the bundler will know to update only the impacted parts of your page -- without a full page reload. `eslint-plugin-react-refresh` enforces that your components are structured in a way that integrations such as [react-refresh](https://www.npmjs.com/package/react-refresh) expect. @@ -28,7 +28,33 @@ npm i -D eslint-plugin-react-refresh ## Usage -This plugin provides a single rule, `react-refresh/only-export-components`. +This plugin provides a single rule, `react-refresh/only-export-components`. There are multiple ways to enable it. + +### Recommended config + +```js +import reactRefresh from "eslint-plugin-react-refresh"; + +export default [ + /* Main config */ + reactRefresh.configs.recommended, +]; +``` + +### Vite config + +This enables the `allowConstantExport` option which is supported by Vite React plugins. + +```js +import reactRefresh from "eslint-plugin-react-refresh"; + +export default [ + /* Main config */ + reactRefresh.configs.vite, +]; +``` + +### Without config ```js import reactRefresh from "eslint-plugin-react-refresh"; @@ -111,7 +137,6 @@ These options are all present on `react-refresh/only-exports-components`. ```ts interface Options { - allowExportNames?: string[]; allowExportNames?: string[]; allowConstantExport?: boolean; customHOCs?: string[]; @@ -145,7 +170,7 @@ Example for [Remix](https://remix.run/docs/en/main/discussion/hot-module-replace ### allowConstantExport (v0.4.0) -> Default: `false` +> Default: `false` (`true` in `vite` config) Don't warn when a constant (string, number, boolean, templateLiteral) is exported aside one or more components. diff --git a/bun.lockb b/bun.lockb index dd12af0..54b4d01 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/eslint.config.js b/eslint.config.js index 95d0741..e7ef99c 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,3 +1,10 @@ import baseConfig from "@arnaud-barre/eslint-config"; -export default baseConfig; +export default [ + ...baseConfig, + { + rules: { + "@arnaud-barre/no-default-export": "off", + }, + }, +]; diff --git a/package.json b/package.json index 493c26a..a2c3aa4 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "lint": "bun eslint . --max-warnings 0", "prettier": "bun prettier-ci --write", "prettier-ci": "prettier --ignore-path=.gitignore --check '**/*.{js,ts,json,md,yml}'", - "ci": "tsc && bun lint && bun prettier-ci && bun test && bun run build" + "ci": "tsc && bun lint && bun prettier-ci && bun test && bun run build && cd dist && publint" }, "prettier": {}, "peerDependencies": { @@ -25,6 +25,7 @@ "bun-types": "^1.1.31", "eslint": "^9.13.0", "prettier": "3.0.3", + "publint": "^0.2.12", "typescript": "~5.6" } } diff --git a/scripts/bundle.ts b/scripts/bundle.ts index 1a9c064..f33a7d4 100755 --- a/scripts/bundle.ts +++ b/scripts/bundle.ts @@ -17,6 +17,7 @@ await build({ }); execSync("cp LICENSE README.md dist/"); +execSync("cp src/types.ts dist/index.d.ts"); writeFileSync( "dist/package.json", @@ -26,10 +27,12 @@ writeFileSync( description: "Validate that your components can safely be updated with Fast Refresh", version: packageJSON.version, + type: "commonjs", author: "Arnaud Barré (https://github.com/ArnaudBarre)", license: packageJSON.license, repository: "github:ArnaudBarre/eslint-plugin-react-refresh", main: "index.js", + types: "index.d.ts", keywords: [ "eslint", "eslint-plugin", diff --git a/src/index.ts b/src/index.ts index e352741..ef12772 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,26 @@ import { onlyExportComponents } from "./only-export-components.ts"; -export const rules = { - "only-export-components": onlyExportComponents, +const plugin = { + rules: { + "only-export-components": onlyExportComponents, + }, +}; + +export default { + rules: plugin.rules, + configs: { + recommended: { + plugins: { "react-refresh": plugin }, + rules: { "react-refresh/only-export-components": "error" }, + }, + vite: { + plugins: { "react-refresh": plugin }, + rules: { + "react-refresh/only-export-components": [ + "error", + { allowConstantExport: true }, + ], + }, + }, + }, }; -// eslint-disable-next-line @arnaud-barre/no-default-export -export default { rules }; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..d3deec2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,14 @@ +type Config = { + plugins: { "react-refresh": { rules: Record } }; + rules: Record; +}; + +declare const _default: { + rules: Record; + configs: { + recommended: Config; + vite: Config; + }; +}; + +export default _default; diff --git a/yarn.lock b/yarn.lock index c7dd1b7..c80c2ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: 0E2E8C648B832E19-f03f14fd57c82352-DE4F31B62A420BFD-c372ca07548ab99e +# bun ./bun.lockb --hash: 200298EB1539E3D4-a461114ade976598-38DCF0097CD18ABB-5c9d03f73b5bf09b "@arnaud-barre/eslint-config@^5.1.0": @@ -633,7 +633,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint@>=7, eslint@>=8.56.0, "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.13.0: +eslint@>=7, eslint@>=8.40, eslint@>=8.56.0, "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^8.57.0 || ^9.0.0", eslint@^9.13.0: version "9.13.0" resolved "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz" integrity sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA== @@ -833,11 +833,27 @@ flatted@^3.2.9: resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +glob@^8.0.1: + version "8.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + once "^1.3.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + fs.realpath "^1.0.0" + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" @@ -894,6 +910,13 @@ ignore@^5.2.0, ignore@^5.3.1: resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== +ignore-walk@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-5.0.1.tgz" + integrity sha512-yemi4pMf51WKT7khInJqAvsIGzoqYXblnsz0ql8tM+yi1EKYTY1evX4NAbJrLL/Aanr2HyZeluqU+Oi7MGHokw== + dependencies: + minimatch "^5.0.1" + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" @@ -912,6 +935,19 @@ indent-string@^4.0.0: resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" @@ -1059,6 +1095,13 @@ minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimatch@^9.0.4: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" @@ -1066,6 +1109,11 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" +mri@^1.1.0: + version "1.2.0" + resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" @@ -1091,6 +1139,35 @@ normalize-package-data@^2.5.0: hosted-git-info "^2.1.4" validate-npm-package-license "^3.0.1" +npm-bundled@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-2.0.1.tgz" + integrity sha512-gZLxXdjEzE/+mOstGDqR6b0EkhJ+kM6fxM6vUuckuctuVPh80Q6pw/rSZj9s4Gex9GxWtIicO1pc8DB9KZWudw== + dependencies: + npm-normalize-package-bin "^2.0.0" + +npm-normalize-package-bin@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-2.0.0.tgz" + integrity sha512-awzfKUO7v0FscrSpRoogyNm0sajikhBWpU0QMrW09AMi9n1PoKU6WaIqUzuJSQnpciZZmJ/jMZ2Egfmb/9LiWQ== + +npm-packlist@^5.1.3: + version "5.1.3" + resolved "https://registry.npmjs.org/npm-packlist/-/npm-packlist-5.1.3.tgz" + integrity sha512-263/0NGrn32YFYi4J533qzrQ/krmmrWwhKkzwTuM4f/07ug51odoaNjUexxO4vxlzURHcmYMH1QjvHjsNDKLVg== + dependencies: + glob "^8.0.1" + ignore-walk "^5.0.1" + npm-bundled "^2.0.0" + npm-normalize-package-bin "^2.0.0" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + optionator@^0.9.3: version "0.9.4" resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" @@ -1168,7 +1245,7 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -picocolors@^1.0.0, picocolors@^1.1.0: +picocolors@^1.0.0, picocolors@^1.1.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== @@ -1193,6 +1270,15 @@ prettier@3.0.3: resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz" integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== +publint@^0.2.12: + version "0.2.12" + resolved "https://registry.npmjs.org/publint/-/publint-0.2.12.tgz" + integrity sha512-YNeUtCVeM4j9nDiTT2OPczmlyzOkIXNtdDZnSuajAxS/nZ6j3t7Vs9SUB4euQNddiltIwu7Tdd3s+hr08fAsMw== + dependencies: + npm-packlist "^5.1.3" + picocolors "^1.1.1" + sade "^1.8.1" + punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" @@ -1260,6 +1346,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +sade@^1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz" + integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== + dependencies: + mri "^1.1.0" + "semver@2 || 3 || 4 || 5": version "5.7.2" resolved "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz" @@ -1432,6 +1525,11 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"