@barefootjs/pebble compiles components to Pebble templates (.peb) rendered by a small Java runtime (dev.barefootjs.pebble) over a plain PebbleEngine. Spring Boot, Ktor or a plain Servlet app all work the same way.
#Install
npm install @barefootjs/pebble// vite.config.ts
import { defineConfig } from 'vite'
import { barefoot } from '@barefootjs/pebble/vite'
export default defineConfig({
plugins: barefoot({ components: ['components'], templates: 'src/main/resources/templates' }),
})The runtime is not published to Maven Central. Vendor the java/ sources from the npm package into your Gradle or Maven build (or include its Gradle project as a composite build); its one dependency is io.pebbletemplates:pebble.
#Render from your server
import dev.barefootjs.pebble.Bf;
import dev.barefootjs.pebble.ext.SetBlockExtension;
import io.pebbletemplates.pebble.PebbleEngine;
import io.pebbletemplates.pebble.loader.FileLoader;
import io.pebbletemplates.pebble.template.PebbleTemplate;
import java.io.StringWriter;
import java.util.Map;
FileLoader loader = new FileLoader("/abs/path/to/src/main/resources/templates");
loader.setSuffix(".peb");
PebbleEngine engine = new PebbleEngine.Builder()
.loader(loader)
.strictVariables(false)
.extension(new SetBlockExtension())
.build();
Bf bf = new Bf("Counter_0", engine, Map.of()); // scope id, engine (for render_child), child manifest
PebbleTemplate template = engine.getTemplate("counter");
StringWriter writer = new StringWriter();
template.evaluate(writer, Map.of("initial", 0, "bf", bf));
String html = "<!doctype html><body>" + writer + bf.scripts() + "</body>";Three settings are required. strictVariables(false) lets a missing prop render empty instead of throwing. SetBlockExtension adds the {% set name %}…{% endset %} block-capture form that stock Pebble lacks; children and slots do not render without it. The bf context entry is Bf(scopeId, engine, manifest): the engine resolves child templates for render_child, and the manifest maps child template names to their prop defaults — Map.of() for a component without children, otherwise ManifestLoader.loadFromBuildManifest(templatesDir.resolve("manifest.json")) over the manifest.json that vite build writes next to the templates (ManifestLoader.load reads the _bf_manifest.json sidecar the conformance harness writes instead).
#Notes
FileLoaderrejects a relative prefix; pass an absolute path.- Template names are the snake_cased component name:
UserCard→user_card.peb. - Pebble has no
??, no strict equality and no lambdas; the adapter routes those throughbf.*helpers, so nothing needs configuring. - Variadic helpers take a single Pebble list literal, because Pebble's method resolver requires an exact parameter count.
- No Gradle wrapper is committed; build with a
gradleonPATH(CI uses Java 21).
Example: integrations/spring.