vanillaforge
v1.9.2
Published
VanillaForge - A modern, lightweight vanilla JavaScript framework for forging Single Page Applications
Maintainers
Readme
VanillaForge
A small, zero-dependency JavaScript framework for building Single Page Applications with plain web standards.
VanillaForge gives you components, client-side routing, and an event bus in a
few small ES modules — no dependencies and no required build step. It runs
straight from src/ in the browser; the build is only for producing an
optimized bundle.
Why VanillaForge?
- Zero runtime dependencies — ships as plain ES modules.
- Small — the core is ~14.5 KB min+gzip (~48 KB minified).
- Batteries-included — icons, CSS/theming, alerts, self-hosted fonts, and a shared reactive store, all built in. No Font Awesome, Bootstrap, SweetAlert, or Google Fonts required — but you can still bring them in if you want.
- Component composition — embed child components directly inside parent templates. Each child has isolated state, props, lifecycle, and event handling.
- Plugin system — every subsystem (icons, theme, alerts, fonts, store) is a plugin you can install, replace, or skip.
- Efficient updates — re-renders are applied with a tiny DOM-morphing diff, so only changed nodes are touched and focused inputs keep their cursor (see How rendering works).
- Client-side routing — history API, route params (
/users/:id), and a configurable fallback route. - Declarative events — wire DOM events to methods with
data-*attributes; the framework handles delegation and cleanup.
Quick Start
Scaffold a new project
npx create-vanillaforge my-app
cd my-app
npm install
npm run devThe CLI prompts for a template. Pass --template=<name> to skip the prompt:
npx create-vanillaforge my-app --template=minimal # no plugins
npx create-vanillaforge my-app --template=full # all plugins
npx create-vanillaforge my-app --template=todo-app
npx create-vanillaforge my-app --template=router-appSee create-vanillaforge/README.md for the full CLI reference.
Install in an existing project
npm install vanillaforgeWork on the framework itself
git clone https://github.com/Steve-GitCodex/vanillaforge.git
cd vanillaforge
npm install
npm run dev # build to dist/ and serve
npm run example # Todo app (no build needed)
npm run example:router # Routing + params demoYour first component:
import { createApp, BaseComponent, iconsPlugin } from './src/framework.js';
class HelloWorld extends BaseComponent {
constructor(eventBus, props = {}) {
super(eventBus, props);
this.name = 'hello-world';
this.state = { count: 0 };
}
getTemplate() {
return `
<div class="hello">
<h1>Hello, VanillaForge!</h1>
<p>Clicked ${this.state.count} times.</p>
<button data-action="inc">
${this.icon('plus', { size: 16 })} Click me
</button>
</div>
`;
}
getMethods() {
return { inc: () => this.setState({ count: this.state.count + 1 }) };
}
}
const app = createApp({ debug: true });
app.use(iconsPlugin); // built-in SVG icons
await app.initialize({ routes: { '/': HelloWorld } });
await app.start();The page needs a mount element (default id main-content, configurable via
createApp({ mountId })):
<div id="main-content"></div>
<script type="module" src="./app.js"></script>How rendering works
Calling setState() re-runs your getTemplate() and morphs the result onto
the live DOM instead of replacing innerHTML. The morph:
- patches only attributes/text/nodes that actually changed;
- preserves the focus and caret/selection of a focused input, so typing is never interrupted by a re-render;
- reconciles lists by
data-key, so reordering or removing an item reuses the existing DOM nodes instead of rebuilding the list.
getTemplate() {
return `<ul>${this.state.items
.map((it) => `<li data-key="${it.id}">${it.label}</li>`)
.join('')}</ul>`;
}Note: a full re-render still re-runs the whole template (then diffs it). Moving to fine-grained, signal-based updates is on the roadmap.
Declarative events
Bind DOM events to getMethods() entries with attributes. Each attribute maps to
exactly one event so a handler fires once:
| Attribute | Fires on | Typical use |
| -------------- | --------- | --------------------------- |
| data-action | click | buttons, links |
| data-change | change | checkboxes, radios, selects |
| data-input | input | text inputs, textareas |
| data-keydown | keydown | keyboard shortcuts |
| data-submit | submit | forms |
Handlers receive (event, matchedElement). Listeners are delegated to the
component's root element once and cleaned up automatically on destroy.
Examples
- Todo App — local state, filtering, keyed list, and
focus-preserving input. Run with
npm run example. - Routing demo — a list view and a
/users/:iddetail view driven by route params. Run withnpm run example:router.
Testing
Tests run on Vitest with happy-dom (dev dependencies only — the framework itself stays dependency-free):
npm testCoverage includes the DOM morph (focus/selection preservation, keyed lists), the component lifecycle and event delegation, the router, the event bus, and both examples.
Build
npm run build # bundle src/app.js + CSS into dist/
NODE_ENV=production npm run build # minified bundleThe build uses esbuild to bundle and tree-shake, and copies/minifies discovered CSS. See docs/build-system.md.
Documentation
- Components Guide
- Component Composition
- Plugin System & Built-in Icons
- Routing System
- Event Bus
- API Reference
- Build System
- GitHub Pages
- Roadmap
For maintainers returning after time away: see DEVELOPMENT.md.
Security
Templates return plain HTML strings — escape all user-supplied values before interpolating them. VanillaForge ships a dedicated helper:
import { escapeHtml, html } from 'vanillaforge'; // or from './src/framework.js'
// Escape individual values
getTemplate() {
return `<h1>${escapeHtml(this.state.title)}</h1>`;
}
// Or use the html tagged template — every interpolation is auto-escaped
getTemplate() {
return html`<h1>${this.state.title}</h1>`;
}this.icon() and this.child() already return safe RawHtml — no extra
escaping needed. See Components — Escaping and XSS.
Content Security Policy: the theme, alerts, and fonts plugins inject
<style> elements at runtime, so your CSP requires style-src 'unsafe-inline'
(or a nonce/hash approach). Stricter CSP support is on the roadmap.
Browser Support
Modern browsers with ES2020+ support: Chrome 80+, Firefox 72+, Safari 14+, Edge 80+.
Roadmap
All five built-in plugins (icons, theme, alerts, fonts, store), component
composition, signals, route loaders, TypeScript types, the scaffold CLI, and
npm publication of both vanillaforge and create-vanillaforge are complete.
Full release history in CHANGELOG.md.
Contributing
Issues and pull requests are welcome. Please run npm test and npm run lint
before opening a PR.
License
MIT — see LICENSE.
