Brua
Install
npm install @meginhufr/brua-web
GitHub: Meginhufr/brua-web

Web

React, Vue, Svelte and plain HTML: one package, a provider with your base URL, then components by id.

The package has the AssetResponse types, the variant selection rule, <picture> rendering and a component for each framework. Every renderer puts the dark-mode files behind a media query and AVIF and WebP as sources, and sets alt, lang, width and height on the <img>, so the browser picks the file and dark mode switches live. Animations show their still.

Install

Shell
npm install @meginhufr/brua-web

React, Vue and Svelte are optional peers: install the one you use.

Point it at your organization

Every released asset of your organization lives under one base URL on the edge, with no key. The examples use Brua's own organization; yours is on the Integrate page of the back office, or comes from the colleague who set Brua up.

TypeScript
import { createBrua } from "@meginhufr/brua-web";

const brua = createBrua({ base: "https://cdn.brua.dev/assets/991ceed8-a70e-4a5f-b3d3-e0b6b3d93e44", lang: "en" });
const asset = await brua.asset("bridge");   // undefined when nothing is released under the id

The client reads {base}/{id}.json, keeps it for a minute, the edge's own lifetime, and shares it with every component that asks for the same id.

Render by id

React
import { BruaProvider, BruaPicture } from "@meginhufr/brua-web/react";

<BruaProvider base="https://cdn.brua.dev/assets/991ceed8-a70e-4a5f-b3d3-e0b6b3d93e44" lang="en">
  <BruaPicture id="bridge" sizes="(min-width: 600px) 50vw, 100vw" fallback={<div className="hero skeleton" />} />
</BruaProvider>
Vue
<script setup>
import { BruaPicture, provideBrua } from "@meginhufr/brua-web/vue";
provideBrua({ base: "https://cdn.brua.dev/assets/991ceed8-a70e-4a5f-b3d3-e0b6b3d93e44", lang: "en" });   // once, in the root component
</script>

<BruaPicture id="bridge" sizes="(min-width: 600px) 50vw, 100vw"><div class="hero skeleton" /></BruaPicture>
Svelte
<script>
  import { BruaPicture, setBrua } from "@meginhufr/brua-web/svelte";
  setBrua({ base: "https://cdn.brua.dev/assets/991ceed8-a70e-4a5f-b3d3-e0b6b3d93e44", lang: "en" });   // once, in the root component
</script>

<BruaPicture id="bridge" sizes="(min-width: 600px) 50vw, 100vw"><div class="hero skeleton"></div></BruaPicture>
TypeScript
// Without a framework: the client gives the asset, pictureHtml renders it.
import { createBrua, pictureHtml } from "@meginhufr/brua-web";

const brua = createBrua({ base: "https://cdn.brua.dev/assets/991ceed8-a70e-4a5f-b3d3-e0b6b3d93e44", lang: "en" });
const asset = await brua.asset("bridge");
if (asset) element.innerHTML = pictureHtml(asset, { sizes: "(min-width: 600px) 50vw, 100vw" });

Until the asset has loaded the component renders its fallback: the fallback prop, the default slot or the children. Attributes such as class go on the <img>. The component also takes asset instead of id, for a snapshot your backend embedded in its own JSON.

Where a <picture> can't go, such as a CSS background or a canvas, useBruaUrl(asset, { targetWidth: 320 }) in React, useBruaUrl in Vue and bruaUrl in Svelte give one URL that follows the appearance, Reduce Motion and the display.

What you get

  • Light and dark from the system, switching live, with no request of yours.
  • The still of an animation under Reduce Motion, and the still as the loading placeholder otherwise. Lottie and Rive are rendered by their own players from the variant URLs.
  • The density and width the display needs, from the asset's scales or width ladder, and AVIF, WebP or PNG as the browser supports.
  • The alt text in the language you asked for, on the <img>, with lang set; an empty alt for decorative assets.
  • A merge in Brua reaches the page within a minute, with no deploy of yours.

Next