A Whole Website in a Single JavaScript File
This site is a pretty standard demo website; a site with links to different pages. Nothing to write home about except that the whole website is contained within a single JavaScript file, and is rendered dynamically, just in time, at the edge, close to the user.
The routing is fairly minimal: we use the
router
module which uses
URLPattern
under the hood for pattern matching.
/** @jsx h */
/// <reference no-default-lib="true"/>
/// <reference lib="dom" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
import { serve } from "https://deno.land/std@0.140.0/http/server.ts";
import { router } from "https://crux.land/router@0.0.11";
import { h, ssr } from "https://crux.land/nanossr@0.0.4";
const render = (component) => ssr(() => <App>{component}</App>);
serve(router(
{
"/": () => render(<Landing />),
"/stats": () => render(<Stats />),
"/bagels": () => render(<Bagels />),
},
() => render(<NotFound />),
));
We have 3 paths: /
for the main page, and/stats
for some statistics, and
/bagels
to display various bagels that we sell. We also have a fallback
handler (the last argument in the snippet), which will be called when none of
the paths match, acting as a 404 page. These are served using the serve
function from the standard library.
For rendering of the JSX, we use nanossr
,
which is a tiny server-side renderer that uses twind
under the hood for
styling. This is invoked by using the ssr
function, to which a callback is
passed which should return JSX. The ssr
function returns a
Response
. We
create a render
function to remove the need for larger duplication of code,
just to tidy things up.
But before any of this, you probably noticed some funny looking comments. Let’s go through them:
/** @jsx h */
: this pragma tells Deno which function we want to use for transpiling the JSX./// <reference no-default-lib="true"/>
: Deno comes with various TypeScript libraries enabled by default; with this comment, we tell it to not use those, but instead only the ones we specify manually./// <reference lib="dom" />
,/// <reference lib="dom.asynciterable" />
, and/// <reference lib="deno.ns" />
: these libraries provide various typings. Thedom
one gives us various typings related to the DOM, anddom.asynciterable
defines functions that need async iterators as these are not included in the normaldom
library as of writing. Thedeno.ns
library gives us typings for any of the functions and classes under theDeno.*
namespace, likeDeno.readFile
. You can read more about the various libraries available in the manual.
Let’s look at some actual code; in the code snippet earlier you might have
noticed that we wrap all route components in an App
component. This is defined
as:
function App({ children }) {
return (
<div class="min-h-screen">
<NavBar />
{children}
</div>
);
}
It’s fairly compact: it returns a div and inside that we have our NavBar
which
is our menu navigation at the top of the page. We’re doing templating
server-side, sharing components between different pages using the same language
one would do this client-side.
Hosted on Deno Deploy, this little website is able to acheive a perfect pagespeed score. Serviced from an anycast IP address over encrypted HTTP from 29 data centers around the world, the site is fully managed and will be available indefinitely at zero cost.
Everything mentioned here can be viewed on a playground.