@rettangoli/fe
v2.0.0
Published
Frontend framework for building reactive web components
Maintainers
Readme
Rettangoli Frontend
A modern frontend framework that uses YAML for view definitions, web components for composition, and Immer for state management. Build reactive applications with minimal complexity using 4 types of files.
Features
- 🗂️ Four-File Architecture -
.view.yaml,.store.js,.handlers.js,.schema.yamlscale from single page to complex applications - 📝 YAML Views - Declarative UI definitions that compile to virtual DOM
- 🧩 Web Components - Standards-based component architecture
- 🔄 Reactive State - Immer-powered immutable state management
- ⚡ Fast Development - Auto reload with Vite integration
- 🎯 Template System - Jempl templating for dynamic content
- 🧪 Testing Ready - Pure functions and dependency injection for easy testing
Quick Start
rtgl fe build # Build components
rtgl fe watch # Start dev serverDocumentation
- Developer Quickstart - Complete introduction and examples
- View System - Complete YAML syntax
- Schema System - Component API and metadata
- Store Management - State patterns
- Event Handlers - Event handling
- Internationalization - Locale files, view usage, and locale switching
- Prerendering - Render components to HTML at build time and hydrate them in the browser
Architecture
Technology Stack
Runtime:
Build & Development:
- Vite 8 - Rolldown-powered production bundling and dev server
Browser Native:
- Web Components - Component encapsulation
Development
Prerequisites
- Node.js 20.19+, Node.js 22.12+, or Bun
- A
rettangoli.config.yamlfile in your project root
Setup
- Install dependencies:
bun install- Create project structure:
# Scaffold a new component into ./example/components/MyButton/ (--dir changes the folder)
node ../rettangoli-cli/cli.js fe scaffold --category components --component-name MyButtonThe scaffold names the element custom- plus the kebab-case component name
(MyButton gives custom-my-button), so the generated .schema.yaml passes
rtgl fe check. Edit componentName in it to use another tag. A name that
cannot give a valid custom element name stops the scaffold before anything is
written.
- Start development:
# Build once
node ../rettangoli-cli/cli.js fe build
# Watch for changes (recommended)
node ../rettangoli-cli/cli.js fe watchProject Structure
src/
├── cli/ # `@rettangoli/fe/cli`
│ ├── build.js # Build component bundles
│ ├── watch.js # Development server with auto reload
│ ├── check.js # Contract checks
│ ├── scaffold.js # Component scaffolding
│ ├── examples.js # Generate examples for testing
│ ├── vitePlugin.js # Vite plugin and the virtual entry module
│ ├── frontendEntrySource.js # Generated browser entry
│ └── blank/ # Component templates
├── core/ # Environment-neutral view, store, runtime and contract logic
├── web/ # Custom Element runtime, hydration and styles
├── server/ # `@rettangoli/fe/server`: prerendering
├── contracts/ # `@rettangoli/fe/contracts`
├── commonBuild.js # File discovery shared by the CLI and the server
├── createComponent.js # Component factory
├── createWebPatch.js # Internal virtual DOM patching
├── parser.js # Template to virtual DOM
└── index.js # Main exportsVite Integration
@rettangoli/fe uses Vite directly through the Node API, behind the existing FE CLI commands.
rtgl fe builduses Vite 8's Rolldown-poweredvite.build()with a virtual entry module generated from configured component files.rtgl fe build --sourcemapemits linked source maps;--sourcemap hiddenemits maps without asourceMappingURLcomment, for keeping them private. Maps are off by default.rtgl fe watchusesvite.createServer(), warms the virtual entry during startup, and serves the configuredoutfilepath via middleware.- Watch projects can configure
fe.publicDirto serve static assets from the project root without Vite transformations. - The watch server's root and entry URL come from
outfile. If the path has astaticsegment, the root is the directory before the first one and the entry URL is the path after it (./vt/static/main.jsgives root./vtand/main.js). Otherwise the root is the parent of the outfile's directory and the URL is the outfile relative to it (_site/public/main.jsgives root_siteand/public/main.js). A bareoutfile: main.jsputs the root above the project, so keep the outfile in a directory.rtgl fe watchprints[Watch] Root:and[Watch] Entry:. fe.watchEntrycan provide a project bootstrap module at the configuredoutfileURL. The module can register framework primitives and importvirtual:rettangoli-fe-entryfor Rettangoli component HMR.- FE runtime source is generated in memory (virtual module), so no temporary generated JS files are required.
- Contract validation, YAML parsing, and template parsing still run before code generation.
- Component directories, setup files, and locale files are registered explicitly so watch mode also sees sources outside the served static root.
Current Vite features used by FE:
- Build API (
build) for production bundles. - Dev Server API (
createServer) for watch mode serving. - Custom plugin hooks:
resolveId+loadfor the FE virtual entry (virtual:rettangoli-fe-entry).handleHotUpdatefor FE file change detection.configureServerfor reload fallbacks and serving the configured output entry URL.
- Rolldown output control through Vite (
entryFileNames,chunkFileNames,assetFileNames) to preserve CLIoutfilebehavior.
Notes:
- Watch mode preserves component instances and store state while applying compatible
.view.yaml,.constants.yaml,.handlers.js,.methods.js,.store.js, and.schema.yamledits through HMR. Locale YAML edits replace the development catalogs while retaining the selected locale and existing subscriptions. - Changes to setup and component file additions or removals perform a full reload because they can change registration or project topology. An update rejected by the runtime as incompatible, including a component-name or observed-prop change, also falls back to a full reload.
- No dedicated CSS plugin pipeline is enabled in FE at this time.
Configuration
Create a rettangoli.config.yaml file in your project root:
fe:
dirs:
- "./src/components"
- "./src/pages"
setup: "setup.js"
outfile: "./dist/bundle.js"
publicDir: "./static"
watchEntry: "./src/watch.js"
i18n:
dir: "./src/i18n"
defaultLocale: "en"
fallbackLocale: "en"
locales:
- "en"
- "vi"
examples:
outputDir: "./vt/specs/examples"publicDir is optional and only affects rtgl fe watch. Its files are served
at / without Vite transformations. This is useful for extensionless or other
binary assets that must retain their source filenames.
watchEntry is also watch-only. When present, that module is transformed and
served at the outfile URL instead of serving the FE virtual entry directly.
Import virtual:rettangoli-fe-entry from it to retain component HMR while adding
project bootstrap code such as primitive custom-element registration.
Setup Contract
setup.js should export deps only.
createWebPatch/h wiring is internalized by the framework.
The generated entry imports the setup module before any component module, so
bootstrap code that setup imports first, such as error reporting, runs before
component modules are evaluated.
const deps = {
components: {},
pages: {},
};
export { deps };Action Listeners
In .view.yaml, listeners can dispatch store actions directly with action.
This path auto-runs render after the action executes.
refs:
inputEmail:
eventListeners:
input:
action: setEmail
payload:
value: ${_event.target.value}Store action:
export const setEmail = ({ state }, { value }) => {
state.email = value;
};Runtime-injected action payload fields:
_event: the original DOM event, the same objecthandlerlisteners get_action(internal dispatch metadata)
The payload is evaluated once, against the view data and _event. Text in a
value that looks like a template (${...}) stays literal. The action receives
copies of plain objects and arrays, so it may change them; the event, dates,
files and class instances arrive as the same instances.
Testing
Unit and Contract Tests
- Puty contract tests (
spec/) — YAML-driven pure-function specs for view, store, schema, and handler contracts. - Vitest integration tests (
test/) — runtime behavior tests for component lifecycle, props, events, and DOM.
bun run test # all tests
bun run test:puty # contract tests only
bun run test:vitest # integration tests onlyEnd-to-End Testing
FE has two E2E suites in this package:
packages/rettangoli-fe/e2e/dashboardpackages/rettangoli-fe/e2e/interactions
Use this workflow:
- Build FE with the local repo CLI (
cli.js) so it uses your current FE source. - Run VT in Docker for stable Playwright runtime.
The commands below run the mounted workspace CLI (packages/rettangoli-cli),
so they use the rtgl and @rettangoli/vt of your checkout; the Docker image
supplies the matching Playwright 1.57.0 browsers. The image is referenced by
digest, so it cannot change under you.
Docker image:
IMAGE="han4wluc/rtgl@sha256:85769959d834eaf984210f4b2f1f7de07677e81578c98d86680ba116c03a7d45"Dashboard suite:
(cd packages/rettangoli-fe/e2e/dashboard && node ../../../rettangoli-cli/cli.js fe build)
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/dashboard "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt screenshot
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/dashboard "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt reportInteractions suite:
(cd packages/rettangoli-fe/e2e/interactions && node ../../../rettangoli-cli/cli.js fe build)
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/interactions "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt screenshot
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/interactions "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt reportAccept intentional visual changes:
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/dashboard "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt accept
docker run --rm -v "$(pwd):/workspace" -w /workspace/packages/rettangoli-fe/e2e/interactions "$IMAGE" node /workspace/packages/rettangoli-cli/cli.js vt acceptVT specs live under each suite's vt/specs/ directory.
Examples
e2e/dashboardande2e/interactionsare complete FE projects. The end-to-end suites above build and test them.examples/example1in the repository is a small todos app.
