不错的想法!结合 Elysia、HTMX、UnoCSS 以及 React,我们可以构建一个功能强大但简洁的文本编辑器。在这个项目中,我们将使用 TypeScript 启用高阶函数来增强代码的复用性和可读性。Bun 是一个新的 JavaScript 运行时,可以通过其高性能进行快速开发和运行。
项目结构
my-text-editor/
├── src/
│ ├── server.ts
│ ├── api.ts
│ └── types.ts
├── public/
│ ├── index.html
│ └── styles.css
├── src/
│ ├── components/
│ │ └── TextEditor.tsx
│ └── App.tsx
├── tsconfig.json
├── package.json
├── bunfig.toml
└── vite.config.js
1. 初始化项目
创建项目目录并初始化 Bun 项目
mkdir my-text-editor
cd my-text-editor
bun init安装依赖
bun add elysia htmx react react-dom @types/react @types/react-dom unocss
bun add -d typescript vite @vitejs/plugin-react配置 TypeScript
创建 tsconfig.json:
bun add --dev typescript
bun x tsc --init编辑生成的 tsconfig.json 文件:
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"jsx": "react-jsx",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}2. 创建服务器端和 API
在 src 目录下创建 server.ts、api.ts 和 types.ts。
types.ts 用于定义数据类型:
export interface TextDocument {
id: number;
content: string;
}api.ts 用于处理文本文档的 CRUD 操作:
import { TextDocument } from './types';
let documents: TextDocument[] = [];
let currentId = 1;
export async function getDocument(id: number): Promise<TextDocument | undefined> {
return documents.find(doc => doc.id === id);
}
export async function createDocument(content: string): Promise<TextDocument> {
const newDocument: TextDocument = { id: currentId++, content };
documents.push(newDocument);
return newDocument;
}
export async function updateDocument(id: number, content: string): Promise<TextDocument | undefined> {
const document = documents.find(doc => doc.id === id);
if (document) {
document.content = content;
return document;
}
return undefined;
}server.ts 用于创建 Elysia 服务器:
import { Elysia } from 'elysia';
import { getDocument, createDocument, updateDocument } from './api';
import { TextDocument } from './types';
const app = new Elysia();
app.get('/', async (req, res) => {
res.type('text/html');
res.sendFile('public/index.html');
});
app.get('/documents/:id', async (req, res) => {
const id = parseInt(req.params.id, 10);
const document = await getDocument(id);
if (document) {
res.json(document);
} else {
res.status(404).send("Document not found");
}
});
app.post('/documents', async (req, res) => {
const { content } = await req.json() as TextDocument;
const newDocument = await createDocument(content);
res.json(newDocument);
});
app.put('/documents/:id', async (req, res) => {
const id = parseInt(req.params.id, 10);
const { content } = await req.json() as TextDocument;
const updatedDocument = await updateDocument(id, content);
if (updatedDocument) {
res.json(updatedDocument);
} else {
res.status(404).send("Document not found");
}
});
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});3. 创建前端应用
Vite 配置
创建 vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});React 组件
在 src/components 目录下创建 TextEditor.tsx:
import React, { useState, useEffect } from 'react';
import { TextDocument } from '../types';
interface TextEditorProps {
documentId: number;
}
const TextEditor: React.FC<TextEditorProps> = ({ documentId }) => {
const [content, setContent] = useState('');
useEffect(() => {
const fetchData = async () => {
const response = await fetch(`/documents/${documentId}`);
const document: TextDocument = await response.json();
setContent(document.content);
};
fetchData();
}, [documentId]);
const handleSave = async () => {
await fetch(`/documents/${documentId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content }),
});
};
return (
<div className="p-4 border">
<textarea
className="w-full h-64 p-2 border"
value={content}
onChange={(e) => setContent(e.target.value)}
/>
<button onClick={handleSave} className="mt-2 p-2 bg-blue-500 text-white">
Save
</button>
</div>
);
};
export default TextEditor;创建应用入口
在 src 目录下创建 App.tsx:
import React from 'react';
import ReactDOM from 'react-dom';
import TextEditor from './components/TextEditor';
const App: React.FC = () => {
return (
<div className="app">
<h1 className="text-2xl font-bold">Text Editor</h1>
<TextEditor documentId={1} />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));4. HTMX 和 UnoCSS 配置
在 public 目录下创建 index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Text Editor</title>
<script type="module" src="/src/App.tsx"></script>
<link rel="stylesheet" href="https://unpkg.com/@unocss/reset@latest/antfu.css">
<link rel="stylesheet" href="styles.css">
</head>
<body class="p-4">
<div id="root"></div>
</body>
</html>创建 public/styles.css:
/* Source: UnoCSS or similar atomic CSS library */
.text-2xl {
font-size: 1.5rem;
}
.font-bold {
font-weight: 700;
}
.border {
border: 1px solid #ddd;
}
.p-4 {
padding: 1rem;
}
.mt-2 {
margin-top: .5rem;
}
.p-2 {
padding: .5rem;
}
.bg-blue-500 {
background-color: #3b82f6;
}
.text-white {
color: #fff;
}5. 启动服务器
运行服务器:
bun run src/server.ts启动 Vite 开发服务器:
bun x vite打开浏览器并访问 http://localhost:3000 ,你将看到一个文本编辑器,可以检索和保存文本文档。
总结
通过这个示例项目,我们展示了如何结合 Elysia、HTMX、UnoCSS 和 React 来创建一个简单但功能强大的文本编辑器。关键点如下:
- Elysia:设置路由和 API 端点,处理文本文档的 CRUD 操作。
- HTMX:在前端 HTML 中使用 HTMX 进行数据交互。
- UnoCSS:使用原子化样式表进行样式管理。
- React:使用 React 创建组件和处理用户交互。
希望这个示例能帮助你更好地理解如何结合这些技术来构建复杂的应用。如果有任何问题或需要进一步的帮助,请随时提问!