Skip to content

Latest commit

 

History

History
172 lines (124 loc) · 8.1 KB

File metadata and controls

172 lines (124 loc) · 8.1 KB

@localess/svelte

Svelte 5 integration for Localess. Rendering-only: component registry, editable attributes, Visual Editor sync, rich-text rendering. Does not fetch data — see "SSR with SvelteKit" for how server-side fetching fits in. There is no Vite plugin — register components by passing a components map to localessInit() directly (see "Component Registry" below for why).

Peer dependency: Svelte ^5.0.0.

Installation

npm install @localess/svelte svelte

CSR Quick Start

localessInit() must run synchronously during a component's initialization (Svelte's setContext constraint) — call it at the top of a root +layout.svelte's <script>, not inside onMount:

<!-- +layout.svelte -->
<script lang="ts">
  import { localessInit } from '@localess/svelte';
  import type { Snippet } from 'svelte';

  let { children }: { children: Snippet } = $props();

  localessInit({
    origin: import.meta.env.VITE_LOCALESS_ORIGIN,
    spaceId: import.meta.env.VITE_LOCALESS_SPACE_ID,
    token: import.meta.env.VITE_LOCALESS_TOKEN, // public token only
    components: { page: Page, button: Button },
    enableSync: true,
  });
</script>

{@render children()}
<script lang="ts">
  import { getLocaless, LocalessComponent } from '@localess/svelte';

  const content = await getLocaless().getContentBySlug('home');
</script>

<LocalessComponent data={content.data} assets={content.assets} links={content.links} references={content.references} />

LocalessDocument — static renderer + live sync

Wraps LocalessComponent and subscribes to Visual Editor input/change events automatically when enableSync is active, updating the rendered content in place. Does not fetch content — pass the full Content object (e.g. from getLocaless().getContentBySlug(...) or a SvelteKit load() function) as the document prop.

<script lang="ts">
  import { getLocaless, LocalessDocument } from '@localess/svelte';

  const content = await getLocaless().getContentBySlug('home');
</script>

<LocalessDocument document={content} />

Prefer LocalessDocument over LocalessComponent whenever the rendered content should update live inside the Visual Editor iframe; use LocalessComponent directly for nested blocks within an already-synced tree.

Component Registry

Pass components: { schemaKey: Component } to localessInit({...}) directly:

<script lang="ts">
  import { localessInit } from '@localess/svelte';
  import Page from './lib/components/localess/Page.svelte';
  import Button from './lib/components/localess/Button.svelte';

  localessInit({ origin, spaceId, token, components: { page: Page, button: Button } });
</script>

There's no Vite plugin auto-discovering these from a folder. That was tried and removed: localessInit()'s setContext call only works when it runs synchronously during a component's own initialization, and a Vite virtual module's top-level code always finishes evaluating before the importing component's function body runs — so a plugin-generated module can never safely drive it.

Editable Attributes

<LocalessComponent data={blok} />                <!-- applies data-ll-id/data-ll-schema automatically -->
<section use:localessEditable={blok}>...</section> <!-- manual application -->
<h1 {...localessEditableField<Page>('title')}>{blok.title}</h1> <!-- field-level, spread onto the element -->

Visual Editor Sync

enableSync: true (in localessInit({...})) injects the sync script and activates the localessSync store. It's a no-op outside the Localess Visual Editor iframe.

<script lang="ts">
  import { localessSync } from '@localess/svelte';

  const latest = localessSync(['input', 'change']);
</script>

<p>{$latest?.data}</p>

Rich Text Rendering

Built on @localess/richtext (see docs/richtext.md) — no TipTap at runtime, reactive via $derived (updates on Visual Editor live-sync):

<script lang="ts">
  import { LocalessRichText, type LocalessSchemaProps } from '@localess/svelte';
  import type { Article } from '../shared/models/localess'; // your content types — `body` is a `ContentRichText` field

  let { data }: LocalessSchemaProps<Article> = $props();
</script>

<LocalessRichText content={data.body} />

The content prop is a LocalessRichTextInput (a ContentRichText field value, a rich text document/node/node array, or null/undefined); output is an HTML string rendered via {@html}. String-based per-node overrides: <LocalessRichText content={data.body} renderers={{ paragraph: ({ children }) =>

${children}
}} />. A custom link renderer receives an already-sanitized attrs.href, but a string renderer must still escape what it interpolates: use escapeAttr for attribute values and escapeHtml for text (both re-exported, with sanitizeUrl).

SSR with SvelteKit

@localess/svelte is rendering-only — SSR data-fetching goes through SvelteKit's own +page.server.ts load() convention, calling localessClient (re-exported from @localess/svelte — never import @localess/client directly) with a secret token:

// src/routes/[...slug]/+page.server.ts
import { localessClient } from '@localess/svelte';
import { LOCALESS_ORIGIN, LOCALESS_SPACE_ID, LOCALESS_TOKEN } from '$env/static/private';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const client = localessClient({
    origin: LOCALESS_ORIGIN,
    spaceId: LOCALESS_SPACE_ID,
    token: LOCALESS_TOKEN, // secret, server-only
  });
  return { content: await client.getContentBySlug(params.slug || 'home') };
};
<!-- src/routes/[...slug]/+page.svelte -->
<script lang="ts">
  import { LocalessDocument } from '@localess/svelte';
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<LocalessDocument document={data.content} />

SvelteKit's own data-prop serialization hydrates the server-fetched result to the client — @localess/svelte needs no hydration mechanism of its own. Call localessInit() in the root +layout.svelte with a public token only if you also want Visual Editor sync on top, then LocalessDocument picks up live input/change events automatically; use LocalessComponent instead if you don't need sync.

API Reference

Export Kind Description
localessInit(options) Function Initializes the client + component registry, sets Svelte context
getLocaless() Function Returns the client from context
localessClient(options) Function Raw client factory, re-exported from @localess/client, for standalone SSR data-loading outside localessInit
LocalessComponent Component Dynamic schema-to-component renderer
LocalessDocument Component Wraps LocalessComponent and re-renders on Visual Editor sync events
localessEditable Action Applies data-ll-id/data-ll-schema
localessEditableField(name) Function Applies data-ll-field, spread onto an element
localessSync(event) Function Visual Editor bridge event subscription, returns a Readable
LocalessRichText Component Renders a rich text field (content, renderers? props), reactive via $derived
escapeHtml, escapeAttr, sanitizeUrl Functions Escaping and link-href allowlist for custom string renderers, re-exported from @localess/richtext
LocalessApiError Class Re-exported from @localess/client
LocalessComponentProps, LocalessDocumentProps, LocalessSchemaProps, LocalessSvelteInitOptions Types Local prop/option types
LocalessClient, LocalessClientOptions, EventToAppOf, EventToAppType Types Re-exported from @localess/client
Content, ContentData, ContentDataSchema, Assets, Links, References Types Re-exported from @localess/model
LocalessRichTextDocument, LocalessRichTextInput, LocalessRichTextMark, LocalessRichTextNode Types Re-exported from @localess/richtext

See packages/svelte/SKILL.md for the full usage guide (also shipped inside the npm package).