Skip to content

zixiao-labs/Chen-the-Dawnstreak

Repository files navigation

Chen-the-Dawnstreak

npm version React 19 License: MIT

赤刃明霄陈 — 轻量级 React 元框架:文件路由 · 数据钩子 · SSR · PWA · 多平台脚手架。

Star过200就在README里放赤刃明霄陈立绘

Quick Start — CLI

npx chen-the-dawnstreak my-app
# or
npx chen my-app

交互式选择项目类型:

类型 说明
Web Vite + React
Web + PWA 额外生成 manifest.json、Service Worker
Desktop (Electron) 额外生成 Electron 主进程/预加载脚本
Desktop (Tauri) 额外生成 src-tauri/ Rust 项目

CLI 不会自动安装依赖,创建完成后按提示手动执行 npm install

Installation

npm install chen-the-dawnstreak

Peer requirement: React 19+.

Quick Start

import { ChenRouter, Routes, Route, Link, useFetch } from 'chen-the-dawnstreak';

function App() {
  return (
    <ChenRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </ChenRouter>
  );
}

Router

Thin wrapper around react-router v7.

import { ChenRouter, Route, Routes, Link, useNavigate } from 'chen-the-dawnstreak';

Re-exports: ChenRouter, Route, Routes, Link, NavLink, Navigate, Outlet, useNavigate, useParams, useSearchParams, useLocation, useMatch.

Data Fetching

useFetch

import { useFetch } from 'chen-the-dawnstreak';

function UserList() {
  const { data, loading, error, refetch } = useFetch<User[]>('/api/users');

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <ul>
      {data?.map((u) => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

useMutation

import { useMutation } from 'chen-the-dawnstreak';

function CreateUser() {
  const { mutate, loading } = useMutation<User, { name: string }>('/api/users');

  return (
    <button disabled={loading} onClick={() => mutate({ name: 'Chen' })}>
      Create
    </button>
  );
}

Forms

useForm

Lightweight form management hook with validation, similar to React Hook Form.

import { useForm } from 'chen-the-dawnstreak';

function LoginForm() {
  const { register, handleSubmit, errors, isSubmitting } = useForm({
    defaultValues: { email: '', password: '' },
    validationRules: {
      email: { required: 'Email is required', pattern: { value: /^\S+@\S+$/, message: 'Invalid email' } },
      password: { required: true, minLength: { value: 8, message: 'Min 8 characters' } },
    },
    onSubmit: async (values) => {
      await fetch('/api/login', { method: 'POST', body: JSON.stringify(values) });
    },
  });

  return (
    <form onSubmit={handleSubmit}>
      <input {...register('email')} placeholder="Email" />
      {errors.email && <p>{errors.email}</p>}
      <input {...register('password')} type="password" placeholder="Password" />
      {errors.password && <p>{errors.password}</p>}
      <button type="submit" disabled={isSubmitting}>Login</button>
    </form>
  );
}

Returns: { values, errors, touched, isValid, isSubmitting, register, handleSubmit, setValue, setValues, reset, validate, validateField }.

Validation rules: required, min, max, minLength, maxLength, pattern, validate (async), custom.

useController

For controlled components that don't use standard onChange:

import { useForm, useController } from 'chen-the-dawnstreak';

function MyForm() {
  const form = useForm({ defaultValues: { role: 'user' } });
  const { field, fieldState } = useController(form, { name: 'role' });

  return <select value={field.value} onChange={field.onChange}>...</select>;
}

State Management

createStore (Context-based)

Creates a scoped store with React Context. Each StoreProvider holds its own state.

import { createStore } from 'chen-the-dawnstreak';

const { StoreProvider, useStore, useStoreDispatch, useStoreSelector } = createStore({
  name: 'counter',
  initialState: { count: 0, label: 'Counter' },
});

function Counter() {
  const count = useStoreSelector((s) => s.count);
  const dispatch = useStoreDispatch();
  return <button onClick={() => dispatch((s) => ({ count: s.count + 1 }))}>Count: {count}</button>;
}

function App() {
  return (
    <StoreProvider>
      <Counter />
    </StoreProvider>
  );
}

createSimpleStore (Global singleton)

Module-level store, no Provider needed. Good for app-wide state like themes or auth.

import { createSimpleStore } from 'chen-the-dawnstreak';

const themeStore = createSimpleStore({ dark: false });

function ThemeToggle() {
  const { dark } = themeStore.useStore();
  const dispatch = themeStore.useDispatch();
  return <button onClick={() => dispatch({ dark: !dark })}>{dark ? 'Light' : 'Dark'}</button>;
}

Server Actions / RSC

useServerAction

Wraps React 19's useActionState for server actions:

import { useServerAction } from 'chen-the-dawnstreak';

// Server action (in a "use server" file)
async function createUser(prev: { error?: string }, formData: FormData) {
  "use server";
  const name = formData.get('name') as string;
  // ... create user
  return { error: undefined };
}

// Client component
function CreateUserForm() {
  const { state, execute, isPending } = useServerAction(createUser, { error: undefined });

  return (
    <form action={execute}>
      <input name="name" placeholder="Name" />
      <button type="submit" disabled={isPending}>Create</button>
      {state.error && <p>{state.error}</p>}
    </form>
  );
}

Re-exports: useFormStatus (from react-dom), useOptimistic (from react).

SSR

Chen supports server-side rendering with streaming and string-based APIs.

Setup

// vite.config.ts
import { chen } from 'chen-the-dawnstreak/vite-plugin';

export default defineConfig({
  plugins: [react(), chen({ ssr: true })],
});

The ssr: true option stubs CSS imports during SSR builds and configures ssr.noExternal.

Server-side rendering

import { renderToStream, renderToHTML, createHTMLShell, ChenSSRRouter } from 'chen-the-dawnstreak/ssr';
import express from 'express';

const app = express();
app.use(express.static('dist/client'));

app.get('*', (req, res) => {
  const element = (
    <ChenSSRRouter location={req.url}>
      <App />
    </ChenSSRRouter>
  );

  // Option 1: Streaming (recommended)
  renderToStream(element, res, {
    clientEntry: '/assets/main.js',
    title: 'My App',
    cssHref: '/assets/style.css',
  });

  // Option 2: String rendering
  // const html = renderToHTML(element, { clientEntry: '/assets/main.js', title: 'My App' });
  // res.send(html);
});

API

  • renderToStream(element, writable, options) — streams HTML with React's renderToPipeableStream
  • renderToHTML(element, options) — returns complete HTML string
  • createHTMLShell(options) — returns { beforeContent, afterContent } for custom streaming
  • ChenSSRRouter — re-export of StaticRouter from react-router
  • isBrowser / isServer — environment detection constants

Vite Plugin

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { chen } from 'chen-the-dawnstreak/vite-plugin';

export default defineConfig({
  plugins: [react(), chen()],
});

File Routing

Place page files under src/pages/ according to convention. The plugin automatically generates routes, eliminating the need to manually write <Routes>/<Route>.

chen({ routes: true })
// Or custom directory
chen({ routes: { dir: 'src/pages' } })

Add type references in vite-env.d.ts:

/// <reference types="chen-the-dawnstreak/vite-plugin/client" />

Use in the application entry point:

import { ChenRouter } from 'chen-the-dawnstreak';
import { ChenRoutes } from 'virtual:chen-routes';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <ChenRouter>
    <ChenRoutes />
  </ChenRouter>
);

File Conventions:

File Route Description
pages/index.tsx / Homepage
pages/about.tsx /about Static Routing
pages/blog/index.tsx /blog Index
pages/blog/[id].tsx /blog/:id Dynamic Parameters
pages/[...slug].tsx * Wildcard catch-all
pages/_layout.tsx Root Layout, must contain <Outlet />
pages/blog/_layout.tsx /blog Nested Layout
pages/_404.tsx * Custom 404 Page

_layout.tsx Example:

import { Outlet } from 'chen-the-dawnstreak';

export default function Layout() {
  return (
    <>
      <nav>...</nav>
      <Outlet />
    </>
  );
}

Page files are automatically split using React.lazy. Adding/deleting files during development triggers an automatic refresh.

PWA Support

chen({ pwa: true })
// Or custom configuration
chen({
  pwa: {
    name: 'My App',
    shortName: 'App',
    themeColor: '#6750a4',
    backgroundColor: '#ffffff',
    display: 'standalone',
    icons: [
      { src: '/icons/icon-192x192.png', sizes: '192x192', type: 'image/png' },
      { src: '/icons/icon-512x512.png', sizes: '512x512', type: 'image/png' },
    ],
  },
})

After enabling PWA, manifest.json and sw.js are automatically generated during the build process, and the manifest link, theme-color meta, and Service Worker registration scripts are injected into the HTML.

Roadmap

  • Router (react-router v7 wrapper)
  • File-based routing
  • Data fetching hooks (useFetch, useMutation)
  • Form management (useForm, useController)
  • State management (createStore, createSimpleStore)
  • Server actions (RSC)
  • SSR support (streaming + string)
  • Build tooling (Vite plugin)
  • CLI scaffolding tool (Web / PWA / Electron / Tauri)

License

MIT