Skip to content

deno, node and deno-sw hello world examples #552

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ func writeOutConsts(version string) error {
"examples/php/hello-world/public/hello-world.html": helloWorldExamplePHP,
"examples/zig/httpz/hello-world/src/hello-world.html": helloWorldExample,
"examples/zig/tokamak/hello-world/hello-world.html": helloWorldExample,
"examples/typescript/deno/public/hello-world.html": helloWorldExample,
"examples/typescript/node/public/hello-world.html": helloWorldExample,
"examples/rust/axum/hello-world/hello-world.html": helloWorldExample,
"examples/rust/rocket/hello-world/hello-world.html": helloWorldExample,
}
Expand Down
8 changes: 8 additions & 0 deletions examples/typescript/deno-sw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
This is a sample app that can serve the same responses from both the Deno backend and the Service Worker while offline.

To test it, start the server with `deno run start` and load the site at `http://localhost:8000`.

The service worker will install in the browser and static assets will be cached. You can then either turn on offline mode in your browser's dev tools Network tab, or turn off the Deno server (there will be a slight delay in the response as it tries to fetch from the network first). Reload the page while offline and it will load from the service worker! And the SSE response when you click Start will be rendered from the SW as well.

If you want to expand upon it, it would be best to modify the `shared-router.ts` file, which is what both versions ultimately use (other than for loading static assets, which require different mechanisms in each environment). You can use `deno run dev` to rebuild the service worker (if your OS is capable of using esbuild) and start the server.

21 changes: 21 additions & 0 deletions examples/typescript/deno-sw/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as esbuild from "https://deno.land/x/esbuild/mod.js";
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader";

await esbuild.build({
plugins: [...denoPlugins()],
write: true,
entryPoints: ["./src/service-worker.ts"],
outfile: "./src/static/service-worker.js",
bundle: true,
minify: false,
format: "esm",
legalComments: "none",
platform: "browser",
conditions: ["worker", "browser"],
resolveExtensions: [".ts", ".js", ".mjs"],
loader: {
".ts": "ts",
},
});

esbuild.stop();
12 changes: 12 additions & 0 deletions examples/typescript/deno-sw/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"imports": {
"@hono/hono": "jsr:@hono/hono@^4.6.19",
"@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.9.0"
},
"tasks": {
"start": "deno run -A ./src/deno.ts",
"build": "deno run -A build.ts",
"dev": "deno task build && deno run start"
},
"nodeModulesDir": "auto"
}
172 changes: 172 additions & 0 deletions examples/typescript/deno-sw/deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions examples/typescript/deno-sw/src/deno.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Hono } from "jsr:@hono/hono/tiny";
import { serveStatic } from "jsr:@hono/hono/deno";
import { createRouter } from "./shared-router.ts";

const app = new Hono();

// Serve static files
app.use("/static/*", serveStatic({ root: "./src" }));

// Serve the service worker from the root path - this is need so that it has scope for the entire application rather than just /static
app.use("/service-worker.js", serveStatic({ path: "./src/static/service-worker.js" }));

// Mount the shared router at the root path
app.route("/", createRouter());

Deno.serve({
port: 8000,
hostname: "127.0.0.1",
}, app.fetch);
39 changes: 39 additions & 0 deletions examples/typescript/deno-sw/src/hello-world.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export function getHelloWorldHtml() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<title>Datastar SDK Demo</title>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/service-worker.js');
console.log('ServiceWorker registration successful with scope:', registration.scope);
} catch (err) {
console.error('ServiceWorker registration failed:', err);
}
});
}
</script>
<script src="https://unpkg.com/@tailwindcss/browser@4"></script>
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>
</head>
<body class="bg-white dark:bg-gray-900 text-lg max-w-xl mx-auto my-16">
<div data-signals-delay="400" class="bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5 space-y-2">
<div class="flex justify-between items-center">
<h1 class="text-gray-900 dark:text-white text-3xl font-semibold">Datastar SDK Demo</h1>
<img src="https://data-star.dev/static/images/rocket.png" alt="Rocket" width="64" height="64" />
</div>
<p class="mt-2">SSE events will be streamed from the backend to the frontend.</p>
<div class="space-x-2">
<label for="delay">Delay in milliseconds</label>
<input data-bind-delay id="delay" type="number" step="100" min="0" class="w-36 rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-sky-500 focus:outline focus:outline-sky-500 dark:disabled:border-gray-700 dark:disabled:bg-gray-800/20" />
</div>
<button data-on-click="@get('/hello-world')" class="rounded-md bg-sky-500 px-5 py-2.5 leading-5 font-semibold text-white hover:bg-sky-700 hover:text-gray-100 cursor-pointer">Start</button>
</div>
<div class="my-16 text-8xl font-bold text-transparent" style="background: linear-gradient(to right in oklch, red, orange, yellow, green, blue, blue, violet); background-clip: text">
<div id="message">Hello, world!</div>
</div>
</body>
</html>`;
}
88 changes: 88 additions & 0 deletions examples/typescript/deno-sw/src/service-worker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { createRouter } from "./shared-router.ts";

declare const self: ServiceWorkerGlobalScope;

const CACHE_NAME = 'datastar-cache';
const CORE_ASSETS = [
'/',
'https://unpkg.com/@tailwindcss/browser@4',
'https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js',
'https://data-star.dev/static/images/rocket.png'
];

const router = createRouter();

self.addEventListener('install', (event) => {
console.log('SW Install');
event.waitUntil(
(async () => {
const cache = await caches.open(CACHE_NAME);
for (const url of CORE_ASSETS) {
try {
const fetchOptions = url.includes('rocket') ? { mode: 'no-cors' as RequestMode } : undefined;
const response = await fetch(url, fetchOptions);
await cache.put(url, response);
console.log('Cached:', url);
} catch (error) {
console.error('Failed to cache:', url, error);
}
}
await self.skipWaiting();
})()
);
});

self.addEventListener('activate', (event) => {
console.log('SW Activate');
event.waitUntil(self.clients.claim());
});

self.addEventListener('fetch', (event) => {
event.respondWith(
(async () => {
const cache = await caches.open(CACHE_NAME);
const url = new URL(event.request.url);

// Try cache first for core assets
if (CORE_ASSETS.includes(url.toString()) || CORE_ASSETS.includes(url.pathname)) {
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
console.log('SW Cache Hit:', event.request.url);
return cachedResponse;
}
}

// If browser is set to offline, use router for dynamic routes
if (!self.navigator.onLine) {
console.log('Browser is offline, using router fallback');
return await router.fetch(event.request);
}

// Try network
try {
console.log('SW Fetch:', event.request.url);

const fetchOptions = event.request.url.includes('rocket') ? { mode: 'no-cors' as RequestMode } : undefined;

const networkResponse = await fetch(event.request, fetchOptions);

// Cache successful GET requests for core assets
if (networkResponse.ok &&
event.request.method === 'GET'
&& (CORE_ASSETS.includes(url.toString()) || CORE_ASSETS.includes(url.pathname))
)
{
await cache.put(event.request, networkResponse.clone());
console.log('SW Cache Put:', event.request.url);
}

return networkResponse;
} catch (error) {
// Fall back to router for dynamic routes
console.log('Network request failed, using router fallback');
return await router.fetch(event.request);
}
})()
);

});
Loading