lunx.docs
DocsPlugins

Plugins Guide

Vite and Rollup plugins, with Vite's lifecycle, in dev and in the build.

Plugin basics

Add plugins to the plugins array in lunx.config.ts, or keep them in an existing vite.config.*, which lunx reads as it is. They run with Vite's lifecycle in dev and in the build: apply, enforce, config, configResolved, resolveId / load / transform (virtual modules included), transformIndexHtml, configureServer, renderChunk, generateBundle, writeBundle and closeBundle. Plugins that import viteget lunx's stand-in, so Vite does not need to be installed. Framework projects (SvelteKit, Nuxt, Astro, React Router…) run on lunx's Vite-compatible plugin host.

Checked in a browser on every release, in dev and in the build: vite-plugin-pwa, @tailwindcss/vite, unocss, vite-plugin-svgr, vite-tsconfig-paths, unplugin-auto-import, unplugin-icons, vite-plugin-html, vite-plugin-static-copy, rollup-plugin-visualizer, @mdx-js/rollup, vite-plugin-glsl, vite-imagetools, vite-plugin-wasm, vite-plugin-node-polyfills, vite-plugin-checker and vite-plugin-inspect. Not supported: @vitejs/plugin-legacy, whose legacy chunks need a SystemJS build that Rolldown does not write.

typescriptlunx.config.ts
import { defineConfig } from 'lunx-dev'

export default defineConfig({
  plugins: [
    {
      name: 'banner',
      transform(code, id) {
        if (id.endsWith('/src/main.ts')) return `console.log('built ${new Date().toISOString()}')\n${code}`
      },
    },
  ],
})

Built in: no plugin needed

  • Framework compilers instead of @vitejs/plugin-react, @vitejs/plugin-vue, @sveltejs/vite-plugin-svelte, vite-plugin-solid and @preact/preset-vite (an existing config that lists them keeps working).
  • SVG as a component instead of vite-plugin-svgr / vite-svg-loader: ?react (React, Preact) or ?component (React, Preact, Vue). The root <svg>'s attributes are props you can override.
  • .wasm?init, as in Vite: const instance = await init(imports).
  • %ENV% in index.html, as in Vite: %VITE_APP_TITLE%, %MODE%, any import.meta.env value.
  • PWA instead of vite-plugin-pwa: pwa: { manifest } in lunx.config writes the manifest and a service worker that precaches the build (see PWA).
  • Type errors in dev instead of vite-plugin-checker: server: { typecheck: true } runs your tsc (or vue-tsc) in watch mode and lists errors in the terminal and in the overlay's Type Errors tab.
  • tsconfig paths instead of vite-tsconfig-paths.
  • Electron instead of vite-plugin-electron (see the Electron page).
  • lunx analyze instead of rollup-plugin-visualizer.
tsxsrc/App.tsx
import Logo from './logo.svg?react'
import init from './add.wasm?init'

const { exports } = await init()

export default function App() {
  return <Logo className="logo" width={32} aria-label={`2 + 3 = ${exports.add(2, 3)}`} />
}
An installed plugin still wins
With vite-plugin-svgr in your config, ?react imports go to it, with its options.

TypeScript types for these (assets, ?raw, ?url, ?react, .wasm?init, CSS modules, import.meta.env, import.meta.hot, import.meta.glob) come with lunx:

typescriptsrc/lunx-env.d.ts
/// <reference types="lunx-dev/client" />

The TypeScript starters have this file already. Or add "types": ["lunx-dev/client"] to tsconfig.json.

Lunx plugin packages

The lunx repository also holds plugins written for lunx (packages/lunx-plugin-*). They are not published to npm yet.

@lunx/plugin-env

Schema-validated environment variables; only LUNX_PUBLIC_ variables reach the browser.

@lunx/plugin-compress

Extra compression settings (lunx build already writes .gz and .br copies).

@lunx/plugin-icons

On-demand icons from Iconify.

@lunx/plugin-image

WebP conversion and responsive srcsets.

@lunx/plugin-mock

Mock API handlers in development.

@lunx/plugin-inspect

Module graph inspector at /__lunx_inspect__.

@lunx/plugin-checker

TypeScript and ESLint diagnostics in the dev server overlay.

@lunx/plugin-legacy

A legacy bundle for older browsers.

@lunx/plugin-auto-import

Auto-imported APIs and components.

Plugin lifecycle hooks

HookWhen it runsUse case
resolveId()Module resolution phaseRedirect imports to virtual modules or alternative paths
load()Module loading phaseLoad module content from a custom source (virtual modules)
transform()Compilation phaseModify source code — strip logs, inject metadata, transpile DSLs
generateBundle()After bundling, pre-writeModify or add output chunks and assets
closeBundle()After files are writtenPost-build tasks — upload assets, send notifications

Plugin sandboxing

Lunx enforces a strict permission sandbox for all plugins. Third-party plugins must declare required permissions in their package.json. Undeclared access is blocked and logged.

jsonplugin/package.json
{
  "name": "my-lunx-plugin",
  "lunx": {
    "permissions": [
      "fs:read",          // read files from the project directory
      "fs:write:dist",    // write files to the output directory only
      "network:fetch"     // make outbound HTTP requests
    ]
  }
}
bash
# Audit permissions of all installed plugins
lunx security plugins

🛡 Plugin audit...
  vite-plugin-svgr        — permissions: none (safe)
  vite-plugin-checker     — permissions: fs:read (expected)
  some-third-party-plugin — permissions: fs:write:/ (!) BLOCKED
  → Remove or review: some-third-party-plugin
Third-party plugin supply chain
Always run lunx security plugins after installing any third-party plugin. A plugin with fs:write:/ access could overwrite arbitrary files outside your project directory.