npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

leaf-blade

v0.0.2

Published

Blade template engine for Leaf framework - Laravel Blade-like syntax for JavaScript/TypeScript

Downloads

10

Readme

🌿 Leaf Blade

Blade template engine cho Leaf framework - Laravel Blade-like syntax cho JavaScript/TypeScript.

Version License TypeScript Bun

📖 Tiếng Việt | English

📦 Cài Đặt

npm install leaf-blade

🚀 Sử Dụng

1. Cài Đặt Plugin

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);

2. Sử Dụng trong Routes

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" },
    ],
  });
});

3. Sử Dụng Trực Tiếp

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;
});

4. Sử Dụng Engine Trực Tiếp

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",
});

⚙️ Tùy Chọn

BladeOptions

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)
}

✨ Tính Năng

Template Syntax

  • Layout inheritance: @extends, @section, @yield
  • Partials: @include với hỗ trợ data
  • Conditionals: @if, @elseif, @else, @endif
  • Loops: @foreach, @for, @while
  • Variables: {{ }} (escaped), {!! !!} (raw)
  • Comments: {{-- --}}
  • JavaScript blocks: @js ... @endjs (chạy JavaScript code)

Performance

  • 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

📖 Hướng Dẫn Chi Tiết

1. Layout Inheritance (@extends + @section + @yield)

{{-- layouts/app.blade.html --}}
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title', 'Default Title')</title>
</head>
<body>
    @yield('content')
</body>
</html>

{{-- pages/home.blade.html --}}
@extends('layouts.app')

@section('title', 'Home Page')

@section('content')
    <h1>Welcome!</h1>
@endsection

2. Include Partials (@include)

{{-- Include simple --}}
@include('partials.header')

{{-- Include với data --}}
@include('partials.user-card', { user: user, showEmail: true })

3. Conditionals (@if, @elseif, @else, @endif)

@if(user)
    <p>Welcome, {{ user.name }}!</p>
@elseif(guest)
    <p>Please login</p>
@else
    <p>Hello guest</p>
@endif

4. Loops (@foreach, @for, @while)

{{-- Foreach --}}
@foreach(posts as post)
    <article>
        <h2>{{ post.title }}</h2>
    </article>
@endforeach

{{-- Foreach with key --}}
@foreach(items as key => item)
    <div>{{ key }}: {{ item }}</div>
@endforeach

{{-- For loop --}}
@for(i = 0; i < 10; i++)
    <span>Item {{ i }}</span>
@endfor

{{-- While loop --}}
@while(condition)
    <p>Content</p>
@endwhile

5. Variables

{{-- 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 }}

6. Comments

{{-- This is a comment, removed in production --}}
{{-- Comments có thể nhiều dòng
     và sẽ bị xóa khi render --}}

7. JavaScript Blocks (@js ... @endjs)

@js
const items = ['apple', 'banana', 'orange'];
const count = items.length;
@endjs

<p>Total: {{ count }} items</p>

@js
let sum = 0;
for (let i = 0; i < items.length; i++) {
  sum += items[i].length;
}
@endjs

<p>Total characters: {{ sum }}</p>

Lưu ý: Không được sử dụng return statement trong @js blocks.

📝 Ví Dụ Chi Tiết

Layout Template

{{-- views/blade/layouts/app.blade.html --}}
<!DOCTYPE html>
<html lang="{{ lang || 'vi' }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@yield('title', 'Leaf App')</title>

    @if(css)
    <link rel="stylesheet" href="{{ css }}">
    @endif
</head>
<body>
    @include('partials.header')

    <main>
        @yield('content')
    </main>

    @include('partials.footer')

    @if(js)
    <script type="module" src="{{ js }}"></script>
    @endif
</body>
</html>

Page Template

{{-- views/blade/home.blade.html --}}
@extends('layouts.app')

@section('title', 'Home - Leaf App')

@section('content')
<div id="app">
    <h1>Chào mừng đến với Leaf!</h1>

    @if(features && features.length > 0)
    <div class="features">
        @foreach(features as feature)
        <div class="feature-card">
            <h3>{{ feature.title }}</h3>
            <p>{{ feature.description }}</p>
        </div>
        @endforeach
    </div>
    @endif
</div>
@endsection

Partial Template

{{-- views/blade/partials/header.blade.html --}}
<header>
    <nav>
        <a href="/">Home</a>
        <a href="/about">About</a>
    </nav>
</header>

📁 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

1. Tổ Chức Templates

  • Layouts: layouts/ - Page structure
  • Partials: partials/ - Reusable UI pieces
  • Components: components/ - UI components
  • Pages: Root hoặc pages/ - Page templates

2. Naming Convention

  • Use kebab-case cho file names: user-profile.blade.html
  • Use camelCase cho variables trong templates: {{ userName }}

3. Performance

  • Enable cache trong production: cache: true
  • Enable minification: minify: true
  • Use partials để tránh duplicate code

4. Security

  • Always use {{ }} for user input (escaped)
  • Only use {!! !!} for trusted HTML content

🎯 Advanced Features

Nested Sections

@extends('layouts.app')

@section('title', 'Page Title')

@section('content')
    <div class="container">
        @section('inner-content')
            <p>Default inner content</p>
        @endsection
    </div>
@endsection

Conditional Includes

@if(user)
    @include('partials.user-menu', { user: user })
@else
    @include('partials.guest-menu')
@endif

Loop Variables

@foreach(items as index => item)
    @if(index === 0)
        <div class="first">{{ item }}</div>
    @else
        <div>{{ item }}</div>
    @endif
@endforeach

🐛 Troubleshooting

Template not found

// Đảm bảo viewsDir đúng
bladePlugin({
  viewsDir: path.join(process.cwd(), "views/blade"),
});

Section not rendering

{{-- Đảm bảo có @yield trong layout --}}
@yield('content')

{{-- Và @section trong page --}}
@section('content')
    Content here
@endsection

Include not found

{{-- Sử dụng relative path từ viewsDir --}}
@include('partials.header')  ✅
@include('views/blade/partials/header')  ❌

Cache issues

// Clear cache programmatically
const renderer = new BladeRenderer({ ... });
await renderer.clearCache();

📋 Changelog

[0.0.1] - 2025-11-29

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.applayouts/app.blade.html)
  • Auto-escaping by default
  • Raw HTML output support
  • Optional chaining in expressions
  • Error handling with context

🧪 Testing

bun test

📝 License

ISC