The HTML document Vite builds, not a route. It links
pages/styles.css, loads
src/entry-client.ts as a module, and holds the one
element the router renders every page into.
Four scripts - dev, build,
preview and typecheck - plus
jafjs as the only runtime dependency. The dev
dependencies are Vite, TypeScript, @swc/core and Node's
types.
Every script calls a bare binary name, so the same manifest runs
under npm, pnpm, yarn or bun.
vite.config.ts
Registers JAF's Vite plugins: expressions, JetShake, the compiler,
the file router and component discovery. It also sets the dev port
to 5173, builds to dist/, and aliases
@ to src/.
Strict mode, noEmit (Vite does the compiling), bundler
module resolution, and @/* mapped to
./src/*. It covers src,
config, pages and the Vite config itself,
so typecheck sees the whole project.
.gitignore
Four lines: node_modules, dist,
.DS_Store and *.local. The scaffolder also
runs git init for you unless you tell it not to.
pnpm-workspace.yaml
Written only when you scaffold with pnpm. It allows the two
dependency build scripts a JAF app genuinely needs -
@swc/core and esbuild, which each link a
native binary.
pnpm blocks those by default, and without this file the install
exits with ERR_PNPM_IGNORED_BUILDS.
config/
jaf.config.js
Where the components live, which routing rules to use, and the
router's own options: file routing, link prefetching, the mount
selector, and native View Transitions on route changes.
routing.js
Names the routing conventions rather than listing routes: which
directory is the pages tree, what a layout file is called, where
error pages live, and which paths are never routes.
Build-time code splitting: vendor groupings, whether shared
components get their own chunk, and route groups that should
ship together. The defaults are empty, which is a per-route
split.
pages/
index.html
The home route. A page is one file: an optional
<script>, the template, and optional scoped
styles. No imports and no route entry to add.
<script>
let count = 0
</script>
<h1>Hello, JAF!</h1>
<button onclick="count++">{count}</button>
about.html
A second page, and the whole of what routing to
/about takes: the file name is the URL.
layout.html
The shared shell for every page beside and below it. It renders
nav and calls <slot /> where the page goes,
and it stays mounted across navigation.
components/
Button.html
A sample component. Anything in a components directory is
found by file name, so
<Button /> works in any page with no
import statement.
Card.html
The second sample, showing props and a slot. Component files
are excluded from routing, so they never become URLs.
styles.css
Global styles for the app - or the Tailwind entry point if you
picked Tailwind. Per-component styles live in the component,
scoped, so this file stays small.
src/
entry-client.ts
One line. It boots the framework: router, component discovery
and reactivity all start from this import.
import 'jafjs/entry-client'
llm/
skills/
A snapshot of the JAF skills library, copied in at scaffold time
so the guidance matches the version in your
package.json. One file per concept, with an index
in README.md.
AGENTS.md
The open convention most agent tools read. It points at
llm/skills/, lists the project's commands, and spells
out the rules that trip agents up - let is reactive,
control-flow tags are lowercase, events are plain
onclick.
CLAUDE.md
A pointer to AGENTS.md rather than a copy of it, so
there is one file to keep true as your project grows.
There is no route configuration (unless you prefer to use a config file).
pages/about.html is /about
pages/about/index.html is /about
pages/components/Button.html is <Button /> -
anywhere in your app, without requiring an import statement.
Your Assistant Comes Configured
JAF is an LLM-optimized framework.
Every template ships JAF's skills library as llm/skills/, plus an
AGENTS.md (and a CLAUDE.md pointing at it) that
tells your coding assistant to read the relevant skill before writing any JAF
code.
Your First Component
Create pages/components/Counter.html.
A component is one file: an optional script, a template, and optional scoped
styles.
source
<script>letcount=0</script><buttonclass="qs-counter"onclick="count++">
Clicked <spanclass="qs-count">{count}</span> times
</button>