diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index b039f82856..c5e748aa07 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -3,6 +3,207 @@ import { existsSync, mkdirSync, readFileSync } from 'node:fs' import { z } from 'zod' import { virtualRootRouteSchema } from './filesystem/virtual/config' import type { GeneratorPlugin } from './plugin/types' +import type { VirtualRootRoute } from '@tanstack/virtual-file-routes' + +export interface ConfigOptions { + /** + * 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 + *
+   * src/routes
+   * |── posts
+   * │   ├── -components  // Ignored with routeFileIgnorePrefix of '-'
+   * │   │   ├── Post.tsx
+   * │   ├── index.tsx
+   * │   ├── route.tsx
+   * 
+ * + * @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 + /** + * This option let's you append content to the end of the generated route tree file. + * + * @default [] + */ + routeTreeFileFooter?: Array + /** + * 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 + /** + * Configures which URI characters are allowed in path params that would ordinarily be escaped by encodeURIComponent. + * + * @link [API Docs](https://tanstack.com/router/latest/docs/framework/react/api/router/RouterOptionsType#pathparamsallowedcharacters-property) + * @link [Guide](https://tanstack.com/router/latest/docs/framework/react/guide/path-params#allowed-characters) + */ + pathParamsAllowedCharacters?: Array< + ';' | ':' | '@' | '&' | '=' | '+' | '$' | ',' + > + customScaffolding?: unknown + experimental?: { + enableCodeSplitting?: boolean + } + plugins?: Array + /** + * Atomic file writes (route files and the generated route tree file) are implemented by creating a temporary file first and then renaming it to their actual location. + * + * This config option allows to configure the path of the temp directory that will be used for creating those temporary files. If it is a relative path, it will be resolved to the current working directory. If this value is not set, process.env.TSR_TMP_DIR will be used. If process.env.TSR_TMP_DIR is not set, it will default to .tanstack/tmp relative to the current working directory. + */ + tmpDir?: string +} export const baseConfigSchema = z.object({ target: z.enum(['react', 'solid']).optional().default('react'), @@ -53,9 +254,9 @@ export const configSchema = baseConfigSchema.extend({ .optional(), plugins: z.array(z.custom()).optional(), tmpDir: z.string().optional().default(''), -}) +}) satisfies z.ZodType -export type Config = z.infer +export type Config = z.output type ResolveParams = { configDirectory: string @@ -66,7 +267,7 @@ export function resolveConfigPath({ configDirectory }: ResolveParams) { } export function getConfig( - inlineConfig: Partial = {}, + inlineConfig: ConfigOptions = {}, configDirectory?: string, ): Config { if (configDirectory === undefined) { diff --git a/packages/router-generator/src/filesystem/virtual/config.ts b/packages/router-generator/src/filesystem/virtual/config.ts index 5960c25ef8..574348a25f 100644 --- a/packages/router-generator/src/filesystem/virtual/config.ts +++ b/packages/router-generator/src/filesystem/virtual/config.ts @@ -38,8 +38,8 @@ const virtualRouteNodeSchema = z.union([ physicalSubTreeSchema, ]) -export const virtualRootRouteSchema: z.ZodType = z.object({ +export const virtualRootRouteSchema = z.object({ type: z.literal('root'), file: z.string(), children: z.array(virtualRouteNodeSchema).optional(), -}) +}) satisfies z.ZodType diff --git a/packages/router-generator/src/index.ts b/packages/router-generator/src/index.ts index 58ddef589c..13506a1867 100644 --- a/packages/router-generator/src/index.ts +++ b/packages/router-generator/src/index.ts @@ -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' export type { FileEventType, FileEvent, GeneratorEvent } from './generator' diff --git a/packages/router-plugin/src/core/config.ts b/packages/router-plugin/src/core/config.ts index e2999ebe5c..8fd2ac6985 100644 --- a/packages/router-plugin/src/core/config.ts +++ b/packages/router-plugin/src/core/config.ts @@ -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' @@ -78,6 +79,18 @@ const codeSplittingOptionsSchema = z.object({ }) export type DeletableNodes = (typeof DELETABLE_NODES)[number] +export interface ConfigOptions extends GeneratorConfigOptions { + /** + * Enables route generation. + * @default true + */ + enableRouteGeneration?: boolean + /** + * Additional fine grained control for code splitting. + */ + codeSplittingOptions?: CodeSplittingOptions +} + export const configSchema = generatorConfigSchema.extend({ enableRouteGeneration: z.boolean().optional(), codeSplittingOptions: z @@ -94,9 +107,9 @@ export const configSchema = generatorConfigSchema.extend({ .optional(), }) .optional(), -}) +}) satisfies z.ZodType -export const getConfig = (inlineConfig: Partial, root: string) => { +export const getConfig = (inlineConfig: ConfigInput, root: string) => { const config = getGeneratorConfig(inlineConfig, root) return configSchema.parse({ ...config, ...inlineConfig }) diff --git a/packages/router-plugin/src/core/route-autoimport-plugin.ts b/packages/router-plugin/src/core/route-autoimport-plugin.ts index dbb60be8d2..d57e7e5452 100644 --- a/packages/router-plugin/src/core/route-autoimport-plugin.ts +++ b/packages/router-plugin/src/core/route-autoimport-plugin.ts @@ -3,17 +3,17 @@ import babel from '@babel/core' import * as template from '@babel/template' import { getConfig } from './config' import { debug } from './utils' -import type { Config } from './config' +import type { ConfigInput } from './config' import type { UnpluginFactory } from 'unplugin' /** * This plugin adds imports for createFileRoute and createLazyFileRoute to the file route. */ export const unpluginRouteAutoImportFactory: UnpluginFactory< - Partial | undefined + ConfigInput | undefined > = (options = {}) => { let ROOT: string = process.cwd() - let userConfig = options as Config + let userConfig = options return { name: 'tanstack-router:autoimport', diff --git a/packages/router-plugin/src/core/router-code-splitter-plugin.ts b/packages/router-plugin/src/core/router-code-splitter-plugin.ts index 1a0d39752b..516cc36f91 100644 --- a/packages/router-plugin/src/core/router-code-splitter-plugin.ts +++ b/packages/router-plugin/src/core/router-code-splitter-plugin.ts @@ -20,7 +20,8 @@ import { decodeIdentifier } from './code-splitter/path-ids' import { debug } from './utils' import type { CodeSplitGroupings, SplitRouteIdentNodes } from './constants' import type { GetRoutesByFileMapResultValue } from '@tanstack/router-generator' -import type { Config } from './config' +import type { Config, ConfigInput } from './config' + import type { UnpluginContextMeta, UnpluginFactory, @@ -48,7 +49,7 @@ class FoundPluginInBeforeCode extends Error { externalPlugin: BannedBeforeExternalPlugin, pluginFramework: string, ) { - super(`We detected that the '${externalPlugin.pkg}' was passed before '@tanstack/router-plugin/${pluginFramework}'. 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/${pluginFramework}'. Please make sure that '@tanstack/router-plugin' is passed before '${externalPlugin.pkg}' and try again: e.g. plugins: [ tanstackRouter(), // Place this before ${externalPlugin.usage} @@ -61,7 +62,7 @@ plugins: [ const PLUGIN_NAME = 'unplugin:router-code-splitter' export const unpluginRouterCodeSplitterFactory: UnpluginFactory< - Partial | undefined + ConfigInput | undefined > = (options = {}, { framework }) => { let ROOT: string = process.cwd() let userConfig = options as Config diff --git a/packages/router-plugin/src/core/router-composed-plugin.ts b/packages/router-plugin/src/core/router-composed-plugin.ts index b92615096c..89ca3a807c 100644 --- a/packages/router-plugin/src/core/router-composed-plugin.ts +++ b/packages/router-plugin/src/core/router-composed-plugin.ts @@ -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 { ConfigInput } from './config' + import type { UnpluginFactory } from 'unplugin' export const unpluginRouterComposedFactory: UnpluginFactory< - Partial | undefined + ConfigInput | undefined > = (options = {}, meta) => { - const getPlugin = (pluginFactory: UnpluginFactory>) => { + const getPlugin = (pluginFactory: UnpluginFactory) => { const plugin = pluginFactory(options, meta) if (!Array.isArray(plugin)) { return [plugin] diff --git a/packages/router-plugin/src/core/router-generator-plugin.ts b/packages/router-plugin/src/core/router-generator-plugin.ts index 3f40a0c570..1191705d83 100644 --- a/packages/router-plugin/src/core/router-generator-plugin.ts +++ b/packages/router-plugin/src/core/router-generator-plugin.ts @@ -3,14 +3,14 @@ import { Generator, resolveConfigPath } from '@tanstack/router-generator' import { getConfig } from './config' import type { GeneratorEvent } from '@tanstack/router-generator' +import type { Config, ConfigInput } from './config' import type { FSWatcher } from 'chokidar' import type { UnpluginFactory } from 'unplugin' -import type { Config } from './config' const PLUGIN_NAME = 'unplugin:router-generator' export const unpluginRouterGeneratorFactory: UnpluginFactory< - Partial | undefined + ConfigInput | undefined > = (options = {}) => { const ROOT: string = process.cwd() let userConfig = options as Config diff --git a/packages/router-plugin/src/core/router-hmr-plugin.ts b/packages/router-plugin/src/core/router-hmr-plugin.ts index edabfee6d6..5206e3dc3b 100644 --- a/packages/router-plugin/src/core/router-hmr-plugin.ts +++ b/packages/router-plugin/src/core/router-hmr-plugin.ts @@ -2,8 +2,8 @@ import { generateFromAst, logDiff, parseAst } from '@tanstack/router-utils' import { routeHmrStatement } from './route-hmr-statement' import { debug } from './utils' import { getConfig } from './config' +import type { Config, ConfigInput } from './config' import type { UnpluginFactory } from 'unplugin' -import type { Config } from './config' /** * This plugin adds HMR support for file routes. @@ -17,7 +17,7 @@ const includeCode = [ 'createRootRouteWithContext(', ] export const unpluginRouterHmrFactory: UnpluginFactory< - Partial | undefined + ConfigInput | undefined > = (options = {}) => { let ROOT: string = process.cwd() let userConfig = options as Config