From 390877dcac8a60aeb156290fbb71681df92c239c Mon Sep 17 00:00:00 2001 From: laklov Date: Sun, 2 Feb 2025 21:46:52 +0100 Subject: [PATCH 1/6] feat: React router 7 framework plugin --- .vscode/settings.json | 2 +- .../plugins/react-router/app/entry.client.tsx | 0 .../plugins/react-router/app/entry.server.tsx | 0 .../plugins/react-router/app/root.tsx | 0 .../plugins/react-router/app/routes.ts | 7 ++ .../react-router/app/routes/another-route.tsx | 0 .../plugins/react-router/app/routes/home.tsx | 0 .../react-router/app/routes/layout.tsx | 0 .../plugins/react-router/package.json | 7 ++ .../react-router/react-router.config.ts | 7 ++ packages/knip/schema.json | 4 ++ packages/knip/src/plugins/index.ts | 2 + .../knip/src/plugins/react-router/index.ts | 71 +++++++++++++++++++ .../knip/src/plugins/react-router/types.ts | 8 +++ packages/knip/src/schema/plugins.ts | 1 + packages/knip/src/types/PluginNames.ts | 2 + .../knip/test/plugins/react-router.test.ts | 21 ++++++ 17 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 packages/knip/fixtures/plugins/react-router/app/entry.client.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/app/entry.server.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/app/root.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/app/routes.ts create mode 100644 packages/knip/fixtures/plugins/react-router/app/routes/another-route.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/app/routes/home.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/app/routes/layout.tsx create mode 100644 packages/knip/fixtures/plugins/react-router/package.json create mode 100644 packages/knip/fixtures/plugins/react-router/react-router.config.ts create mode 100644 packages/knip/src/plugins/react-router/index.ts create mode 100644 packages/knip/src/plugins/react-router/types.ts create mode 100644 packages/knip/test/plugins/react-router.test.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 34322c9ba..04230143f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,7 @@ "editor.defaultFormatter": "biomejs.biome" }, "[typescript]": { - "editor.defaultFormatter": "biomejs.biome" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" diff --git a/packages/knip/fixtures/plugins/react-router/app/entry.client.tsx b/packages/knip/fixtures/plugins/react-router/app/entry.client.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/app/entry.server.tsx b/packages/knip/fixtures/plugins/react-router/app/entry.server.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/app/root.tsx b/packages/knip/fixtures/plugins/react-router/app/root.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/app/routes.ts b/packages/knip/fixtures/plugins/react-router/app/routes.ts new file mode 100644 index 000000000..09d402403 --- /dev/null +++ b/packages/knip/fixtures/plugins/react-router/app/routes.ts @@ -0,0 +1,7 @@ +export default [ + { file: "routes/home.tsx", index: true }, + { + file: "routes/layout.tsx", + children: [{ file: "routes/another-route.tsx" }], + }, +]; diff --git a/packages/knip/fixtures/plugins/react-router/app/routes/another-route.tsx b/packages/knip/fixtures/plugins/react-router/app/routes/another-route.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/app/routes/home.tsx b/packages/knip/fixtures/plugins/react-router/app/routes/home.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/app/routes/layout.tsx b/packages/knip/fixtures/plugins/react-router/app/routes/layout.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/knip/fixtures/plugins/react-router/package.json b/packages/knip/fixtures/plugins/react-router/package.json new file mode 100644 index 000000000..e841463d6 --- /dev/null +++ b/packages/knip/fixtures/plugins/react-router/package.json @@ -0,0 +1,7 @@ +{ + "name": "@fixtures/react-router", + "version": "*", + "devDependencies": { + "@react-router/dev": "*" + } +} \ No newline at end of file diff --git a/packages/knip/fixtures/plugins/react-router/react-router.config.ts b/packages/knip/fixtures/plugins/react-router/react-router.config.ts new file mode 100644 index 000000000..6ff16f917 --- /dev/null +++ b/packages/knip/fixtures/plugins/react-router/react-router.config.ts @@ -0,0 +1,7 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + // Config options... + // Server-side render by default, to enable SPA mode set this to `false` + ssr: true, +} satisfies Config; diff --git a/packages/knip/schema.json b/packages/knip/schema.json index 7803e67ef..a4944a210 100644 --- a/packages/knip/schema.json +++ b/packages/knip/schema.json @@ -502,6 +502,10 @@ "title": "react-cosmos plugin configuration (https://knip.dev/reference/plugins/react-cosmos)", "$ref": "#/definitions/plugin" }, + "react-router": { + "title": "react-router plugin configuration (https://knip.dev/reference/plugins/react-router)", + "$ref": "#/definitions/plugin" + }, "release-it": { "title": "Release It plugin configuration (https://knip.dev/reference/plugins/release-it)", "$ref": "#/definitions/plugin" diff --git a/packages/knip/src/plugins/index.ts b/packages/knip/src/plugins/index.ts index 566702c9e..f55f79e1b 100644 --- a/packages/knip/src/plugins/index.ts +++ b/packages/knip/src/plugins/index.ts @@ -54,6 +54,7 @@ import { default as postcss } from './postcss/index.js'; import { default as preconstruct } from './preconstruct/index.js'; import { default as prettier } from './prettier/index.js'; import { default as reactCosmos } from './react-cosmos/index.js'; +import { default as reactRouter } from './react-router/index.js'; import { default as releaseIt } from './release-it/index.js'; import { default as remark } from './remark/index.js'; import { default as remix } from './remix/index.js'; @@ -147,6 +148,7 @@ export const Plugins = { preconstruct, prettier, 'react-cosmos': reactCosmos, + 'react-router': reactRouter, 'release-it': releaseIt, remark, remix, diff --git a/packages/knip/src/plugins/react-router/index.ts b/packages/knip/src/plugins/react-router/index.ts new file mode 100644 index 000000000..6eafa36ac --- /dev/null +++ b/packages/knip/src/plugins/react-router/index.ts @@ -0,0 +1,71 @@ +import { existsSync } from "node:fs"; +import type { + IsPluginEnabled, + Plugin, + ResolveEntryPaths, +} from "../../types/config.js"; +import { toEntry } from "../../util/input.js"; +import { join } from "../../util/path.js"; +import { hasDependency, load } from "../../util/plugin.js"; +import type { PluginConfig, RouteConfigEntry } from "./types.js"; + +// https://reactrouter.com/start/framework/routing + +const title = "react-router"; + +const enablers = ["@react-router/dev"]; + +const isEnabled: IsPluginEnabled = ({ dependencies }) => + hasDependency(dependencies, enablers); + +const config: string[] = ["react-router.config.{js,ts}"]; + +const entry: string[] = []; + +const production: string[] = []; + +const resolveEntryPaths: ResolveEntryPaths = async ( + localConfig, + options +) => { + const { configFileDir } = options; + const appDirectory = localConfig.appDirectory ?? "app"; + const appDir = join(configFileDir, appDirectory); + + let routeConfig: RouteConfigEntry[] = []; + + const routesPathTs = join(appDir, "routes.ts"); + const routesPathJs = join(appDir, "routes.js"); + + if (existsSync(routesPathTs)) { + routeConfig = await load(routesPathTs); + } else if (existsSync(routesPathJs)) { + routeConfig = await load(routesPathJs); + } + + const mapRoute = (route: RouteConfigEntry): string[] => { + return [ + join(appDir, route.file), + ...(route.children ? route.children.flatMap(mapRoute) : []), + ]; + }; + + const routes = routeConfig.flatMap(mapRoute); + + return [ + join(appDir, "routes.{js,ts}"), + join(appDir, "root.{jsx,tsx}"), + join(appDir, "entry.{client,server}.{js,jsx,ts,tsx}"), + ...routes, + ].map(toEntry); +}; + +export default { + title, + enablers, + isEnabled, + config, + entry, + production, + resolveEntryPaths, +} satisfies Plugin; diff --git a/packages/knip/src/plugins/react-router/types.ts b/packages/knip/src/plugins/react-router/types.ts new file mode 100644 index 000000000..40866b20e --- /dev/null +++ b/packages/knip/src/plugins/react-router/types.ts @@ -0,0 +1,8 @@ +export type PluginConfig = { + appDirectory?: string; +}; + +export interface RouteConfigEntry { + file: string; + children?: RouteConfigEntry[]; +} diff --git a/packages/knip/src/schema/plugins.ts b/packages/knip/src/schema/plugins.ts index 813b71ed8..b77ff9042 100644 --- a/packages/knip/src/schema/plugins.ts +++ b/packages/knip/src/schema/plugins.ts @@ -68,6 +68,7 @@ export const pluginsSchema = z.object({ preconstruct: pluginSchema, prettier: pluginSchema, 'react-cosmos': pluginSchema, + 'react-router': pluginSchema, 'release-it': pluginSchema, remark: pluginSchema, remix: pluginSchema, diff --git a/packages/knip/src/types/PluginNames.ts b/packages/knip/src/types/PluginNames.ts index c45cc6c72..cb22d414b 100644 --- a/packages/knip/src/types/PluginNames.ts +++ b/packages/knip/src/types/PluginNames.ts @@ -55,6 +55,7 @@ export type PluginName = | 'preconstruct' | 'prettier' | 'react-cosmos' + | 'react-router' | 'release-it' | 'remark' | 'remix' @@ -148,6 +149,7 @@ export const pluginNames = [ 'preconstruct', 'prettier', 'react-cosmos', + 'react-router', 'release-it', 'remark', 'remix', diff --git a/packages/knip/test/plugins/react-router.test.ts b/packages/knip/test/plugins/react-router.test.ts new file mode 100644 index 000000000..e6943acd8 --- /dev/null +++ b/packages/knip/test/plugins/react-router.test.ts @@ -0,0 +1,21 @@ +import { test } from "bun:test"; +import assert from "node:assert/strict"; +import { main } from "../../src/index.js"; +import { resolve } from "../../src/util/path.js"; +import baseArguments from "../helpers/baseArguments.js"; +import baseCounters from "../helpers/baseCounters.js"; + +const cwd = resolve("fixtures/plugins/react-router"); + +test("Find dependencies with the react-router plugin", async () => { + const { issues, counters } = await main({ + ...baseArguments, + cwd, + }); + + assert.deepEqual(counters, { + ...baseCounters, + processed: 8, + total: 8, + }); +}); From 22f0a572409dab060a2927fe760a402ad67324e3 Mon Sep 17 00:00:00 2001 From: laklov Date: Sun, 2 Feb 2025 21:49:33 +0100 Subject: [PATCH 2/6] Lint --- .../knip/src/plugins/react-router/index.ts | 47 +++++++------------ .../knip/test/plugins/react-router.test.ts | 18 +++---- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/packages/knip/src/plugins/react-router/index.ts b/packages/knip/src/plugins/react-router/index.ts index 6eafa36ac..cfcdb44b6 100644 --- a/packages/knip/src/plugins/react-router/index.ts +++ b/packages/knip/src/plugins/react-router/index.ts @@ -1,41 +1,33 @@ -import { existsSync } from "node:fs"; -import type { - IsPluginEnabled, - Plugin, - ResolveEntryPaths, -} from "../../types/config.js"; -import { toEntry } from "../../util/input.js"; -import { join } from "../../util/path.js"; -import { hasDependency, load } from "../../util/plugin.js"; -import type { PluginConfig, RouteConfigEntry } from "./types.js"; +import { existsSync } from 'node:fs'; +import type { IsPluginEnabled, Plugin, ResolveEntryPaths } from '../../types/config.js'; +import { toEntry } from '../../util/input.js'; +import { join } from '../../util/path.js'; +import { hasDependency, load } from '../../util/plugin.js'; +import type { PluginConfig, RouteConfigEntry } from './types.js'; // https://reactrouter.com/start/framework/routing -const title = "react-router"; +const title = 'react-router'; -const enablers = ["@react-router/dev"]; +const enablers = ['@react-router/dev']; -const isEnabled: IsPluginEnabled = ({ dependencies }) => - hasDependency(dependencies, enablers); +const isEnabled: IsPluginEnabled = ({ dependencies }) => hasDependency(dependencies, enablers); -const config: string[] = ["react-router.config.{js,ts}"]; +const config: string[] = ['react-router.config.{js,ts}']; const entry: string[] = []; const production: string[] = []; -const resolveEntryPaths: ResolveEntryPaths = async ( - localConfig, - options -) => { +const resolveEntryPaths: ResolveEntryPaths = async (localConfig, options) => { const { configFileDir } = options; - const appDirectory = localConfig.appDirectory ?? "app"; + const appDirectory = localConfig.appDirectory ?? 'app'; const appDir = join(configFileDir, appDirectory); let routeConfig: RouteConfigEntry[] = []; - const routesPathTs = join(appDir, "routes.ts"); - const routesPathJs = join(appDir, "routes.js"); + const routesPathTs = join(appDir, 'routes.ts'); + const routesPathJs = join(appDir, 'routes.js'); if (existsSync(routesPathTs)) { routeConfig = await load(routesPathTs); @@ -44,18 +36,15 @@ const resolveEntryPaths: ResolveEntryPaths = async ( } const mapRoute = (route: RouteConfigEntry): string[] => { - return [ - join(appDir, route.file), - ...(route.children ? route.children.flatMap(mapRoute) : []), - ]; + return [join(appDir, route.file), ...(route.children ? route.children.flatMap(mapRoute) : [])]; }; const routes = routeConfig.flatMap(mapRoute); return [ - join(appDir, "routes.{js,ts}"), - join(appDir, "root.{jsx,tsx}"), - join(appDir, "entry.{client,server}.{js,jsx,ts,tsx}"), + join(appDir, 'routes.{js,ts}'), + join(appDir, 'root.{jsx,tsx}'), + join(appDir, 'entry.{client,server}.{js,jsx,ts,tsx}'), ...routes, ].map(toEntry); }; diff --git a/packages/knip/test/plugins/react-router.test.ts b/packages/knip/test/plugins/react-router.test.ts index e6943acd8..9e7c1ae53 100644 --- a/packages/knip/test/plugins/react-router.test.ts +++ b/packages/knip/test/plugins/react-router.test.ts @@ -1,14 +1,14 @@ -import { test } from "bun:test"; -import assert from "node:assert/strict"; -import { main } from "../../src/index.js"; -import { resolve } from "../../src/util/path.js"; -import baseArguments from "../helpers/baseArguments.js"; -import baseCounters from "../helpers/baseCounters.js"; +import { test } from 'bun:test'; +import assert from 'node:assert/strict'; +import { main } from '../../src/index.js'; +import { resolve } from '../../src/util/path.js'; +import baseArguments from '../helpers/baseArguments.js'; +import baseCounters from '../helpers/baseCounters.js'; -const cwd = resolve("fixtures/plugins/react-router"); +const cwd = resolve('fixtures/plugins/react-router'); -test("Find dependencies with the react-router plugin", async () => { - const { issues, counters } = await main({ +test('Find dependencies with the react-router plugin', async () => { + const { counters } = await main({ ...baseArguments, cwd, }); From bd3dc325a5fa707022c6250503cfe6ca6f98e99a Mon Sep 17 00:00:00 2001 From: laklov Date: Sun, 2 Feb 2025 21:53:43 +0100 Subject: [PATCH 3/6] Remove changes in .vscode --- .vscode/settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 04230143f..34322c9ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,7 @@ "editor.defaultFormatter": "biomejs.biome" }, "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "biomejs.biome" }, "[typescriptreact]": { "editor.defaultFormatter": "biomejs.biome" From 65f48233006cd055ae2dc80b56f835afffc4024e Mon Sep 17 00:00:00 2001 From: Lasse Narula Date: Tue, 4 Feb 2025 20:40:44 +0100 Subject: [PATCH 4/6] Some fixes after smoke test --- packages/knip/src/plugins/react-router/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/knip/src/plugins/react-router/index.ts b/packages/knip/src/plugins/react-router/index.ts index cfcdb44b6..d8907de53 100644 --- a/packages/knip/src/plugins/react-router/index.ts +++ b/packages/knip/src/plugins/react-router/index.ts @@ -4,6 +4,7 @@ import { toEntry } from '../../util/input.js'; import { join } from '../../util/path.js'; import { hasDependency, load } from '../../util/plugin.js'; import type { PluginConfig, RouteConfigEntry } from './types.js'; +import vite from '../vite/index.js'; // https://reactrouter.com/start/framework/routing @@ -13,7 +14,7 @@ const enablers = ['@react-router/dev']; const isEnabled: IsPluginEnabled = ({ dependencies }) => hasDependency(dependencies, enablers); -const config: string[] = ['react-router.config.{js,ts}']; +const config: string[] = ['react-router.config.{js,ts}', ...vite.config]; const entry: string[] = []; @@ -24,6 +25,10 @@ const resolveEntryPaths: ResolveEntryPaths = async (localConfig, o const appDirectory = localConfig.appDirectory ?? 'app'; const appDir = join(configFileDir, appDirectory); + // If using flatRoutes from @react-router/fs-routes it will throw an error if this variable is not defined + // @ts-ignore + globalThis.__reactRouterAppDirectory = appDir; + let routeConfig: RouteConfigEntry[] = []; const routesPathTs = join(appDir, 'routes.ts'); From 572282598af51d80ed4eee9f0b355a9ffac3e971 Mon Sep 17 00:00:00 2001 From: Lasse Narula Date: Tue, 4 Feb 2025 20:42:56 +0100 Subject: [PATCH 5/6] Adjust fixture to test relative route file --- packages/knip/fixtures/plugins/react-router/app/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/knip/fixtures/plugins/react-router/app/routes.ts b/packages/knip/fixtures/plugins/react-router/app/routes.ts index 09d402403..37fc8cfdb 100644 --- a/packages/knip/fixtures/plugins/react-router/app/routes.ts +++ b/packages/knip/fixtures/plugins/react-router/app/routes.ts @@ -2,6 +2,6 @@ export default [ { file: "routes/home.tsx", index: true }, { file: "routes/layout.tsx", - children: [{ file: "routes/another-route.tsx" }], + children: [{ file: "./routes/another-route.tsx" }], }, ]; From 07472550d227513e596b0d79f9c71f1fe44ee48b Mon Sep 17 00:00:00 2001 From: Lasse Narula Date: Wed, 5 Feb 2025 18:45:36 +0100 Subject: [PATCH 6/6] organize imports --- packages/knip/src/plugins/react-router/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/knip/src/plugins/react-router/index.ts b/packages/knip/src/plugins/react-router/index.ts index d8907de53..61dda5526 100644 --- a/packages/knip/src/plugins/react-router/index.ts +++ b/packages/knip/src/plugins/react-router/index.ts @@ -3,8 +3,8 @@ import type { IsPluginEnabled, Plugin, ResolveEntryPaths } from '../../types/con import { toEntry } from '../../util/input.js'; import { join } from '../../util/path.js'; import { hasDependency, load } from '../../util/plugin.js'; -import type { PluginConfig, RouteConfigEntry } from './types.js'; import vite from '../vite/index.js'; +import type { PluginConfig, RouteConfigEntry } from './types.js'; // https://reactrouter.com/start/framework/routing