feat: init
This commit is contained in:
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
# prod
|
||||
dist/
|
||||
|
||||
# dev
|
||||
.yarn/
|
||||
!.yarn/releases
|
||||
.vscode/*
|
||||
!.vscode/launch.json
|
||||
!.vscode/*.code-snippets
|
||||
.idea/workspace.xml
|
||||
.idea/usage.statistics.xml
|
||||
.idea/shelf
|
||||
|
||||
# deps
|
||||
node_modules/
|
||||
.wrangler
|
||||
|
||||
# env
|
||||
.env
|
||||
.env.production
|
||||
.dev.vars
|
||||
|
||||
# logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
# Tencent Cloud EdgeOne
|
||||
.env
|
||||
.edgeone
|
||||
.edgeone/*
|
||||
.tef_dist/*
|
||||
|
||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
||||
307
README.md
Normal file
307
README.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# EdgeOne Pages Hono Application
|
||||
|
||||
This is a modern Web application built on the [Hono](https://hono.dev/) framework, deployed on the EdgeOne Pages platform.
|
||||
|
||||
Live demo: https://hono-template.edgeone.app
|
||||
|
||||
## Deploy
|
||||
|
||||
[](https://edgeone.ai/pages/new?from=github&template=hono)
|
||||
|
||||
## 🚀 Project Features
|
||||
|
||||
- **Modular Route Design** - Clear route organization structure
|
||||
- **Server-Side Rendering** - Page rendering using JSX and HTML templates
|
||||
- **File Upload** - File upload functionality support
|
||||
- **Book Management** - Example CRUD operations
|
||||
- **Error Handling** - Beautiful 404 and 500 error pages
|
||||
- **TypeScript Support** - Complete type definitions
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
functions/
|
||||
├── index.tsx # Main entry file
|
||||
├── [[default]].ts # EdgeOne Functions default route
|
||||
├── env.ts # Environment type definitions
|
||||
├── components/ # Components directory
|
||||
│ └── Layout.tsx # Page layout component
|
||||
└── routers/ # Route modules
|
||||
├── index.ts # Unified route exports
|
||||
├── book.tsx # Book related routes
|
||||
├── ssr.tsx # Server-side rendering routes
|
||||
└── upload.ts # File upload routes
|
||||
```
|
||||
|
||||
## 🛣️ Route Details
|
||||
|
||||
### Static Routes
|
||||
|
||||
| Path | Method | Description |
|
||||
|------|------|------|
|
||||
| `/` | GET | Static home page, serves `index.html` from public directory |
|
||||
|
||||
**Examples:**
|
||||
- `https://hono.edgeone.app/` - Static home page
|
||||
|
||||
### SSR Routes (`/ssr`)
|
||||
|
||||
| Path | Method | Description |
|
||||
|------|------|------|
|
||||
| `/ssr/:name` | GET | Dynamic SSR page, displays personalized welcome message |
|
||||
|
||||
**Examples:**
|
||||
- `https://hono.edgeone.app/ssr/john` - Shows "Hello john!" page
|
||||
|
||||
### Book Management Routes (`/book`)
|
||||
|
||||
| Path | Method | Description |
|
||||
|------|------|------|
|
||||
| `/book` | GET | Get all books list page |
|
||||
| `/book/:id` | GET | Get specific book details page |
|
||||
| `/book` | POST | Create new book (API endpoint) |
|
||||
|
||||
**Examples:**
|
||||
- `https://hono.edgeone.app/book` - Book list
|
||||
- `https://hono.edgeone.app/book/1` - Details of the first book
|
||||
|
||||
**Create Book API Request Example:**
|
||||
```bash
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "New Book Title",
|
||||
"author": "Author Name"
|
||||
}'
|
||||
```
|
||||
|
||||
**Supported Features:**
|
||||
- CORS cross-origin support
|
||||
|
||||
### File Upload Routes (`/upload`)
|
||||
|
||||
| Path | Method | Description |
|
||||
|------|------|------|
|
||||
| `/upload` | POST | File upload endpoint |
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
## 📖 Detailed API Documentation
|
||||
|
||||
### Basic Information
|
||||
|
||||
- **Base URL**: `https://hono.edgeone.app`
|
||||
- **Content-Type**: `application/json`
|
||||
- **Encoding**: UTF-8
|
||||
|
||||
### API Details
|
||||
|
||||
#### 1. File Upload
|
||||
|
||||
**Endpoint**: `POST /upload`
|
||||
|
||||
**Description**: Upload files to server
|
||||
|
||||
**Request Format**: `multipart/form-data`
|
||||
|
||||
**Request Parameters**:
|
||||
- `file` (required): File to upload
|
||||
|
||||
**curl Request Examples**:
|
||||
```bash
|
||||
# Upload text file
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@/path/to/your/file.txt"
|
||||
|
||||
# Upload image file
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@/path/to/image.jpg"
|
||||
|
||||
# Upload with custom filename
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@document.pdf;filename=my-document.pdf"
|
||||
```
|
||||
|
||||
**Response Example**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "File uploaded successfully",
|
||||
"fileName": "file.txt"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Response**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "No file provided"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Create Book
|
||||
|
||||
**Endpoint**: `POST /book`
|
||||
|
||||
**Description**: Create new book record
|
||||
|
||||
**Request Parameters**:
|
||||
```json
|
||||
{
|
||||
"title": "Book Title",
|
||||
"author": "Author Name"
|
||||
}
|
||||
```
|
||||
|
||||
**Parameter Description**:
|
||||
- `title` (optional): Book title, defaults to "Untitled"
|
||||
- `author` (optional): Author name, defaults to "Unknown"
|
||||
|
||||
**curl Request Examples**:
|
||||
```bash
|
||||
# Create book with complete information
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Dream of the Red Chamber",
|
||||
"author": "Cao Xueqin"
|
||||
}'
|
||||
|
||||
# Create book with only title
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "New Book Title"
|
||||
}'
|
||||
|
||||
# Create empty book (using defaults)
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
**Response Example**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Book created successfully",
|
||||
"book": {
|
||||
"id": "abc123def",
|
||||
"title": "Book Title",
|
||||
"author": "Author Name",
|
||||
"createdAt": "2023-12-01T10:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Get Book Information
|
||||
|
||||
**curl Request Examples**:
|
||||
```bash
|
||||
# Get all books list
|
||||
curl -X GET https://hono.edgeone.app/book
|
||||
|
||||
# Get specific book details
|
||||
curl -X GET https://hono.edgeone.app/book/1
|
||||
|
||||
# Get personal page
|
||||
curl -X GET https://hono.edgeone.app/john
|
||||
```
|
||||
|
||||
### Error Code Description
|
||||
|
||||
| Error Code | HTTP Status Code | Description |
|
||||
|-----------|-------------|------|
|
||||
| `VALIDATION_ERROR` | 400 | Request parameter validation failed |
|
||||
| `FILE_UPLOAD_ERROR` | 400 | File upload failed |
|
||||
| `NOT_FOUND` | 404 | Resource not found |
|
||||
| `INTERNAL_ERROR` | 500 | Internal server error |
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
- All API endpoints currently have no rate limiting
|
||||
- Client-side request frequency control is recommended
|
||||
|
||||
### CORS Support
|
||||
|
||||
All API endpoints support cross-origin access, response headers include:
|
||||
- `Access-Control-Allow-Origin: *`
|
||||
- `Access-Control-Allow-Methods: POST, GET, OPTIONS`
|
||||
- `Access-Control-Allow-Headers: Content-Type, Authorization`
|
||||
|
||||
## 🔧 Development
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start development server
|
||||
edgeone pages dev
|
||||
```
|
||||
|
||||
|
||||
## 🌐 Environment Variables
|
||||
|
||||
The project uses the following environment variables and global objects:
|
||||
|
||||
- `my_kv` - KV storage instance for data persistence
|
||||
|
||||
## 🛡️ Security Features
|
||||
|
||||
### IP Restriction (Optional)
|
||||
|
||||
The project includes IP restriction middleware configuration (commented by default), which can limit access sources:
|
||||
|
||||
```typescript
|
||||
app.use('*', ipRestriction(/* configuration */));
|
||||
```
|
||||
|
||||
## 📝 API Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Operation successful",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "ERROR_CODE",
|
||||
"message": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 UI Design
|
||||
|
||||
The project adopts modern UI design:
|
||||
- Responsive layout
|
||||
- System font stack
|
||||
- Card-style design
|
||||
- Unified color theme
|
||||
- Elegant error pages
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
- **hono** - Web framework
|
||||
- **@edgeone/ef-types** - EdgeOne Functions type definitions
|
||||
- **edgeone** - EdgeOne CLI tool
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Welcome to submit Issues and Pull Requests to improve this project.
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT License
|
||||
306
README_zh-CN.md
Normal file
306
README_zh-CN.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# EdgeOne Pages Hono 应用程序
|
||||
|
||||
这是一个基于 [Hono](https://hono.dev/) 框架构建的现代 Web 应用程序,部署在 EdgeOne Pages 平台上。
|
||||
|
||||
在线演示:https://hono.edgeone.site
|
||||
|
||||
## 部署
|
||||
|
||||
[](https://console.cloud.tencent.com/edgeone/pages/new?template=hono)
|
||||
|
||||
## 🚀 项目特性
|
||||
|
||||
- **模块化路由设计** - 清晰的路由组织结构
|
||||
- **服务端渲染** - 使用 JSX 和 HTML 模板进行页面渲染
|
||||
- **文件上传** - 文件上传功能支持
|
||||
- **图书管理** - 示例 CRUD 操作
|
||||
- **错误处理** - 精美的 404 和 500 错误页面
|
||||
- **TypeScript 支持** - 完整的类型定义
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
functions/
|
||||
├── index.tsx # 主入口文件
|
||||
├── [[default]].ts # EdgeOne Functions 默认路由
|
||||
├── env.ts # 环境类型定义
|
||||
├── components/ # 组件目录
|
||||
│ └── Layout.tsx # 页面布局组件
|
||||
└── routers/ # 路由模块
|
||||
├── index.ts # 统一路由导出
|
||||
├── book.tsx # 图书相关路由
|
||||
├── ssr.tsx # 服务端渲染路由
|
||||
└── upload.ts # 文件上传路由
|
||||
```
|
||||
|
||||
## 🛣️ 路由详情
|
||||
|
||||
### 静态路由
|
||||
|
||||
| 路径 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/` | GET | 静态首页,从 public 目录提供 `index.html` |
|
||||
|
||||
**示例:**
|
||||
- `https://hono.edgeone.app/` - 静态首页
|
||||
|
||||
### SSR 路由 (`/ssr`)
|
||||
|
||||
| 路径 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/ssr/:name` | GET | 动态 SSR 页面,显示个性化欢迎消息 |
|
||||
|
||||
**示例:**
|
||||
- `https://hono.edgeone.app/ssr/john` - 显示 "Hello john!" 页面
|
||||
|
||||
### 图书管理路由 (`/book`)
|
||||
|
||||
| 路径 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/book` | GET | 获取所有图书列表页面 |
|
||||
| `/book/:id` | GET | 获取特定图书详情页面 |
|
||||
| `/book` | POST | 创建新图书(API 端点) |
|
||||
|
||||
**示例:**
|
||||
- `https://hono.edgeone.app/book` - 图书列表
|
||||
- `https://hono.edgeone.app/book/1` - 第一本书的详情
|
||||
|
||||
**创建图书 API 请求示例:**
|
||||
```bash
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "新书标题",
|
||||
"author": "作者姓名"
|
||||
}'
|
||||
```
|
||||
|
||||
**支持的功能:**
|
||||
- CORS 跨域支持
|
||||
|
||||
### 文件上传路由 (`/upload`)
|
||||
|
||||
| 路径 | 方法 | 描述 |
|
||||
|------|------|------|
|
||||
| `/upload` | POST | 文件上传端点 |
|
||||
|
||||
**示例:**
|
||||
```bash
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@example.txt"
|
||||
```
|
||||
|
||||
## 📖 详细 API 文档
|
||||
|
||||
### 基本信息
|
||||
|
||||
- **Base URL**: `https://hono.edgeone.app`
|
||||
- **Content-Type**: `application/json`
|
||||
- **编码**: UTF-8
|
||||
|
||||
### API 详情
|
||||
|
||||
#### 1. 文件上传
|
||||
|
||||
**端点**: `POST /upload`
|
||||
|
||||
**描述**: 上传文件到服务器
|
||||
|
||||
**请求格式**: `multipart/form-data`
|
||||
|
||||
**请求参数**:
|
||||
- `file` (必需): 要上传的文件
|
||||
|
||||
**curl 请求示例**:
|
||||
```bash
|
||||
# 上传文本文件
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@/path/to/your/file.txt"
|
||||
|
||||
# 上传图片文件
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@/path/to/image.jpg"
|
||||
|
||||
# 上传并自定义文件名
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F "file=@document.pdf;filename=my-document.pdf"
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "文件上传成功",
|
||||
"fileName": "file.txt"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "未提供文件"
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. 创建图书
|
||||
|
||||
**端点**: `POST /book`
|
||||
|
||||
**描述**: 创建新的图书记录
|
||||
|
||||
**请求参数**:
|
||||
```json
|
||||
{
|
||||
"title": "图书标题",
|
||||
"author": "作者姓名"
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
- `title` (可选): 图书标题,默认为 "Untitled"
|
||||
- `author` (可选): 作者姓名,默认为 "Unknown"
|
||||
|
||||
**curl 请求示例**:
|
||||
```bash
|
||||
# 创建包含完整信息的图书
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "红楼梦",
|
||||
"author": "曹雪芹"
|
||||
}'
|
||||
|
||||
# 只创建标题的图书
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "新书标题"
|
||||
}'
|
||||
|
||||
# 创建空图书(使用默认值)
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
|
||||
**响应示例**:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "图书创建成功",
|
||||
"book": {
|
||||
"id": "abc123def",
|
||||
"title": "图书标题",
|
||||
"author": "作者姓名",
|
||||
"createdAt": "2023-12-01T10:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 获取图书信息
|
||||
|
||||
**curl 请求示例**:
|
||||
```bash
|
||||
# 获取所有图书列表
|
||||
curl -X GET https://hono.edgeone.app/book
|
||||
|
||||
# 获取特定图书详情
|
||||
curl -X GET https://hono.edgeone.app/book/1
|
||||
|
||||
# 获取个人页面
|
||||
curl -X GET https://hono.edgeone.app/john
|
||||
```
|
||||
|
||||
### 错误码说明
|
||||
|
||||
| 错误码 | HTTP 状态码 | 描述 |
|
||||
|-----------|-------------|------|
|
||||
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
|
||||
| `FILE_UPLOAD_ERROR` | 400 | 文件上传失败 |
|
||||
| `NOT_FOUND` | 404 | 资源未找到 |
|
||||
| `INTERNAL_ERROR` | 500 | 内部服务器错误 |
|
||||
|
||||
### 频率限制
|
||||
|
||||
- 目前所有 API 端点均无频率限制
|
||||
- 建议客户端进行请求频率控制
|
||||
|
||||
### CORS 支持
|
||||
|
||||
所有 API 端点均支持跨域访问,响应头包含:
|
||||
- `Access-Control-Allow-Origin: *`
|
||||
- `Access-Control-Allow-Methods: POST, GET, OPTIONS`
|
||||
- `Access-Control-Allow-Headers: Content-Type, Authorization`
|
||||
|
||||
## 🔧 开发
|
||||
|
||||
### 本地开发
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 启动开发服务器
|
||||
edgeone pages dev
|
||||
```
|
||||
|
||||
## 🌐 环境变量
|
||||
|
||||
项目使用以下环境变量和全局对象:
|
||||
|
||||
- `my_kv` - KV 存储实例,用于数据持久化
|
||||
|
||||
## 🛡️ 安全特性
|
||||
|
||||
### IP 限制(可选)
|
||||
|
||||
项目包含 IP 限制中间件配置(默认注释),可以限制访问来源:
|
||||
|
||||
```typescript
|
||||
app.use('*', ipRestriction(/* 配置 */));
|
||||
```
|
||||
|
||||
## 📝 API 响应格式
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "操作成功",
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "ERROR_CODE",
|
||||
"message": "错误描述"
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 UI 设计
|
||||
|
||||
项目采用现代化 UI 设计:
|
||||
- 响应式布局
|
||||
- 系统字体栈
|
||||
- 卡片式设计
|
||||
- 统一色彩主题
|
||||
- 优雅的错误页面
|
||||
|
||||
## 📦 依赖
|
||||
|
||||
- **hono** - Web 框架
|
||||
- **@edgeone/ef-types** - EdgeOne Functions 类型定义
|
||||
- **edgeone** - EdgeOne CLI 工具
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交 Issues 和 Pull Requests 来改进这个项目。
|
||||
|
||||
## <20><> 许可证
|
||||
|
||||
MIT License
|
||||
1
functions/[[default]].ts
Normal file
1
functions/[[default]].ts
Normal file
@@ -0,0 +1 @@
|
||||
export { onRequest } from './index';
|
||||
138
functions/components/Layout.tsx
Normal file
138
functions/components/Layout.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import { html } from 'hono/html';
|
||||
|
||||
export interface SiteData {
|
||||
title: string;
|
||||
children?: any;
|
||||
}
|
||||
|
||||
export const Layout = (props: SiteData) =>
|
||||
html`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${props.title}</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||
sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #007acc;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.nav-links {
|
||||
margin: 20px 0;
|
||||
}
|
||||
.nav-links ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.nav-links li {
|
||||
margin: 10px 0;
|
||||
}
|
||||
.nav-links a {
|
||||
color: #007acc;
|
||||
text-decoration: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.nav-links a:hover {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
.back-link {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
.back-link a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-link a:hover {
|
||||
color: #007acc;
|
||||
}
|
||||
pre {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
code {
|
||||
background-color: #f8f9fa;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
h2 {
|
||||
color: #2c3e50;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
h3 {
|
||||
color: #34495e;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
ul li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
strong {
|
||||
color: #2c3e50;
|
||||
}
|
||||
.highlight-box {
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.highlight-box h3 {
|
||||
margin-top: 0;
|
||||
color: #495057;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">${props.children}</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
export const Content = (props: { siteData: SiteData; name: string; children?: any }) => (
|
||||
<Layout {...props.siteData}>
|
||||
<h1>{props.name}</h1>
|
||||
{props.children || (
|
||||
<p>
|
||||
Welcome to our Hono application. This page was rendered server-side with
|
||||
JSX.
|
||||
</p>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
26
functions/env.ts
Normal file
26
functions/env.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
interface ListResult {
|
||||
complete: boolean;
|
||||
cursor: string;
|
||||
keys: Array<ListKey>;
|
||||
}
|
||||
interface ListKey {
|
||||
key: string;
|
||||
}
|
||||
declare class KVNamespace {
|
||||
put(
|
||||
key: string,
|
||||
value: string | ArrayBuffer | ArrayBufferView | ReadableStream
|
||||
): Promise<void>;
|
||||
get(
|
||||
key: string,
|
||||
object?: { type: string }
|
||||
): Promise<string | object | ArrayBuffer | ReadableStream>;
|
||||
delete(key: string): Promise<void>;
|
||||
list(config: {
|
||||
prefix?: string;
|
||||
limit?: number;
|
||||
cursor?: string;
|
||||
}): Promise<ListResult>;
|
||||
}
|
||||
|
||||
export type { KVNamespace };
|
||||
182
functions/index.tsx
Normal file
182
functions/index.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Context, Hono } from 'hono';
|
||||
// import { ipRestriction } from 'hono/ip-restriction';
|
||||
import type { KVNamespace } from './env';
|
||||
import { book, upload, ssr } from './routers/index';
|
||||
|
||||
declare global {
|
||||
let my_kv: KVNamespace;
|
||||
}
|
||||
|
||||
const app = new Hono().basePath('/');
|
||||
|
||||
// Register route modules
|
||||
app.route('/book', book);
|
||||
app.route('/upload', upload);
|
||||
app.route('/ssr', ssr);
|
||||
|
||||
// IP restriction middleware (optional)
|
||||
// app.use(
|
||||
// '*',
|
||||
// ipRestriction(
|
||||
// c => ({
|
||||
// remote: {
|
||||
// // @ts-expect-error
|
||||
// address: c.req.raw.eo.clientIp,
|
||||
// addressType:
|
||||
// String(
|
||||
// // @ts-expect-error
|
||||
// c.req.raw.eo.clientIp
|
||||
// ).indexOf('::') === -1
|
||||
// ? 'IPv4'
|
||||
// : 'IPv6'
|
||||
// }
|
||||
// }),
|
||||
// {
|
||||
// denyList: [],
|
||||
// allowList: [ '127.0.0.1', '::1']
|
||||
// }
|
||||
// )
|
||||
// );
|
||||
|
||||
const notFound = async (c: Context) => {
|
||||
return c.html(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 - Page Not Found</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 100px auto;
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #e74c3c;
|
||||
font-size: 72px;
|
||||
margin: 0;
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>404</h1>
|
||||
<h2>Page Not Found</h2>
|
||||
<p>The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p>
|
||||
<p><a href="/">← Go back to home</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
404
|
||||
);
|
||||
};
|
||||
|
||||
// Fallback to static directory
|
||||
app.notFound(async (c) => {
|
||||
const url = new URL(c.req.url);
|
||||
|
||||
if (url.pathname === '/') {
|
||||
url.pathname = '/index.html';
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url.toString(), {
|
||||
headers: c.req.header()
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentType = res.headers.get('Content-Type')!;
|
||||
const body = await res.arrayBuffer();
|
||||
|
||||
return new Response(body, {
|
||||
status: res.status,
|
||||
headers: {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return notFound(c);
|
||||
}
|
||||
return notFound(c);
|
||||
});
|
||||
|
||||
app.onError((err, c) => {
|
||||
return c.html(
|
||||
`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>500 - Internal Server Error</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
max-width: 600px;
|
||||
margin: 100px auto;
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #e74c3c;
|
||||
font-size: 72px;
|
||||
margin: 0;
|
||||
}
|
||||
h2 {
|
||||
color: #333;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>500</h1>
|
||||
<h2>Internal Server Error</h2>
|
||||
<p>Something went wrong on our server. Please try again later.</p>
|
||||
<p>Error: ${err.message}</p>
|
||||
<p><a href="/">← Go back to home</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
500
|
||||
);
|
||||
});
|
||||
|
||||
// EdgeOne Functions export
|
||||
export function onRequest(context: {
|
||||
request: Request;
|
||||
params: Record<string, string>;
|
||||
env: Record<string, any>;
|
||||
}): Response | Promise<Response> {
|
||||
return app.fetch(context.request, context.env);
|
||||
}
|
||||
105
functions/routers/book.tsx
Normal file
105
functions/routers/book.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Hono } from 'hono';
|
||||
import { Content } from '../components/Layout';
|
||||
|
||||
const book = new Hono();
|
||||
|
||||
// Get all books list
|
||||
book.get('/', (c) => {
|
||||
const props = {
|
||||
name: '📚 Book Library',
|
||||
siteData: {
|
||||
title: 'Books - Hono App',
|
||||
},
|
||||
children: (
|
||||
<div>
|
||||
<p>Browse our collection of books</p>
|
||||
<div style="margin: 20px 0;">
|
||||
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||
<h3><a href="/book/1">The Art of Programming</a></h3>
|
||||
<p>A comprehensive guide to software development</p>
|
||||
</div>
|
||||
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||
<h3><a href="/book/2">JavaScript: The Good Parts</a></h3>
|
||||
<p>Essential JavaScript programming techniques</p>
|
||||
</div>
|
||||
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||
<h3><a href="/book/3">Clean Code</a></h3>
|
||||
<p>Writing maintainable and readable code</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="back-link">
|
||||
<a href="/">← Back to home</a>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
return c.html(Content(props));
|
||||
});
|
||||
|
||||
// Get specific book
|
||||
book.get('/:id', (c) => {
|
||||
const id = c.req.param('id');
|
||||
const books: Record<string, any> = {
|
||||
'1': { title: 'The Art of Programming', author: 'John Doe', description: 'A comprehensive guide to software development principles and practices.' },
|
||||
'2': { title: 'JavaScript: The Good Parts', author: 'Douglas Crockford', description: 'Essential JavaScript programming techniques and best practices.' },
|
||||
'3': { title: 'Clean Code', author: 'Robert C. Martin', description: 'A handbook of agile software craftsmanship.' }
|
||||
};
|
||||
|
||||
const bookData = books[id];
|
||||
if (!bookData) {
|
||||
const props = {
|
||||
name: 'Book Not Found',
|
||||
siteData: {
|
||||
title: 'Book Not Found',
|
||||
},
|
||||
children: (
|
||||
<div>
|
||||
<p>The book with ID {id} was not found.</p>
|
||||
<div class="back-link">
|
||||
<a href="/book">← Back to books</a>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
return c.html(Content(props), 404);
|
||||
}
|
||||
|
||||
const props = {
|
||||
name: `📖 ${bookData.title}`,
|
||||
siteData: {
|
||||
title: `${bookData.title} - Book Details`,
|
||||
},
|
||||
children: (
|
||||
<div>
|
||||
<div style="background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0;">
|
||||
<p><strong>Author:</strong> {bookData.author}</p>
|
||||
<p><strong>Book ID:</strong> {id}</p>
|
||||
<p><strong>Description:</strong> {bookData.description}</p>
|
||||
</div>
|
||||
<div class="back-link">
|
||||
<a href="/book">← Back to books</a>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
return c.html(Content(props));
|
||||
});
|
||||
|
||||
// Create new book (API endpoint)
|
||||
book.post('/', async (c) => {
|
||||
const body = await c.req.json();
|
||||
return c.json({
|
||||
success: true,
|
||||
message: 'Book created successfully',
|
||||
book: {
|
||||
id: Math.random().toString(36).substr(2, 9),
|
||||
title: body.title || 'Untitled',
|
||||
author: body.author || 'Unknown',
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export default book;
|
||||
3
functions/routers/index.ts
Normal file
3
functions/routers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as book } from './book';
|
||||
export { default as upload } from './upload';
|
||||
export { default as ssr } from './ssr';
|
||||
18
functions/routers/ssr.tsx
Normal file
18
functions/routers/ssr.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Hono } from 'hono';
|
||||
import { Layout, Content } from '../components/Layout';
|
||||
|
||||
const ssr = new Hono();
|
||||
|
||||
// Dynamic page route
|
||||
ssr.get('/:name', (c) => {
|
||||
const { name } = c.req.param();
|
||||
const props = {
|
||||
name: name,
|
||||
siteData: {
|
||||
title: `Hello ${name} - JSX Sample`,
|
||||
},
|
||||
};
|
||||
return c.html(<Content {...props} />);
|
||||
});
|
||||
|
||||
export default ssr;
|
||||
17
functions/routers/upload.ts
Normal file
17
functions/routers/upload.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Hono } from 'hono';
|
||||
|
||||
const upload = new Hono();
|
||||
|
||||
// File upload endpoint
|
||||
upload.post('/', async (c) => {
|
||||
const body = await c.req.parseBody();
|
||||
console.log(body['file'], 'File uploaded');
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: 'File uploaded successfully',
|
||||
fileName: body['file']
|
||||
});
|
||||
});
|
||||
|
||||
export default upload;
|
||||
5755
package-lock.json
generated
Normal file
5755
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "edgeone-pages-hono-app",
|
||||
"scripts": {
|
||||
"deploy": "rm -rf ./node_modules && edgeone pages deploy ./ -n edgeone-hono-app -t ",
|
||||
"sync-docs": "node scripts/sync-docs.js",
|
||||
"build": "npm run sync-docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"highlight.js": "^11.11.1",
|
||||
"hono": "^4.7.10",
|
||||
"marked": "^15.0.12",
|
||||
"marked-highlight": "^2.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edgeone/ef-types": "^1.0.5",
|
||||
"edgeone": "^1.0.21"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
345
public/index.html
Normal file
345
public/index.html
Normal file
@@ -0,0 +1,345 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>EdgeOne Pages Hono Application</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>EdgeOne Pages Hono Application</h1>
|
||||
<p>This is a modern Web application built on the <a href="https://hono.dev/" target="_blank" rel="noopener noreferrer">Hono</a> framework, deployed on the EdgeOne Pages platform.</p>
|
||||
<p>Live demo: <a href="https://hono.edgeone.app" target="_blank" rel="noopener noreferrer">https://hono.edgeone.app</a></p>
|
||||
<h2>Deploy</h2>
|
||||
<p><a href="https://edgeone.ai/pages/new?from=github&template=hono" target="_blank" rel="noopener noreferrer"><img src="https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg" alt="Deploy with EdgeOne Pages" /></a></p>
|
||||
<h2>🚀 Project Features</h2>
|
||||
<ul>
|
||||
<li><strong>Modular Route Design</strong> - Clear route organization structure</li>
|
||||
<li><strong>Server-Side Rendering</strong> - Page rendering using JSX and HTML templates</li>
|
||||
<li><strong>File Upload</strong> - File upload functionality support</li>
|
||||
<li><strong>Book Management</strong> - Example CRUD operations</li>
|
||||
<li><strong>Error Handling</strong> - Beautiful 404 and 500 error pages</li>
|
||||
<li><strong>TypeScript Support</strong> - Complete type definitions</li>
|
||||
</ul>
|
||||
<h2>📁 Project Structure</h2>
|
||||
<pre><code>functions/
|
||||
├── index.tsx # Main entry file
|
||||
├── [[default]].ts # EdgeOne Functions default route
|
||||
├── env.ts # Environment type definitions
|
||||
├── components/ # Components directory
|
||||
│ └── Layout.tsx # Page layout component
|
||||
└── routers/ # Route modules
|
||||
├── index.ts # Unified route exports
|
||||
├── book.tsx # Book related routes
|
||||
├── ssr.tsx # Server-side rendering routes
|
||||
└── upload.ts # File upload routes
|
||||
</code></pre>
|
||||
<h2>🛣️ Route Details</h2>
|
||||
<h3>Static Routes</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>/</code></td>
|
||||
<td>GET</td>
|
||||
<td>Static home page, serves <code>index.html</code> from public directory</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>https://hono.edgeone.app/</code> - Static home page</li>
|
||||
</ul>
|
||||
<h3>SSR Routes (<code>/ssr</code>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>/ssr/:name</code></td>
|
||||
<td>GET</td>
|
||||
<td>Dynamic SSR page, displays personalized welcome message</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>https://hono.edgeone.app/ssr/john</code> - Shows "Hello john!" page</li>
|
||||
</ul>
|
||||
<h3>Book Management Routes (<code>/book</code>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>/book</code></td>
|
||||
<td>GET</td>
|
||||
<td>Get all books list page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/book/:id</code></td>
|
||||
<td>GET</td>
|
||||
<td>Get specific book details page</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/book</code></td>
|
||||
<td>POST</td>
|
||||
<td>Create new book (API endpoint)</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p><strong>Examples:</strong></p>
|
||||
<ul>
|
||||
<li><code>https://hono.edgeone.app/book</code> - Book list</li>
|
||||
<li><code>https://hono.edgeone.app/book/1</code> - Details of the first book</li>
|
||||
</ul>
|
||||
<p><strong>Create Book API Request Example:</strong></p>
|
||||
<pre><code class="language-bash">curl -X POST https://hono.edgeone.app/book \
|
||||
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||
-d <span class="hljs-string">'{
|
||||
"title": "New Book Title",
|
||||
"author": "Author Name"
|
||||
}'</span>
|
||||
</code></pre>
|
||||
<p><strong>Supported Features:</strong></p>
|
||||
<ul>
|
||||
<li>CORS cross-origin support</li>
|
||||
</ul>
|
||||
<h3>File Upload Routes (<code>/upload</code>)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path</th>
|
||||
<th>Method</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>/upload</code></td>
|
||||
<td>POST</td>
|
||||
<td>File upload endpoint</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p><strong>Example:</strong></p>
|
||||
<pre><code class="language-bash">curl -X POST https://hono.edgeone.app/upload \
|
||||
-F <span class="hljs-string">"file=@example.txt"</span>
|
||||
</code></pre>
|
||||
<h2>📖 Detailed API Documentation</h2>
|
||||
<h3>Basic Information</h3>
|
||||
<ul>
|
||||
<li><strong>Base URL</strong>: <code>https://hono.edgeone.app</code></li>
|
||||
<li><strong>Content-Type</strong>: <code>application/json</code></li>
|
||||
<li><strong>Encoding</strong>: UTF-8</li>
|
||||
</ul>
|
||||
<h3>API Details</h3>
|
||||
<h4>1. File Upload</h4>
|
||||
<p><strong>Endpoint</strong>: <code>POST /upload</code></p>
|
||||
<p><strong>Description</strong>: Upload files to server</p>
|
||||
<p><strong>Request Format</strong>: <code>multipart/form-data</code></p>
|
||||
<p><strong>Request Parameters</strong>:</p>
|
||||
<ul>
|
||||
<li><code>file</code> (required): File to upload</li>
|
||||
</ul>
|
||||
<p><strong>curl Request Examples</strong>:</p>
|
||||
<pre><code class="language-bash"><span class="hljs-comment"># Upload text file</span>
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F <span class="hljs-string">"file=@/path/to/your/file.txt"</span>
|
||||
|
||||
<span class="hljs-comment"># Upload image file</span>
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F <span class="hljs-string">"file=@/path/to/image.jpg"</span>
|
||||
|
||||
<span class="hljs-comment"># Upload with custom filename</span>
|
||||
curl -X POST https://hono.edgeone.app/upload \
|
||||
-F <span class="hljs-string">"file=@document.pdf;filename=my-document.pdf"</span>
|
||||
</code></pre>
|
||||
<p><strong>Response Example</strong>:</p>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"File uploaded successfully"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"fileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file.txt"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<p><strong>Error Response</strong>:</p>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"No file provided"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<h4>2. Create Book</h4>
|
||||
<p><strong>Endpoint</strong>: <code>POST /book</code></p>
|
||||
<p><strong>Description</strong>: Create new book record</p>
|
||||
<p><strong>Request Parameters</strong>:</p>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book Title"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"author"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Author Name"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<p><strong>Parameter Description</strong>:</p>
|
||||
<ul>
|
||||
<li><code>title</code> (optional): Book title, defaults to "Untitled"</li>
|
||||
<li><code>author</code> (optional): Author name, defaults to "Unknown"</li>
|
||||
</ul>
|
||||
<p><strong>curl Request Examples</strong>:</p>
|
||||
<pre><code class="language-bash"><span class="hljs-comment"># Create book with complete information</span>
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||
-d <span class="hljs-string">'{
|
||||
"title": "Dream of the Red Chamber",
|
||||
"author": "Cao Xueqin"
|
||||
}'</span>
|
||||
|
||||
<span class="hljs-comment"># Create book with only title</span>
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||
-d <span class="hljs-string">'{
|
||||
"title": "New Book Title"
|
||||
}'</span>
|
||||
|
||||
<span class="hljs-comment"># Create empty book (using defaults)</span>
|
||||
curl -X POST https://hono.edgeone.app/book \
|
||||
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||
-d <span class="hljs-string">'{}'</span>
|
||||
</code></pre>
|
||||
<p><strong>Response Example</strong>:</p>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book created successfully"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"book"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"abc123def"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book Title"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"author"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Author Name"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"createdAt"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2023-12-01T10:00:00.000Z"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<h4>3. Get Book Information</h4>
|
||||
<p><strong>curl Request Examples</strong>:</p>
|
||||
<pre><code class="language-bash"><span class="hljs-comment"># Get all books list</span>
|
||||
curl -X GET https://hono.edgeone.app/book
|
||||
|
||||
<span class="hljs-comment"># Get specific book details</span>
|
||||
curl -X GET https://hono.edgeone.app/book/1
|
||||
|
||||
<span class="hljs-comment"># Get personal page</span>
|
||||
curl -X GET https://hono.edgeone.app/john
|
||||
</code></pre>
|
||||
<h3>Error Code Description</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Error Code</th>
|
||||
<th>HTTP Status Code</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>VALIDATION_ERROR</code></td>
|
||||
<td>400</td>
|
||||
<td>Request parameter validation failed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>FILE_UPLOAD_ERROR</code></td>
|
||||
<td>400</td>
|
||||
<td>File upload failed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>NOT_FOUND</code></td>
|
||||
<td>404</td>
|
||||
<td>Resource not found</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>INTERNAL_ERROR</code></td>
|
||||
<td>500</td>
|
||||
<td>Internal server error</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<h3>Rate Limiting</h3>
|
||||
<ul>
|
||||
<li>All API endpoints currently have no rate limiting</li>
|
||||
<li>Client-side request frequency control is recommended</li>
|
||||
</ul>
|
||||
<h3>CORS Support</h3>
|
||||
<p>All API endpoints support cross-origin access, response headers include:</p>
|
||||
<ul>
|
||||
<li><code>Access-Control-Allow-Origin: *</code></li>
|
||||
<li><code>Access-Control-Allow-Methods: POST, GET, OPTIONS</code></li>
|
||||
<li><code>Access-Control-Allow-Headers: Content-Type, Authorization</code></li>
|
||||
</ul>
|
||||
<h2>🔧 Development</h2>
|
||||
<h3>Local Development</h3>
|
||||
<pre><code class="language-bash"><span class="hljs-comment"># Install dependencies</span>
|
||||
npm install
|
||||
|
||||
<span class="hljs-comment"># Start development server</span>
|
||||
npm run dev
|
||||
</code></pre>
|
||||
<h3>Deployment</h3>
|
||||
<pre><code class="language-bash"><span class="hljs-comment"># Deploy to EdgeOne</span>
|
||||
npm run deploy
|
||||
</code></pre>
|
||||
<h2>🌐 Environment Variables</h2>
|
||||
<p>The project uses the following environment variables and global objects:</p>
|
||||
<ul>
|
||||
<li><code>my_kv</code> - KV storage instance for data persistence</li>
|
||||
</ul>
|
||||
<h2>🛡️ Security Features</h2>
|
||||
<h3>IP Restriction (Optional)</h3>
|
||||
<p>The project includes IP restriction middleware configuration (commented by default), which can limit access sources:</p>
|
||||
<pre><code class="language-typescript">app.<span class="hljs-title function_">use</span>(<span class="hljs-string">'*'</span>, <span class="hljs-title function_">ipRestriction</span>(<span class="hljs-comment">/* configuration */</span>));
|
||||
</code></pre>
|
||||
<h2>📝 API Response Format</h2>
|
||||
<h3>Success Response</h3>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Operation successful"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<h3>Error Response</h3>
|
||||
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||
<span class="hljs-attr">"error"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"ERROR_CODE"</span><span class="hljs-punctuation">,</span>
|
||||
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Error description"</span>
|
||||
<span class="hljs-punctuation">}</span>
|
||||
</code></pre>
|
||||
<h2>🎨 UI Design</h2>
|
||||
<p>The project adopts modern UI design:</p>
|
||||
<ul>
|
||||
<li>Responsive layout</li>
|
||||
<li>System font stack</li>
|
||||
<li>Card-style design</li>
|
||||
<li>Unified color theme</li>
|
||||
<li>Elegant error pages</li>
|
||||
</ul>
|
||||
<h2>📦 Dependencies</h2>
|
||||
<ul>
|
||||
<li><strong>hono</strong> - Web framework</li>
|
||||
<li><strong>@edgeone/ef-types</strong> - EdgeOne Functions type definitions</li>
|
||||
<li><strong>edgeone</strong> - EdgeOne CLI tool</li>
|
||||
</ul>
|
||||
<h2>🤝 Contributing</h2>
|
||||
<p>Welcome to submit Issues and Pull Requests to improve this project.</p>
|
||||
<h2>📄 License</h2>
|
||||
<p>MIT License</p>
|
||||
|
||||
|
||||
<div class="highlight-box">
|
||||
<h3>📝 Development Notes</h3>
|
||||
<p>This is a complete example application demonstrating various features and best practices of the Hono framework on EdgeOne Functions.</p>
|
||||
<p>The project structure is clear, code is well organized, and suitable as a starting template for other projects.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
244
public/style.css
Normal file
244
public/style.css
Normal file
@@ -0,0 +1,244 @@
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||
sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #1a202c;
|
||||
border-bottom: 3px solid #3182ce;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #2d3748;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 600;
|
||||
border-bottom: 2px solid #e2e8f0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #4a5568;
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #718096;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3182ce;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #2c5282;
|
||||
border-bottom-color: #3182ce;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 20px 0;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
th {
|
||||
background: #4a5568;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
text-align: left;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f7fafc;
|
||||
}
|
||||
|
||||
tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f1f5f9;
|
||||
color: #e53e3e;
|
||||
padding: 3px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #1a202c;
|
||||
color: #e2e8f0;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
margin: 20px 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
border: 1px solid #2d3748;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
background: #1a202c !important;
|
||||
color: #e2e8f0 !important;
|
||||
}
|
||||
|
||||
.highlight-box {
|
||||
background: linear-gradient(135deg, #e6fffa 0%, #f0fff4 100%);
|
||||
border-left: 4px solid #38a169;
|
||||
padding: 24px;
|
||||
margin: 30px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.1);
|
||||
}
|
||||
|
||||
.highlight-box h3 {
|
||||
color: #2f855a;
|
||||
margin-top: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.highlight-box p {
|
||||
color: #2d3748;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.highlight-box p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 4px solid #cbd5e0;
|
||||
padding-left: 20px;
|
||||
margin: 20px 0;
|
||||
font-style: italic;
|
||||
color: #4a5568;
|
||||
background: #f7fafc;
|
||||
padding: 16px 20px;
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 15px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #a0aec0;
|
||||
}
|
||||
124
scripts/sync-docs.js
Normal file
124
scripts/sync-docs.js
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { marked } = require('marked');
|
||||
const { markedHighlight } = require('marked-highlight');
|
||||
const hljs = require('highlight.js');
|
||||
|
||||
marked.use(markedHighlight({
|
||||
langPrefix: 'hljs language-',
|
||||
highlight(code, lang) {
|
||||
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||
return hljs.highlight(code, { language }).value;
|
||||
}
|
||||
}));
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.link = function(token) {
|
||||
const href = token.href;
|
||||
const title = token.title;
|
||||
const text = token.text;
|
||||
const titleAttr = title ? ` title="${title}"` : '';
|
||||
|
||||
if (text.includes('<img') || text.match(/^!\[.*\]\(.*\)$/)) {
|
||||
if (text.match(/^!\[(.*?)\]\((.*?)\)$/)) {
|
||||
const imgMatch = text.match(/^!\[(.*?)\]\((.*?)\)$/);
|
||||
const altText = imgMatch[1];
|
||||
const imgSrc = imgMatch[2];
|
||||
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer"><img src="${imgSrc}" alt="${altText}" /></a>`;
|
||||
}
|
||||
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||
}
|
||||
|
||||
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||
};
|
||||
|
||||
marked.setOptions({
|
||||
breaks: false,
|
||||
gfm: true,
|
||||
headerIds: true,
|
||||
mangle: false,
|
||||
pedantic: false,
|
||||
sanitize: false,
|
||||
silent: false,
|
||||
smartLists: true,
|
||||
smartypants: false,
|
||||
xhtml: false,
|
||||
renderer: renderer
|
||||
});
|
||||
|
||||
const readmePath = path.join(__dirname, '../README.md');
|
||||
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
|
||||
|
||||
function parseReadme(content) {
|
||||
const titleMatch = content.match(/^# (.+)$/m);
|
||||
const title = titleMatch ? titleMatch[1] : 'EdgeOne Pages Hono Application';
|
||||
|
||||
const descMatch = content.match(/This is a modern Web application[^\n]+/);
|
||||
const description = descMatch ? descMatch[0] : '';
|
||||
|
||||
const liveDemoMatch = content.match(/Live demo: (.+)$/m);
|
||||
const liveDemo = liveDemoMatch ? liveDemoMatch[0] : '';
|
||||
|
||||
const hasDeployButton = content.includes('Deploy with EdgeOne Pages');
|
||||
|
||||
const htmlContent = marked(content);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
liveDemo,
|
||||
hasDeployButton,
|
||||
htmlContent
|
||||
};
|
||||
}
|
||||
|
||||
function generateHTML(data) {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${data.title}</title>
|
||||
<link rel="stylesheet" href="./style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
${data.htmlContent}
|
||||
|
||||
<div class="highlight-box">
|
||||
<h3>📝 Development Notes</h3>
|
||||
<p>This is a complete example application demonstrating various features and best practices of the Hono framework on EdgeOne Functions.</p>
|
||||
<p>The project structure is clear, code is well organized, and suitable as a starting template for other projects.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
console.log('📖 Reading README.md...');
|
||||
const parsedData = parseReadme(readmeContent);
|
||||
|
||||
console.log('🔄 Generating HTML with marked...');
|
||||
const htmlContent = generateHTML(parsedData);
|
||||
|
||||
console.log('💾 Writing to public/index.html...');
|
||||
const outputPath = path.join(__dirname, '../public/index.html');
|
||||
fs.writeFileSync(outputPath, htmlContent, 'utf-8');
|
||||
|
||||
console.log('✅ Successfully converted README.md to HTML using marked');
|
||||
} catch (error) {
|
||||
console.error('❌ Error converting docs:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = { parseReadme, generateHTML };
|
||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"lib": [
|
||||
"ESNext"
|
||||
],
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "hono/jsx",
|
||||
"types": ["@edgeone/ef-types"]
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user