Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Template Laravel Package

mindy/template

A lightweight templating package for Laravel that layers a simple, theme-friendly template system on top of Blade. It supports layouts, sections, partials, and compiled view caching, making it easy to build reusable, maintainable front-end views.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with an exact pin. With 1 star and 0 dependents, treat every version bump as a potential breaking change:

composer require mindy/template
# then edit composer.json to freeze the version:
# "mindy/template": "1.0.3"

Read these three files before writing any code, in this order:

  1. composer.json — check the require block for the PHP range and any wrapped twig/twig constraint. A pin like twig/twig:^1.x is a hard stop before you invest further.
  2. tests/ — for a package with no README ecosystem, the test suite is the documentation. It reveals the real constructor signature, the render call, and the valid option keys. Every README claim should be verified there.
  3. src/ — find the engine factory/selector (the class that decides Twig vs. Jinja2 vs. DTE). That is the seam you will wrap, so read it before designing around it.

First use case — render one page. The API is likely shaped like this; verify against src/:

use Mindy\Template\Engine;

$engine = new Engine([
    'path'  => __DIR__ . '/views',
    'cache' => storage_path('framework/views/mindy'),
]);

echo $engine->render('pages/home.twig', [
    'title' => 'Dashboard',
    'user'  => $user->toArray(),
]);

Before moving on, confirm three things on this single render:

  • Where compiled output lands (and whether it writes into your repo — add it to .gitignore).
  • Whether output is auto-escaped (render a template containing <script>alert(1)</script> inside a variable and inspect the HTML).
  • Which exception type is thrown for a missing template, so you can register a handler.

Only after those three answers are known should you attempt layouts and partials.

Implementation Patterns

Wrap the engine behind an adapter, always. Because the package targets three syntaxes, its public API is the lowest common denominator. Put a single fence between your app and the package:

final class TemplateView
{
    public function __construct(
        private Engine $engine,
        private array $globals = [],
    ) {}

    public function render(string $template, array $data = []): string
    {
        return $this->engine->render($template, array_merge($this->globals, $data));
    }
}

Bind this as a singleton in AppServiceProvider so the compiled-template path is reused per request. This costs twenty minutes and buys you: an escape hatch if upstream goes quiet, one place to inject globals and CSRF helpers, and one place to swap engines later.

Pick one dialect and enforce it. The Twig / Jinja2 / DTE support is both the selling point and the biggest day-to-day hazard. Twig and Jinja2 look interchangeable and are not — {% set %} scoping differs, filter names diverge (|default behaves differently), and loop variable access (loop.index vs. Jinja's loop.index0 in some versions) will silently produce wrong output. Choose one syntax, document it in the README of your app, add a linter rule if possible, and reject review requests that mix dialects.

For Laravel integration, don't rely on the package to do it. There is almost certainly no Mindy\Template\LaravelServiceProvider and no View factory bridge. Wire it manually:

// AppServiceProvider::register()
$this->app->singleton(TemplateView::class, function ($app) {
    return new TemplateView(
        new Engine([
            'path'  => resource_path('templates'),
            'cache' => storage_path('framework/views/mindy'),
        ]),
        [
            'csrf_token' => csrf_token(),
            'auth_user'  => optional(auth()->user())->toArray(),
            'route'      => fn ($name, $params = []) => route($name, $params),
        ],
    );
});

If you need route()/csrf_token()/__() inside templates, register each as a function on the engine in this same block — do not add them ad-hoc in controllers.

Isolate into non-critical surfaces first. The right rollout is: emails, PDF letters, static exports, admin-only preview pages. Never user-facing HTML on the first cutover. Feature-flag the renderer, run Blade and mindy side-by-side in staging, and diff outputs before flipping.

Cache warm the compiled templates during deploy. Whatever the cache directory is, add a deploy step that renders every template once so the first production request doesn't pay cold-compile cost — and so a broken template fails the deploy, not the user:

php artisan mindy:warm   # or a small command that iterates resource_path('templates')

Gotchas and Tips

Auto-escaping is the first thing to verify, not the last. Multi-dialect engines often auto-escape only in one dialect's mode, or apply the wrong escape strategy for HTML contexts. Render a template with {{ value }} where value = '<img src=x onerror=alert(1)>' and inspect the HTML output before you let any user-influenced data near it. If output is not escaped, wrap the engine to apply htmlspecialchars($output, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') at the adapter boundary — but be aware that will also escape intentional HTML in templates and force you to add an explicit raw filter.

Dialect divergence will silently break output, not throw. This is the highest-probability daily bug. Symptom: a page renders but a variable is blank, a loop index is off by one, or a filter returns the input unchanged. Fix: keep one small tests/Templates/ suite that renders each template with known fixtures and asserts on strings. Run it in CI. You are your own compatibility suite.

Cache invalidation is your problem, not the package's. There is no Laravel view:clear equivalent. Wire one:

Artisan::command('mindy:clear', function () {
    File::cleanDirectory(storage_path('framework/views/mindy'));
});

Call it in your deploy script before warming. If you skip this, stale compiled templates will survive code changes and produce "works on my machine" failures that look like data bugs.

Exception handling will leak internals. With no framework integration, a missing template or a syntax error will throw a raw engine exception with the full template source path and line. Catch the package's exception type (find it in src/) in a middleware and convert to a 500 or 404 with a logged message — otherwise a bad variable name in an email template shows the recipient a filesystem path.

Debugging strategy: dump to the compiled output. When a template does the wrong thing, the compiled file in the cache directory is your friend. Open it, read the generated PHP, and you usually see in ten seconds whether the problem is your template or the engine's parse. This is the single most useful habit with a small, undocumented engine.

Extension points to look for. Before adopting, grep src/ for function addFunction, addFilter, addExtension, or an ExtensionInterface — the ability to register custom filters and functions is what makes the engine usable for a real app (URL builders, money formatting, timezone-aware dates). If those hooks are absent or undocumented, you are locked into the built-in filter set and will end up post-processing rendered strings in PHP, which defeats the purpose.

Vendor the source. Once you commit, run composer require then copy vendor/mindy/template/ into a packages/ directory in your repo, and point the autoloader there. That way a composer update you didn't intend can't silently swap in a new version with different escaping behavior, and if the repo goes away you still have the code.

Fork readiness checklist. Before production, do a one-hour walkthrough of src/ with a second engineer covering: (a) the parser entry point, (b) the escaping decision, (c) the compiler output format, (d) how the cache path is resolved. If any of those four takes more than fifteen minutes to explain, this package is not yet ready for your use case — go to Blade.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons