SvelteKit
XenolithGraph ships a Svelte adapter (@xenolithengine/graph-svelte) and a framework-agnostic Web Component (@xenolithengine/graph-wc). Both run only in the browser, so SvelteKit needs to be told to skip them on the server.
Install
pnpm add @xenolithengine/graph-editor @xenolithengine/graph-svelte pixi.js# or, for the framework-agnostic web-component path:pnpm add @xenolithengine/graph-editor @xenolithengine/graph-wc pixi.jspixi.js is a peer dependency.
Pattern 1 — Svelte adapter (recommended)
<script lang="ts"> import { browser } from '$app/environment' import { onMount } from 'svelte'
let Editor: any = null onMount(async () => { if (browser) Editor = (await import('$lib/Editor.svelte')).default })</script>
{#if Editor} <svelte:component this={Editor} />{:else} <div style="height:100vh;background:#121212;"></div>{/if}<script lang="ts"> import { xenolith } from '@xenolithengine/graph-svelte' import { myGraph } from '$lib/data'
const onNodeClick = (e: CustomEvent) => console.log('clicked', e.detail.nodeId)</script>
<div use:xenolith={{ graph: myGraph, fitOnLoad: true, minimap: true }} on:node-click={onNodeClick} style="height:100vh"></div>The Svelte adapter is an action (use:xenolith={props}) rather than a component. Pass the graph + options as props; the action re-dispatches every editor event off the host element as a kebab-named CustomEvent (node-click, edge-connected, selection-changed, …). For imperative setup that needs the editor instance (registering schemas, custom plugins), use pattern 2 (Web Component) and listen for its ready event.
Pattern 2 — Web Component (framework-neutral)
<script lang="ts"> import { browser } from '$app/environment' import { onMount } from 'svelte' import { myGraph } from '$lib/data'
let el: HTMLElement onMount(async () => { if (!browser) return const { register } = await import('@xenolithengine/graph-wc') register() // defines <xenolith-graph> // The WC takes `graph` via the JS property — set it after the element is in the DOM and the // editor mounts itself + loads it. For imperative work (registering schemas, plugins) poll // `el.editor` until it's non-null. ;(el as any).graph = myGraph ;(el as any).setAttribute('fit-on-load', '') })</script>
<xenolith-graph bind:this={el} class="xeno" minimap style="display:block;height:100vh"></xenolith-graph>Disable SSR for editor routes
If a route ONLY renders the editor:
export const ssr = falseUse this when the route’s only purpose is the editor — keep SSR enabled for content-heavy routes.
Why no SSR mode
The renderer needs WebGL + a real <canvas>. There’s no static-HTML output mode. If you need a server-rendered preview of a graph (a thumbnail in a list, an OG card), call editor.exportImage() on the client and upload the PNG.
Vite config — dedupe PIXI
If you import PIXI from multiple places, keep a single physical copy:
export default defineConfig({ resolve: { dedupe: ['pixi.js'] }, optimizeDeps: { include: ['pixi.js'] },})PIXI’s extension registry throws “Extension type X already has a handler” if you load it twice — same trap we hit in our own monorepo.