Run the same JSX components on a PHP backend. BarefootJS compiles your JSX into a marked template plus client JS; on the server, a small PHP runtime renders those templates. The runtime is deliberately template-engine- and web-framework-agnostic, so one implementation drives multiple stacks.

JSX → IR → marked template (.twig / .blade.php) + Component.client.js


   BarefootJS runtime  ──delegates──▶  pluggable backend
   (@barefootjs/php, `barefootjs/php`)       (Twig | Laravel Blade)

#Two backends, one runtime

Backend Template syntax Compile-time package Runtime Where it runs
Twig {{ }} / {% %} @barefootjs/twig Barefoot\TwigBackend any PHP web app (Slim, plain PHP) — no framework
Laravel Blade {{ }} / @if / @foreach @barefootjs/blade Barefoot\BladeBackend illuminate/view standalone — no Laravel application/container required

Both compile-time packages emit per-component template files and the shared client JS, and both render through the same engine-agnostic PHP runtime (Barefoot\BarefootJS, shipped by @barefootjs/php / Composer package barefootjs/php). The only thing that differs is the backend: a tiny object that implements the five operations the runtime delegates to.

Both adapters are near-mechanical ports of @barefootjs/jinja (the Jinja2 adapter — see the Python Adapter) to their respective template syntax, handling the JS/PHP semantics divergences (truthiness, stringification, reserved-word identifier mangling, and evaluator-only higher-order-callback lowering, since neither Twig nor Blade has a lambda expression) in one uniform place.

#The backend contract

Everything that depends on how a template renders — JSON marshalling, raw-string marking, JSX-children materialisation, named-template rendering, and template-variable-name mangling — lives behind a backend object with five methods:

Method Purpose
encode_json($data) Serialize a value for bf-p props / inline JSON
mark_raw($str) Mark already-safe HTML so the engine won't re-escape it
materialize($value) Resolve captured JSX children to a string
render_named($name, $bf, $vars) Render a child component's template
ident($name) Mangle a prop name into an engine-safe template variable (Twig: grammar keywords like forfor_; Blade: render-scope collisions like looploop_)

Because that is the only engine-specific surface, the runtime (Barefoot\BarefootJS + Barefoot\Evaluator, in packages/adapter-php/src/) is shared unchanged between TwigBackend and BladeBackend.

#Twig

npm install @barefootjs/twig

Configure the build (barefoot.config.ts):

import { createConfig } from '@barefootjs/twig/build'

export default createConfig({
  components: ['./src/components'],
  outDir: './dist',
})

bf build emits .twig templates plus client JS under outDir. On the PHP side, require barefootjs/twig via Composer and point a TwigBackend at the emitted templates — it builds a FilesystemLoader-backed Twig\Environment with the defaults the templates assume (autoescape: 'html', strict_variables: false); pass a pre-built Environment via 'env' to customize. Rendering goes through the shared runtime: construct a Barefoot\BarefootJS over the backend and hand it to render_named:

use Barefoot\BarefootJS;
use Barefoot\TwigBackend;

$backend = new TwigBackend([
    'paths' => ['dist/templates'],
]);
$bf = new BarefootJS(null, ['backend' => $backend]);

$html = $backend->render_named('user_card', $bf, ['name' => 'Ada']);

Twig's default escaper emits "/' for "/', where the Perl/Go/Python adapters emit the numeric "/' forms — the adapter accounts for this byte-form difference so output stays consistent across backends.

#Laravel Blade

npm install @barefootjs/blade

Configure the build (barefoot.config.ts):

import { createConfig } from '@barefootjs/blade/build'

export default createConfig({
  components: ['./src/components'],
  outDir: './dist',
})

bf build emits .blade.php templates plus client JS under outDir. Blade runs on illuminate/view used standalone — no Laravel application or service container required. Construct a Factory (Filesystem + an event Dispatcher + an EngineResolver registering a blade engine over a BladeCompiler + a FileViewFinder, all wired together by the Factory — see Barefoot\BladeBackend's constructor — new BladeBackend(['paths' => …]) wires all of that for you) pointed at the emitted templates. Rendering goes through the shared runtime, same as Twig:

use Barefoot\BarefootJS;
use Barefoot\BladeBackend;

$backend = new BladeBackend([
    'paths' => ['dist/templates'],
]);
$bf = new BarefootJS(null, ['backend' => $backend]);

$html = $backend->render_named('user_card', $bf, ['name' => 'Ada']);

Blade's {{ }} echo (Illuminate\Support\e()) emits named HTML entity forms ("/' via ENT_QUOTES) where Perl/Go/markupsafe emit the numeric "/' forms — the adapter accounts for this byte-form difference so output stays consistent across backends.

#Template output shape

Both backends share the same hydration-marker contract:

  • One template file per component, named by snake-casing the PascalCase component name (UserCarduser_card.twig / user_card.blade.php).
  • Hydration markers use the same runtime method names as every other adapter's bf.* calls, spelled as PHP method calls on the $bf variable (bf.scope_attr() / $bf->scope_attr(), bf.hydration_attrs() / $bf->hydration_attrs(), text_start/text_end, comment(...), …) — see spec/template-helpers.md for the shared helper contract.
  • Every text/attribute interpolation of a possibly-non-string value is routed through string(...) (or bool_str(...) for boolean-shaped values); every non-comparison condition position is routed through truthy(...); every JS ===/!== comparison routes through eq(...)/neq(...) — PHP's own ==/=== are either loose or number-representation-sensitive in ways that diverge from JS strict equality.

#PHP runtime

packages/adapter-php (Composer package barefootjs/php, npm package @barefootjs/php) is a self-contained, engine-agnostic PHP package with no template-engine dependency. It implements the bf object every emitted template calls into: hydration markers, context propagation (provide_context/use_context), child-component rendering (render_child), script registration, and the JS-compatible helper library (string, bool_str, truthy, number, floor/ceil/round, array/string helpers, spread_attrs, query, eq/neq, …). TwigBackend (packages/adapter-twig/php/, Composer package barefootjs/twig) and BladeBackend (packages/adapter-blade/php/, Composer package barefootjs/blade) both depend on it via a composer path repository, so adding a third PHP template engine means implementing the five-method backend contract above, not re-porting the runtime.

#Examples

Runnable end-to-end apps that render the same shared components on a PHP backend live under integrations/php (Twig) and integrations/blade (Blade).

#See also