Skip to content

Repository files navigation

@tub/web JSR

A type-safe CSS-in-TypeScript library and no-magic HTTP router for Deno.

@tub/web merges two capabilities into one package:

  • CSS: scoped class generation, CSS variable theming, at-rules, and style composition — all at build time with zero runtime overhead.
  • Web server: a standards-based HTTP router built directly on Deno.ServeHandler, a directory-based route builder, and a site() convenience function for full-stack Deno applications.

Installation

deno add jsr:@tub/web

CSS

Basic Styles

import{render,style}from"@tub/web";constbutton=style({backgroundColor: "blue",color: "white",padding: "8px 16px",borderRadius: "4px",});// Use in DOMelement.className=button.toString();// Render CSSconstcss=render(button);

Nested Selectors

import{render,style}from"@tub/web";constcard=style({padding: "16px",transition: "transform 0.2s",select: {"&:hover": {transform: "scale(1.02)"},"& > h2": {marginTop: "0"},"@media (min-width: 768px)": {padding: "24px"},},});console.log(render(card));

Custom Selectors

import{join,render,style}from"@tub/web";constbody=style("body",{margin: "0",fontFamily: "sans-serif"});constheader=style("#header",{position: "fixed",top: "0"});console.log(render(join(body,header)));

CSS Variables / Theming

import{fallback,join,render,style,theme}from"@tub/web";constcolors=theme({colors: {primary: "blue",secondary: "green",brand: {light: "#eef",dark: "#335"},},spacing: "8px",});constcard=style({color: colors.colors.primary,backgroundColor: colors.colors.brand.light,padding: colors.spacing,});constbutton=style({color: colors.colors.primary,borderColor: fallback(colors.colors.secondary,"gray"),});constlightTheme=style(":root",colors);constdarkColors=colors.create({colors: {primary: "white",secondary: "#ccc",brand: {light: "#335",dark: "#eef"},},});constdarkTheme=style(".dark",darkColors);console.log(render(join(lightTheme,darkTheme,card)));

Combining Styles

import{render,style,use}from"@tub/web";constbase=style({padding: "8px"});constprimary=style({backgroundColor: "blue",color: "white"});constlarge=style({fontSize: "1.25rem"});constclassName=use(base,primary,large);// "abc123 def456 ghi789"

At-Rules

import{at,join,render}from"@tub/web";constroboto=at("@font-face",{fontFamily: "Roboto",src: "url('/fonts/roboto.woff2') format('woff2')",fontWeight: "400",fontDisplay: "swap",});constthemeColor=at("@property --theme-color",{syntax: '"<color>"',inherits: "true",initialValue: "blue",});constthumbs=at("@counter-style thumbs",{system: "cyclic",symbols: "👍",suffix: " ",});console.log(render(join(roboto,themeColor,thumbs)));

Render Options

import{MINIMAL_RENDER_OPTIONS,render,STANDARD_RENDER_OPTIONS,style,}from"@tub/web";constbutton=style({color: "blue"});console.log(render(button));// human-readableconsole.log(render(button,STANDARD_RENDER_OPTIONS));// same as defaultconsole.log(render(button,MINIMAL_RENDER_OPTIONS));// minified

Reusable Properties

import{properties,style}from"@tub/web";constflexCenter=properties({display: "flex",alignItems: "center",justifyContent: "center",});constcard=style({ ...flexCenter,padding: "16px"});

Style Groups

import{group,render,style}from"@tub/web";constg=group({button: {primary: style({backgroundColor: "blue",color: "white"}),secondary: style({backgroundColor: "gray",color: "black"}),},text: {heading: style({fontSize: "2rem",fontWeight: "bold"}),body: style({fontSize: "1rem"}),},});element.className=g.button.primary.toString();console.log(render(g));

Web Server

Deno provides a powerful, standards-compliant HTTP server. Most router modules still implement patterns from Node.js — untyped middleware, magic next callbacks, and automagical conventions. @tub/web takes a different approach:

  1. No templating tools or magic project setup.
  2. Builds directly on Deno.ServeHandler and web standards.
  3. Type-safe path parameter parsing at the type level.
  4. Composes well enough to implement both SSR and autodocumenting API frameworks.

Manual Router

import{context,handle,html,router}from"@tub/web/router";constapp=router(context({}),{routes: [handle("GET /",()=>html("<h1>Welcome!</h1>")),handle("GET /users/:id",(_,params)=>html(`<h1>User: ${params.id}</h1>`),),],});Deno.serve(app.handle);

Route Files with the Builder

The route builder walks a directory and converts exported tokens into routes. Place route files anywhere under the root path — the file's path relative to root becomes the URL pattern.

// routes/api/users.tsimport{get,post}from"@tub/web/tokens";import*asRfrom"@tub/web/router";// GET /api/usersexportconstlist=get((_req,_params,_ctx)=>R.json(JSON.stringify([{id: 1,name: "Alice"}])));// POST /api/usersexportconstcreate=post(async(req,_params,_ctx)=>{constbody=awaitreq.json();returnR.json(JSON.stringify(body),R.STATUS_CODE.Created);});
// routes/users/:id.tsimport{del,get}from"@tub/web/tokens";import*asRfrom"@tub/web/router";// GET /users/:id — params.id is type-checkedexportconstshow=get((_req,params,_ctx)=>R.json(JSON.stringify({id: params.id})));// DELETE /users/:idexportconstremove=del((_req,_params,_ctx)=>R.text("Deleted",R.STATUS_CODE.NoContent));

Site Builder (Quickstart)

site() is the fastest way to get a full-stack Deno app running. It wires together the client SPA plugin, server routes plugin, and static file plugin, then returns a request handler ready for Deno.serve.

import{site}from"@tub/web";Deno.serve(awaitsite({root_path: "./src/routes",site_name: "My Application",unsafe_import: (path)=>import(path),}),);

Advanced: Plugins Directly

Use the individual plugins when you need finer control:

import{build}from"@tub/web/builder";import{server_plugin}from"@tub/web/plugin_server";import{static_plugin}from"@tub/web/plugin_static";import{openapi_plugin}from"@tub/web/plugin_openapi";import{deno_fs}from"@tub/web/deno_fs";import{context,router}from"@tub/web/router";constresult=awaitbuilder({root_path: "./src/routes",fs: deno_fs,unsafe_import: (path)=>import(path),plugins: [server_plugin({middleware: [authMiddleware]}),static_plugin({exclude_extensions: [".ts",".tsx"]}),openapi_plugin({info: {title: "My API",version: "1.0.0"}}),],}).build();constapp=router(context({}),{routes: result.site_routes.map((r)=>r.route),});Deno.serve(app.handle);

Schema Validation

Route handlers can declare typed params and body schemas. The server plugin validates incoming requests automatically and returns 400 on failure.

import{get,post}from"@tub/web/tokens";import*asRfrom"@tub/web/router";exportconstget_user=get({params: (s)=>s.struct({id: s.string()}),handler: (_req,params,_body,_ctx)=>R.json(JSON.stringify(params)),});exportconstcreate_user=post({body: (s)=>s.struct({name: s.string(),age: s.number()}),handler: async(_req,_params,body,_ctx)=>R.json(JSON.stringify(body),R.STATUS_CODE.Created),});

Middleware

import{middleware}from"@tub/web/router";constlog=middleware((next)=>async(req,params,ctx)=>{ctx.logger.info(`${req.method}${req.url}`);returnnext(req,params,ctx);});

API Reference

CSS — Core Functions

  • style(input) — Creates a Style with an auto-generated class name
  • style(selector, input) — Creates a Style with a custom selector
  • render(style, options?) — Renders a HasStyles object to a CSS string
  • join(...styles) — Combines multiple HasStyles objects into one for rendering
  • use(...styles) — Combines multiple styles into a class name string
  • properties(input) — Identity function for type-checked style objects
  • group(shape) — Creates a StyleGroup from a tree of styles
  • hasStyles(value) — Type guard for HasStyles objects

CSS — Theming

  • theme(values) — Creates a type-safe CSS variable theme
  • Theme.create(partial) — Creates a variant with merged values
  • fallback(ref, ...values) — Adds fallback values to a CSS variable reference
  • isTheme(value) — Type guard for Theme objects

CSS — At-Rules

  • at(rule, properties) — Creates styles for unnestable at-rules

CSS — Render Options

  • STANDARD_RENDER_OPTIONS — Pretty-printed CSS with newlines and indentation
  • MINIMAL_RENDER_OPTIONS — Minified CSS with no whitespace

Router (@tub/web/router)

  • router(ctx, config) — Creates a router instance
  • context(state, logger?) — Creates a context object for route handlers
  • handle(routeString, handler) — Type-safe route definition from a string
  • route(method, path, handler) — Route definition from parts
  • middleware(m) — Utility for creating typed middleware
  • html(content, status?) — HTML response helper
  • json(content, status?) — JSON response helper
  • text(content, status?) — Plain text response helper
  • response(body, init?) — Generic response helper
  • response_init(status, headers?, statusText?) — Creates a ResponseInit
  • STATUS_CODE — HTTP status code constants
  • STATUS_TEXT — HTTP status text constants
  • HEADER — HTTP header name constants
  • METHODS — HTTP method constants

Tokens (@tub/web/tokens)

Method builders for route files: get, post, put, del, patch, head, options. Each accepts a plain handler or a config object with params, body, output, and handler.

Client page factories for SPA builds: client_route, client_default, client_index, client_wrapper.

Plugins

  • server_plugin(opts?) — Scans files for exported tokens and builds routes
  • static_plugin(opts?) — Serves files directly from the filesystem
  • client_plugin(opts?) — Bundles Preact components into a client SPA
  • openapi_plugin(opts) — Generates an OpenAPI 3.1.0 spec from all routes

Route Builder (@tub/web/builder)

  • builder(config) — Creates a builder that walks a directory and runs plugins

Filesystem (@tub/web/deno_fs)

  • deno_fs — Deno filesystem implementation for the route builder

Inspirations

Contributing

Contributions are welcome! This is an experimental project.

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Development

# Run tests
deno test# Type check
deno check mod.ts
# Format
deno fmt

License

MIT License - see LICENSE for details.

About

Just another NIH web "framework".

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages