Skip to content

docs: add jsdoc comments to plugin/generator types #4204

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: alpha
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 189 additions & 3 deletions packages/router-generator/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,192 @@ import path from 'node:path'
import { existsSync, readFileSync } from 'node:fs'
import { z } from 'zod'
import { virtualRootRouteSchema } from './filesystem/virtual/config'
import type { VirtualRootRoute } from '@tanstack/virtual-file-routes'

export interface ConfigOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big fan of this, helps a lot when in middle of coding :)

/**
* The framework of your application, either `react` or `solid`.
*
* @default 'react'
*/
target?: 'react' | 'solid'
/**
* This option is used to configure the Virtual File Routes feature. See the {@link https://tanstack.com/router/latest/docs/framework/react/routing/virtual-file-routes Virtual File Routes} guide for more information.
*
* @default undefined
*/
virtualRouteConfig?: string | VirtualRootRoute // TODO: This should be a type
/**
* This option is used to identify route files in the route directory. This means that only files that start with this prefix will be considered for routing.
*
* @default '' all files in the route directory will be considered for routing.
*/
routeFilePrefix?: string
/**
* This option is used to ignore specific files and directories in the route directory. This can be useful if you want to "opt-in" certain files or directories that you do not want to be considered for routing.
*
* When using this option, it allows you have structures like this where it let's you co-located related files that are not route files.
*
* @example
* <pre>
* src/routes
* |── posts
* │ ├── -components // Ignored with routeFileIgnorePrefix of '-'
* │ │ ├── Post.tsx
* │ ├── index.tsx
* │ ├── route.tsx
* </pre>
*
* @default '-'
*/
routeFileIgnorePrefix?: string
/**
* This option is used to ignore specific files and directories in the route directory. It can be used in regular expression format. For example, .((css|const).ts)|test-page will ignore files / directories with names containing .css.ts, .const.ts or test-page.
*
* @default undefined
*/
routeFileIgnorePattern?: string
/**
* This is the path to the directory where the route files are located, relative to the cwd (current working directory).
*
* By default, the value is set to the following and cannot be set to an empty string or undefined.
*
* @default './src/routes'
*/
routesDirectory?: string
/**
* This is the path to the file where the generated route tree will be saved, relative to the cwd (current working directory).
*
* By default, the value is set to the following and cannot be set to an empty string or undefined.
*
* @default './src/routeTree.gen.ts'
*/
generatedRouteTree?: string
/**
* When your generated route tree is generated and when you first create a new route, those files will be formatted with the quote style you specify here.
*
* **Tip**: You should ignore the path of your generated route tree file from your linter and formatter to avoid conflicts.
*
* @default 'single'
*/
quoteStyle?: 'single' | 'double',
/**
* When your generated route tree is generated and when you first create a new route, those files will be formatted with semicolons if this option is set to true.
*
* **Tip**: You should ignore the path of your generated route tree file from your linter and formatter to avoid conflicts.
*
* @default false
*/
semicolons?: boolean,
/**
* This option is used to disable generating types for the route tree.
*
* If set to true, the generated route tree will not include any types and will be written as a .js file instead of a .ts file.
*
* @default false
*/
disableTypes?: boolean;
/**
* This option adds file extensions to the route names in the generated route tree.
*
* @default false
*/
addExtensions?: boolean;
/**
* This option turns off the console logging for the route generation process.
*
* @default false
*/
disableLogging?: boolean;
/**
* {@link https://tanstack.com/start TanStack Start} leverages the generatedRouteTree file to also store a JSON tree which allows Start to easily traverse the available route tree to understand the routing structure of the application. This JSON tree is saved at the end of the generated route tree file.
*
* This option allows you to disable the generation of the manifest.
*
* @default false
*/
disableManifestGeneration?: boolean;
/**
* This option turns on the formatting function on the generated route tree file, which can be time-consuming for large projects.
*
* @default true
*/
enableRouteTreeFormatting?: boolean;
__enableAPIRoutesGeneration?: boolean;
/**
* As a framework, TanStack Start supports the concept of API routes. This option configures the base path for API routes.
*
* This means that all API routes will be prefixed with /api.
*
* This configuration value is only useful if you are using TanStack Start.
*
* **Important**: This default value may conflict with your own project's routing if you planned on having a normal route with the same base path. You can change this value to avoid conflicts.
*
* @default '/api'
*/
apiBase?: string;
/**
* This option let's you prepend content to the start of the generated route tree file.
*
* @default
* ```ts
[
'\/* eslint-disable *\/',
'// @ts-nocheck',
'// noinspection JSUnusedGlobalSymbols',
]
* ```
*/
routeTreeFileHeader?: Array<string>;
/**
* This option let's you append content to the end of the generated route tree file.
*
* @default []
*/
routeTreeFileFooter?: Array<string>;
/**
* This feature is only available is you are using the TanStack Router Bundler Plugin.
*
* This option is used to enable automatic code-splitting for non-critical route configuration items. See the "Automatic Code-Splitting" guide for more information.
*
* **Important**: The next major release of TanStack Router (i.e. v2), will have this value defaulted to `true`.
*
* @default false
*/
autoCodeSplitting?: boolean;
/**
* As mentioned in the Routing Concepts guide, an index route is a route that is matched when the URL path is exactly the same as the parent route. The `indexToken` is used to identify the index route file in the route directory.
*
* With a value of `index`, the following filenames would equal the same runtime URL:
*
* ```txt
* src/routes/posts.index.tsx -> /posts/
* src/routes/posts/index.tsx -> /posts/
* ```
*
* @default 'index'
*/
indexToken?: string;
/**
* As mentioned in the Routing Concepts guide, a layout route is rendered at the specified path, and the child routes are rendered within the layout route. The `routeToken` is used to identify the layout route file in the route directory.
*
* With a value of `index`, the following filenames would equal the same runtime URL:
*
* ```txt
* src/routes/posts.tsx -> /posts
* src/routes/posts.route.tsx -> /posts
* src/routes/posts/route.tsx -> /posts
* ```
*
* @default 'route'
*/
routeToken?: string;
pathParamsAllowedCharacters?: Array<string>;
customScaffolding?: unknown;
experimental?: {
enableCodeSplitting?: boolean;
};
}

export const baseConfigSchema = z.object({
target: z.enum(['react', 'solid']).optional().default('react'),
Expand Down Expand Up @@ -51,9 +237,9 @@ export const configSchema = baseConfigSchema.extend({
enableCodeSplitting: z.boolean().optional(),
})
.optional(),
})
}) satisfies z.ZodType<ConfigOptions>

export type Config = z.infer<typeof configSchema>
export type Config = z.output<typeof configSchema>

type ResolveParams = {
configDirectory: string
Expand All @@ -64,7 +250,7 @@ export function resolveConfigPath({ configDirectory }: ResolveParams) {
}

export function getConfig(
inlineConfig: Partial<Config> = {},
inlineConfig: ConfigOptions = {},
configDirectory?: string,
): Config {
if (configDirectory === undefined) {
Expand Down
4 changes: 2 additions & 2 deletions packages/router-generator/src/filesystem/virtual/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const virtualRouteNodeSchema = z.union([
physicalSubTreeSchema,
])

export const virtualRootRouteSchema: z.ZodType<VirtualRootRoute> = z.object({
export const virtualRootRouteSchema = z.object({
type: z.literal('root'),
file: z.string(),
children: z.array(virtualRouteNodeSchema).optional(),
})
}) satisfies z.ZodType<VirtualRootRoute>;
2 changes: 1 addition & 1 deletion packages/router-generator/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export {
resolveConfigPath,
baseConfigSchema,
} from './config'
export type { Config, BaseConfig } from './config'
export type { Config, BaseConfig, ConfigOptions } from './config'

export { generator } from './generator'

Expand Down
17 changes: 15 additions & 2 deletions packages/router-plugin/src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
configSchema as generatorConfigSchema,
getConfig as getGeneratorConfig,
} from '@tanstack/router-generator'
import type { ConfigOptions as GeneratorConfigOptions } from '@tanstack/router-generator';
import type { RegisteredRouter, RouteIds } from '@tanstack/router-core'
import type { CodeSplitGroupings } from './constants'

Expand Down Expand Up @@ -62,16 +63,28 @@ const codeSplittingOptionsSchema = z.object({
defaultBehavior: splitGroupingsSchema.optional(),
})

export interface ConfigOptions extends GeneratorConfigOptions {
/**
* Enables route generation.
* @default true
*/
enableRouteGeneration?: boolean
/**
* Additional fine grained control for splitting.
*/
codeSplittingOptions?: CodeSplittingOptions
}

export const configSchema = generatorConfigSchema.extend({
enableRouteGeneration: z.boolean().optional(),
codeSplittingOptions: z
.custom<CodeSplittingOptions>((v) => {
return codeSplittingOptionsSchema.parse(v)
})
.optional(),
})
}) satisfies z.ZodType<ConfigOptions>

export const getConfig = (inlineConfig: Partial<Config>, root: string) => {
export const getConfig = (inlineConfig: ConfigOptions, root: string) => {
const config = getGeneratorConfig(inlineConfig, root)

return configSchema.parse({ ...config, ...inlineConfig })
Expand Down
4 changes: 2 additions & 2 deletions packages/router-plugin/src/core/route-autoimport-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import babel from '@babel/core'
import * as template from '@babel/template'
import { getConfig } from './config'
import { debug, fileIsInRoutesDirectory } from './utils'
import type { Config } from './config'
import type { Config, ConfigOptions } from './config'
import type { UnpluginFactory } from 'unplugin'

/**
* This plugin adds imports for createFileRoute and createLazyFileRoute to the file route.
*/
export const unpluginRouteAutoImportFactory: UnpluginFactory<
Partial<Config> | undefined
ConfigOptions | undefined
> = (options = {}) => {
let ROOT: string = process.cwd()
let userConfig = options as Config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import {
} from './constants'
import { decodeIdentifier } from './code-splitter/path-ids'
import { debug, fileIsInRoutesDirectory } from './utils'
import type { Config, ConfigOptions } from './config'
import type { CodeSplitGroupings, SplitRouteIdentNodes } from './constants'

import type { Config } from './config'
import type {
UnpluginContextMeta,
UnpluginFactory,
Expand Down Expand Up @@ -49,7 +49,7 @@ const bannedBeforeExternalPlugins: Array<BannedBeforeExternalPlugin> = [

class FoundPluginInBeforeCode extends Error {
constructor(externalPlugin: BannedBeforeExternalPlugin, framework: string) {
super(`We detected that the '${externalPlugin.pkg}' was passed before '@tanstack/router-plugin'. Please make sure that '@tanstack/router-plugin' is passed before '${externalPlugin.pkg}' and try again:
super(`We detected that the '${externalPlugin.pkg}' was passed before '@tanstack/router-plugin'. Please make sure that '@tanstack/router-plugin' is passed before '${externalPlugin.pkg}' and try again:
e.g.
plugins: [
TanStackRouter${capitalizeFirst(framework)}(), // Place this before ${externalPlugin.usage}
Expand All @@ -62,7 +62,7 @@ plugins: [
const PLUGIN_NAME = 'unplugin:router-code-splitter'

export const unpluginRouterCodeSplitterFactory: UnpluginFactory<
Partial<Config> | undefined
ConfigOptions | undefined
> = (options = {}, { framework }) => {
let ROOT: string = process.cwd()
let userConfig = options as Config
Expand Down
7 changes: 4 additions & 3 deletions packages/router-plugin/src/core/router-composed-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { unpluginRouterGeneratorFactory } from './router-generator-plugin'
import { unpluginRouterCodeSplitterFactory } from './router-code-splitter-plugin'
import { unpluginRouterHmrFactory } from './router-hmr-plugin'
import { unpluginRouteAutoImportFactory } from './route-autoimport-plugin'
import type { Config } from './config'
import type { ConfigOptions } from './config'

import type { UnpluginFactory } from 'unplugin'

export const unpluginRouterComposedFactory: UnpluginFactory<
Partial<Config> | undefined
ConfigOptions | undefined
> = (options = {}, meta) => {
const getPlugin = (pluginFactory: UnpluginFactory<Partial<Config>>) => {
const getPlugin = (pluginFactory: UnpluginFactory<Partial<ConfigOptions>>) => {
const plugin = pluginFactory(options, meta)
if (!Array.isArray(plugin)) {
return [plugin]
Expand Down
4 changes: 2 additions & 2 deletions packages/router-plugin/src/core/router-generator-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { isAbsolute, join, normalize, resolve } from 'node:path'
import { generator, resolveConfigPath } from '@tanstack/router-generator'

import { getConfig } from './config'
import type { Config, ConfigOptions } from './config';
import type { FSWatcher } from 'chokidar'
import type { UnpluginFactory } from 'unplugin'
import type { Config } from './config'

let lock = false
const checkLock = () => lock
Expand All @@ -15,7 +15,7 @@ const setLock = (bool: boolean) => {
const PLUGIN_NAME = 'unplugin:router-generator'

export const unpluginRouterGeneratorFactory: UnpluginFactory<
Partial<Config> | undefined
ConfigOptions | undefined
> = (options = {}) => {
let ROOT: string = process.cwd()
let userConfig = options as Config
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an unrelated point, this type casting here is risky. Config is the z.output type, which will contain defined properties due to the default option, but what's being passed it hasn't been parsed by Zod yet (and so is the z.input type).

For example, userConfig.routesDirectory may not have been defined yet, so the usage on L24-26 is unsafe.

There is some complexity here, because we can't immediately parse the provided schema, as there may be later config retrieved from a tsr.config.ts file (here), and the default values would override anything provided in the config file. This could be fixed by allowing the file config to override the inline config, if that is the desired hierarchy.

Expand Down
4 changes: 2 additions & 2 deletions packages/router-plugin/src/core/router-hmr-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { generateFromAst, logDiff, parseAst } from '@tanstack/router-utils'
import { getConfig } from './config'
import { routeHmrStatement } from './route-hmr-statement'
import { debug, fileIsInRoutesDirectory } from './utils'
import type { Config } from './config'
import type { Config, ConfigOptions } from './config'
import type { UnpluginFactory } from 'unplugin'

/**
Expand All @@ -11,7 +11,7 @@ import type { UnpluginFactory } from 'unplugin'
* handles HMR for code-split routes itself.
*/
export const unpluginRouterHmrFactory: UnpluginFactory<
Partial<Config> | undefined
ConfigOptions | undefined
> = (options = {}) => {
let ROOT: string = process.cwd()
let userConfig = options as Config
Expand Down