Open Source 02 / 34
Pocketknife
Pocketknife is the portable Blade — a Go engine that safely renders a subset of Laravel Blade as static HTML, paired with a Laravel package that renders the identical files in a real app. It's the engine behind DevDojo Sites.
Pocketknife is the portable Blade — a Go engine that statically renders a closed subset of Laravel's Blade templating language, paired with a Composer package (devdojo/pocketknife) that makes the identical files render in a real Laravel application. One project, two renderers, one contract: everything the static renderer supports produces byte-identical output through real Blade. It's the engine behind DevDojo Sites, and it's built in the same open-source spirit as the rest of the toolbox, with a standalone public release (thedevdojo/pocketknife) on the roadmap.
Why it exists
A DevDojo site and a Laravel app are meant to be points on one dial, not two products. Blade is the language at both ends — a site's pages are .blade.php files in a Laravel-shaped folder structure — so turning a site into an app should move zero files.
The catch is serving. Real Blade compiles to arbitrary PHP, so it can never run multi-tenant on shared static infrastructure. Pocketknife resolves that with a no-eval Go renderer for a well-defined Blade subset: it parses templates into an AST and renders them itself — it never executes PHP, never evals, and never panics on user input. Blade syntax becomes safe to serve at the static tier, and anything the subset doesn't cover isn't an error — it's a tier signal: "this needs your site running as a full app."
What's in the box
| Part | What it is |
|---|---|
| The Go engine | Scanner, parser, renderer, and expression evaluator for the Blade subset. Byte-faithful to your HTML — everything outside the dialect passes through verbatim. |
The devdojo/pocketknife package |
The Laravel half: renders the same site tree natively in any Laravel app, with the same data bindings — and can serve it as live pages. |
| The conformance suite | Language-agnostic fixture cases rendered by the Go engine and by real Blade, byte-diffed in CI. Real Laravel is the oracle — anything the two renderers can't agree on doesn't ship in the subset. |
| The CLI | pocketknife compile (a JSON stdin/stdout protocol for host applications) and pocketknife lint (diagnostics for a whole site tree). |
The dialect at a glance
The subset reads like everyday Blade — the full grammar, with exact behavior, is in the Sites syntax reference:
- Echoes —
{{ $expr }}(escaped, exactly Laravel'se()),{!! $expr !!}(raw),{{-- comments --}}(removed). - Control flow —
@if/@elseif/@else,@foreach(with$loop),@break/@continuewith optional conditions. - Components —
<x-sections.hero heading="…"/>resolving underresources/views/components/,@propsdefaults, bound attributes (:plans="$plans"), default and named slots. Layouts are ordinary components wrapping{{ $slot }}. - Expressions — variables,
$post->title,$items[0],??, comparisons and boolean logic, with PHP's truthiness and loose-equality rules faithfully reproduced. - Data —
resources/data/site.jsonbinds as$site, JSON collections bind by filename, and markdown content folders become collections with renderedcontent. - Assets —
@vite(['resources/css/site.css'])renders a static projection: the Tailwind v4 CDN plus your CSS entry inlined. The same line switches to real Vite output once the site runs as an app with built assets.
Two deliberate strictnesses: undefined variables are build errors (real Blade throws at runtime; rendering silent blanks would hide typos), with ?? as the sanctioned default pattern — and every diagnostic carries a file, line, and column.
Tier signals, not failures
Valid Blade outside the subset — @auth, @php, function calls, $attributes — is never silently dropped and never crashes the build. It renders literally in the output and is reported as a structured app-tier diagnostic, with the construct named. Hosts like Sites surface these as an upgrade prompt: this page needs your site running as an application. That's the dial working as designed — write real Blade freely, and the engine tells you which tier it belongs to.
The Laravel package
Because .blade.php is Laravel's native Blade extension, a Pocketknife site needs zero configuration to render in a real app — copy a page into resources/views/ and it just works. The devdojo/pocketknife package adds the rest of the contract:
- Registers the site's view and component roots (your app's own views always win).
- Binds the data contract —
$site, collections, and content collections — fromresources/data/. - Provides the static-projection
@vitewhile the app has no built assets, then steps aside for real Vite. - Optionally serves the site tree as live pages: a fallback route renders each page as native Blade and serves
public/**assets, GET/HEAD only, with app routes always taking precedence.
That serving mode is what powers connecting a Laravel app to Sites: your app pulls the site's Blade source and renders it natively — real templates, not exported HTML.
Where it fits
Every Sites compile — builder previews, the published site, exports, and the AI's build-loop lint — runs through the Pocketknife engine, so what you see in the builder is exactly what ships. And because the conformance suite pins the engine to real Blade byte-for-byte, the site you build today is a Laravel app waiting to be switched on. For the wider toolbox, see the open source overview.