Plugins
A plugin extends an app at build time (Vite), at request time (middleware), through app hooks (observability/caching), and through the asset pipeline.
// ramonyx.config.ts
import { imageOptimizer } from "@ramonyx/plugin-image";
import { defineConfig } from "ramonyx";
import { analytics } from "./src/plugins/analytics.ts";
export default defineConfig({
plugins: [analytics(), imageOptimizer()],
});ramonyx dev|build|start loads plugins from the config. A custom server entry (src/server.ts)
still receives their hooks and is wrapped by their middleware — plugins are never duplicated.
Writing a plugin
// src/plugins/analytics.ts
import { definePlugin } from "@ramonyx/bundler";
import type { Middleware } from "ramonyx";
const middleware: Middleware = async (context, next) => {
const started = performance.now();
const response = await next();
console.log(context.request.method, new URL(context.request.url).pathname, response.status, Math.round(performance.now() - started));
return response;
};
export const analytics = () =>
definePlugin({
name: "analytics",
middleware: () => middleware,
hooks: () => ({
onLoaderEnd: (event) => {
/* record loader timing */
},
}),
});| Hook | Runs | Typical use |
|---|---|---|
vite |
build/dev | extra Vite plugins (CSS, fonts, aliases) |
middleware |
before routing | logging, headers, early blocking |
hooks |
request lifecycle | timing, metrics, invalidation |
assets |
build, per file | derived outputs (image variants) + dimensions |
middleware must be edge-safe (no node:*) if you deploy the edge artifact. Middleware runs
before application middleware, in plugin order.
Asset hook
export const plugin = definePlugin({
name: "thumbnails",
async assets(_context, asset) {
if (!asset.path.endsWith(".png")) return undefined;
return {
meta: { width: 800, height: 600 },
outputs: [{ path: "img/hero-400.webp", data: encoded, width: 400, format: "webp" }],
};
},
});Derived files are hashed automatically and recorded as variants in the manifest, so
assets.image() and Image can render them.
Publishing a plugin
Third-party plugins live in node_modules, where Node does not strip TypeScript. Ship compiled
JavaScript:
{
"name": "ramonyx-plugin-analytics",
"type": "module",
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
"peerDependencies": { "@ramonyx/bundler": "^0.4.0", "ramonyx": "^0.4.0" },
"engines": { "node": ">=24" }
}- Use
peerDependencies(notdependencies) so types/contracts follow the app's Ramonyx version. - Heavy dependencies should be optional peers (
peerDependenciesMeta) and imported lazily with a clear error — see how@ramonyx/plugin-imagetreatssharp. - Test the hooks directly and in a real app (
ramonyx build|dev|start). - Contract stability: the plugin contract becomes stable after one release cycle without additive changes. Until then it may gain optional hooks (always additive).
Limitations (honest)
- No plugin registry or
ramonyx add— install with your package manager. - No per-route/per-loader plugin hooks (pre-routing middleware + app hooks only).
- Plugin order is config order; there is no explicit priority field.