feat: init
This commit is contained in:
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;
|
||||
Reference in New Issue
Block a user