fe-scaffold

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

FE Scaffolding

FE 脚手架

$ARGUMENTS
를 파싱하여 해당하는 타입의 보일러플레이트를 생성한다.
解析
$ARGUMENTS
并生成对应类型的模板文件。

지원 타입

支持的类型

타입설명예시
component
React 컴포넌트 + 테스트
/fe-scaffold component UserProfile
page
Next.js App Router 페이지
/fe-scaffold page dashboard
api
Route Handler (API)
/fe-scaffold api users
hook
커스텀 훅 + 테스트
/fe-scaffold hook useDebounce
store
Zustand 스토어
/fe-scaffold store auth
feature
기능 모듈 (컴포넌트 + 훅 + 타입 + 테스트)
/fe-scaffold feature checkout
form
React Hook Form + Zod 스키마 폼
/fe-scaffold form LoginForm
인자가 없으면 사용자에게 어떤 타입을 생성할지 질문한다.
类型说明示例
component
React组件 + 测试
/fe-scaffold component UserProfile
page
Next.js App Router页面
/fe-scaffold page dashboard
api
Route Handler (API)
/fe-scaffold api users
hook
自定义Hook + 测试
/fe-scaffold hook useDebounce
store
Zustand Store
/fe-scaffold store auth
feature
功能模块(组件 + Hook + 类型 + 测试)
/fe-scaffold feature checkout
form
React Hook Form + Zod Schema表单
/fe-scaffold form LoginForm
如果没有传入参数,会询问用户要生成哪种类型的模板。

템플릿 규칙

模板规则

component

component

파일 생성 위치:
src/components/[이름]/[이름].tsx
+
[이름].test.tsx
tsx
// src/components/[Name]/[Name].tsx
import { cn } from "@/lib/utils";

interface [Name]Props {
  className?: string;
  children?: React.ReactNode;
}

function [Name]({ className, children }: [Name]Props) {
  return (
    <div className={cn("", className)}>
      {children}
    </div>
  );
}

export { [Name] };
export type { [Name]Props };
tsx
// src/components/[Name]/[Name].test.tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [Name] } from "./[Name]";

describe("[Name]", () => {
  it("renders children", () => {
    render(<[Name]>test</[Name]>);
    expect(screen.getByText("test")).toBeInTheDocument();
  });
});
文件生成位置:
src/components/[名称]/[名称].tsx
+
[名称].test.tsx
tsx
// src/components/[Name]/[Name].tsx
import { cn } from "@/lib/utils";

interface [Name]Props {
  className?: string;
  children?: React.ReactNode;
}

function [Name]({ className, children }: [Name]Props) {
  return (
    <div className={cn("", className)}>
      {children}
    </div>
  );
}

export { [Name] };
export type { [Name]Props };
tsx
// src/components/[Name]/[Name].test.tsx
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [Name] } from "./[Name]";

describe("[Name]", () => {
  it("renders children", () => {
    render(<[Name]>test</[Name]>);
    expect(screen.getByText("test")).toBeInTheDocument();
  });
});

page

page

파일 생성 위치:
src/app/[이름]/page.tsx
+
layout.tsx
(필요 시) +
loading.tsx
tsx
// src/app/[name]/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "[Name]",
  description: "[Name] page",
};

export default function [Name]Page() {
  return (
    <main>
      <h1>[Name]</h1>
    </main>
  );
}
tsx
// src/app/[name]/loading.tsx
import { Skeleton } from "@/components/ui/skeleton";

export default function [Name]Loading() {
  return <Skeleton className="h-screen w-full" />;
}
文件生成位置:
src/app/[名称]/page.tsx
+
layout.tsx
(需要时) +
loading.tsx
tsx
// src/app/[name]/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "[Name]",
  description: "[Name] page",
};

export default function [Name]Page() {
  return (
    <main>
      <h1>[Name]</h1>
    </main>
  );
}
tsx
// src/app/[name]/loading.tsx
import { Skeleton } from "@/components/ui/skeleton";

export default function [Name]Loading() {
  return <Skeleton className="h-screen w-full" />;
}

api

api

파일 생성 위치:
src/app/api/[이름]/route.ts
typescript
// src/app/api/[name]/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  try {
    // TODO: implement
    return NextResponse.json({ data: [] });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    // TODO: implement
    return NextResponse.json({ data: body }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}
文件生成位置:
src/app/api/[名称]/route.ts
typescript
// src/app/api/[name]/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  try {
    // TODO: implement
    return NextResponse.json({ data: [] });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    // TODO: implement
    return NextResponse.json({ data: body }, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

hook

hook

파일 생성 위치:
src/hooks/[이름].ts
+
[이름].test.ts
typescript
// src/hooks/[useName].ts
import { useCallback, useState } from "react";

function [useName]() {
  // TODO: implement
  return {};
}

export { [useName] };
typescript
// src/hooks/[useName].test.ts
import { renderHook, act } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [useName] } from "./[useName]";

describe("[useName]", () => {
  it("should work", () => {
    const { result } = renderHook(() => [useName]());
    expect(result.current).toBeDefined();
  });
});
文件生成位置:
src/hooks/[名称].ts
+
[名称].test.ts
typescript
// src/hooks/[useName].ts
import { useCallback, useState } from "react";

function [useName]() {
  // TODO: implement
  return {};
}

export { [useName] };
typescript
// src/hooks/[useName].test.ts
import { renderHook, act } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { [useName] } from "./[useName]";

describe("[useName]", () => {
  it("should work", () => {
    const { result } = renderHook(() => [useName]());
    expect(result.current).toBeDefined();
  });
});

store

store

파일 생성 위치:
src/stores/[이름]Store.ts
typescript
// src/stores/[name]Store.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";

interface [Name]State {
  // TODO: define state
}

interface [Name]Actions {
  // TODO: define actions
  reset: () => void;
}

const initial[Name]State: [Name]State = {
  // TODO: initial values
};

const use[Name]Store = create<[Name]State & [Name]Actions>()(
  devtools(
    (set) => ({
      ...initial[Name]State,
      reset: () => set(initial[Name]State),
    }),
    { name: "[name]-store" }
  )
);

export { use[Name]Store };
export type { [Name]State, [Name]Actions };
文件生成位置:
src/stores/[名称]Store.ts
typescript
// src/stores/[name]Store.ts
import { create } from "zustand";
import { devtools } from "zustand/middleware";

interface [Name]State {
  // TODO: define state
}

interface [Name]Actions {
  // TODO: define actions
  reset: () => void;
}

const initial[Name]State: [Name]State = {
  // TODO: initial values
};

const use[Name]Store = create<[Name]State & [Name]Actions>()(
  devtools(
    (set) => ({
      ...initial[Name]State,
      reset: () => set(initial[Name]State),
    }),
    { name: "[name]-store" }
  )
);

export { use[Name]Store };
export type { [Name]State, [Name]Actions };

feature

feature

기능 모듈 전체를 생성한다. 위치:
src/components/[feature]/
src/components/[feature]/
├── [Feature].tsx           # 메인 컴포넌트
├── [Feature].test.tsx      # 테스트
├── use[Feature].ts         # 기능 전용 훅
├── [feature].types.ts      # 타입 정의
└── index.ts                # barrel export
生成完整的功能模块。位置:
src/components/[feature]/
src/components/[feature]/
├── [Feature].tsx           # 主组件
├── [Feature].test.tsx      # 测试文件
├── use[Feature].ts         # 功能专属Hook
├── [feature].types.ts      # 类型定义
└── index.ts                # 统一导出文件

form

form

React Hook Form + Zod 기반 폼 컴포넌트를 생성한다.
tsx
// src/components/[Name]/[Name].tsx
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

const [name]Schema = z.object({
  // TODO: define schema
  email: z.string().email(),
});

type [Name]Values = z.infer<typeof [name]Schema>;

interface [Name]Props {
  onSubmit: (values: [Name]Values) => void;
}

function [Name]({ onSubmit }: [Name]Props) {
  const form = useForm<[Name]Values>({
    resolver: zodResolver([name]Schema),
    defaultValues: {
      email: "",
    },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="email@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  );
}

export { [Name] };
export type { [Name]Values };
生成基于React Hook Form + Zod的表单组件。
tsx
// src/components/[Name]/[Name].tsx
"use client";

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";

const [name]Schema = z.object({
  // TODO: define schema
  email: z.string().email(),
});

type [Name]Values = z.infer<typeof [name]Schema>;

interface [Name]Props {
  onSubmit: (values: [Name]Values) => void;
}

function [Name]({ onSubmit }: [Name]Props) {
  const form = useForm<[Name]Values>({
    resolver: zodResolver([name]Schema),
    defaultValues: {
      email: "",
    },
  });

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="email@example.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  );
}

export { [Name] };
export type { [Name]Values };

실행 규칙

执行规则

  1. $ARGUMENTS
    에서 타입과 이름을 파싱
  2. 프로젝트의 기존 구조를 Glob/Read로 확인하여 실제 경로 패턴에 맞춤
  3. 이미 존재하는 파일이 있으면 덮어쓰지 않고 사용자에게 확인
  4. 파일 생성 후 생성된 파일 목록을 출력
  5. shadcn/ui 컴포넌트가 필요한데 없으면 설치 명령어 안내
  1. $ARGUMENTS
    中解析类型和名称
  2. 通过Glob/Read检查项目现有结构,匹配实际路径模式
  3. 如果文件已存在,不会直接覆盖,而是询问用户确认
  4. 文件生成后,输出已生成的文件列表
  5. 如果需要shadcn/ui组件但未安装,会提供安装命令提示