@barefootjs/cli 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,193 @@
1
+ ---
2
+ title: PHP Adapter
3
+ description: Render BarefootJS components from PHP — one engine-agnostic runtime with Twig and Laravel Blade backends.
4
+ ---
5
+
6
+ # PHP Adapter
7
+
8
+ Run the same JSX components on a PHP backend. BarefootJS compiles your JSX into
9
+ a **marked template** plus **client JS**; on the server, a small PHP runtime
10
+ renders those templates. The runtime is deliberately **template-engine- and
11
+ web-framework-agnostic**, so one implementation drives multiple stacks.
12
+
13
+ ```
14
+ JSX → IR → marked template (.twig / .blade.php) + Component.client.js
15
+
16
+
17
+ BarefootJS runtime ──delegates──▶ pluggable backend
18
+ (@barefootjs/php, `barefootjs/php`) (Twig | Laravel Blade)
19
+ ```
20
+
21
+ ## Two backends, one runtime
22
+
23
+ | Backend | Template syntax | Compile-time package | Runtime | Where it runs |
24
+ |---------|-----------------|----------------------|---------|---------------|
25
+ | Twig | `{{ }}` / `{% %}` | `@barefootjs/twig` | `Barefoot\TwigBackend` | **any PHP web app** (Slim, plain PHP) — no framework |
26
+ | Laravel Blade | `{{ }}` / `@if` / `@foreach` | `@barefootjs/blade` | `Barefoot\BladeBackend` | `illuminate/view` standalone — no Laravel application/container required |
27
+
28
+ Both compile-time packages emit per-component template files and the shared
29
+ client JS, and both render through the same engine-agnostic PHP runtime
30
+ (`Barefoot\BarefootJS`, shipped by `@barefootjs/php` / Composer package
31
+ `barefootjs/php`). The only thing that differs is the **backend**: a tiny
32
+ object that implements the five operations the runtime delegates to.
33
+
34
+ Both adapters are near-mechanical ports of `@barefootjs/jinja` (the Jinja2
35
+ adapter — see the [Python Adapter](./python-adapter.md)) to their respective
36
+ template syntax, handling the JS/PHP semantics divergences (truthiness,
37
+ stringification, reserved-word identifier mangling, and evaluator-only
38
+ higher-order-callback lowering, since neither Twig nor Blade has a lambda
39
+ expression) in one uniform place.
40
+
41
+ ### The backend contract
42
+
43
+ Everything that depends on *how* a template renders — JSON marshalling,
44
+ raw-string marking, JSX-children materialisation, named-template
45
+ rendering, and template-variable-name mangling — lives behind a backend
46
+ object with five methods:
47
+
48
+ | Method | Purpose |
49
+ |--------|---------|
50
+ | `encode_json($data)` | Serialize a value for `bf-p` props / inline JSON |
51
+ | `mark_raw($str)` | Mark already-safe HTML so the engine won't re-escape it |
52
+ | `materialize($value)` | Resolve captured JSX children to a string |
53
+ | `render_named($name, $bf, $vars)` | Render a child component's template |
54
+ | `ident($name)` | Mangle a prop name into an engine-safe template variable (Twig: grammar keywords like `for` → `for_`; Blade: render-scope collisions like `loop` → `loop_`) |
55
+
56
+ Because that is the *only* engine-specific surface, the runtime
57
+ (`Barefoot\BarefootJS` + `Barefoot\Evaluator`, in `packages/adapter-php/src/`)
58
+ is shared unchanged between `TwigBackend` and `BladeBackend`.
59
+
60
+ ## Twig
61
+
62
+ ```
63
+ npm install @barefootjs/twig
64
+ ```
65
+
66
+ Configure the build (`barefoot.config.ts`):
67
+
68
+ ```typescript
69
+ import { createConfig } from '@barefootjs/twig/build'
70
+
71
+ export default createConfig({
72
+ components: ['./src/components'],
73
+ outDir: './dist',
74
+ })
75
+ ```
76
+
77
+ `bf build` emits `.twig` templates plus client JS under `outDir`. On the PHP
78
+ side, require `barefootjs/twig` via Composer and point a `TwigBackend` at the
79
+ emitted templates — it builds a `FilesystemLoader`-backed `Twig\Environment`
80
+ with the defaults the templates assume (`autoescape: 'html'`,
81
+ `strict_variables: false`); pass a pre-built Environment via `'env'` to
82
+ customize. Rendering goes through the shared runtime: construct a
83
+ `Barefoot\BarefootJS` over the backend and hand it to `render_named`:
84
+
85
+ ```php
86
+ use Barefoot\BarefootJS;
87
+ use Barefoot\TwigBackend;
88
+
89
+ $backend = new TwigBackend([
90
+ 'paths' => ['dist/templates'],
91
+ ]);
92
+ $bf = new BarefootJS(null, ['backend' => $backend]);
93
+
94
+ $html = $backend->render_named('user_card', $bf, ['name' => 'Ada']);
95
+ ```
96
+
97
+ Twig's default escaper emits `"`/`'` for `"`/`'`, where the
98
+ Perl/Go/Python adapters emit the numeric `"`/`'` forms — the adapter
99
+ accounts for this byte-form difference so output stays consistent across
100
+ backends.
101
+
102
+ ## Laravel Blade
103
+
104
+ ```
105
+ npm install @barefootjs/blade
106
+ ```
107
+
108
+ Configure the build (`barefoot.config.ts`):
109
+
110
+ ```typescript
111
+ import { createConfig } from '@barefootjs/blade/build'
112
+
113
+ export default createConfig({
114
+ components: ['./src/components'],
115
+ outDir: './dist',
116
+ })
117
+ ```
118
+
119
+ `bf build` emits `.blade.php` templates plus client JS under `outDir`. Blade
120
+ runs on `illuminate/view` used **standalone** — no Laravel application or
121
+ service container required. Construct a `Factory` (`Filesystem` + an event
122
+ `Dispatcher` + an `EngineResolver` registering a `blade` engine over a
123
+ `BladeCompiler` + a `FileViewFinder`, all wired together by the `Factory` —
124
+ see `Barefoot\BladeBackend`'s constructor — `new BladeBackend(['paths' =>
125
+ …])` wires all of that for you) pointed at the emitted templates. Rendering
126
+ goes through the shared runtime, same as Twig:
127
+
128
+ ```php
129
+ use Barefoot\BarefootJS;
130
+ use Barefoot\BladeBackend;
131
+
132
+ $backend = new BladeBackend([
133
+ 'paths' => ['dist/templates'],
134
+ ]);
135
+ $bf = new BarefootJS(null, ['backend' => $backend]);
136
+
137
+ $html = $backend->render_named('user_card', $bf, ['name' => 'Ada']);
138
+ ```
139
+
140
+ Blade's `{{ }}` echo (`Illuminate\Support\e()`) emits named HTML entity forms
141
+ (`"`/`'` via `ENT_QUOTES`) where Perl/Go/markupsafe emit the numeric
142
+ `"`/`'` forms — the adapter accounts for this byte-form difference so
143
+ output stays consistent across backends.
144
+
145
+ ## Template output shape
146
+
147
+ Both backends share the same hydration-marker contract:
148
+
149
+ - One template file per component, named by snake-casing the PascalCase
150
+ component name (`UserCard` → `user_card.twig` / `user_card.blade.php`).
151
+ - Hydration markers use the same runtime method names as every other
152
+ adapter's `bf.*` calls, spelled as PHP method calls on the `$bf` variable
153
+ (`bf.scope_attr()` / `$bf->scope_attr()`, `bf.hydration_attrs()` /
154
+ `$bf->hydration_attrs()`, `text_start`/`text_end`, `comment(...)`, …) — see
155
+ [`spec/template-helpers.md`](https://github.com/piconic-ai/barefootjs/blob/main/spec/template-helpers.md)
156
+ for the shared helper contract.
157
+ - Every text/attribute interpolation of a possibly-non-string value is routed
158
+ through `string(...)` (or `bool_str(...)` for boolean-shaped values); every
159
+ non-comparison condition position is routed through `truthy(...)`; every JS
160
+ `===`/`!==` comparison routes through `eq(...)`/`neq(...)` — PHP's own
161
+ `==`/`===` are either loose or number-representation-sensitive in ways that
162
+ diverge from JS strict equality.
163
+
164
+ ## PHP runtime
165
+
166
+ `packages/adapter-php` (Composer package `barefootjs/php`, npm package
167
+ `@barefootjs/php`) is a self-contained, engine-agnostic PHP package with no
168
+ template-engine dependency. It implements the `bf` object every emitted
169
+ template calls into: hydration markers, context propagation
170
+ (`provide_context`/`use_context`), child-component rendering
171
+ (`render_child`), script registration, and the JS-compatible helper library
172
+ (`string`, `bool_str`, `truthy`, `number`, `floor`/`ceil`/`round`,
173
+ array/string helpers, `spread_attrs`, `query`, `eq`/`neq`, …). `TwigBackend`
174
+ (`packages/adapter-twig/php/`, Composer package `barefootjs/twig`) and
175
+ `BladeBackend` (`packages/adapter-blade/php/`, Composer package
176
+ `barefootjs/blade`) both depend on it via a composer `path` repository, so
177
+ adding a third PHP template engine means implementing the five-method backend
178
+ contract above, not re-porting the runtime.
179
+
180
+ ## Examples
181
+
182
+ Runnable end-to-end apps that render the same shared components on a PHP
183
+ backend live under
184
+ [`integrations/php`](https://github.com/piconic-ai/barefootjs/tree/main/integrations/php)
185
+ (Twig) and
186
+ [`integrations/blade`](https://github.com/piconic-ai/barefootjs/tree/main/integrations/blade)
187
+ (Blade).
188
+
189
+ ## See also
190
+
191
+ - [Python Adapter](./python-adapter.md) — the Jinja2 adapter this port maps from
192
+ - [Adapter Architecture](./adapter-architecture.md) — the `TemplateAdapter` interface and IR contract
193
+ - [Writing a Custom Adapter](./custom-adapter.md)
@@ -0,0 +1,100 @@
1
+ ---
2
+ title: Python Adapter
3
+ description: Render BarefootJS components from Python via Jinja2 — no framework required (Flask, Django, bare WSGI).
4
+ ---
5
+
6
+ # Python Adapter
7
+
8
+ Run the same JSX components on a Python backend. BarefootJS compiles your
9
+ JSX into a **Jinja2 marked template** plus **client JS**; on the server, a
10
+ small Python runtime (`barefootjs`) renders those templates through a plain
11
+ `jinja2.Environment` — no framework is required, so Flask, Django, or bare
12
+ WSGI all work the same way.
13
+
14
+ ```
15
+ JSX → IR → marked template (.jinja) + Component.client.js
16
+
17
+
18
+ BarefootJS runtime ──delegates──▶ jinja2.Environment
19
+ (python/barefootjs/)
20
+ ```
21
+
22
+ This adapter is a near-mechanical port of `@barefootjs/xslate` (the
23
+ Text::Xslate/Kolon adapter — see the [Perl Adapter](./perl-adapter.md)) to
24
+ Jinja2 syntax, handling the JS/Python semantics divergences (truthiness,
25
+ stringification, reserved-word identifier mangling, and evaluator-only
26
+ higher-order-callback lowering, since Jinja has no lambda expression) in one
27
+ uniform place rather than per fixture.
28
+
29
+ ## Template output shape
30
+
31
+ - One `.jinja` file per component, named by snake-casing the PascalCase
32
+ component name (`UserCard` → `user_card.jinja`).
33
+ - Hydration markers use the same runtime method names as every other
34
+ adapter's `bf.*` calls (`bf.scope_attr()`, `bf.hydration_attrs()`,
35
+ `bf.text_start`/`text_end`, `bf.comment(...)`, …).
36
+ - Every text/attribute interpolation of a possibly-non-string value is
37
+ routed through `bf.string(...)` (or `bf.bool_str(...)` for boolean-shaped
38
+ values); every non-comparison condition position is routed through
39
+ `bf.truthy(...)`.
40
+
41
+ ## Python runtime
42
+
43
+ `python/barefootjs/` (shipped inside `@barefootjs/jinja`) is a
44
+ self-contained Python package with only one dependency, `jinja2`. It
45
+ implements the engine-agnostic `bf` object every emitted template calls
46
+ into: hydration markers, context propagation
47
+ (`provide_context`/`use_context`), child-component rendering
48
+ (`render_child`), script registration, and the JS-compatible helper library
49
+ (`string`, `bool_str`, `truthy`, `number`, `floor`/`ceil`/`round`,
50
+ array/string helpers, `spread_attrs`, `query`, …).
51
+
52
+ ## Usage
53
+
54
+ ```
55
+ npm install @barefootjs/jinja
56
+ ```
57
+
58
+ Configure the build (`barefoot.config.ts`):
59
+
60
+ ```typescript
61
+ import { createConfig } from '@barefootjs/jinja/build'
62
+
63
+ export default createConfig({
64
+ components: ['./src/components'],
65
+ outDir: './dist',
66
+ })
67
+ ```
68
+
69
+ `bf build` emits `.jinja` templates plus client JS under `outDir`. On the
70
+ Python side, vendor `python/barefootjs/` (from `@barefootjs/jinja`) into
71
+ your app and render a component by constructing a `jinja2.Environment` over
72
+ a `FileSystemLoader` pointed at the emitted templates, with the exact
73
+ settings this adapter's output assumes:
74
+
75
+ ```python
76
+ import jinja2
77
+ from barefootjs import backend_jinja
78
+
79
+ env = jinja2.Environment(
80
+ loader=jinja2.FileSystemLoader("dist/templates"),
81
+ autoescape=True,
82
+ undefined=jinja2.ChainableUndefined,
83
+ trim_blocks=True,
84
+ lstrip_blocks=True,
85
+ )
86
+
87
+ html = backend_jinja.render_named(env, "user_card", vars={"name": "Ada"})
88
+ ```
89
+
90
+ `trim_blocks`/`lstrip_blocks` are required because the adapter places
91
+ `{% … %}` control tags on their own source line; `ChainableUndefined` is
92
+ required so a missing nested attribute (`missing.deep`) renders as empty
93
+ rather than raising.
94
+
95
+ ## See also
96
+
97
+ - [Perl Adapter](./perl-adapter.md) — the Text::Xslate/Kolon adapter this port maps from
98
+ - [Rust Adapter](./rust-adapter.md) — a near-verbatim port of this adapter targeting minijinja, with identical template output
99
+ - [Adapter Architecture](./adapter-architecture.md) — the `TemplateAdapter` interface and IR contract
100
+ - [Writing a Custom Adapter](./custom-adapter.md)
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Ruby Adapter
3
+ description: Render BarefootJS components from Ruby via ERB — runs under any Rack app (Sinatra, Rails).
4
+ ---
5
+
6
+ # Ruby Adapter
7
+
8
+ Run the same JSX components on a Ruby backend. BarefootJS compiles your JSX
9
+ into an **ERB marked template** plus **client JS**; on the server, a small
10
+ Ruby runtime (`BarefootJS`) renders those templates through Ruby's stdlib
11
+ `ERB` — no web framework is required, so it runs under any Rack app
12
+ (Sinatra, Rails, or plain Rack).
13
+
14
+ ```
15
+ JSX → IR → marked template (.erb) + Component.client.js
16
+
17
+
18
+ BarefootJS runtime ──delegates──▶ BarefootJS::Backend::Erb
19
+ (lib/barefoot_js.rb) (stdlib ERB)
20
+ ```
21
+
22
+ It is a Ruby port of the Perl runtime (`BarefootJS.pm`, see the
23
+ [Perl Adapter](./perl-adapter.md)), keeping method names 1:1 (`bf.scope_attr`,
24
+ `bf.hydration_attrs`, `bf.render_child`, …) so the compile-time adapter and
25
+ runtime share one naming contract across languages.
26
+
27
+ ## Backend contract
28
+
29
+ Like the Perl and Text::Xslate ports, everything that depends on *how* a
30
+ template renders is delegated to a small `backend` object:
31
+
32
+ | Method | Purpose |
33
+ |--------|---------|
34
+ | `encode_json(data)` | Serialize a value for `bf-p` props / inline JSON |
35
+ | `mark_raw(str)` | Mark already-safe HTML (identity for ERB — see below) |
36
+ | `materialize(value)` | Resolve captured JSX children to a string |
37
+ | `render_named(name, bf, vars)` | Render a child component's `.erb` template |
38
+
39
+ Unlike Kolon or Twig, stdlib ERB's `<%= %>` does **not** auto-escape, so
40
+ `mark_raw` is a no-op — the compiled templates call `bf.h(...)` explicitly
41
+ wherever escaping is required. `mark_raw` exists purely so runtime helpers
42
+ that already produce finished HTML (e.g. `spread_attrs`) share one
43
+ `backend.mark_raw(...)` call shape with every other BarefootJS backend.
44
+
45
+ ## Usage
46
+
47
+ ```
48
+ npm install @barefootjs/erb
49
+ ```
50
+
51
+ Configure the build (`barefoot.config.ts`):
52
+
53
+ ```typescript
54
+ import { createConfig } from '@barefootjs/erb/build'
55
+
56
+ export default createConfig({
57
+ components: ['./components'],
58
+ outDir: 'dist',
59
+ })
60
+ ```
61
+
62
+ `bf build` emits one `.erb` file per component plus the client JS bundle. On
63
+ the Ruby side, vendor `lib/barefoot_js.rb` (from `@barefootjs/erb`) into your
64
+ app and construct the ERB backend against the output directory:
65
+
66
+ ```ruby
67
+ require 'barefoot_js'
68
+ require 'barefoot_js/backend/erb'
69
+
70
+ backend = BarefootJS::Backend::Erb.new(path: 'dist/templates')
71
+ bf = BarefootJS::Context.new(backend)
72
+ bf._scope_id("Counter_#{rand(1_000_000)}")
73
+
74
+ html = backend.render_named('Counter', bf, { count: 0 })
75
+ ```
76
+
77
+ Each compiled `.erb` template receives exactly two locals: `bf` (the
78
+ `BarefootJS::Context` for this render) and `v` (a symbol-keyed Hash holding
79
+ every prop/signal/memo the template references) — e.g.
80
+ `<%= bf.h(v[:count]) %>`, `<%= bf.spread_attrs(bag) %>` — stdlib ERB's
81
+ `<%=` never auto-escapes, so both plain and already-safe-HTML helpers use
82
+ the same tag; there is no separate raw-output tag like Mojolicious's `<%==`.
83
+
84
+ ## See also
85
+
86
+ - [Perl Adapter](./perl-adapter.md) — the runtime this port mirrors method-for-method
87
+ - [Adapter Architecture](./adapter-architecture.md) — the `TemplateAdapter` interface and IR contract
88
+ - [Writing a Custom Adapter](./custom-adapter.md)
@@ -0,0 +1,136 @@
1
+ ---
2
+ title: Rust Adapter
3
+ description: Render BarefootJS components from Rust via minijinja — no framework required (axum, actix-web, warp).
4
+ ---
5
+
6
+ # Rust Adapter
7
+
8
+ Run the same JSX components on a Rust backend. BarefootJS compiles your JSX
9
+ into a **minijinja marked template** plus **client JS**; on the server, a
10
+ small Rust runtime (crate `barefootjs`) renders those templates through a
11
+ plain [`minijinja::Environment`](https://docs.rs/minijinja) — no framework
12
+ is required, so axum, actix-web, warp, or bare `hyper` all work the same
13
+ way.
14
+
15
+ ```
16
+ JSX → IR → marked template (.j2) + Component.client.js
17
+
18
+
19
+ BarefootJS runtime ──delegates──▶ minijinja::Environment
20
+ (runtime/, crate barefootjs)
21
+ ```
22
+
23
+ This adapter is a near-verbatim port of `@barefootjs/jinja` (the Python
24
+ adapter — see the [Python Adapter](./python-adapter.md)) to the `minijinja`
25
+ Rust crate. **The emitted template syntax is identical** to
26
+ `@barefootjs/jinja`'s output — minijinja 2.21 is Jinja2-compatible for
27
+ everything this adapter emits. Only the identity fields differ (`.j2`
28
+ extension) plus the render engine that interprets the syntax at request
29
+ time (a Rust `minijinja::Environment` instead of Python's
30
+ `jinja2.Environment`).
31
+
32
+ ## Template output shape
33
+
34
+ - One `.j2` file per component, named by snake-casing the PascalCase
35
+ component name (`UserCard` → `user_card.j2`).
36
+ - Hydration markers use the same runtime method names as every other
37
+ adapter's `bf.*` calls (`bf.scope_attr()`, `bf.hydration_attrs()`,
38
+ `bf.text_start`/`text_end`, `bf.comment(...)`, …).
39
+ - Every text/attribute interpolation of a possibly-non-string value is
40
+ routed through `bf.string(...)` (or `bf.bool_str(...)` for boolean-shaped
41
+ values); every non-comparison condition position is routed through
42
+ `bf.truthy(...)`.
43
+
44
+ ## The minijinja Environment contract
45
+
46
+ This adapter's output assumes an `Environment` built with a specific set of
47
+ options — `ChainableUndefined`, `trim_blocks`/`lstrip_blocks`, HTML
48
+ auto-escaping forced on, and a custom formatter. Rather than assembling
49
+ these yourself, call the crate's `build_environment`, which constructs the
50
+ `Environment` per that contract:
51
+
52
+ ```rust
53
+ use barefootjs::backend_minijinja::build_environment;
54
+
55
+ let env = build_environment(templates_dir); // .j2 files
56
+ ```
57
+
58
+ `trim_blocks`/`lstrip_blocks` are required because `{% … %}` control tags
59
+ sit on their own source line; without them every such line leaks a stray
60
+ newline/indentation into the rendered HTML. The internal formatter escapes
61
+ strings with MarkupSafe-compatible entities (`&#39;`, not minijinja's
62
+ default `&#x27;`) and formats numbers with JS `String(n)` semantics
63
+ (`1.0` → `1`), matching every other adapter's byte-for-byte output.
64
+
65
+ ## Rust runtime
66
+
67
+ `runtime/` (crate `barefootjs`, deps: `minijinja`, `serde`, `serde_json`)
68
+ implements the engine-agnostic `bf` object every emitted template calls
69
+ into: hydration markers, context propagation
70
+ (`provide_context`/`use_context`), child-component rendering
71
+ (`render_child`), script registration, and the JS-compatible helper library
72
+ (`string`, `bool_str`, `truthy`, `number`, `floor`/`ceil`/`round`,
73
+ array/string helpers, `spread_attrs`, `query`, …).
74
+
75
+ ## Usage
76
+
77
+ ```
78
+ npm install @barefootjs/rust
79
+ ```
80
+
81
+ Configure the build (`barefoot.config.ts`):
82
+
83
+ ```typescript
84
+ import { createConfig } from '@barefootjs/rust/build'
85
+
86
+ export default createConfig({
87
+ components: ['./src/components'],
88
+ outDir: './dist',
89
+ })
90
+ ```
91
+
92
+ `bf build` emits `.j2` templates plus client JS under `outDir`. On the Rust
93
+ side, depend on the `barefootjs` crate, build the `Environment` via
94
+ `build_environment` (per the contract above), and render a component
95
+ through a `RenderSession` + root `BfInstance`:
96
+
97
+ ```rust
98
+ use axum::{routing::get, Router};
99
+ use barefootjs::{backend_minijinja, BfInstance, JsValue, RenderSession};
100
+ use minijinja::Environment;
101
+ use std::collections::BTreeMap;
102
+ use std::path::PathBuf;
103
+ use std::sync::Arc;
104
+
105
+ async fn user_card(env: Arc<Environment<'static>>) -> axum::response::Html<String> {
106
+ let session = RenderSession::new();
107
+ let root = BfInstance::root(Arc::clone(&session), "UserCard_0");
108
+ let vars = JsValue::Object(BTreeMap::from([("name".to_string(), JsValue::String("Ada".into()))]));
109
+
110
+ let html = backend_minijinja::render_named(&env, "user_card", root.as_mj_value(), &vars).unwrap();
111
+ axum::response::Html(html)
112
+ }
113
+
114
+ #[tokio::main]
115
+ async fn main() {
116
+ let env = Arc::new(backend_minijinja::build_environment(&PathBuf::from("dist/templates")));
117
+ let app = Router::new().route("/", get(move || user_card(env.clone())));
118
+ let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
119
+ axum::serve(listener, app).await.unwrap();
120
+ }
121
+ ```
122
+
123
+ A full runnable app wiring this up with manifest-driven child-component
124
+ registration lives at
125
+ [`integrations/axum`](https://github.com/piconic-ai/barefootjs/tree/main/integrations/axum)
126
+ (see `src/render.rs`'s `render_component` for the production-shaped version
127
+ of the snippet above). The crate also ships a `bf-render` binary, a
128
+ conformance renderer used by the adapter's own test suite (`cargo build
129
+ --bin bf-render`) — most hosts should link the `barefootjs` library crate
130
+ directly, as above, rather than shelling out to the binary.
131
+
132
+ ## See also
133
+
134
+ - [Python Adapter](./python-adapter.md) — the Jinja2 adapter this is a near-verbatim port of, with identical template syntax
135
+ - [Adapter Architecture](./adapter-architecture.md) — the `TemplateAdapter` interface and IR contract
136
+ - [Writing a Custom Adapter](./custom-adapter.md)
@@ -23,6 +23,10 @@ JSX Source
23
23
  | [`HonoAdapter`](./adapters/hono-adapter.md) | `.tsx` | Hono / JSX-based servers | `@barefootjs/hono` |
24
24
  | [`GoTemplateAdapter`](./adapters/go-template-adapter.md) | `.tmpl` + `_types.go` | Go `html/template` | `@barefootjs/go-template` |
25
25
  | [Perl](./adapters/perl-adapter.md) | `.ep` / `.tx` | Mojolicious, Text::Xslate (PSGI/Plack) | `@barefootjs/mojolicious`, `@barefootjs/xslate` |
26
+ | [Ruby](./adapters/ruby-adapter.md) | `.erb` | stdlib ERB (any Rack app — Sinatra, Rails) | `@barefootjs/erb` |
27
+ | [Python](./adapters/python-adapter.md) | `.jinja` | Jinja2 (Flask, Django, bare WSGI) | `@barefootjs/jinja` |
28
+ | [PHP](./adapters/php-adapter.md) | `.twig` / `.blade.php` | Twig (Slim, plain PHP), Laravel Blade (`illuminate/view` standalone) | `@barefootjs/twig`, `@barefootjs/blade` |
29
+ | [Rust](./adapters/rust-adapter.md) | `.j2` | minijinja (axum, actix-web, warp) | `@barefootjs/rust` |
26
30
  | [CSR](./adapters/csr.md) | — (client-rendered) | None (browser-only) | `@barefootjs/client` |
27
31
 
28
32
  > CSR is not an IR→template adapter. It renders components directly in the browser using client-side template functions — use it when the server can't (or shouldn't) emit the initial HTML.
@@ -31,6 +35,8 @@ The `GoTemplateAdapter` is web-framework-agnostic: its `html/template` output ru
31
35
 
32
36
  The Perl adapters share one engine-agnostic runtime (`BarefootJS`): `@barefootjs/mojolicious` targets Mojolicious EP, and `@barefootjs/xslate` targets Text::Xslate (Kolon) and runs under any PSGI/Plack app. See the [Perl Adapter](./adapters/perl-adapter.md) page.
33
37
 
38
+ The Ruby, Python, and Rust adapters are each single-backend, engine-agnostic ports of that same runtime model to another language: `@barefootjs/erb` (Ruby/ERB), `@barefootjs/jinja` (Python/Jinja2), and `@barefootjs/rust` (Rust/minijinja) — none of them require a specific web framework. The PHP adapters follow the same pattern but, like Perl, share one engine-agnostic runtime (`@barefootjs/php`) across two backends: `@barefootjs/twig` targets Twig and `@barefootjs/blade` targets Laravel Blade (via `illuminate/view` standalone). `@barefootjs/twig`, `@barefootjs/blade`, and `@barefootjs/rust` are themselves near-mechanical ports of `@barefootjs/jinja`'s Jinja2 syntax, so all emit near-identical templates. See the [PHP Adapter](./adapters/php-adapter.md) page.
39
+
34
40
  ## Pages
35
41
 
36
42
  | Topic | Description |
@@ -39,5 +45,9 @@ The Perl adapters share one engine-agnostic runtime (`BarefootJS`): `@barefootjs
39
45
  | [Hono Adapter](./adapters/hono-adapter.md) | Configuration and output format for Hono / JSX-based servers |
40
46
  | [Go Template Adapter](./adapters/go-template-adapter.md) | Configuration and output format for Go `html/template` |
41
47
  | [Perl Adapter](./adapters/perl-adapter.md) | Mojolicious and Text::Xslate (PSGI/Plack) backends, sharing one runtime |
48
+ | [Ruby Adapter](./adapters/ruby-adapter.md) | ERB backend, running under any Rack app |
49
+ | [Python Adapter](./adapters/python-adapter.md) | Jinja2 backend, running under any Python web framework |
50
+ | [PHP Adapter](./adapters/php-adapter.md) | Twig (any PHP web app) and Laravel Blade (`illuminate/view` standalone) backends |
51
+ | [Rust Adapter](./adapters/rust-adapter.md) | minijinja backend, running under any Rust web framework |
42
52
  | [CSR](./adapters/csr.md) | Client-side rendering without a server-rendered template |
43
53
  | [Writing a Custom Adapter](./adapters/custom-adapter.md) | Step-by-step guide to implementing your own adapter |
@@ -141,10 +141,18 @@ Simple subtraction: `(a, b) => a.field - b.field`:
141
141
  // ✅ SSR-compilable
142
142
  {items().sort((a, b) => a.price - b.price).map(...)} // ascending
143
143
  {items().toSorted((a, b) => b.date - a.date).map(...)} // descending
144
-
145
- // ❌ BF021 — block bodies, localeCompare, ternary operators, etc. are not supported
146
- {items().sort((a, b) => { return a.price - b.price }).map(...)}
147
- {items().sort((a, b) => a.name.localeCompare(b.name)).map(...)}
144
+ {items().sort((a, b) => { return a.price - b.price }).map(...)} // single-return block body
145
+ {items().sort((a, b) => a.name.localeCompare(b.name)).map(...)} // zero-arg localeCompare
146
+
147
+ // A bare identifier reference to a same-file const/function comparator
148
+ // resolves one hop and compiles like the inline arrow above (#2090):
149
+ const byPrice = (a, b) => a.price - b.price
150
+ {items().sort(byPrice).map(...)}
151
+
152
+ // ❌ BF021 — locale/options localeCompare, and an unresolved comparator
153
+ // (imported, a prop, or an alias chain) are not supported
154
+ {items().sort((a, b) => a.name.localeCompare(b.name, 'ja', { numeric: true })).map(...)}
155
+ {items().sort(importedCmp).map(...)}
148
156
  ```
149
157
 
150
158
  #### Workaround
@@ -45,6 +45,10 @@
45
45
  - [Go Template Adapter](https://barefootjs.dev/docs/adapters/go-template-adapter.md): Generate Go html/template files and type definitions from the compiler's IR.
46
46
  - [Hono Adapter](https://barefootjs.dev/docs/adapters/hono-adapter.md): Generate Hono JSX templates from the compiler's IR for Hono-based servers.
47
47
  - [Perl Adapter](https://barefootjs.dev/docs/adapters/perl-adapter.md): Render BarefootJS components from Perl — one engine-agnostic runtime with Mojolicious and Text::Xslate (PSGI/Plack) backends.
48
+ - [PHP Adapter](https://barefootjs.dev/docs/adapters/php-adapter.md): Render BarefootJS components from PHP — one engine-agnostic runtime with Twig and Laravel Blade backends.
49
+ - [Python Adapter](https://barefootjs.dev/docs/adapters/python-adapter.md): Render BarefootJS components from Python via Jinja2 — no framework required (Flask, Django, bare WSGI).
50
+ - [Ruby Adapter](https://barefootjs.dev/docs/adapters/ruby-adapter.md): Render BarefootJS components from Ruby via ERB — runs under any Rack app (Sinatra, Rails).
51
+ - [Rust Adapter](https://barefootjs.dev/docs/adapters/rust-adapter.md): Render BarefootJS components from Rust via minijinja — no framework required (axum, actix-web, warp).
48
52
 
49
53
  ## Advanced
50
54
 
@@ -76,7 +76,7 @@ return <div>...</div>
76
76
  ))}
77
77
  ```
78
78
 
79
- Supported comparator shapes: `(a, b) => a - b`, `(a, b) => a.field - b.field`, `(a, b) => a.localeCompare(b)`, `(a, b) => a.field.localeCompare(b.field)`, relational-ternary returns (`(a, b) => a.field > b.field ? 1 : -1`, including the 3-way `a < b ? -1 : a > b ? 1 : 0` form), and any of these `||`-chained for multi-key tie-breaks. A single-`return` block body (`(a, b) => { return a.field - b.field }`) works too. Reverse the operands (or the ternary sign) for descending order. Other shapes function references (`sort(myCmp)`), multi-statement block bodies, and `localeCompare(b, locale, opts)` — produce a compile error; use `/* @client */` in that case.
79
+ Supported comparator shapes: `(a, b) => a - b`, `(a, b) => a.field - b.field`, `(a, b) => a.localeCompare(b)`, `(a, b) => a.field.localeCompare(b.field)`, relational-ternary returns (`(a, b) => a.field > b.field ? 1 : -1`, including the 3-way `a < b ? -1 : a > b ? 1 : 0` form), and any of these `||`-chained for multi-key tie-breaks. A single-`return` block body (`(a, b) => { return a.field - b.field }`) works too. Reverse the operands (or the ternary sign) for descending order. A bare identifier reference to a same-file `const`/`function` comparator (`sort(byPrice)`) resolves one hop and compiles the same as the inline arrow. Other shapes — multi-statement block bodies, `localeCompare(b, locale, opts)`, and an unresolved reference (imported, a prop, or an alias chain like `const c2 = c1`) — produce a compile error; use `/* @client */` in that case.
80
80
 
81
81
 
82
82
  ## Event Handling
@@ -106,8 +106,10 @@ Some JavaScript expressions cannot be translated into marked template syntax. Wh
106
106
  | `.filter()` with destructured param (`({done}) => done`) | works (runs as JS) | **BF101** |
107
107
  | `.filter()` with `function` keyword callback | works | **BF101** |
108
108
  | `.reduce()`, `.forEach()`, `.flatMap()` | works | **BF101** |
109
- | Nested higher-order in filter predicate (`x => x.tags.filter(...).length > 0`) | works | **BF101** |
110
- | Sort comparator that's a function reference, multi-statement block body, or `localeCompare(b, locale, opts)` | **BF021** (all adapters) | **BF021** |
109
+ | Nested `.filter()` / `.map()` in a filter predicate (`x => x.tags.filter(...).length > 0`) | works | works |
110
+ | Nested `.some()` / `.find()` / `.reduce()` in a filter predicate | works | **BF101** |
111
+ | Sort comparator that's a multi-statement block body or `localeCompare(b, locale, opts)` | **BF021** (all adapters) | **BF021** |
112
+ | Sort comparator that's a function reference to an imported/prop identifier, or an alias chain (`const c2 = c1`) | **BF021** (all adapters) | **BF021** |
111
113
  | `typeof` in a filter predicate | **BF021** (all adapters) | **BF021** |
112
114
 
113
115
  `BF021` is raised at the IR layer and applies to every adapter. `BF101` is raised by adapters that can't lower the expression to their template language. Either way, add [`/* @client */`](./client-directive.md) to opt into client-only evaluation and suppress the error.
@@ -116,12 +118,21 @@ Some JavaScript expressions cannot be translated into marked template syntax. Wh
116
118
 
117
119
  **Nested higher-order methods:**
118
120
 
121
+ A nested `.filter()` / `.map()` inside a filter predicate's callback body lowers on every adapter — the runtime evaluator serializes the nested call (the callback arrow travels with it) instead of refusing it:
122
+
123
+ ```tsx
124
+ // ✅ Nested `.filter()` / `.map()` now compiles everywhere
125
+ {items().filter(x => x.tags.filter(t => t.active).length > 0).map(t => t.name)}
126
+ ```
127
+
128
+ A nested `.some()` / `.find()` / `.reduce()` still has no faithful Go/Mojo lowering (they return a boolean-from-search / element / fold, not a per-element projection), so they still refuse:
129
+
119
130
  ```tsx
120
131
  // ❌ BF101 on Go/Mojo; works on Hono
121
- {items().filter(x => x.tags.filter(t => t.active).length > 0).map(...)}
132
+ {items().filter(x => x.tags.some(t => t.active)).map(t => t.name)}
122
133
 
123
134
  // ✅ Add /* @client */ to evaluate on the client
124
- {/* @client */ items().filter(x => x.tags.filter(t => t.active).length > 0).map(...)}
135
+ {/* @client */ items().filter(x => x.tags.some(t => t.active)).map(t => t.name)}
125
136
  ```
126
137
 
127
138
  **`.reduce()` / `.forEach()` / `.flatMap()`:**
@@ -156,7 +167,7 @@ Some JavaScript expressions cannot be translated into marked template syntax. Wh
156
167
 
157
168
  ### Patterns that error on all adapters
158
169
 
159
- **Unsupported sort comparators** (imperative block bodies, function references):
170
+ **Unsupported sort comparators** (imperative block bodies, unresolved function references):
160
171
 
161
172
  A value-producing block body normalizes to an expression — pure `const`
162
173
  bindings inline (let-inline) and a value-producing `if` / early `return`
@@ -184,4 +195,22 @@ Only a genuinely imperative comparator — one that re-assigns a local, loops, o
184
195
  ))}
185
196
  ```
186
197
 
198
+ A same-file `const`/`function` comparator reference resolves one hop and
199
+ compiles (see the "Sort comparators" section above); an **imported** or
200
+ **aliased** reference does not — the compiler follows the identifier back
201
+ only one binding, so it can't see through a re-export or `const c2 = c1`:
202
+
203
+ ```tsx
204
+ // ❌ BF021 — `byPrice` is imported, not declared in this file
205
+ import { byPrice } from './comparators'
206
+ function SortedList({ items }: { items: Item[] }) {
207
+ return <ul>{items.sort(byPrice).map((item) => <li key={item.id}>{item.name}</li>)}</ul>
208
+ }
209
+
210
+ // ✅ Use /* @client */, or inline / re-declare the comparator locally
211
+ {/* @client */ items().sort(byPrice).map(item => (
212
+ <Item key={item.id} item={item} />
213
+ ))}
214
+ ```
215
+
187
216
  See the [TodoApp example](https://github.com/piconic-ai/barefootjs/blob/main/integrations/shared/components/TodoApp.tsx) for a real-world component using `/* @client */`.