好的, 我们可以使用 Bun、Elysia 和 Next. Js 重写之前的 Todo 项目。这个组合将提供一个高性能的全栈解决方案。以下是实现的步骤和代码:

  1. 首先, 创建一个新的 Next. Js 项目:
bun create next-app todo-elysia
cd todo-elysia
  1. 安装必要的依赖:
bun add elysia @elysiajs/html marked
  1. 创建 Elysia 服务器文件 server.ts:
import { Elysia, t } from 'elysia';
import { html } from '@elysiajs/html';
import { marked } from 'marked';
 
let todos = [
  { id: 1, text: "Learn **Elysia**", completed: false },
  { id: 2, text: "Build a _Todo_ app", completed: false },
];
 
const app = new Elysia()
  .use(html())
  .get('/', () => 'Hello Elysia')
  .get('/api/todos', () => todos)
  .post('/api/todos', ({ body }) => {
    const newTodo = {
      id: todos.length + 1,
      text: body.text,
      completed: false
    };
    todos.push(newTodo);
    return newTodo;
  }, {
    body: t.Object({
      text: t.String()
    })
  })
  .put('/api/todos/:id', ({ params, body }) => {
    const todo = todos.find(t => t.id === parseInt(params.id));
    if (todo) {
      todo.text = body.text;
      todo.completed = body.completed;
    }
    return todo;
  }, {
    params: t.Object({
      id: t.String()
    }),
    body: t.Object({
      text: t.String(),
      completed: t.Boolean()
    })
  })
  .delete('/api/todos/:id', ({ params }) => {
    todos = todos.filter(t => t.id !== parseInt(params.id));
    return { success: true };
  }, {
    params: t.Object({
      id: t.String()
    })
  })
  .get('/api/todos/render/:id', ({ params }) => {
    const todo = todos.find(t => t.id === parseInt(params.id));
    if (todo) {
      return html(`<div>${marked(todo.text)}</div>`);
    }
    return 'Todo not found';
  }, {
    params: t.Object({
      id: t.String()
    })
  })
  .listen(3000);
 
console.log(`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`);
  1. 更新 package.json 的 scripts:
"scripts": {
  "dev": "bun run server.ts & next dev",
  "build": "next build",
  "start": "next start",
  "lint": "next lint"
}
  1. 创建 pages/index.tsx:
import { useState, useEffect } from 'react';
import { marked } from 'marked';
 
interface Todo {
  id: number;
  text: string;
  completed: boolean;
}
 
export default function Home() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [newTodo, setNewTodo] = useState('');
  const [editingId, setEditingId] = useState<number | null>(null);
  const [editingText, setEditingText] = useState('');
 
  useEffect(() => {
    fetchTodos();
  }, []);
 
  const fetchTodos = async () => {
    const response = await fetch('/api/todos');
    const data = await response.json();
    setTodos(data);
  };
 
  const addTodo = async (e: React.FormEvent) => {
    e.preventDefault();
    if (newTodo.trim()) {
      Const response = await fetch ('/api/todos', {
        Method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ text: newTodo })
      });
      const data = await response.json();
      setTodos([...todos, data]);
      setNewTodo('');
    }
  };
 
  const updateTodo = async (id: number, text: string, completed: boolean) => {
    const response = await fetch(`/api/todos/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ text, completed })
    });
    const updatedTodo = await response.json();
    setTodos(todos.map(todo => todo.id === id ? updatedTodo : todo));
  };
 
  const deleteTodo = async (id: number) => {
    await fetch(`/api/todos/${id}`, { method: 'DELETE' });
    setTodos(todos.filter(todo => todo.id !== id));
  };
 
  const startEditing = (todo: Todo) => {
    setEditingId(todo.id);
    setEditingText(todo.text);
  };
 
  const saveEdit = async () => {
    if (editingId !== null) {
      await updateTodo(editingId, editingText, todos.find(t => t.id === editingId)?.completed || false);
      setEditingId(null);
      setEditingText('');
    }
  };
 
  const renderTodoText = async (id: number) => {
    const response = await fetch(`/api/todos/render/${id}`);
    const html = await response.text();
    return { __html: html };
  };
 
  return (
    <div className="p-4 max-w-2xl mx-auto">
      <h1 className="text-3xl font-bold mb-4">Todo App with Markdown</h1>
      <form onSubmit={addTodo} className="mb-4">
        <input
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          placeholder="Add a new todo"
          className="border p-2 mr-2 w-full mb-2"
        />
        <button type="submit" className="bg-blue-500 text-white p-2 rounded hover:bg-blue-600">Add</button>
      </form>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} className="mb-4 bg-gray-100 p-4 rounded">
            {editingId === todo.id ? (
              <div>
                <textarea
                  value={editingText}
                  onChange={(e) => setEditingText(e.target.value)}
                  className="w-full p-2 border rounded mb-2"
                />
                <button onClick={saveEdit} className="bg-green-500 text-white p-2 rounded mr-2">Save</button>
                <button onClick={() => setEditingId(null)} className="bg-gray-500 text-white p-2 rounded">Cancel</button>
              </div>
            ) : (
              <div>
                <div className="flex items-center mb-2">
                  <input
                    type="checkbox"
                    checked={todo.completed}
                    onChange={(e) => updateTodo(todo.id, todo.text, e.target.checked)}
                    className="mr-2"
                  />
                  <span className={todo.completed ? 'line-through' : ''}>{todo.text}</span>
                  <button onClick={() => startEditing(todo)} className="ml-auto text-blue-500 mr-2">Edit</button>
                  <button onClick={() => deleteTodo(todo.id)} className="text-red-500">Remove</button>
                </div>
                <div dangerouslySetInnerHTML={renderTodoText(todo.id)} className="markdown-content text-sm text-gray-600" />
              </div>
            )}
          </li>
        ))}
      </ul>
    </div>
  );
}
  1. 添加一些基本样式, 创建 styles/globals. Css:
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';
 
.markdown-content {
  @apply prose prose-sm;
}
  1. 更新 pages/_app. Tsx:
import '../styles/globals.css'
import type { AppProps } from 'next/app'
 
function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}
 
export default MyApp
  1. 配置 Tailwind CSS, 创建 tailwind.config.js:
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [
    require('@tailwindcss/typography'),
  ],
}
  1. 安装 Tailwind CSS 和插件:
bun add -d tailwindcss postcss autoprefixer @tailwindcss/typography

这个实现使用了 Bun 作为运行时, Elysia 作为后端 API 服务器, Next. Js 作为前端框架。它保留了之前的 Todo 功能, 包括 Markdown 支持和编辑功能。

要运行这个项目, 使用以下命令:

bun run dev

这将同时启动 Elysia 服务器和 Next. Js 开发服务器。你可以在浏览器中访问 http://localhost:3000 来查看和使用这个 Todo 应用。

这个设置提供了一个强大的全栈解决方案, 结合了 Bun 的高性能, Elysia 的轻量级 API 服务, 以及 Next. Js 的强大前端功能。它适合构建现代的、高性能的 web 应用。