
Security News
Vite+ Joins the Push to Consolidate JavaScript Tooling
Evan You announces Vite+, a commercial, Rust-powered toolchain built on the Vite ecosystem to unify JavaScript development and fund open source.
@rettangoli/fe
Advanced tools
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 just 3 types of files.
.view.yaml
, .store.js
, .handlers.js
files scale from single page to complex applicationsProduction usage (when rtgl is installed globally):
rtgl fe build # Build components
rtgl fe watch # Start dev server
rtgl fe scaffold # Create new component
Runtime:
Build & Development:
Browser Native:
rettangoli.config.yaml
file in your project rootbun install
# Scaffold a new component
node ../rettangoli-cli/cli.js fe scaffold --category components --name MyButton
# Build once
node ../rettangoli-cli/cli.js fe build
# Watch for changes (recommended)
node ../rettangoli-cli/cli.js fe watch
src/
├── cli/
│ ├── build.js # Build component bundles
│ ├── watch.js # Development server with hot reload
│ ├── scaffold.js # Component scaffolding
│ ├── examples.js # Generate examples for testing
│ └── blank/ # Component templates
├── createComponent.js # Component factory
├── createWebPatch.js # Virtual DOM patching
├── parser.js # YAML to JSON converter
├── common.js # Shared utilities
└── index.js # Main exports
Each component consists of three files:
component-name/
├── component-name.handlers.js # Event handlers
├── component-name.store.js # State management
└── component-name.view.yaml # UI structure and styling
Views are written in YAML and compiled to virtual DOM at build time.
template:
- div#myid.class1.class2 custom-attribute=abcd:
- rtgl-text: "Hello World"
- rtgl-button: "Click Me"
Compiles to:
<div id="myid" class="class1 class2" custom-attribute="abcd">
<rtgl-text>Hello World</rtgl-text>
<rtgl-button>Click Me</rtgl-button>
</div>
elementName: my-custom-component
template:
- rtgl-view:
- rtgl-text: "My Component"
When passing data to components, there's an important distinction:
template:
- custom-component title=Hello .items=items
title=Hello
): Always string values, passed as HTML attributes.items=items
): JavaScript values from viewData, passed as component propertiesAttributes become HTML attributes, while props are JavaScript objects/arrays/functions passed directly to the component.
Views do not support complex variable expressions like ${myValue || 4}
. All values must be pre-computed in the toViewData
store function:
❌ Don't do this:
template:
- rtgl-text: "${user.name || 'Guest'}"
- rtgl-view class="${isActive ? 'active' : 'inactive'}"
✅ Do this instead:
// In your .store.js file
export const toViewData = ({ state, props, attrs }) => {
return {
...state,
displayName: state.user.name || 'Guest',
statusClass: state.isActive ? 'active' : 'inactive'
};
};
template:
- rtgl-text: "${displayName}"
- rtgl-view class="${statusClass}"
styles:
'#title':
font-size: 24px
color: blue
'@media (min-width: 768px)':
'#title':
font-size: 32px
refs:
submitButton:
eventListeners:
click:
handler: handleSubmit
template:
- rtgl-button#submitButton: "Submit"
Loops:
template:
- rtgl-view:
projects:
$for project, index in projects:
- rtgl-view#project-${project.id}:
- rtgl-text: "${project.name}"
- rtgl-text: "${project.description}"
- rtgl-text: "Item ${index}"
❌ This will not work. Prop references can only be taken from viewDate, not from loop variables
template:
- rtgl-view:
- $for project, index in projects:
- rtgl-view#project-${project.id}:
- custom-component .item=project:
✅ This is the workaround
template:
- rtgl-view:
- $for project, index in projects:
- rtgl-view#project-${project.id}:
- custom-component .item=projects[${index}]:
Conditionals:
template:
- rtgl-view:
$if isLoggedIn:
- user-dashboard: []
$else:
- login-form: []
# Multiple conditions with logical operators
template:
- rtgl-view:
$if user.age >= 18 && user.verified:
- admin-panel: []
$elif user.age >= 13:
- teen-dashboard: []
$else:
- kid-dashboard: []
For more advanced templating features, see the Jempl documentation.
Define component interfaces with JSON Schema:
viewDataSchema:
type: object
properties:
title:
type: string
default: "My Component"
items:
type: array
items:
type: object
propsSchema:
type: object
properties:
onSelect:
type: function
attrsSchema:
type: object
properties:
variant:
type: string
enum: [primary, secondary]
export const INITIAL_STATE = Object.freeze({
title: "My App",
items: [],
loading: false
});
export const toViewData = ({ state, props, attrs }) => {
return {
...state,
itemCount: state.items.length,
hasItems: state.items.length > 0
};
};
export const selectItems = (state) => state.items;
export const selectIsLoading = (state) => state.loading;
export const setLoading = (state, isLoading) => {
state.loading = isLoading; // Immer makes this immutable
};
export const addItem = (state, item) => {
state.items.push(item);
};
export const removeItem = (state, itemId) => {
const index = state.items.findIndex(item => item.id === itemId);
if (index !== -1) {
state.items.splice(index, 1);
}
};
// Called when component mounts
export const handleOnMount = (deps) => {
const { store, render } = deps;
// Load initial data
store.setLoading(true);
loadData().then(data => {
store.setItems(data);
store.setLoading(false);
render();
});
// Return cleanup function
return () => {
// Cleanup code here
};
};
export const handleSubmit = async (event, deps) => {
const { store, render, attrs, props } = deps;
event.preventDefault();
const formData = new FormData(event.target);
const newItem = Object.fromEntries(formData);
store.addItem(newItem);
render();
// Dispatch custom event
deps.dispatchEvent(new CustomEvent('item-added', {
detail: { item: newItem }
}));
};
export const handleItemClick = (event, deps) => {
const itemId = event.target.id.replace('item-', '');
console.log('Item clicked:', itemId);
};
// In your setup.js file
const componentDependencies = {
apiClient: new ApiClient(),
router: new Router()
};
export const deps = {
components: componentDependencies,
pages: {}
};
Access in handlers:
export const handleLoadData = async (event, deps) => {
const { apiClient } = deps.components;
const data = await apiClient.fetchItems();
// ... handle data
};
Create a rettangoli.config.yaml
file in your project root:
fe:
dirs:
- "./src/components"
- "./src/pages"
setup: "setup.js"
outfile: "./dist/bundle.js"
examples:
outputDir: "./vt/specs/examples"
Use visual testing with rtgl vt
:
rtgl vt generate
rtgl vt report
For a complete working example, see the todos app in examples/example1/
.
FAQs
Frontend framework for building reactive web components
The npm package @rettangoli/fe receives a total of 76 weekly downloads. As such, @rettangoli/fe popularity was classified as not popular.
We found that @rettangoli/fe demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Evan You announces Vite+, a commercial, Rust-powered toolchain built on the Vite ecosystem to unify JavaScript development and fund open source.
Security News
Ruby Central’s incident report on the RubyGems.org access dispute sparks backlash from former maintainers and renewed debate over project governance.
Research
/Security News
Socket researchers uncover how threat actors weaponize Discord across the npm, PyPI, and RubyGems ecosystems to exfiltrate sensitive data.