Meridian handbook
Extending

First-party extensions

The extensions that ship beside the engine binary and load by name from engine.yaml (today the vite extension): what each adds, where its file lives in a release and in the container image, and how to turn it on.

A first-party extension is an extension built and released with the engine, against the same meridian-extend module your own extensions use. It ships as a .so file beside the engine binary and is never compiled into it, so a project that does not name it does not load it. You name one in engine.yaml by its name alone:

# config/engine.yaml
extensions:
  - vite
Where the engine looksWhen
extensions/<name>.so in the directory of the running engine binaryAlways, for a bare name
/app/extensions/vite.soIn the -vips container image, where the engine is /app/engine-dmo

The same rules apply as to your own extensions: they load only in an engine built with cgo (the -vips image), on Linux, macOS or FreeBSD. A name the engine does not ship is refused at its line: this engine has no extension named <name> (looked for .../extensions/<name>.so); a path names a project's own, extensions/<name>.so.

engine-dmo version --project . lists a loaded first-party extension among the bundle’s modules.

Where you can get one today

A release publishes container images only, so the one place a first-party extension ships is the -vips image (/app/extensions/vite.so, linux/amd64). The pure Go image, including every arm64 host, cannot load it, and a project that names vite is refused there at boot and by check. Run check and serve on the same image you deploy. A project that does not name vite serves its assets from public/ on either image.

vite

Connects a Vite build to the template asset helpers and the development loop. A project that builds no frontend, or builds it with another tool, leaves it out and is served from the files in public/.

It addsWhat it does
The asset resolverassets.css('main'), assets.js('main'), assets.js('legacy', false) and preload() print the content-hashed files a Vite build wrote, read from the build manifest at public/build/.vite/manifest.json (or the site’s assets.manifest), with each bundle’s CSS and the chunks it imports. A bundle the manifest does not hold falls back to public/css/<name>.css and public/js/<name>.js.
A dev server under engine-dmo serve --devWhen the project has a vite.config.js (or .mjs, .ts, .mts, .cjs, .cts) and Vite installed at node_modules/.bin/vite, the engine starts Vite on a free loopback port (--port <n> --strictPort --host 127.0.0.1) before it boots, points the helpers at it with hot reloading, admits its origin and inline styles in the development content security policy, and stops it on exit. Vite runs with MERIDIAN_VITE_ORIGIN, MERIDIAN_PROJECT and MERIDIAN_ENGINE in its environment. With a config and no installed Vite, the engine says so and serves public/ as it is.
engine-dmo assets budget [--site key] [--strict]Each site’s built CSS and JavaScript, plain and gzipped, with a bundle named legacy counted apart, against site.yaml’s assets.budgets and the previous build (recorded in .meridian/assets-sizes.json). --strict exits 1 when a budget is exceeded.
engine-dmo assets unused --site key [--bundle name]The CSS selectors in a site’s bundles that match nothing in that site’s templates. A report; it changes nothing.

assets.url(), the plain public/ fallback and engine-dmo assets manifest (each site’s resolved components, blocks, template globs and bundle entries, for a bundler) are the engine’s own and work without the extension.

The whole workflow, from vite.config.js to the production build, is in The frontend build.

engine-dmo check --project .                 # loads the extension; refuses the project if it cannot
engine-dmo serve --dev --project .           # starts Vite beside the engine when the project has a config
engine-dmo build --project .                 # the project's frontend build, then check
engine-dmo assets budget --strict --project . # in CI, after the build

engine-cms behaves the same.

On this page