Getting started
Four steps from nothing to a working reactive component. The first three need no tooling at all; step four is the npm path for bundled projects.
1. Install
Pick one. The script tag is not a demo mode: it is a production path.
Option A, a script tag. No package manager, no build:
<script src="https://cdn.jsdelivr.net/npm/lemonadejs@6/dist/lemonade.min.js"></script>
Option B, npm. For bundlers and TypeScript projects:
npm install lemonadejs
2. Create one file
Save this as index.html. It is the entire application: no compiler, no
CLI, no scaffold. The result panel above the code is this exact file
running on this page.
<!doctype html>
<html>
<body>
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/lemonadejs@6/dist/lemonade.min.js"></script>
<script>
const { html, mount } = lemonade;
const Counter = (props, { state }) => {
const count = state(0);
return html`<div>
<h1>${count}</h1>
<button onclick="${() => count.value++}">+1</button>
</div>`;
};
mount(Counter, document.getElementById('app'));
</script>
</body>
</html>3. Open it in a browser
Double-click the file, or serve the folder:
npx serve .
You should see a number and a button, and the number goes up when you click. That is a reactive state, an event, and a mounted component: the three primitives you will use every day.
state(0)creates a reactive box;${count}in the template is live (State).onclick="${() => ...}": events are always functions, never strings (Events).mount()returns{ el, unmount }; unmounting disposes everything (Lifecycle).
4. Or use npm
The same component as a module. TypeScript declarations ship in the package:
import { html, mount, type Component } from 'lemonadejs';
const Counter: Component = (props, { state }) => {
const count = state(0);
return html`<div>
<p>${count}</p>
<button onclick="${() => count.value++}">+1</button>
</div>`;
};
mount(Counter, document.getElementById('app'));
The package’s exports map serves the dev build (warnings on) under the
development condition and the stripped production build otherwise.
webpack and Vite set that condition from their mode automatically; with
raw esbuild, pass it yourself in development:
npx esbuild src/app.ts --bundle --outfile=dist/app.js # production
npx esbuild src/app.ts --bundle --conditions=development --watch ... # development
Dev build vs production
One file, two builds. dist/lemonade.min.js is the production IIFE
(global lemonade, all dev-only code eliminated, so production pays
zero). During development load dist/lemonade.dev.js instead: the same
API plus every warning and check (LJS-* codes, explain()).
<!-- development: warnings and checks on -->
<script src="https://cdn.jsdelivr.net/npm/lemonadejs@6/dist/lemonade.dev.js"></script>
With the dev build loaded, mistakes answer back: mutate without
touch() and LJS-201 tells you; misspell a registered component and
LJS-104 names it; write onClick and LJS-305 corrects the casing.
Ship the silent, smaller production build.
The complete API fits in one machine-readable file, which states “if something is not here, it does not exist.” Paste it into any agent:
llms.txt: the API in ~2k tokens
Where to go next
Read Templates first: four rules govern every
$, and everything else follows from them. State,
Events, Two-way binding and
Components are the daily working set;
Lifecycle and Destroy cover how
things mount and, provably, die. When a component becomes a product,
Contracts is the publishing layer,
Tests is the proof, and React and
Web components are the doors it ships through.