From a2c4e525a7bafa2b83df408d4489c398fb6dd446 Mon Sep 17 00:00:00 2001 From: oedotme Date: Wed, 11 Jan 2023 20:56:04 +0200 Subject: [PATCH] feat(plugins): update generation + support pending, error, loader, action --- .../basic/src/pages/index.tsx | 6 + .../basic/src/routes.gen.tsx | 14 +- plugins/tanstack-react-router/package.json | 1 + plugins/tanstack-react-router/src/generate.ts | 173 +++++------------- plugins/tanstack-react-router/src/index.ts | 2 +- plugins/tanstack-react-router/src/template.ts | 23 +++ 6 files changed, 83 insertions(+), 136 deletions(-) create mode 100644 plugins/tanstack-react-router/src/template.ts diff --git a/examples/tanstack-react-router/basic/src/pages/index.tsx b/examples/tanstack-react-router/basic/src/pages/index.tsx index 09a718b..dd940bd 100644 --- a/examples/tanstack-react-router/basic/src/pages/index.tsx +++ b/examples/tanstack-react-router/basic/src/pages/index.tsx @@ -1,3 +1,9 @@ +export const PendingComponent = () =>
Pending
+export const ErrorComponent = () =>
Error
+ +export const Loader = () => console.log('Loader') +export const Action = () => console.log('Action') + export default function Home() { return

Home - Basic

} diff --git a/examples/tanstack-react-router/basic/src/routes.gen.tsx b/examples/tanstack-react-router/basic/src/routes.gen.tsx index 5a5f8da..357212f 100644 --- a/examples/tanstack-react-router/basic/src/routes.gen.tsx +++ b/examples/tanstack-react-router/basic/src/routes.gen.tsx @@ -1,3 +1,4 @@ +// Generouted, changes to this file will be overriden import { Fragment } from 'react' import { createReactRouter, createRouteConfig, lazy, RouterProvider } from '@tanstack/react-router' @@ -7,10 +8,17 @@ import NoMatch from './pages/404' const root = createRouteConfig({ component: App || Fragment }) const _404 = root.createRoute({ path: '*', component: NoMatch || Fragment }) const posts_layout = root.createRoute({ path: 'posts', component: lazy(() => import('./pages/posts/_layout')) }) -const about = root.createRoute({ path: 'about', component: lazy(() => import('./pages/about')) }) -const index = root.createRoute({ path: '/', component: lazy(() => import('./pages/index')) }) -const postsid = posts_layout.createRoute({ path: '$id', component: lazy(() => import('./pages/posts/[id]')) }) const postsindex = posts_layout.createRoute({ path: '/', component: lazy(() => import('./pages/posts/index')) }) +const postsid = posts_layout.createRoute({ path: '$id', component: lazy(() => import('./pages/posts/[id]')) }) +const about = root.createRoute({ path: 'about', component: lazy(() => import('./pages/about')) }) +const index = root.createRoute({ + path: '/', + component: lazy(() => import('./pages/index')), + pendingComponent: lazy(() => import('./pages/index').then((m) => ({ default: m.PendingComponent }))), + errorComponent: lazy(() => import('./pages/index').then((m) => ({ default: m.ErrorComponent }))), + loader: (...args) => import('./pages/index').then((m) => m.Loader.apply(m.Loader, args as any)), + action: (...args) => import('./pages/index').then((m) => m.Action.apply(m.Action, args as any)), +}) const config = root.addChildren([posts_layout.addChildren([postsindex, postsid]), about, index, _404]) diff --git a/plugins/tanstack-react-router/package.json b/plugins/tanstack-react-router/package.json index c3ffb2a..fe89489 100644 --- a/plugins/tanstack-react-router/package.json +++ b/plugins/tanstack-react-router/package.json @@ -54,6 +54,7 @@ "pre-release": "pnpm build" }, "devDependencies": { + "@generouted/core": "workspace:*", "fast-glob": "^3.2.12", "tsup": "^6.5.0", "typescript": "^4.9.4", diff --git a/plugins/tanstack-react-router/src/generate.ts b/plugins/tanstack-react-router/src/generate.ts index 9d11c95..18badb9 100644 --- a/plugins/tanstack-react-router/src/generate.ts +++ b/plugins/tanstack-react-router/src/generate.ts @@ -1,47 +1,13 @@ -import { writeFileSync, readFileSync } from 'fs' +import { writeFileSync } from 'fs' import fg from 'fast-glob' +import { patterns as corePatterns, getRoutes } from '@generouted/core' + import { format } from './format' import { Options } from './options' +import { template } from './template' -const template = `import { Fragment } from 'react' -import { createReactRouter, createRouteConfig, lazy, RouterProvider } from '@tanstack/react-router' - -// __imports__ - -// __modules__ - -const config = root.addChildren([ - // __routes__, - _404, -]) - -const router = createReactRouter({ routeConfig: config, defaultPreload: 'intent' }) - -declare module '@tanstack/react-router' { - interface RegisterRouter { - router: typeof router - } -} - -export const Routes = () => -` -const patterns = { - route: [/^.?\/src\/pages\/|^\/pages\/|\.(jsx|tsx)$/g, ''], - splat: [/\[\.{3}.+\]/, '*'], - param: [/\[([^\]]+)\]/g, '$$$1'], - slash: [/index|\./g, '/'], -} as const - -type BaseRoute = { path?: string; children?: BaseRoute[] } & Record - -const getRouteId = (path: string) => path.replace(...patterns.route).replace(/\W/g, '') -const getRouteExports = (content: string) => ({ - default: /^export\s+default\s/gm.test(content), - loader: /^export\s+(const|function)\s+Loader(\s|\()/gm.test(content), - action: /^export\s+(const|function)\s+Action(\s|\()/gm.test(content), - errorElement: /^export\s+(const|function)\s+ErrorElement(\s|\()/gm.test(content), -}) +const patterns = Object.assign(corePatterns, { param: [/\[([^\]]+)\]/g, '$$$1'] }) const generateRoutes = async () => { const source = ['./src/pages/**/[\\w[]*.{jsx,tsx}'] @@ -50,109 +16,52 @@ const generateRoutes = async () => { const imports: string[] = [] const modules: string[] = [] - const filteredRoutes = files - .filter((key) => !key.includes('/_') || /(_app|_layout)\.(jsx|tsx)$/.test(key)) - .sort((a, z) => +z.includes('_layout') - +a.includes('_layout')) - .sort((a, z) => +z.includes('pages/_app') - +a.includes('pages/_app')) - - const ids = filteredRoutes.map((route) => getRouteId(route)) + const { routes, preserved, exports, count } = getRoutes( + files, + (key, exports) => { + const { pendingComponent: pending, errorComponent: error, loader, action } = exports + const module = `import('./pages/${key.replace(...patterns.route)}')` + + return { + _component: `lazy(() => ${module})`, + _pendingComponent: pending ? `lazy(() => ${module}.then((m) => ({ default: m.PendingComponent })))` : '', + _errorComponent: error ? `lazy(() => ${module}.then((m) => ({ default: m.ErrorComponent })))` : '', + _loader: loader ? `(...args) => ${module}.then((m) => m.Loader.apply(m.Loader, args as any))` : '', + _action: action ? `(...args) => ${module}.then((m) => m.Action.apply(m.Action, args as any))` : '', + } + }, + patterns + ) - if (ids.includes('_app')) { + if (preserved._app && exports['_app'].default) { imports.push(`import App from './pages/_app'`) modules.push(`const root = createRouteConfig({ component: App || Fragment })`) } else { modules.push(`const root = createRouteConfig({ component: Fragment })`) } - if (ids.includes('404')) { + if (preserved._404 && exports['404'].default) { imports.push(`import NoMatch from './pages/404'`) - const props = [`path: '*'`, 'component: NoMatch || Fragment'] - modules.push(`const _404 = root.createRoute({ ${props.join(', ')} })`) + modules.push(`const _404 = root.createRoute({ path: '*', component: NoMatch || Fragment })`) } else { - const props = [`path: '*'`, 'component: Fragment'] - modules.push(`const _404 = root.createRoute({ ${props.join(', ')} })`) + modules.push(`const _404 = root.createRoute({ path: '*', component: Fragment })`) } - const routes = filteredRoutes.reduce((routes, key) => { - const id = getRouteId(key) + const config = JSON.stringify(routes, function (key, value) { + if (key === 'id') { + const { id, pid, path, ...properties } = this - const content = readFileSync(key, { encoding: 'utf-8' }) - const exports = getRouteExports(content) + const options = Object.entries(properties) + .filter(([key, value]) => key.startsWith('_') && Boolean(value)) + .map(([key, value]) => `${key.replace('_', '')}: ${value}`) - if (!exports.default) return routes - if (['_app', '404'].includes(id) || ids.includes(id + '_layout')) return routes - - const route = { - component: `lazy(() => import('./pages/${key.replace(...patterns.route)}'))`, + const props = [path ? `path: '${path}'` : `id: '${id}'`, ...options].filter(Boolean) + modules.push(`const ${id} = ${pid}.createRoute({ ${props.join(', ')} })`) } - const segments = key - .replace(...patterns.route) - .replace(...patterns.splat) - .replace(...patterns.param) - .split('/') - .filter(Boolean) - - segments.reduce((parent, segment, index) => { - const path = segment.replace(...patterns.slash) - const root = index === 0 - const leaf = index === segments.length - 1 && segments.length > 1 - const node = !root && !leaf - const layout = segment === '_layout' - const insert = /^\w|\//.test(path) ? 'unshift' : 'push' - - if (root) { - const dynamic = path.startsWith(':') || path === '*' - if (dynamic) return parent - - const last = segments.length === 1 - if (last) { - routes.push({ id, path }) - const props = [`path: '${path}'`, `component: ${route.component}`] - modules.push(`const ${id} = root.createRoute({ ${props.join(', ')} })`) - return parent - } - } - - if (root || node) { - const current = root ? routes : parent.children - const found = current?.find((route) => route.path === path) - if (found) found.children ??= [] - else { - const _id = segments.slice(0, index + 1).join('') - const pid = parent?.id || 'root' - const props = [`path: '${path}'`] - const route = `const ${_id} = ${pid}.createRoute({ ${props.join(', ')} })` - if (!(_id + '_layout' === id) && !modules.includes(route)) modules.push(route) - current?.[insert]({ id: _id, path, children: [] }) - } - return found || (current?.[insert === 'unshift' ? 0 : current.length - 1] as BaseRoute) - } - - if (layout) { - const _id = segments.slice(0, index - 1).join('') - const pid = _id || 'root' - const props = [`path: '${parent?.path}'`, `component: ${route.component}`] - modules.push(`const ${id} = ${pid}.createRoute({ ${props.join(', ')} })`) - return Object.assign(parent, { id }) - } - - if (leaf) { - const pid = parent?.id || 'root' - const props = [`path: '${path}'`, `component: ${route.component}`] - modules.push(`const ${id} = ${pid}.createRoute({ ${props.join(', ')} })`) - parent?.children?.[insert]({ id, path }) - } - - return parent - }, {} as BaseRoute) - - return routes - }, [] as BaseRoute[]) - - const __imports__ = imports.join('\n') - const __modules__ = modules.join('\n') - const __routes__ = JSON.stringify(routes, (key, value) => (key !== 'path' ? value : undefined)) + if (['pid', 'path'].includes(key) || key.startsWith('_')) return undefined + return value + }) .replace(/"id":"(\w+)"/g, '$1') .replace(/^\[|\]$|{|}/g, '') .replace(/\[/g, '([') @@ -160,12 +69,12 @@ const generateRoutes = async () => { .replace(/,"children":/g, '.addChildren') .replace(/\),/g, '),\n ') - const file = template - .replace('// __imports__', __imports__) - .replace('// __modules__', __modules__) - .replace('// __routes__', __routes__) + const content = template + .replace('// imports', imports.join('\n')) + .replace('// modules', modules.join('\n')) + .replace('// config', config) - return { content: file, count: ids.length - 1 } + return { content, count } } let latestContent = '' diff --git a/plugins/tanstack-react-router/src/index.ts b/plugins/tanstack-react-router/src/index.ts index 85d8c27..a388d5b 100644 --- a/plugins/tanstack-react-router/src/index.ts +++ b/plugins/tanstack-react-router/src/index.ts @@ -4,7 +4,7 @@ import { generate } from './generate' import { defaultOptions } from './options' export default function Generouted(): Plugin { - const resolvedOptions = Object.assign(defaultOptions, {}) + const resolvedOptions = { ...defaultOptions } return { name: 'generouted/tanstack-react-router', diff --git a/plugins/tanstack-react-router/src/template.ts b/plugins/tanstack-react-router/src/template.ts new file mode 100644 index 0000000..ffae94c --- /dev/null +++ b/plugins/tanstack-react-router/src/template.ts @@ -0,0 +1,23 @@ +export const template = `// Generouted, changes to this file will be overriden +import { Fragment } from 'react' +import { createReactRouter, createRouteConfig, lazy, RouterProvider } from '@tanstack/react-router' + +// imports + +// modules + +const config = root.addChildren([ + // config, + _404, +]) + +const router = createReactRouter({ routeConfig: config, defaultPreload: 'intent' }) + +declare module '@tanstack/react-router' { + interface RegisterRouter { + router: typeof router + } +} + +export const Routes = () => +`