A minimalist and performant JavaScript library for building SPAs
Documentation β’ Templates β’ CLI β’ Performance β’ Examples
Beni.js is a lightweight, performant JavaScript library designed for developers who want to build modern single-page applications without the complexity of heavyweight frameworks. Born from the need for a simple yet powerful solution, Beni.js provides everything you need to create reactive SPAs while staying under 5KB gzipped.
"Simplicity without sacrifice" - Beni.js believes that small doesn't mean limited. We provide:
- Intuitive API that feels natural to JavaScript developers
- Zero dependencies - no external libraries, no vendor lock-in
- Performance by default - optimized rendering and minimal overhead
- Developer experience first - hot reload, CLI tools, and great debugging
- Production ready - advanced minification and optimization tools
- π Performant: Optimized rendering with requestAnimationFrame and smart batching
- π― Minimalist: Simple and intuitive API with zero dependencies
- π¦ Lightweight: Less than 5KB gzipped
- π Reactive: Built-in reactive state management
- π£οΈ Routing: File-based routing with dynamic parameters
- π Templates: HTML template system with components and data binding
- π¨ Framework Agnostic: Works with any CSS framework
- π§ CLI Tools: Complete development environment with hot reload
- π± Mobile Ready: Touch-friendly and responsive
- π Universal: Works in browsers, Node.js, and with bundlers
npm install beni-js
import { createApp } from 'beni-js';
const app = createApp({
container: '#app',
hashRouting: true
});
app.route('/', () => {
app.render(`
<div>
<h1>Welcome to Beni.js!</h1>
<p>A minimalist SPA framework</p>
</div>
`);
});
app.route('/user/:id', (params) => {
app.render(`<h1>User: ${params.id}</h1>`);
});
app.setState('counter', 0);
app.subscribe('counter', (value) => {
console.log('Counter:', value);
});
app.init();
# Create new project
npx beni-js create my-app
cd my-app
npm install
# Start development server with hot reload
npm run dev
# Install optimization tools (optional but recommended)
npx beni install-optimization
# Build for production with minification
npm run build
# Analyze bundle size and performance
npx beni analyze
# Serve production build with compression
npm run serve
Beni.js includes a complete production optimization system that significantly improves performance:
Automatic minification for all file types:
- HTML: Remove comments, collapse whitespace, minify inline CSS/JS
- JavaScript: Mangle variables, remove console.log, dead code elimination
- CSS: Remove comments, merge rules, optimize selectors
- Automatic: Works out of the box with
beni build
Production server includes performance optimizations:
- Gzip compression: Automatic .gz file generation and serving
- Smart caching: Optimized cache headers for different file types
- ETag support: Efficient cache validation
- Asset optimization: Remove unnecessary files and optimize loading
Built-in tools to monitor and optimize your app:
# Analyze bundle size and compression
beni analyze
# Detailed file-by-file analysis
beni analyze --detailed
# Check for optimization opportunities
beni analyze
# Install optimization dependencies (optional but recommended)
beni install-optimization
# Create optimized configuration
beni create-config
# Build with all optimizations
beni build
# Serve with compression and caching
beni serve
With optimization enabled, typical savings:
File Type | Original | Minified | Gzipped | Total Savings |
---|---|---|---|---|
HTML | 100KB | 85KB | 22KB | 78% |
JavaScript | 150KB | 95KB | 35KB | 77% |
CSS | 80KB | 65KB | 18KB | 78% |
Total | 330KB | 245KB | 75KB | π 77% |
Beni.js provides a powerful template system that allows you to separate your HTML from JavaScript, making your code more organized and maintainable.
Templates are HTML files stored in the src/templates/
directory:
src/templates/home.html
<div class="home-page">
<header class="hero">
<h1>{{ title }}</h1>
<p>{{ subtitle }}</p>
</header>
<main class="content">
<!-- Loop through features -->
{{#each features}}
<div class="feature-card">
<div class="icon">{{ icon }}</div>
<h3>{{ title }}</h3>
<p>{{ description }}</p>
</div>
{{/each}}
<!-- Conditional content -->
{{#if showCTA}}
<div class="cta-section">
<a href="#/signup" class="btn btn-primary">{{ ctaText }}</a>
</div>
{{/if}}
</main>
<!-- Include components -->
<beni-footer></beni-footer>
</div>
<h1>{{ title }}</h1>
<p>Welcome, {{ user.name }}!</p>
<span data-state="counter">{{ counter }}</span>
{{#each items}}
<li>
<strong>{{ this.title }}</strong>
<p>{{ this.description }}</p>
<small>Index: {{ @index }}</small>
</li>
{{/each}}
{{#if isLoggedIn}}
<p>Welcome back, {{ user.name }}!</p>
{{/if}}
{{#unless isGuest}}
<button>Admin Panel</button>
{{/unless}}
<!-- Self-closing component -->
<beni-header title="My App" showNav="true" />
<!-- Component with content -->
<beni-card>
<h3>Card Title</h3>
<p>Card content goes here</p>
</beni-card>
import { createApp } from 'beni-js';
const app = createApp({ container: '#app' });
app.route('/', async () => {
// Load template function
const homeTemplate = await template('home');
// Render with data
const html = homeTemplate({
title: 'Welcome to Beni.js',
subtitle: 'Build amazing SPAs with ease',
features: [
{ icon: 'π', title: 'Fast', description: 'Optimized performance' },
{ icon: 'π¦', title: 'Light', description: 'Under 5KB gzipped' },
{ icon: 'π―', title: 'Simple', description: 'Intuitive API' }
],
showCTA: true,
ctaText: 'Get Started'
});
app.render(html);
});
Register custom helpers for reusable logic:
// Register helpers
app.registerHelper('formatDate', (date) => {
return new Date(date).toLocaleDateString();
});
app.registerHelper('currency', (amount) => {
return `$${amount.toFixed(2)}`;
});
app.registerHelper('truncate', (text, length) => {
return text.length > length ?
text.substring(0, length) + '...'
: text;
});
Using in templates:
<div class="post">
<h3>{{ title }}</h3>
<p>{{ truncate content 150 }}</p>
<div class="meta">
<span>{{ formatDate publishedAt }}</span>
<span>Price: {{ currency price }}</span>
</div>
</div>
Complete development toolkit:
# Project setup
beni create <name> # Create new project
beni create-config # Generate configuration file
# Development
beni dev # Start development server with hot reload
# Production
beni build # Build with minification and optimization
beni serve # Serve production build with compression
# Optimization
beni install-optimization # Install minification dependencies
beni analyze # Analyze bundle size and performance
beni analyze --detailed # Detailed file-by-file analysis
Create beni.config.js
for advanced customization:
module.exports = {
// Minification settings
minify: true, // Enable minification
optimize: true, // Enable optimizations
compress: true, // Generate .gz files
dropConsole: true, // Remove console.log in production
// Detailed minification options
minification: {
html: {
enabled: true,
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
},
javascript: {
enabled: true,
mangle: true, // Shorten variable names
compress: {
drop_console: true, // Remove console.*
drop_debugger: true, // Remove debugger
passes: 2 // Multiple optimization passes
}
},
css: {
enabled: true,
preset: 'default', // cssnano preset
discardComments: true, // Remove comments
mergeRules: true // Combine similar rules
}
},
// Server configuration
devServer: {
port: 3000,
hotReload: true
},
prodServer: {
port: 8080,
compression: true, // Enable gzip
caching: true // Enable cache headers
}
};
Creates a new Beni.js application instance.
const app = createApp({
container: '#app', // Target container selector
hashRouting: true, // Use hash-based routing (#/path)
animationDuration: 300, // Page transition duration (ms)
templatesDir: 'src/templates' // Templates directory (CLI only)
});
Register a route with its handler function.
// Simple route
app.route('/', () => {
app.render('<h1>Home Page</h1>');
});
// Route with parameters
app.route('/user/:id', (params) => {
app.render(`<h1>User: ${params.id}</h1>`);
});
// Route with multiple parameters
app.route('/user/:id/post/:postId', (params) => {
app.render(`
<h1>User ${params.id}</h1>
<p>Post: ${params.postId}</p>
`);
});
// 404 route
app.route('404', () => {
app.render('<h1>404 - Page Not Found</h1>');
});
Programmatically navigate to a route.
// Navigate to home
app.navigate('/');
// Navigate with parameters
app.navigate('/user/123');
// Navigate to external URL
window.location.href = 'https://external-site.com';
Set application state. Updates are batched and automatically trigger re-renders.
// Set simple value
app.setState('counter', 10);
// Set object
app.setState('user', {
name: 'John Doe',
email: 'john@example.com'
});
// Set nested property
app.setState('user.name', 'Jane Doe');
Get current state value.
const counter = app.getState('counter');
const user = app.getState('user');
const userName = app.getState('user.name');
Subscribe to state changes.
// Subscribe to specific key
app.subscribe('counter', (newValue, oldValue) => {
console.log(`Counter changed from ${oldValue} to ${newValue}`);
});
// Subscribe to object changes
app.subscribe('user', (newUser) => {
console.log('User updated:', newUser);
});
Render HTML content to the app container.
// Render static HTML
app.render('<h1>Hello World</h1>');
// Render template result
const homeTemplate = await template('home');
app.render(homeTemplate(data));
// Render with state binding
app.render(`
<div>
<span data-state="counter">0</span>
<button onclick="app.setState('counter', app.getState('counter') + 1)">
Increment
</button>
</div>
`);
Load and compile a template by name.
// Load template
const homeTemplate = await template('home');
// Render with data
const html = homeTemplate({
title: 'My App',
items: ['item1', 'item2', 'item3']
});
app.render(html);
Register template helpers for reusable logic.
app.registerHelper('formatDate', (date) => {
return new Date(date).toLocaleDateString();
});
app.registerHelper('upper', (str) => {
return str.toUpperCase();
});
Beni.js supports all modern browsers:
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
For older browsers, include polyfills:
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
Beni.js is designed for performance with production optimizations:
- Bundle size: < 5KB gzipped (core library)
- Runtime overhead: Minimal
- Memory usage: Efficient
- Rendering: Optimized with requestAnimationFrame
- State updates: Batched and debounced
- Templates: Pre-compiled for production
- Minification: Advanced HTML/CSS/JS optimization
- Compression: Automatic gzip with 70%+ size reduction
Framework | Bundle Size | Time to Interactive | Memory Usage | Template Performance | Production Ready |
---|---|---|---|---|---|
Beni.js | 4.2KB | 0.8s | 2.1MB | Pre-compiled | β Full optimization |
React | 42KB | 1.2s | 3.8MB | Runtime JSX | |
Vue | 34KB | 1.0s | 3.2MB | Runtime templates | |
Svelte | 10KB | 0.9s | 2.5MB | Pre-compiled |
import { createApp } from 'beni-js';
import { jest } from '@jest/globals';
describe('Beni.js App with Templates', () => {
let app;
beforeEach(() => {
document.body.innerHTML = '<div id="app"></div>';
app = createApp({ container: '#app' });
});
test('should render template with data', async () => {
// Mock template function
global.template = jest.fn().mockResolvedValue((data) => `<h1>${data.title}</h1>`);
app.route('/', async () => {
const homeTemplate = await template('home');
app.render(homeTemplate({ title: 'Test' }));
});
await app.init();
expect(document.querySelector('#app h1')).toBeTruthy();
expect(document.querySelector('#app h1').textContent).toBe('Test');
});
test('should handle state updates in templates', async () => {
app.setState('counter', 5);
app.route('/', () => {
app.render('<span data-state="counter">0</span>');
});
await app.init();
expect(document.querySelector('[data-state="counter"]').textContent).toBe('5');
app.setState('counter', 10);
expect(document.querySelector('[data-state="counter"]').textContent).toBe('10');
});
});
# Build optimized version
beni build
# Deploy to Netlify with compression
netlify deploy --prod --dir=dist
# Deploy to Vercel with edge caching
vercel --prod dist
# Deploy to GitHub Pages
gh-pages -d dist
# Build stage
FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage with nginx
FROM nginx:alpine
# Copy optimized build
COPY --from=builder /app/dist /usr/share/nginx/html
# Enable gzip in nginx
RUN echo 'gzip on; gzip_types text/css application/javascript text/html;' > /etc/nginx/conf.d/gzip.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
<!-- Use from CDN -->
<script src="https://unpkg.com/beni-js@latest/dist/index.umd.js"></script>
<!-- Or specific version -->
<script src="https://unpkg.com/beni-js@1.0.7/dist/index.umd.js"></script>
We welcome contributions! Please see our Contributing Guide for details.
# Clone repository
git clone https://github.com/yourusername/beni-js.git
cd beni-js
# Install dependencies
npm install
# Install optimization tools
npm run install-optimization
# Run development build
npm run dev
# Run tests
npm test
# Build for production
npm run build
# Analyze performance
npm run analyze
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT License - see the LICENSE file for details.
- Inspired by modern frontend frameworks with a focus on simplicity
- Template system inspired by Handlebars and Mustache
- Built with performance and developer experience in mind
- Production optimization tools inspired by Webpack and Vite
- Thanks to the JavaScript community for continuous innovation