@barefootjs/cli 0.17.0 → 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.
- package/dist/docs/core/adapters/php-adapter.md +193 -0
- package/dist/docs/core/adapters/python-adapter.md +100 -0
- package/dist/docs/core/adapters/ruby-adapter.md +88 -0
- package/dist/docs/core/adapters/rust-adapter.md +136 -0
- package/dist/docs/core/adapters.md +10 -0
- package/dist/docs/core/advanced/error-codes.md +12 -4
- package/dist/docs/core/llms.txt +4 -0
- package/dist/docs/core/rendering/jsx-compatibility.md +35 -6
- package/dist/index.js +1926 -746
- package/package.json +4 -4
|
@@ -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 (`'`, not minijinja's
|
|
62
|
+
default `'`) 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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
package/dist/docs/core/llms.txt
CHANGED
|
@@ -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.
|
|
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
|
|
110
|
-
|
|
|
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.
|
|
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.
|
|
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 */`.
|