Skip to content
lxsmnsycPublic

About

A component language and compiler for web apps that resume instead of hydrating

Resources

Stars

8 stars

Watchers

1 watching

Forks

Repository files navigation

brumate: web apps that resume, not hydrate. A component language and compiler for the web.

brumate

A component language and compiler for resumable web apps.

brumate compiles single-file components into server-rendered HTML and small client chunks. The client does not hydrate. It reads the state that the server wrote into the page and loads the code for an event handler only when that event fires.

The compiler generates a server module that renders HTML with the state, and client modules that resume it. A Vite plugin compiles .brm files in an app.

This repository holds the compiler and its runtime, the type checker and editor support, the website, examples, benchmarks and smoke tests. brumate is at 0.x: the page format can change between versions, so the server and the client must come from the same version.

Goals

  • Ship only the client code that an interaction can reach. Markup that never changes ships as HTML and nothing else.
  • Never run component code again on the client to rebuild state.
  • Keep the loader, the only code that loads before the page is used, under 1 KB. It is about 2 KB after gzip today.
  • Keep the syntax small: scripts, markup with # blocks, and a few compile-time functions.

Example

<script>
  import { $memo, $props, $signal } from 'brumate/signals';
  import { prevent } from 'brumate/events';
  import { persist } from './todos.server';

  const { initial } = $props<{ initial: Todo[] }>();
  let todos = $signal(initial);
  let draft = $signal('');
  const left = $memo(todos.filter((todo) => !todo.done).length);

  async function add() {
    todos = [...todos, { id: crypto.randomUUID(), title: draft, done: false }];
    draft = '';
    await persist(todos);
  }
</script>

<h1>Todos</h1>
<form onSubmit=(prevent(add))>
  <input value=draft onInput=((event) => (draft = event.currentTarget.value))>
</form>
<ul>
  #for (todo of todos by todo.id) {
    <li class={ done: todo.done }>{todo.title}</li>
  }
</ul>
<p>{left} left</p>

The <h1> produces no client code. The #for item template ships because add can change todos. persist is a server function, so the client only gets a call stub.

Try it

pnpm install
pnpm build
pnpm --filter example-todos dev

examples/todos renders a todo list and a counter on the server. In the browser, only the loader runs until the page loads.

examples/tracker is a small issue tracker with sign-in, middleware, form validation, optimistic updates, streaming and a prerendered page. Run it with pnpm --filter example-tracker dev.

examples/hackernews is a Hacker News reader with feeds, threads of comments and users, cached on the server. Run it with pnpm --filter example-hackernews dev.

examples/ui-kit is a package of components, and examples/kit-app is an app that uses it.

Documents

The documentation is in website/docs, and the website shows it with samples that run in the page.

Design notes for contributors are in docs:

  • Grammar defines the file format in EBNF.
  • Intermediate representation defines the data that each compiler stage produces.
  • Compiler describes the pipeline, the output and the runtime.
  • Decisions lists what is decided and what is still open.
  • Proposals lists features that would be good to add, with a sketch of each.
  • Prior art compares other resumable and partially hydrated frameworks.

Development

pnpm install
pnpm build
pnpm test
pnpm typecheck
pnpm lint
pnpm format
pnpm e2e                          # browser tests of the website and examples
pnpm bench                        # benchmarks, against other frameworks
pnpm --filter website check:samples   # the live samples of the docs compile

Build first: the examples and the website use the built brumate package.

The tests in packages/brumate/test/fixtures parse and analyze each .brm file. The output of every stage is compared with a snapshot in test/__snapshots__. The tests in packages/brumate/test/render compile each component, render it on the server, and compare the modules, the HTML and the state with a snapshot. The tests in test/resume.test.ts load that HTML into a DOM, start the loader, and check that events update the page.

Branches

Minor work (features, minor fixes, docs of unreleased work) goes to pull requests against next. Hotfixes go to pull requests against main. Releases are published from main only, and next publishes nothing. After each release from main, merge main into next. A minor release promotes next into main in one pull request. See Decisions.

Benchmarks

benchmarks builds the same three pages with brumate, Qwik 1, Qwik 2 and Marko 6: a small counter and todo app, a dashboard with 50 rarely used widgets, and a table of rows from js-framework-benchmark. It compares the client code each page loads, the size of the server-rendered HTML, how fast the server renders, and the work of the browser after a click. It needs a local Chrome for the browser metrics.

pnpm bench

benchmarks/README.md describes each metric and has the results of a run.

Smoke tests

e2e builds the website and the examples, serves them, and uses them in Chrome. Run it with pnpm e2e.

Name

Brumation is the dormant state of reptiles in cold weather. A reptile in brumation stays inactive but wakes up briefly to drink when it needs to, then goes back to rest. A brumate page does the same with its code. It loads a handler only when an event needs it.

License

MIT © lxsmnsyc

About

A component language and compiler for web apps that resume instead of hydrating

Resources

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages