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

23
templates/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "cloudflare-worker-base-test",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.13.13",
"@cloudflare/vitest-pool-workers": "^0.8.19",
"typescript": "^5.5.2",
"vite": "^7.1.10",
"vitest": "~3.2.0",
"wrangler": "^4.43.0"
},
"dependencies": {
"hono": "^4.10.1"
}
}

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%;
}
}

84
templates/src/index.ts Normal file
View File

@@ -0,0 +1,84 @@
/**
* Cloudflare Worker with Hono
*
* CRITICAL: Export pattern to prevent "Cannot read properties of undefined (reading 'map')" error
* See: https://github.com/honojs/hono/issues/3955
*
* ✅ CORRECT: export default app (for Hono apps)
* ❌ WRONG: export default { fetch: app.fetch } (causes build errors with Vite)
*
* Exception: If you need multiple handlers (scheduled, tail, etc.), use Module Worker format:
* export default {
* fetch: app.fetch,
* scheduled: async (event, env, ctx) => { ... }
* }
*/
import { Hono } from 'hono'
// Type-safe environment bindings
type Bindings = {
ASSETS: Fetcher
}
const app = new Hono<{ Bindings: Bindings }>()
/**
* API Routes
*
* These routes are handled by the Worker BEFORE static assets due to
* "run_worker_first": ["/api/*"] in wrangler.jsonc
*/
app.get('/api/hello', (c) => {
return c.json({
message: 'Hello from Cloudflare Workers!',
timestamp: new Date().toISOString(),
})
})
app.get('/api/data', (c) => {
return c.json({
items: [
{ id: 1, name: 'Item 1', description: 'First item' },
{ id: 2, name: 'Item 2', description: 'Second item' },
{ id: 3, name: 'Item 3', description: 'Third item' },
],
count: 3,
})
})
app.post('/api/echo', async (c) => {
const body = await c.req.json()
return c.json({
received: body,
method: c.req.method,
})
})
/**
* Health check endpoint
*/
app.get('/api/health', (c) => {
return c.json({
status: 'ok',
version: '1.0.0',
environment: c.env ? 'production' : 'development',
})
})
/**
* Fallback to Static Assets
*
* Any route not matched above will be served from the public/ directory
* thanks to Workers Static Assets
*/
app.all('*', (c) => {
// Let Cloudflare Workers handle static assets automatically
return c.env.ASSETS.fetch(c.req.raw)
})
/**
* Export the Hono app directly (ES Module format)
* This is the correct pattern for Cloudflare Workers with Hono + Vite
*/
export default app

45
templates/tsconfig.json Normal file
View File

@@ -0,0 +1,45 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es2021",
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": ["es2021"],
/* Specify what JSX code is generated. */
"jsx": "react-jsx",
/* Specify what module code is generated. */
"module": "es2022",
/* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "Bundler",
/* Enable importing .json files */
"resolveJsonModule": true,
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"allowJs": true,
/* Enable error reporting in type-checked JavaScript files. */
"checkJs": false,
/* Disable emitting files from a compilation. */
"noEmit": true,
/* Ensure that each file can be safely transpiled without relying on other imports. */
"isolatedModules": true,
/* Allow 'import x from y' when a module doesn't have a default export. */
"allowSyntheticDefaultImports": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,
/* Enable all strict type-checking options. */
"strict": true,
/* Skip type checking all .d.ts files. */
"skipLibCheck": true,
"types": [
"./worker-configuration.d.ts"
]
},
"exclude": ["test"],
"include": ["worker-configuration.d.ts", "src/**/*.ts"]
}

11
templates/vite.config.ts Normal file
View File

@@ -0,0 +1,11 @@
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [
cloudflare({
// Optional: Configure the plugin if needed
// See: https://developers.cloudflare.com/workers/vite-plugin/
}),
],
})

52
templates/wrangler.jsonc Normal file
View File

@@ -0,0 +1,52 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "cloudflare-worker-base-test",
"main": "src/index.ts",
"account_id": "0460574641fdbb98159c98ebf593e2bd",
"compatibility_date": "2025-10-11",
"observability": {
"enabled": true
},
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/
*
* CRITICAL: run_worker_first prevents SPA fallback from intercepting API routes
* See: https://github.com/cloudflare/workers-sdk/issues/8879
*/
"assets": {
"directory": "./public/",
"binding": "ASSETS",
"not_found_handling": "single-page-application",
"run_worker_first": ["/api/*"]
}
/**
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" }
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" }
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}