diff --git a/README.md b/README.md
index 6f53e9b..b2bb0a7 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,15 @@
-# Serverless Microapp
+# Serverless React App
+Domain: `cf.neweradesign.net`
## Client Hosted on Cloudflare Workers
+- uses Wrangler for orchestration
- uses itty-router for Express-like routing
-- uses handlebars for views engine
+- uses React for client side framework
+- uses custom webpack config for build
- 100,000 free requests per day
## Server Hosted on AWS Lambda
+- uses AWS CDK for orchestration
- uses node engine
- best for database operations (i.e. working with MongoDB)
- 10,000 free requests per day
diff --git a/client/package.json b/client/package.json
index 1acdd43..a527f1f 100644
--- a/client/package.json
+++ b/client/package.json
@@ -5,25 +5,30 @@
"description": "tech tools hosted on cloudflare worker",
"main": "index.js",
"scripts": {
- "build": "npm run compilehbs && npm run transpilehbs && rm src/*-original.js",
- "compilehbs": "handlebars -e hbs -f src/pages-original.js src/views/pages/ && handlebars -e hbs -p -f src/partials-original.js src/views/partials/",
+ "build": "webpack",
"deploy": "wrangler deploy src/index.js",
"prepare": "husky",
- "start": "wrangler dev src/index.js",
- "test": "echo \"Error: no test specified\" && exit 1",
- "transpilehbs": "hbs-import-transpile src/pages-original.js > assets/pages.js && hbs-import-transpile src/partials-original.js > assets/partials.js"
+ "start": "wrangler dev src/index.js"
},
- "type": "module",
"author": "",
"license": "ISC",
"devDependencies": {
- "hbs-import-transpile": "^1.0.4"
+ "@babel/core": "^7.24.8",
+ "@babel/preset-env": "^7.24.8",
+ "@babel/preset-react": "^7.24.7",
+ "babel-loader": "^9.1.3",
+ "css-loader": "^7.1.2",
+ "file-loader": "^6.2.0",
+ "style-loader": "^4.0.0",
+ "webpack": "^5.93.0",
+ "webpack-cli": "^5.1.4"
},
"dependencies": {
+ "@cloudflare/kv-asset-handler": "^0.3.4",
"buffer": "^6.0.3",
- "handlebars": "^4.7.8",
- "hbs-async-render": "^1.0.1",
"itty-router": "^2.6.6",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"serverless-cloudflare-workers": "^1.2.0"
}
}
diff --git a/client/src/views/partials/logos/cloudflare.hbs b/client/public/cloudflare.svg
similarity index 94%
rename from client/src/views/partials/logos/cloudflare.hbs
rename to client/public/cloudflare.svg
index 4df92a9..f0b1152 100644
--- a/client/src/views/partials/logos/cloudflare.hbs
+++ b/client/public/cloudflare.svg
@@ -3,19 +3,19 @@
diff --git a/client/src/App.jsx b/client/src/App.jsx
new file mode 100644
index 0000000..969bc67
--- /dev/null
+++ b/client/src/App.jsx
@@ -0,0 +1,34 @@
+import React, { useState } from 'react';
+import logo from '../public/cloudflare.svg'
+import { Header } from "./components/Header/Header";
+import { Footer } from "./components/Footer/Footer";
+
+export const App = () => {
+ const [count, setCount] = useState(0);
+ const tester = "Victor E";
+
+ return (
+
+
+
+
+
+
+
+
+
+
Hello, Cloudflare Workers!
+
This is a basic React page deployed on Cloudflare Workers.
+
+ Your name: { tester }
+
+
{ tester }
+
+
Count: { count }
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/client/src/components/Footer/Footer.jsx b/client/src/components/Footer/Footer.jsx
new file mode 100644
index 0000000..e4bdff5
--- /dev/null
+++ b/client/src/components/Footer/Footer.jsx
@@ -0,0 +1,5 @@
+export const Footer = () => (
+
+);
\ No newline at end of file
diff --git a/client/src/components/Header/Header.jsx b/client/src/components/Header/Header.jsx
new file mode 100644
index 0000000..07d334e
--- /dev/null
+++ b/client/src/components/Header/Header.jsx
@@ -0,0 +1,7 @@
+export const Header = () => (
+
+);
\ No newline at end of file
diff --git a/client/src/index.js b/client/src/index.js
index fda9fb6..a2d02fa 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -1,29 +1,34 @@
-import { Router } from 'itty-router'
-import { base64Handler, postHandler, rootHandler } from './routers'
-import { registerHBHelper } from './utils/hbsAsyncHelper.js'
-import '../assets/pages.js';
-import '../assets/partials.js';
+import { Router } from 'itty-router';
+import * as routes from "./routers";
-// Register HB
-registerHBHelper();
-
-// Create a new router
const router = Router();
-router.get("/", rootHandler);
+
+const { base64Handler, healthHandler, postHandler, rootHandler, routesAndAssetsHandler } = routes;
+
+/** The rootHandler will serve the React app accordingly
+ * Other routes can be defined as neeeded
+ */
+router.get('/', rootHandler);
+// Health route
+router.get('/health', healthHandler);
+// Test route
router.get("/base64/:text", base64Handler);
+// Test post route
router.post("/post", postHandler);
/**
* This is the last route we define, it will match anything that hasn't hit a route we've defined
* above, therefore it's useful as a 404 (and avoids us hitting worker exceptions, so make sure
* to include it!).
-*/
+ */
router.all("*", () => new Response("404, not found!", { status: 404 }));
/**
- * This snippet ties our worker to the router we defined above, all incoming requests
- * are passed to the router where your routes are called and the response is sent.
-*/
-addEventListener('fetch', (e) => {
- e.respondWith(router.handle(e.request))
+ * All incoming requests to the worker are passed to the router
+ * where your routes are called and the response is sent.
+ * routesAndAssetsHandler will map assets and routes accordingly
+ */
+addEventListener('fetch', event => {
+ event.respondWith(routesAndAssetsHandler(event, router));
});
+
diff --git a/client/src/main.js b/client/src/main.js
new file mode 100644
index 0000000..25392e3
--- /dev/null
+++ b/client/src/main.js
@@ -0,0 +1,6 @@
+import { App } from './App';
+import { createRoot } from 'react-dom/client';
+
+// Render your React component instead
+const root = createRoot(document.getElementById('root'));
+root.render();
diff --git a/client/src/routers/handler.js b/client/src/routers/handler.js
new file mode 100644
index 0000000..b510595
--- /dev/null
+++ b/client/src/routers/handler.js
@@ -0,0 +1,27 @@
+import { getAssetFromKV } from "@cloudflare/kv-asset-handler";
+
+export const routesAndAssetsHandler = async (event, router) => {
+ // Extract the request from the event
+ const request = event.request;
+
+ // Check if the request is for bundle.js
+ if (
+ request.url.endsWith('/bundle.js')
+ || request.url.endsWith('/favicon.ico')
+ || request.url.endsWith('.svg')
+ ) {
+ // Serve the bundle.js file from KV storage
+ try {
+ // Pass the entire event object to getAssetFromKV
+ return await getAssetFromKV(event);
+ } catch (e) {
+ return new Response(`Bundle not found: ${e.message}`, {
+ status: 404,
+ statusText: 'Not Found',
+ });
+ }
+ }
+
+ // For any other requests, handle them with the router
+ return router.handle(request);
+};
\ No newline at end of file
diff --git a/client/src/routers/health/router.js b/client/src/routers/health/router.js
new file mode 100644
index 0000000..8874958
--- /dev/null
+++ b/client/src/routers/health/router.js
@@ -0,0 +1 @@
+export const healthHandler = () => new Response("success");
\ No newline at end of file
diff --git a/client/src/routers/index.js b/client/src/routers/index.js
index 03606a1..0e64c0b 100644
--- a/client/src/routers/index.js
+++ b/client/src/routers/index.js
@@ -1,9 +1,13 @@
import { base64Handler } from './encoding/base64/router.js';
+import { healthHandler } from './health/router'
import { postHandler } from './post/router.js';
import { rootHandler } from './root/router.js';
+import { routesAndAssetsHandler } from './handler';
export {
base64Handler,
+ healthHandler,
postHandler,
- rootHandler
+ rootHandler,
+ routesAndAssetsHandler
}
\ No newline at end of file
diff --git a/client/src/routers/root/html.js b/client/src/routers/root/html.js
deleted file mode 100644
index 385e715..0000000
--- a/client/src/routers/root/html.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export const html = `
-
-
-
Hello, world! This is the root page of your Worker template.
- `;
\ No newline at end of file
diff --git a/client/src/routers/root/router.js b/client/src/routers/root/router.js
index 5d6d40c..8bf444e 100644
--- a/client/src/routers/root/router.js
+++ b/client/src/routers/root/router.js
@@ -1,14 +1,25 @@
-import Handlebars from 'handlebars/runtime.js';
-
-import { hbsAsyncRender } from 'hbs-async-render'
-// import { html } from './html';
-
export const rootHandler = async () => {
- const output = await hbsAsyncRender(Handlebars, 'body', {name: "Victor E."});
- return new Response(output, {headers: {'Content-Type': 'text/html'}});
- // return new Response(html, {
- // headers: {
- // "Content-Type": "text/html"
- // }
- // })
+ // const html = ReactDOMServer.renderToString();
+ return new Response(`
+
+
+
+
+
+ React App on Cloudflare Workers
+
+
+
+
+
+
+
+
+
+
+ `, {
+ headers: {
+ 'Content-Type': 'text/html',
+ },
+ });
};
\ No newline at end of file
diff --git a/client/src/utils/hbsAsyncHelper.js b/client/src/utils/hbsAsyncHelper.js
deleted file mode 100644
index 8b2d577..0000000
--- a/client/src/utils/hbsAsyncHelper.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import Handlebars from 'handlebars/runtime.js';
-
-import { registerAsyncHelper } from 'hbs-async-render'
-
-/**
- * Register an asynchronous helper that waits for a second and then resolves with some information
- * that is going to be rendered in the place where `asyncTest` has been used in the Handlebar templates.
- */
-export const registerHBHelper = () => registerAsyncHelper(Handlebars,'asyncTest', function (options, context) {
-
- return new Promise((resolve, reject) => {
- setTimeout(
- function() {
- resolve(`Async render with params: ${options.hash.name}`)
- },
- 200
- );
- });
-
-})
diff --git a/client/src/views/pages/body.hbs b/client/src/views/pages/body.hbs
deleted file mode 100644
index 7b80430..0000000
--- a/client/src/views/pages/body.hbs
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
- {{> structure/htmlhead }}
-
- Cloudflare Workers with Handlebars
-
-
-
-{{> structure/header }}
-
-
-
-
-
-
-
- {{> logos/cloudflare }}
-
-
-
-
This template is rendered by Handlebars in a Cloudflare Worker!