Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:24:36 +08:00
commit ca8c7fd975
15 changed files with 2860 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cloudflare Worker + Hono + Static Assets</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<header>
<h1>🔥 Cloudflare Worker + Hono + Static Assets</h1>
<p>Testing API routes with Workers Static Assets and SPA fallback</p>
</header>
<section class="test-section">
<h2>API Tests</h2>
<p>These API routes are handled by the Worker thanks to <code>run_worker_first</code> configuration.</p>
<div class="test-group">
<button onclick="testHello()">Test /api/hello</button>
<button onclick="testData()">Test /api/data</button>
<button onclick="testEcho()">Test /api/echo (POST)</button>
<button onclick="testHealth()">Test /api/health</button>
</div>
<div id="results">
<h3>Results:</h3>
<pre id="output">Click a button to test the API...</pre>
</div>
</section>
<section class="info-section">
<h2>✅ What This Demonstrates</h2>
<ul>
<li><strong>Static Assets</strong>: This HTML is served from <code>public/</code></li>
<li><strong>API Routes</strong>: <code>/api/*</code> routes are handled by Worker first</li>
<li><strong>SPA Fallback</strong>: Unknown routes return this index.html</li>
<li><strong>Hono Framework</strong>: Type-safe routing with JSON responses</li>
<li><strong>ES Module Format</strong>: Correct export pattern prevents build errors</li>
</ul>
</section>
<footer>
<p>
📚 <a href="https://developers.cloudflare.com/workers/static-assets/">Static Assets Docs</a> |
<a href="https://hono.dev/docs/getting-started/cloudflare-workers">Hono Docs</a> |
<a href="https://developers.cloudflare.com/workers/vite-plugin/">Vite Plugin Docs</a>
</p>
</footer>
</div>
<script src="/script.js"></script>
</body>
</html>

View File

@@ -0,0 +1,83 @@
/**
* API Test Functions
*
* These functions call the Worker API routes and display the results.
* Notice how API routes work seamlessly with static assets thanks to
* the "run_worker_first" configuration in wrangler.jsonc
*/
const output = document.getElementById('output')
function displayResult(data, status = 200) {
const formatted = JSON.stringify(data, null, 2)
output.textContent = `Status: ${status}\n\n${formatted}`
output.style.borderLeft = status === 200 ? '4px solid #4caf50' : '4px solid #f44336'
}
function displayError(error) {
output.textContent = `Error: ${error.message}\n\nCheck console for details.`
output.style.borderLeft = '4px solid #f44336'
console.error('API Error:', error)
}
async function testHello() {
try {
const response = await fetch('/api/hello')
const data = await response.json()
displayResult(data, response.status)
} catch (error) {
displayError(error)
}
}
async function testData() {
try {
const response = await fetch('/api/data')
const data = await response.json()
displayResult(data, response.status)
} catch (error) {
displayError(error)
}
}
async function testEcho() {
try {
const payload = {
test: 'data',
timestamp: new Date().toISOString(),
random: Math.random(),
}
const response = await fetch('/api/echo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
const data = await response.json()
displayResult(data, response.status)
} catch (error) {
displayError(error)
}
}
async function testHealth() {
try {
const response = await fetch('/api/health')
const data = await response.json()
displayResult(data, response.status)
} catch (error) {
displayError(error)
}
}
// Display welcome message on load
window.addEventListener('DOMContentLoaded', () => {
displayResult({
message: 'Welcome! Click a button above to test the API.',
info: 'All API routes are handled by the Cloudflare Worker',
static_assets: 'This HTML/CSS/JS is served from public/ directory',
})
})

178
templates/public/styles.css Normal file
View File

@@ -0,0 +1,178 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container {
max-width: 900px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 3rem 2rem;
text-align: center;
}
header h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
header p {
opacity: 0.9;
font-size: 1.1rem;
}
section {
padding: 2rem;
}
h2 {
color: #667eea;
margin-bottom: 1rem;
font-size: 1.5rem;
}
h3 {
color: #555;
margin-bottom: 0.5rem;
font-size: 1.2rem;
}
.test-section {
border-bottom: 1px solid #e0e0e0;
}
.test-group {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-bottom: 1.5rem;
}
button {
background: #667eea;
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s;
font-weight: 500;
}
button:hover {
background: #5568d3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
button:active {
transform: translateY(0);
}
#results {
background: #f5f5f5;
border-radius: 8px;
padding: 1.5rem;
}
#output {
background: #1e1e1e;
color: #d4d4d4;
padding: 1rem;
border-radius: 6px;
overflow-x: auto;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
white-space: pre-wrap;
word-wrap: break-word;
}
.info-section ul {
list-style: none;
padding-left: 0;
}
.info-section li {
padding: 0.75rem;
margin-bottom: 0.5rem;
background: #f8f9fa;
border-left: 4px solid #667eea;
border-radius: 4px;
}
.info-section strong {
color: #667eea;
}
code {
background: #e8eaf6;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
color: #5e35b1;
}
footer {
background: #f8f9fa;
padding: 1.5rem 2rem;
text-align: center;
border-top: 1px solid #e0e0e0;
}
footer a {
color: #667eea;
text-decoration: none;
font-weight: 500;
margin: 0 0.5rem;
}
footer a:hover {
text-decoration: underline;
}
@media (max-width: 600px) {
body {
padding: 1rem;
}
header {
padding: 2rem 1rem;
}
header h1 {
font-size: 1.5rem;
}
section {
padding: 1.5rem;
}
.test-group {
flex-direction: column;
}
button {
width: 100%;
}
}