Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
a no-build TypeScript fullstack SSR-first framework for developing fast web applications with a shallow learning curve
FullSoak
FullS(tack)oak (FullSoak for short) is a modern (born 2025), no-build TypeScript fullstack framework for building fast web applications with a shallow learning curve. At its core is the Oak http server framework which is inspired by Koa (one of the popular Node.js http frameworks).
Key Differentiators
-
FullSoak is no-build [1]. Zero, zip, zilch, nada. That means: no
tsc
norwebpack
. All files are served from where they are. No surprises. Still, optimizations such as minification and mangling are supported. -
FullSoak supports both JSX and HTM (Hyperscript Tagged Markup) which boasts several enhancements over JSX - but most importantly: both require no separate build step (back to point 1). JSX transformation is automatically applied on a per-file basis.
-
FullSoak is Preact. So: the familiarity of React, but as lean as we need it to be.
-
FullSoak is SSR-first & SSR-optimized. See Deno's explanation on JSX precompile for more details.
-
FullSoak is (mostly) WYSIWYG. Compared to sophisticated frameworks such as Next.js, or Remix, or Deno's Fresh, FullSoak is intended to be quite "feature-poor": 1) you start with a "Controller" file (as in good old "MVC") which 2) renders your TSX component as
text/html
content (i.e. a plain string), and then 3) the content hydrates itself on the client side. For isomorphic use cases, there're no "special-purpose functions" to remember: where & how to write data fetching logic is left entirely at the disposal of the developer.
Example usage
As with most frameworks, fullsoak recommends a certain directory structure. Here's a bare-minimum example for Deno runtime:
Prerequisite: Deno
fullsoak-example |_ src | |_ components | | |_ Shared | | | |_ styles.css | | |_ MyComponent | | |_ index.tsx | | |_ styles.css | |_ main.ts |_ deno.jsonc
// deno.jsonc { "imports": { "fullsoak": "jsr:@fullsoak/fullsoak@0.18.0", "preact": "npm:preact@10.26.5" }, "nodeModulesDir": "auto", "compilerOptions": { // see https://docs.deno.com/runtime/reference/jsx/#jsx-precompile-transform "jsx": "precompile", "jsxImportSource": "preact", "jsxPrecompileSkipElements": ["a", "link"] } }
// src/main.ts import { Controller, Get, ssr, useFullSoak } from "fullsoak"; import { MyComponent } from "./components/MyComponent/index.tsx"; @Controller() class MyController { @Get("/") index() { return ssr(MyComponent); } } const port = 3991; useFullSoak({ port, controllers: [MyController] });
// src/components/MyComponent/index.tsx import type { FunctionComponent } from "preact"; export const MyComponent: FunctionComponent = () => <div>hello, world</div>;
/* src/components/MyComponent/styles.css */ @import "/components/Shared/styles.css";
Then the app can be started up for local development:
deno -A --watch src/main.ts
or simply served directly on production and/or inside a Docker container:
# please supply the Deno security permissions flags to your desire # https://docs.deno.com/runtime/fundamentals/security/#permissions deno src/main.ts
Route-aware Isomorphic Components
Rendering route-aware isomorphic components is supported via preact-iso
. See
examples:
Live Demo / Projects using FullSoak
- https://fullsoak-examples.deno.dev (example on Deno runtime)
- https://fullsoak.onrender.com (example on Bun runtime)
- https://fullsoak.up.railway.app (example on Node.js runtime)
- https://fullsoak-cloudflare-workers-examples.dklab.workers.dev (example on Cloudflare Workers)
- https://fullsoak-guestbook.deno.dev (example with authentication logic)
- wanna list yours? please feel free to open a PR
Trade-offs
Build step, while imposing additional cognitive loads & occasionally hindering a good Developer Experience, has its own benefits. Without build (bundling) step, the optimizations (e.g. resource loaders in build-time or run-time) have to be provisioned in other manners. The high-level wish is to use as much standard web specs as possible (think preload, prefetch, etc.) to make up for what's sacrified by dropping the build step.
Besides, more benchmarks are needed on small & large scale codebases across different use cases (e.g. MPA blog site vs rich-interactive SPA vs even large E-Commerce site) to get an understanding of how feasible / scalable this approach is, and for which scenarios.
Further Reading
- Project Wiki: https://github.com/fullsoak/fullsoak/wiki
- Code examples with Deno runtime: https://github.com/fullsoak/deno-examples
- Code examples with Node.js runtime: https://github.com/fullsoak/nodejs-examples
- Code examples with Bun runtime: https://github.com/fullsoak/bun-examples
- Code examples with Cloudflare Workers: https://github.com/fullsoak/cloudflare-workers-examples
- Discord announcements on library interoperability updates
- Preact's take on No-build Workflows
Add Package
deno add jsr:@fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";
Import directly with a jsr specifier
import * as fullsoak from "jsr:@fullsoak/fullsoak";
Add Package
pnpm i jsr:@fullsoak/fullsoak
pnpm dlx jsr add @fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";
Add Package
yarn add jsr:@fullsoak/fullsoak
yarn dlx jsr add @fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";
Add Package
vlt install jsr:@fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";
Add Package
npx jsr add @fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";
Add Package
bunx jsr add @fullsoak/fullsoak
Import symbol
import * as fullsoak from "@fullsoak/fullsoak";