当然可以使用 UnoCSS 替代 Tailwind CSS。我们需要做一些调整来集成 UnoCSS 到我们的 Next. Js 项目中。以下是修改后的步骤和代码:

  1. 首先,移除 Tailwind CSS 相关的依赖,并安装 UnoCSS:
bun remove tailwindcss postcss autoprefixer @tailwindcss/typography
bun add -d unocss @unocss/webpack
  1. 创建 uno.config.ts 文件在项目根目录:
import { defineConfig, presetUno, presetTypography } from 'unocss'
 
export default defineConfig({
  presets: [
    presetUno(),
    presetTypography(),
  ],
})
  1. 更新 next.config.js
const UnoCSS = require('@unocss/webpack').default
 
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack: (config) => {
    config.plugins.push(UnoCSS())
    return config
  },
}
 
module.exports = nextConfig
  1. 更新 pages/_app.tsx
import 'uno.css'
import type { AppProps } from 'next/app'
 
function MyApp({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}
 
export default MyApp
  1. 删除 tailwind.config.js 文件。

  2. 更新 styles/globals.css

/* 你可以保留一些全局样式,但不再需要导入 Tailwind */
  1. 更新 pages/index.tsx,将 Tailwind 类替换为 UnoCSS 类:
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="prose prose-sm text-sm text-gray-600" />
              </div>
            )}
          </li>
        ))}
      </ul>
    </div>
  );
}

注意:UnoCSS 和 Tailwind CSS 的类名在大多数情况下是相同的,所以我们不需要对 HTML 结构做太多更改。主要的区别在于配置和集成方式。

  1. 如果你想使用 UnoCSS 的 attributify 模式,你可以在 uno.config.ts 中添加相应的预设:
import { defineConfig, presetUno, presetAttributify, presetTypography } from 'unocss'
 
export default defineConfig({
  presets: [
    presetUno(),
    presetAttributify(),
    presetTypography(),
  ],
})

然后你可以在 JSX 中使用属性模式,例如:

<div bg="blue-500" text="white" p="2" rounded>
  This is a button
</div>

这些更改将使你的项目从 Tailwind CSS 迁移到 UnoCSS。UnoCSS 提供了类似的功能,但具有更高的性能和更大的灵活性。记住要根据 UnoCSS 的文档调整任何自定义配置或使用方式。