Initial commit
This commit is contained in:
23
templates/package.json
Normal file
23
templates/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
55
templates/public/index.html
Normal file
55
templates/public/index.html
Normal 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>
|
||||
83
templates/public/script.js
Normal file
83
templates/public/script.js
Normal 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
178
templates/public/styles.css
Normal 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
84
templates/src/index.ts
Normal 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
45
templates/tsconfig.json
Normal 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
11
templates/vite.config.ts
Normal 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
52
templates/wrangler.jsonc
Normal 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" }]
|
||||
}
|
||||
Reference in New Issue
Block a user