Blade template engine for Leaf framework - Laravel Blade-like syntax for JavaScript/TypeScript
npm install leaf-bladeBlade template engine cho Leaf framework - Laravel Blade-like syntax cho JavaScript/TypeScript.
!Version
!License
!TypeScript
!Bun
> 📖 Tiếng Việt | English
``bash`
npm install leaf-blade
`typescript
import { Elysia } from "elysia";
import { bladePlugin } from "leaf-blade";
import path from "path";
const app = new Elysia()
.use(
bladePlugin({
viewsDir: path.join(process.cwd(), "views/blade"),
cache: true,
cacheDir: path.join(process.cwd(), "storage/blade"),
minify: process.env.NODE_ENV === "production",
})
)
.listen(3000);
`
`typescript
import { Elysia } from "elysia";
import { bladeView } from "leaf-blade";
import type { BladeContext } from "leaf-blade";
const app = new Elysia().get("/", async (ctx: BladeContext) => {
return bladeView(ctx, "home", {
title: "Home Page",
description: "Welcome to Leaf",
features: [
{ title: "Fast", description: "Built with Bun" },
{ title: "Modern", description: "Vue 3 + TypeScript" },
],
});
});
`
`typescript
import type { BladeContext } from "leaf-blade";
app.get("/page", async (ctx: BladeContext) => {
const html = await ctx.blade.render("template", {
title: "Page Title",
data: { ... }
});
return html;
});
`
`typescript
import { BladeRenderer } from "leaf-blade";
import path from "path";
const renderer = new BladeRenderer({
viewsDir: path.join(process.cwd(), "views/blade"),
cache: true,
});
const html = await renderer.render("template", {
title: "Page Title",
});
`
`typescript`
interface BladeOptions {
viewsDir?: string; // Thư mục chứa templates (mặc định: "views/blade")
cache?: boolean; // Bật/tắt cache (mặc định: true)
cacheDir?: string; // Thư mục lưu cache (mặc định: "storage/blade")
minify?: boolean; // Bật/tắt minify HTML (mặc định: false)
}
- ✅ Layout inheritance: @extends, @section, @yield@include
- ✅ Partials: với hỗ trợ data@if
- ✅ Conditionals: , @elseif, @else, @endif@foreach
- ✅ Loops: , @for, @while{{ }}
- ✅ Variables: (escaped), {!! !!} (raw){{-- --}}
- ✅ Comments: @js
- ✅ JavaScript blocks: ... @endjs (chạy JavaScript code)
- ✅ In-memory caching: Compiled templates được cache trong memory
- ✅ File-based caching: Compiled templates được lưu vào disk
- ✅ HTML minification: Tự động minify HTML trong production
- ✅ Async I/O: Sử dụng async file operations
`blade
{{-- layouts/app.blade.html --}}
@yield('content')
{{-- pages/home.blade.html --}}
@extends('layouts.app')
@section('title', 'Home Page')
@section('content')
$3
`blade
{{-- Include simple --}}
@include('partials.header'){{-- Include với data --}}
@include('partials.user-card', { user: user, showEmail: true })
`$3
`blade
@if(user)
Welcome, {{ user.name }}!
@elseif(guest)
Please login
@else
Hello guest
@endif
`$3
`blade
{{-- Foreach --}}
@foreach(posts as post)
{{ post.title }}
@endforeach{{-- Foreach with key --}}
@foreach(items as key => item)
{{ key }}: {{ item }}
@endforeach{{-- For loop --}}
@for(i = 0; i < 10; i++)
Item {{ i }}
@endfor
{{-- While loop --}}
@while(condition)
Content
@endwhile
`$3
`blade
{{-- Escaped output (default) - an toàn với XSS --}}
{{ user.name }}
{{ post.title }}{{-- Raw output (HTML) - chỉ dùng cho nội dung đáng tin cậy --}}
{!! user.bio !!}
{!! post.content !!}
{{-- Hỗ trợ optional chaining --}}
{{ user?.profile?.avatar }}
{{ post?.author?.name }}
`$3
`blade
{{-- This is a comment, removed in production --}}
{{-- Comments có thể nhiều dòng
và sẽ bị xóa khi render --}}
`$3
`blade
@js
const items = ['apple', 'banana', 'orange'];
const count = items.length;
@endjsTotal: {{ count }} items
@js
let sum = 0;
for (let i = 0; i < items.length; i++) {
sum += items[i].length;
}
@endjs
Total characters: {{ sum }}
`Lưu ý: Không được sử dụng
return statement trong @js blocks.📝 Ví Dụ Chi Tiết
$3
`blade
{{-- views/blade/layouts/app.blade.html --}}
@yield('title', 'Leaf App') @if(css)
@endif
@include('partials.header')
@yield('content')
@include('partials.footer')
@if(js)
@endif
`$3
`blade
{{-- views/blade/home.blade.html --}}
@extends('layouts.app')@section('title', 'Home - Leaf App')
@section('content')
Chào mừng đến với Leaf!
@if(features && features.length > 0)
@foreach(features as feature)
{{ feature.title }}
{{ feature.description }}
@endforeach
@endif
@endsection
`$3
`blade
{{-- views/blade/partials/header.blade.html --}}
`📁 Cấu Trúc Thư Mục Đề Xuất
`
views/blade/
├── layouts/
│ ├── app.blade.html # Main layout
│ └── admin.blade.html # Admin layout
├── partials/
│ ├── header.blade.html
│ ├── footer.blade.html
│ └── nav.blade.html
├── components/
│ ├── button.blade.html
│ └── card.blade.html
└── pages/
├── home.blade.html
└── about.blade.html
`🔄 So Sánh với Laravel Blade
| Laravel Blade | Leaf Blade | Ghi chú |
| --------------------------- | ----------------------------- | -------------------------- |
|
@extends('layout') | @extends('layouts.app') | ✅ Giống nhau |
| @section('name') | @section('name') | ✅ Giống nhau |
| @yield('name') | @yield('name') | ✅ Giống nhau |
| @include('partial') | @include('partials.header') | ✅ Giống nhau |
| {{ $var }} | {{ user.name }} | ⚠️ Bỏ $ trong JavaScript |
| {!! $html !!} | {!! html !!} | ✅ Giống nhau |
| @if($condition) | @if(condition) | ⚠️ Bỏ $ |
| @foreach($items as $item) | @foreach(items as item) | ⚠️ Bỏ $ |
| @php ... @endphp | @js ... @endjs | ✅ Tương đương |Lưu ý: Vì JavaScript không dùng
$ cho variables, nên syntax đã được điều chỉnh để phù hợp.⚡ Best Practices
$3
- Layouts:
layouts/ - Page structure
- Partials: partials/ - Reusable UI pieces
- Components: components/ - UI components
- Pages: Root hoặc pages/ - Page templates$3
- Use
kebab-case cho file names: user-profile.blade.html
- Use camelCase cho variables trong templates: {{ userName }}$3
- Enable cache trong production:
cache: true
- Enable minification: minify: true
- Use partials để tránh duplicate code$3
- Always use
{{ }} for user input (escaped)
- Only use {!! !!} for trusted HTML content🎯 Advanced Features
$3
`blade
@extends('layouts.app')@section('title', 'Page Title')
@section('content')
@section('inner-content')
Default inner content
@endsection
@endsection
`$3
`blade
@if(user)
@include('partials.user-menu', { user: user })
@else
@include('partials.guest-menu')
@endif
`$3
`blade
@foreach(items as index => item)
@if(index === 0)
{{ item }}
@else
{{ item }}
@endif
@endforeach
`🐛 Troubleshooting
$3
`typescript
// Đảm bảo viewsDir đúng
bladePlugin({
viewsDir: path.join(process.cwd(), "views/blade"),
});
`$3
`blade
{{-- Đảm bảo có @yield trong layout --}}
@yield('content'){{-- Và @section trong page --}}
@section('content')
Content here
@endsection
`$3
`blade
{{-- Sử dụng relative path từ viewsDir --}}
@include('partials.header') ✅
@include('views/blade/partials/header') ❌
`$3
`typescript
// Clear cache programmatically
const renderer = new BladeRenderer({ ... });
await renderer.clearCache();
`📋 Changelog
$3
#### Added
- Initial release of Leaf Blade template engine
- Laravel Blade-like syntax support
- Layout inheritance (
@extends, @section, @yield)
- Partials support (@include)
- Conditionals (@if, @elseif, @else, @endif)
- Loops (@foreach, @for, @while)
- Variables ({{ }}, {!! !!})
- Comments ({{-- --}})
- JavaScript blocks (@js ... @endjs)
- HTML minification support
- Template caching (in-memory + file-based)
- Async file I/O
- TypeScript support
- Elysia plugin integration
- Comprehensive test suite (38 tests)
- Documentation#### Performance
- Multi-layer caching system
- Compiled code cache
- Template content cache
- Includes cache
- Minified output cache
- Async file I/O (non-blocking)
- File stats cache for cache validation
- Optimized compilation with regex caching
#### Features
- Dot notation for template paths (
layouts.app → layouts/app.blade.html)
- Auto-escaping by default
- Raw HTML output support
- Optional chaining in expressions
- Error handling with context🧪 Testing
`bash
bun test
``ISC