Server-side middle-end prioritizing page load times and simplicity.
- Server-side async JSX
- Generate dynamic HTML thanks to resources and JS
- Explicit client-side JavaScript boundaries
- Manipulate and attach JS to sent HTML through JSX refs
- TypeScript-first
- Even for JS manipulation
- Industrial-grade navigation (à la Remix)
- Dynamic nested routes
- Actions through
forms to update reactive resources - Minimum amount of bytes sent over the wire
- Modular design
- Share modules exposing HTML|JS|CSS thanks to the programmatic bundling API
- Extend functionalities, opt-in each package, integrate in larger frameworks
- Programmatic workflow
- Optimized bundling (esbuild under the hood)
- Buildless development
- Simple explicit production build
Classic is not a UI library and depends on none but you can use some. Classic integrates with existing technologies rather than reinventing them.
On top of that, it's modular so parts of it can be used on demand:
- @classic/server - Buildable app server without wrapping tool
- @classic/html - Write HTML and JavaScript as JSX
- @classic/js - Ad hoc (client) JavaScript from typed (server) code
- @classic/router - Client script providing dynamic routing from regular pages
- @classic/morph - Simple element replacement, navigate without page reloads
Typical Classic stack:
- The Web
- Deno - Runtime, LSP, lint, test, DB...
- Classic - HTML/JS/CSS bundling and serving
- Optionally: TailwindCSS - Or any other ad hoc CSS solution
# Prompts a folder name and creates the template
deno run -r --allow-write=. --allow-net https://raw.githubusercontent.com/ngasull/classic/main/examples/hello-world/init.tsRemember: everything runs server-side except what is explicitly wrapped in JS types!
import { dbContext } from "./my-db.ts";
export const YourName = async ({ userId }: { userId: string }) => {
const db = dbContext.use();
const user = await db.user.find(userId);
return (
<div>
Your name will be {user.name}
</div>
);
};import { js } from "@classic/js";
export const YourName = () => {
return (
<div
ref={(div) => js`${div}.innerText = "Your name will be H4CK3D!"`}
>
Your name will be ...
</div>
);
};import type React from "npm:react";
import type { Root } from "npm:react-dom/client";
import { declarePage } from "@classic/server";
import { Bundle } from "@classic/server/bundle";
export const bundle = new Bundle("app");
/*
* Proxied to keep client code explicitly client-side and typed.
* Check the development workflow for more info.
*/
const app = bundle.add<{ render: (root: Root) => void }>(
import.meta.resolve("./app.tsx"),
);
/*
* React could only be imported in `app.tsx`
* We import it as well for demonstration purposes.
*/
const react = bundle.add<typeof import("npm:react-dom/client")>(
"npm:react-dom/client",
);
export default declarePage(() => (
<html>
<body ref={(body) => app.render(react.createRoot(body))} />
</html>
);import type { Bundle } from "@classic/server/bundle";
export const yourName = (bundle: Bundle): JSX.Component => {
const { render } = bundle.add<{ render: (root: Element) => void }>(
"./component-lib.ts",
);
return () => <div ref={(div) => render(div)} />;
};You may check hello-world example