- Quick Start
- CLI Tool
- What's New
- Core Features
- Directives
- Examples
- Advanced Features
- Installation
- API Reference
- Documentation
- Contributing
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div t-data="{ message: 'Hello TinyPine!' }">
<h1 t-text="message"></h1>
<button t-click="message = 'Clicked!'">Click me</button>
</div>
<script src="https://unpkg.com/[email protected]/dist/tinypine.min.js"></script>
<script>
TinyPine.init();
</script>
</body>
</html>npm install tinypineimport TinyPine from "tinypine";
TinyPine.init();Create TinyPine projects in seconds:
# Create a new project
npx tinypine-cli new myapp
# Start development server
cd myapp && npx tinypine-cli serve
# Add features (router, i18n, ui)
npx tinypine-cli add router
npx tinypine-cli add i18n
npx tinypine-cli add ui
# Build for production
npx tinypine-cli buildFeatures:
- 🎨 Templates: Vanilla, Tailwind, SPA, SSR, UI Ready
- 🔌 Modular add-ons: router, i18n, ui, devtools
- ⚡ Vite dev/build integration
Smart client-side routing with navigation guards, dynamic params, and programmatic control:
Smart Router with Dynamic Parameters:
<nav>
<a t-link="'home'">Home</a>
<a t-link="'about'">About</a>
<a t-link="'user/42'">User Profile</a>
</nav>
<!-- Route Views -->
<div t-route="'home'">
<h1>Welcome Home!</h1>
</div>
<div t-route="'about'">
<h1>About Us</h1>
</div>
<!-- Dynamic Parameters -->
<div t-route="'user/:id'">
<div t-data="{}">
<h1 t-text="'User ID: ' + $route.params.id"></h1>
<p t-text="'Path: ' + $route.path"></p>
</div>
</div>
<!-- 404 Fallback -->
<div t-route="*">
<h1>404 - Page Not Found</h1>
</div>
<script>
TinyPine.router({
default: "home",
});
TinyPine.init();
</script>Navigation Guards:
TinyPine.router({
// Global beforeEnter guard
beforeEnter(to, from) {
const isLoggedIn = TinyPine.store("auth").loggedIn;
if (!isLoggedIn && to.path !== "login") {
return "/login"; // Redirect to login
}
return true; // Allow navigation
},
// Global beforeLeave guard
beforeLeave(to, from) {
if (hasUnsavedChanges) {
return confirm("Discard unsaved changes?");
}
return true;
},
// Route-specific guards
routes: {
admin: {
beforeEnter: async (to, from) => {
const isAdmin = await checkAdminRole();
return isAdmin ? true : "/";
},
},
},
});Programmatic Navigation:
<div t-data="{ userId: 42 }">
<!-- Using $router in templates -->
<button t-click="$router.push('/user/' + userId)">View Profile</button>
<button t-click="$router.back()">Go Back</button>
<!-- Access route info -->
<p t-text="'Current: ' + $router.current().path"></p>
</div>t-link Directive with Active State:
<nav>
<a t-link="'home'" class="nav-link">Home</a>
<a t-link="'about'" class="nav-link">About</a>
<a t-link="'contact'" class="nav-link">Contact</a>
</nav>
<style>
.nav-link.active {
color: blue;
font-weight: bold;
}
</style>Route Lifecycle Events:
// Listen to route changes
TinyPine.router.on("route:change", (to, from) => {
console.log("Navigated from", from.path, "to", to.path);
// Track page views
if (typeof gtag !== "undefined") {
gtag("config", "GA_ID", { page_path: to.path });
}
});
// Other events: route:enter, route:leave, route:error
TinyPine.router.on("route:enter", (to) => {
document.title = `My App - ${to.path}`;
});Router Features:
- ✅ Dynamic route matching (
user/:id,blog/:category/:post) - ✅ Nested routes support
- ✅ Wildcard fallback (
*for 404s) - ✅ Navigation guards (sync & async)
- ✅ Query parameters (
?tab=posts&page=1) - ✅ Programmatic navigation API
- ✅ Active link detection
- ✅ Route lifecycle events
- ✅ Scroll behavior control
Powerful async operations and comprehensive form validation:
Enhanced t-fetch with Debounce:
<div t-data="{ search: '', results: [] }">
<!-- Debounce fetch requests -->
<button
t-fetch="'/api/search?q=' + search"
debounce="500"
method="POST"
headers="{ 'Authorization': 'Bearer ' + token }"
>
Search
</button>
<!-- Lifecycle hooks -->
<script>
TinyPine.context({
beforeFetch: async ({ url, method }) => {
console.log("Starting request:", url);
return true; // or false to cancel
},
afterFetch: ({ data }) => {
console.log("Received:", data);
},
});
</script>
</div>Form Validation System:
<tp-form>
<div t-data="{ email: '', password: '' }">
<!-- Built-in validators -->
<input t-model="email" t-validate="required|email" name="email" />
<input
t-model="password"
t-validate="required|minLength:8"
name="password"
type="password"
/>
<!-- Show validation errors -->
<p t-show="$errors.email" t-text="$errors.email[0]?.message"></p>
<!-- Disable submit if invalid -->
<button type="submit" :disabled="$invalid">Submit</button>
</div>
</tp-form>Debounced Inputs:
<div t-data="{ searchQuery: '' }">
<!-- Update state after 300ms of inactivity -->
<input t-model="searchQuery" t-debounce="300" />
<p t-text="'Searching for: ' + searchQuery"></p>
</div>Built-in Validators:
required- Field must not be emptyemail- Valid email formatmin:N/max:N- Numeric rangeminLength:N/maxLength:N- String lengthpattern:regex- Custom regex patternurl- Valid URL formatnumber- Numeric valueinteger- Whole number
Form State Variables:
$errors- Validation error messages per field$valid/$invalid- Overall form validity$touched- Fields that have been focused$dirty- Fields that have been modified$pending- Async operations in progress
Enhanced security and developer experience with powerful new features:
Safe Expression Evaluator:
<!-- Multiple statements with semicolons -->
<div t-data="{ count: 0 }">
<button t-click="const doubled = count * 2; count = doubled">Double</button>
</div>
<!-- Ternary operators -->
<p t-text="count > 10 ? 'High' : 'Low'"></p>
<!-- Arrow functions -->
<button t-click="items.filter(x => x.active).length">Active Count</button>XSS-Protected HTML Rendering:
<div t-data="{ content: '<p>Safe HTML</p>' }">
<!-- Automatically sanitizes dangerous tags and scripts -->
<div t-html="content"></div>
</div>Shorthand Binding Syntax:
<!-- Use :attr instead of t-bind:attr -->
<img :src="imageUrl" :alt="imageAlt" />
<div :class="activeClass" :id="elementId"></div>Enhanced Loop Context:
<ul>
<li t-for="item in items" t-class:first="$first" t-class:last="$last">
<span t-text="$index + 1"></span>: <span t-text="item"></span>
</li>
</ul>New Form Components:
tp-field- Form field wrapper with labels, validation, and error statestp-input- Text input with icons, sizes, and validation statestp-checkbox- Custom checkbox with labelstp-file-upload- Drag & drop file upload with preview
Ready-to-use Tailwind CSS components:
<script src="https://unpkg.com/[email protected]/dist/tinypine.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/tinypine.ui.min.js"></script>
<link
rel="stylesheet"
href="https://unpkg.com/[email protected]/dist/tinypine.ui.css"
/>
<script src="https://cdn.tailwindcss.com"></script>
<div t-data="{ open: false }">
<tp-button color="primary" size="md" icon="check">Save</tp-button>
<tp-modal t-show="open" title="Confirm">
<p>Are you sure?</p>
<tp-button color="outline" t-click="open = false">Cancel</tp-button>
</tp-modal>
<tp-card title="User Info">
<p>Content goes here</p>
</tp-card>
</div>
<script>
TinyPine.init();
</script>Available Components:
tp-button- Button with color, size, type, icon propstp-modal- Modal with title prop, auto backdrop/closetp-card- Card with title prop, Tailwind styling
Theme Support:
TinyPine.theme = "dark"; // or 'light'📖 Complete UI Usage Guide → | 📖 Türkçe Kılavuz →
Run code after a component is rendered:
<div
t-data="{
count: 0,
mounted(el, ctx) {
el.classList.add('mounted');
console.log('Component mounted!');
}
}"
>
<span t-text="'Count: ' + count"></span>
<button t-click="count++">+1</button>
</div>Global mount listener:
TinyPine.onMount((el, ctx) => {
console.log("🌱 Mounted:", el);
});Run cleanup when a component is removed:
<div
t-data="{
count: 0,
beforeUnmount(el, ctx) {
console.log('About to remove...');
},
unmounted(el, ctx) {
console.log('Removed!');
}
}"
>
<span t-text="'Count: ' + count"></span>
</div>Global unmount listener:
TinyPine.onUnmount((el, ctx) => {
console.log("🧹 Cleaned up:", el);
});Emits component:mounted and component:unmounted events via the global event bus.
TinyPine v1.1.1 introduces features for educational and sandbox environments:
Lite Mode:
TinyPine.start("#app", { mode: "lite" });
// Disables: devtools, store, router, i18nSafe Mode:
TinyPine.start("#app", { safe: true });
// Wraps all directive executions in try/catchSilent Debug:
TinyPine.debugOptions.silent = true;
// Suppresses [TinyPine] console logsGlobal Event Bus:
TinyPine.on("directive:click", (el, ctx) => {
console.log("Click detected");
});- ✅ 100 Passing Tests - Comprehensive test coverage
- 📘 TypeScript Support - Full type definitions included
- 🛠️ DevTools Integration - Live debugging & inspection
- 🌍 i18n Ready - Built-in internationalization
- 🔄 Global Store - Shared state management
- 📡 Async Support - t-fetch, t-await directives
- 🌐 Router System - Hash-based navigation
- 🎨 Transitions - Smooth animations built-in
- 🔌 Plugin API - Extensible architecture
- 📊 Performance - Optimized for production
| Directive | Description | Example |
|---|---|---|
t-data |
Create reactive scope | <div t-data="{ count: 0 }"> |
t-text |
Update text content | <span t-text="message"></span> |
t-html |
Update HTML content (XSS-safe, v1.3.0) | <div t-html="content"></div> |
t-show |
Toggle visibility (with transitions) | <p t-show="isVisible">Hello</p> |
t-click |
Click handlers | <button t-click="count++">+</button> |
t-model |
Two-way binding | <input t-model="name"> |
| Directive | Description | Example |
|---|---|---|
t-for |
List rendering | <li t-for="item in items"> |
.prevent/.stop |
Event modifiers | <button t-click="save.prevent"> |
t-init |
Lifecycle hook | <div t-init="console.log('Mounted!')"> |
| Directive | Description | Example |
|---|---|---|
t-bind |
Dynamic attributes | <img t-bind:src="url"> |
t-class |
Conditional classes | <div t-class:active="isActive"> |
t-ref |
DOM references | <input t-ref="input"> |
t-transition |
CSS transitions | <div t-transition="fade"> |
<div t-data="{ count: 0 }">
<button t-click="count--">-</button>
<span t-text="count"></span>
<button t-click="count++">+</button>
</div><div
t-data="{
todos: [],
newTodo: '',
methods: {
add() {
if(this.newTodo) this.todos.push(this.newTodo);
this.newTodo = '';
},
remove(i) {
this.todos.splice(i, 1);
}
}
}"
>
<input t-model="newTodo" placeholder="Add todo" />
<button t-click="methods.add()">Add</button>
<ul>
<li t-for="(todo, i) in todos">
<span t-text="todo"></span>
<button t-click="methods.remove(i)">Remove</button>
</li>
</ul>
</div><div
t-data="{
email: '',
password: '',
loggedIn: false,
methods: {
login() {
this.loggedIn = true;
}
}
}"
>
<div t-show="!loggedIn">
<input t-model="email" placeholder="Email" />
<input t-model="password" type="password" placeholder="Password" />
<button t-click="methods.login()">Login</button>
</div>
<div t-show="loggedIn">
<p t-text="'Welcome, ' + email"></p>
</div>
</div>Create shared state across components:
// Create stores
TinyPine.store("auth", { user: "Guest", loggedIn: false });
TinyPine.store("ui", { theme: "light" });
// Use in any component
<div t-data="{}">
<span t-text="$store.auth.user"></span>
<button t-click="$store.auth.loggedIn = true">Login</button>
</div>;TinyPine.use({
name: "Toast",
init(TinyPine) {
TinyPine.directive("toast", (el, message) => {
alert(message);
});
},
});Fetch data from APIs with race condition control, loading/error states, and lifecycle hooks:
<!-- Basic fetch with loading/error states -->
<div t-data="{ posts: [], $loading: false, $error: null }">
<div t-fetch="'/api/posts'">
<!-- Loading indicator -->
<p t-show="$loading">⏳ Loading posts...</p>
<!-- Error message -->
<p t-show="$error" t-text="'Error: ' + $error"></p>
<!-- Posts list -->
<ul t-show="!$loading && !$error">
<li t-for="post in posts">
<h3 t-text="post.title"></h3>
</li>
</ul>
</div>
</div>
<!-- Advanced: Lifecycle hooks -->
<div t-data="{ users: [] }">
<div
t-fetch="'/api/users'"
@t:onFetchStart="console.log('Fetching...')"
@t:onFetchEnd="console.log('Done!', $event.detail.data)"
@t:onFetchError="console.error('Failed:', $event.detail.error)"
>
<ul>
<li t-for="user in users" t-text="user.name"></li>
</ul>
</div>
</div>
<!-- Dynamic URL with reactive state -->
<div t-data="{ userId: 1, user: null }">
<input t-model="userId" type="number" />
<div t-fetch="'/api/users/' + userId">
<p t-show="$loading">Loading...</p>
<p t-text="user?.name"></p>
</div>
</div>Features:
- ✅ Race Condition Control - Automatically cancels outdated requests
- ✅ AbortController - Properly cancels requests when URL changes
- ✅ State Variables -
$loading,$error,$responseautomatically managed - ✅ Lifecycle Events -
t:onFetchStart,t:onFetchEnd,t:onFetchError - ✅ Request Tracking - Prevents duplicate requests for same URL
Handle promises with loading and error states:
<div t-data="{ user: {} }">
<div t-await="fetch('/api/user').then(r=>r.json())">
<div t-loading="'Loading user...'">⏳ Loading...</div>
<div t-error="'Failed to load user.'">❌ Error!</div>
<p t-text="user?.name || ''"></p>
</div>
</div>Build SPAs with hash-based routing:
<nav>
<a href="#/home">Home</a>
<a href="#/about">About</a>
</nav>
<div t-route="'home'">🏠 Home Page</div>
<div t-route="'about'">ℹ️ About Page</div>
<script>
TinyPine.router({
default: "home",
onChange(route) {
console.log("Route changed →", route);
},
});
</script>Add multi-language support:
<script>
// Setup translations
TinyPine.i18n(
{
en: { greeting: "Hello World!", welcome: "Welcome!" },
tr: { greeting: "Merhaba Dünya!", welcome: "Hoş geldiniz!" },
},
{ default: "en", cache: true }
);
</script>
<div t-data>
<h1 t-text.lang="'greeting'"></h1>
<button t-click="$lang = 'tr'">🇹🇷 Türkçe</button>
<button t-click="$lang = 'en'">🇺🇸 English</button>
</div>Dynamic Locale Loading:
TinyPine.loadLocale("tr", "/lang/tr.json");
TinyPine.loadLocale("en", "/lang/en.json");<button t-click="save.prevent">Prevent default</button>
<button t-click="methods.init.once">Run once</button>
<button t-click="methods.close.outside">Close on outside click</button><script src="https://unpkg.com/[email protected]/dist/tinypine.min.js"></script>
<script>
TinyPine.init();
</script>npm install tinypineTypeScript Support:
import TinyPine from "tinypine";
// Full IntelliSense and type checkingnpm testTest Coverage:
- ✅ 100 passing tests (8 test files)
- ✅ Core directives (t-text, t-show, t-click, t-model, t-for)
- ✅ Form components (tp-input, tp-checkbox, tp-file-upload)
- ✅ Global stores and reactivity
- ✅ Keyed list diffing (v1.3.1)
- ✅ Router and validation (v1.3.1)
- ✅ Context management and lifecycle hooks
- ✅ Edge cases and performance
TinyPine.init(root?)- Initialize TinyPineTinyPine.start(selector, opts?)- Start with options (lite/safe mode)TinyPine.store(name, data)- Create global storeTinyPine.watch(path, callback)- Watch changesTinyPine.use(plugin)- Register pluginTinyPine.directive(name, handler)- Custom directiveTinyPine.component(name, config)- Register custom componentTinyPine.transition(name, config)- Register transition
TinyPine.onMount(callback)- Global mount listenerTinyPine.onUnmount(callback)- Global unmount listenerTinyPine.on(event, callback)- Event listenerTinyPine.off(event, callback)- Remove event listenerTinyPine.emit(event, ...args)- Emit event
$parent- Parent scope data$root- Root scope data$refs- DOM references$el- Current element$store- Global stores$lang- Current i18n language
TinyPine works with any CSS framework:
<link
href="https://cdn.jsdelivr.net/npm/tailwindcss@2/dist/tailwind.min.css"
rel="stylesheet"
/>
<div t-data="{ count: 0 }" class="p-8">
<button t-click="count++" class="px-4 py-2 bg-blue-500 text-white rounded">
Count: <span t-text="count"></span>
</button>
</div>Enable debug mode:
TinyPine.debug = true;
// Console will show: [TinyPine] count changed → 1
// Silent mode (suppress logs)
TinyPine.debugOptions.silent = true;Built-in developer tools panel:
<script>
TinyPine.debug = true;
TinyPine.devtools({ position: "bottom-right", theme: "dark" });
</script>Features:
- 📊 Live Store Inspector
- 🎯 Context Viewer
- ⏱️ Reactivity Timeline
- 📈 Performance Monitor
✅ Chrome 49+ ✅ Firefox 18+ ✅ Safari 10+ ✅ Edge 49+
Works everywhere JavaScript Proxies are supported.
- ✅ v1.0.0 - Stable Release with TypeScript & Tests
- ✅ v1.1.0 - CLI Tool & Ecosystem Expansion
- ✅ v1.1.1 - Sprout.js Readiness (Lite/Safe Modes)
- ✅ v1.1.2 - Component Mount Lifecycle
- ✅ v1.1.3 - Component Unmount Lifecycle
- ✅ v1.2.0 - TinyPine UI Components
- ✅ v1.3.0 - Core Stability & Form Components
- ✅ v1.3.1 - Keyed Diffing & Advanced Features (100% Test Coverage!)
- ✅ v1.4.0 - Async Flow & Form Validation
- ✅ v1.5.0 - Smart Router with Guards & Navigation
- 🔜 v1.6.0 - Performance Optimizations & Virtual DOM (Planned)
- 🔜 v1.7.0 - DevTools 2.0 & Router Inspector (Planned)
- 🔜 v2.0.0 - Ecosystem & TypeScript-first Rewrite (Planned)
Comprehensive guides and best practices:
- Testing Guide - Unit testing, E2E, best practices
- Performance Guide - Optimization tips, benchmarking
- Troubleshooting - Common issues and solutions
- Anti-Patterns - What to avoid and why
- Memory Management - Preventing memory leaks
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - Use freely in your projects!