From d6e63b745a825b79bcde6b04d8c667cb676662f9 Mon Sep 17 00:00:00 2001 From: Vu Van Dung Date: Fri, 15 Dec 2023 10:16:20 +0700 Subject: [PATCH] support custom props of layouts with parallel routes, closes #2 Signed-off-by: Vu Van Dung --- apps/demo/app/parallel-route/@test/page.tsx | 3 ++ apps/demo/app/parallel-route/layout.tsx | 12 ++++++++ apps/demo/app/parallel-route/page.tsx | 3 ++ .../nextjs-route-types/src/generate-files.ts | 10 +++---- .../src/get-file-content.ts | 29 ++++++++++++++----- packages/nextjs-route-types/src/types.ts | 2 ++ 6 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 apps/demo/app/parallel-route/@test/page.tsx create mode 100644 apps/demo/app/parallel-route/layout.tsx create mode 100644 apps/demo/app/parallel-route/page.tsx diff --git a/apps/demo/app/parallel-route/@test/page.tsx b/apps/demo/app/parallel-route/@test/page.tsx new file mode 100644 index 0000000..2f2522e --- /dev/null +++ b/apps/demo/app/parallel-route/@test/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Hello world
; +} diff --git a/apps/demo/app/parallel-route/layout.tsx b/apps/demo/app/parallel-route/layout.tsx new file mode 100644 index 0000000..9054001 --- /dev/null +++ b/apps/demo/app/parallel-route/layout.tsx @@ -0,0 +1,12 @@ +import { expectType } from "ts-expect"; + +import type { LayoutProps } from "./$types"; + +export default function Layout(props: LayoutProps) { + expectType<{ test: React.ReactNode; children: React.ReactNode }>(props); + return ( +
+ {props.test} {props.children} +
+ ); +} diff --git a/apps/demo/app/parallel-route/page.tsx b/apps/demo/app/parallel-route/page.tsx new file mode 100644 index 0000000..2f2522e --- /dev/null +++ b/apps/demo/app/parallel-route/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Hello world
; +} diff --git a/packages/nextjs-route-types/src/generate-files.ts b/packages/nextjs-route-types/src/generate-files.ts index 11b663c..879e6ee 100644 --- a/packages/nextjs-route-types/src/generate-files.ts +++ b/packages/nextjs-route-types/src/generate-files.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; -import type { DirectoryTree } from "./types"; +import type { DirectoryTree, GetFileContent } from "./types"; import { removeCwdFromPath } from "./utils"; const FOLDER_NAME = ".next-types"; @@ -12,14 +12,14 @@ async function generateFilesRecursive( rootAppDir: string, tree: DirectoryTree, pathSoFar: string[], - getFileContent: (dirNames: string[]) => string, + getFileContent: GetFileContent, ) { await Promise.all( tree.map(async item => { const newPath = [...pathSoFar, item.name]; const fullPath = path.join(rootAppDir, ...newPath); await fs.mkdir(fullPath, { recursive: true }); - await fs.writeFile(path.join(fullPath, FILE_NAME), getFileContent(newPath)); + await fs.writeFile(path.join(fullPath, FILE_NAME), getFileContent(newPath, item.children)); await generateFilesRecursive(rootAppDir, item.children, newPath, getFileContent); }), ); @@ -28,10 +28,10 @@ async function generateFilesRecursive( export async function generateFiles( appDir: string, tree: DirectoryTree, - getFileContent: (dirNames: string[]) => string, + getFileContent: GetFileContent, ) { const rootAppDir = path.join(root, removeCwdFromPath(appDir)); await fs.mkdir(rootAppDir, { recursive: true }); - await fs.writeFile(path.join(rootAppDir, FILE_NAME), getFileContent([])); + await fs.writeFile(path.join(rootAppDir, FILE_NAME), getFileContent([], tree)); await generateFilesRecursive(rootAppDir, tree, [], getFileContent); } diff --git a/packages/nextjs-route-types/src/get-file-content.ts b/packages/nextjs-route-types/src/get-file-content.ts index 6aa7a3b..3e7827a 100644 --- a/packages/nextjs-route-types/src/get-file-content.ts +++ b/packages/nextjs-route-types/src/get-file-content.ts @@ -1,3 +1,5 @@ +import type { DirectoryTree, GetFileContent } from "./types"; + enum PathSegmentType { OptionalCatchAll = "OptionalCatchAll", CatchAll = "CatchAll", @@ -27,12 +29,26 @@ function getTsTypeFromPathSegmentType(type: PathSegmentType) { } } -export function getFileContent(path: string[]) { +function getParallelRoutesFromChildren(children: DirectoryTree) { + const parallelRoutes = children + .filter(child => child.name.startsWith("@")) + .map(child => child.name.slice(1)); + if (!parallelRoutes.includes("children")) parallelRoutes.push("children"); + return parallelRoutes; +} + +export const getFileContent: GetFileContent = (path, children) => { const params = getDynamicParamsFromPath(path); - const tsInterfaceContent = params + const paramsTsInterfaceContent = params .map(([type, name]) => ` "${name}": ${getTsTypeFromPathSegmentType(type)}`) .join(";\n") .trim(); + const parallelRoutes = getParallelRoutesFromChildren(children); + const layoutPropsTsInterfaceContent = parallelRoutes + .map(route => ` "${route}": ReactNode`) + .concat(" params: Params") + .join(";\n") + .trim(); return ` import type { NextRequest } from "next/server"; import type { ReactNode } from "react"; @@ -40,17 +56,14 @@ import type { ReactNode } from "react"; type EmptyObject = Record; export type SearchParams = Record; -export type Params = ${params.length ? `{\n ${tsInterfaceContent};\n}` : "EmptyObject"}; +export type Params = ${params.length ? `{\n ${paramsTsInterfaceContent};\n}` : "EmptyObject"}; export type DefaultProps = any; // Need help: I have never used default.tsx and its documentation is still WIP export type ErrorProps = { error: Error & { digest?: string }; reset: () => void; }; -export type LayoutProps = { - children: ReactNode; - params: Params; -}; +export type LayoutProps = {\n ${layoutPropsTsInterfaceContent};\n}; export type LoadingProps = EmptyObject; export type NotFoundProps = EmptyObject; export type PageProps = { @@ -65,4 +78,4 @@ export type RouteHandlerContext = { type HandlerReturn = Response | Promise; export type RouteHandler = (request: NextRequest, context: RouteHandlerContext) => HandlerReturn; `.trimStart(); -} +}; diff --git a/packages/nextjs-route-types/src/types.ts b/packages/nextjs-route-types/src/types.ts index 92c5d0b..283315f 100644 --- a/packages/nextjs-route-types/src/types.ts +++ b/packages/nextjs-route-types/src/types.ts @@ -4,3 +4,5 @@ export interface DirectoryTreeItem { } export type DirectoryTree = DirectoryTreeItem[]; + +export type GetFileContent = (dirNames: string[], children: DirectoryTree) => string;