Skip to content

@svebcomponents/ssr Changelog

0.8.4

Patch Changes

  • 5da5a4c: Inject imports after the whole <script> tag rather than at its first >. A generics attribute may contain a > of its own (generics="TData = DefaultDataPoint<'bar'>"), and the injection landed inside the attribute value, breaking the component with CompileError: Expected token >.
  • Updated dependencies [5da5a4c]
    • @svebcomponents/utils@0.3.1

0.8.3

Patch Changes

  • 42bc693: Strip TypeScript syntax from .svelte.ts rune modules with Oxc before compiling them with Svelte.

0.8.2

Patch Changes

  • 3252405: Recover server-rendered content when the parser refuses a declarative shadow root

    The HTML parser only honours <template shadowrootmode> while the host has no shadow root. When an element’s definition has already loaded, the element upgrades on its start tag and svelte’s constructor calls attachShadow before the template is read, so the parser refuses it and leaves it in the light DOM. The component then threw its server-rendered content away and re-rendered from scratch, and the leftover template stayed behind as a stray light-DOM child — which React reports as a hydration mismatch, discarding the subtree.

    This ordering occurs whenever a host parses serialized markup after the element definition has loaded, including content streamed into a Suspense boundary. Such elements now adopt the matching stranded template themselves, so server-rendered hydration works there as it already did on first paint.

  • 8843f1d: Let bundlers drop the server renderer from client bundles

    Neither package declared a sideEffects field, so a bundler had to assume every module was impure and keep the whole graph — including @lit-labs/ssr and parse5, which cannot run in a browser and were shipping to it.

    @svebcomponents/ssr now lists the three modules that genuinely touch globalThis, and @svebcomponents/ssr-react declares itself free of side effects. In the repository’s Next e2e app this cut the client chunks from 880 kB to 692 kB with no change to any output or API. Host adapters other than React benefit from the same declaration.

0.8.1

Patch Changes

  • 81bc599: Deliver camelCase props to client-rendered custom elements, instead of silently dropping them.

    A host that passed <my-element showRoot threadData={tree}> got both props on a server render but neither on a client render, so the same page rendered differently depending on how a visitor arrived — a SvelteKit client navigation back to the page would quietly lose them, and a reload would bring them back.

    The client wrapper renders the element as <svelte:element this={tag} {...props}>. Svelte compiles that spread to set_attributes, which runs every key through normalize_attribute — whose first step is name.toLowerCase() — for elements in the HTML namespace. That is right for real HTML elements, whose attribute names are case-insensitive, but a custom element’s props are case-sensitive: showRoot arrived as the attribute showroot, matching neither the observed attribute show-root nor the element’s showRoot setter, and threadData arrived as threaddata="[object Object]". <svelte:element> cannot avoid that path — its tag name is only known at runtime, so Svelte compiles even statically-named attributes on it to the spread path rather than to the case-preserving set_custom_element_data it uses for a compile-time-known custom element.

    The wrapper now assigns any prop whose name contains an uppercase letter as a JavaScript property, and spreads the rest as before. That matches what the server wrapper already did (startRender routes every non-kebab-case key to setProperty), so a server-rendered element and a client-rendered one finally receive the same props. Props that do have an attribute representation — class, style, id, slot, part, data-*, aria-*, on* handlers and every kebab-case attribute — keep travelling as attributes, so Svelte’s class/style handling, event delegation and hydration are unaffected.

    The underlying inconsistency is Svelte’s own — two code paths disagree about whether a custom element’s prop names are case-sensitive — and is tracked upstream as sveltejs/svelte#16590. The workaround is marked Svelte 6 TODO (#8) so it can be removed if that ever lands.

  • 7201911: Stop a malformed typed attribute from silently killing a component, and tighten two smaller gaps around prop delivery.

    Malformed typed attributes. An Object/Array typed attribute carrying invalid JSON threw out of Svelte’s own attribute-to-prop conversion. When such an attribute was present in the server markup, the throw escaped the element’s async connectedCallback as an unhandled rejection, leaving the component permanently inert: its server-rendered content stayed on screen and looked correct, but it never hydrated, never mounted, ignored later attribute writes and dispatched no events — with no visible error to explain why. On a write after hydration the same throw escaped a custom element reaction as a page-level error and skipped the update.

    Both paths now skip the unparseable value and warn in dev. The guard is deliberately narrow: an error that is not Svelte’s JSON conversion keeps propagating, so a component’s own failure is never hidden behind a silent no-op. This is really a gap in Svelte’s generated code, so it is marked Svelte 6 TODO (#8).

    Forged transport scripts. hydratable located the server’s serialized rich-prop payload with a querySelector, which returns the first match. The server appends its payload as the last child of the shadow root, so anything matching earlier is page-controlled markup — component content rendered through {@html ...}, for instance — and could impersonate the payload and override server-serialized props on upgrade. The last match is now the authoritative one, and every match is removed so no stale or forged payload lingers in the DOM. Defense in depth rather than a fix for a reachable vulnerability: putting untrusted markup through {@html} is the more serious problem in any scenario where this mattered.

    Prototype-confusing prop names. startRender and the renderer’s setProperty assigned incoming prop names straight onto an object, so a wrapper forwarding a raw prop bag containing __proto__ would have reparented that object — set its [[Prototype]] — instead of storing a prop, and constructor/prototype would have shadowed those names. The blast radius was one element’s props bag; Object.prototype and other objects were never affected, so this is prop injection rather than prototype pollution, and it needed a host to spread an attacker-controlled parsed object as props. Those names are now rejected on the way in, and HydrationHost.setProps skips them when merging. (The two hydratable loops that write to $$d were already covered by their !(name in this.$$d) guard, which is true for every name on Object.prototype.)

0.8.0

Minor Changes

  • 2e0aa3e: Automatically enable Svelte’s async server runtime from generated /ssr entries when the component package uses compilerOptions.experimental.async, and remove the @svebcomponents/ssr/enable-async entry point that hosts used to import by hand.

    Svelte gates async SSR behind a process-wide flag that a component package’s own server bundle cannot flip — it carries its own copy of Svelte, while render() runs on the copy @svebcomponents/ssr imports. Every non-Svelte host therefore had to know about the flag and import it in the right place, in the right order, or async components threw await_invalid at render time. The component package already declares whether it needs the flag; its generated renderer entry now calls the new enableAsyncMode() from @svebcomponents/ssr itself.

    If you import @svebcomponents/ssr/enable-async today, delete the import — a rebuilt component package enables the mode on its own. A hand-written renderer entry can await enableAsyncMode() from @svebcomponents/ssr instead.

0.7.0

Minor Changes

  • c45de74: Remove the runtime-sharing browser and server targets selected by svelte export conditions and the svelteOutDir and ssrSvelteOutDir configuration options. Each generated component now uses one standalone browser target and one server target.

    Remove the externalSvelte option from the @svebcomponents/ssr/tsdown configuration helpers. The helpers now produce the single server build used by @svebcomponents/build.

    Remove runtime svelte conditions from component package exports. The type-only ./svelte subpath for *.svelte-types.d.ts remains supported, as do the raw .svelte conditions published by @svebcomponents/ssr.

0.6.0

Minor Changes

  • 039d2ed: Depend on tsdown instead of asking consumers to install it.

    @svebcomponents/build runs tsdown — svebcomponents imports build() and calls it. A peer dependency says the opposite: that the host provides it. Every consumer therefore had to install a build tool they never invoke, and pick a version.

    That is also what produced a whole class of silent breakage. The declared range was >=0.15.0 while the code depended on deps.alwaysBundle, which tsdown introduced in 0.21.0. Package managers that auto-install peers resolve the bottom of a range, so consumers got 0.15.x — which accepts the config, ignores the option, and externalizes everything the build meant to inline. dist/client then shipped bare specifiers no browser could resolve, and the build reported success throughout.

    tsdown is now an ordinary dependency, pinned to the version this repository builds and tests against. There is no range for a consumer to get wrong, and nothing to install.

    @svebcomponents/ssr keeps tsdown and rolldown as optional peers — it only imports their types, and @svebcomponents/build supplies them.

    Migration

    Remove tsdown from your package if you added it only for this:

    "devDependencies": { "tsdown": "^0.22.0" }

    Keep it if you use tsdown directly for something else.

0.5.0

Minor Changes

  • 86e6596: Resolve a component’s custom element tag from a direct .svelte entry, and stop asking tsdown for declarations it cannot produce.

    svebcomponentsSsr reads the declared tag so the generated SSR renderer can self-register with ElementRendererRegistry instead of making the consuming app do it by hand. It previously found that tag by reading the entry as a script module and following its first relative .svelte import — the shape a svebcomponent entry had when it was a .ts file that re-exported a component. Entries are now the component itself, so the tag is read from the entry directly.

    Declaration generation is also disabled for .svelte entries. tsdown cannot emit declarations for a raw Svelte entry; @svebcomponents/build writes the component’s public declaration from analyzer metadata instead.

    Breaking

    Calling svebcomponentsSsr directly with a .ts/.js entry no longer resolves a tag, so the generated renderer will not self-register. This is a silent change in behaviour — SSR stops producing markup for the element rather than failing the build — because tag resolution is deliberately best-effort and falls open.

    Point the entry at the .svelte component:

    svebcomponentsSsr({ entry: "src/index.ts", outDir: "dist/server" });
    svebcomponentsSsr({ entry: "src/index.svelte", outDir: "dist/server" });

    If the entry has to stay a script module, register the renderer yourself:

    import { ElementRendererRegistry } from "@svebcomponents/ssr";
    ElementRendererRegistry.set("my-element", MyElementRenderer);

    The internal findSvelteImportPath helper is gone. It was never reachable through the package’s exports, so this affects no supported import path.

Patch Changes

  • e7267f8: Documentation pass across the package READMEs ahead of the beta launch.

    • @svebcomponents/ssr, ssr-vue, ssr-react and ssr-astro gained the install command they were missing.
    • @svebcomponents/build’s options table was missing hydratable, ssrEntryFileName and svelteConfig, and did not show how a package with several components composes defineConfig calls.
    • @svebcomponents/ssr’s package-author example used import without types where every other example in the docs uses default with them, and the enable-async opt-in for non-Svelte hosts was undocumented.
    • The three integration READMEs each restated the shared SSR layer’s behaviour — the Lit renderer registry, the server-side svelte requirement, the declarative shadow DOM contract, the definition of an asynchronous component. Each now links to the canonical explanation and keeps only what is specific to its framework.
    • Removed references to internal e2e/* directories, which readers cannot run, and normalised the product name to lowercase svebcomponents.
  • e7267f8: Point the CLI and runtime messages at the documentation’s new URLs.

    The docs site moved its concept pages to paths that match how the sidebar is organised, so the two links printed from package code moved with them:

    • the manifest hint in @svebcomponents/build now points at /guides/build/#element-types--manifest
    • the slotted-component hydration notice in @svebcomponents/ssr now points at /server-rendering/hydration/#limitations

    The old paths are redirected, so messages printed by already-released versions keep resolving.

  • 86e6596: Declare license, description and homepage, and ship the license text in the published tarball.

    Every package was published without a license field and without a license file of its own. npm only includes LICENSE* from the package directory, so the repository’s MIT license never reached consumers and automated license scanners had nothing to read. Each package now carries its own copy of LICENSE.md alongside "license": "MIT".

    description is what npm shows on the package page and in search results, and homepage now points at each package’s reference page on the documentation site.

  • Updated dependencies [e7267f8]

  • Updated dependencies [86e6596]

  • Updated dependencies [86e6596]

  • Updated dependencies [86e6596]

    • @svebcomponents/utils@0.3.0

0.4.0

Minor Changes

  • e858eca: Add @svebcomponents/ssr-react, a React host integration for server-rendering Svelte-built custom elements.

    A CustomElement component drives the element’s registered ElementRenderer on the server and emits declarative shadow DOM; a drop-in JSX runtime routes any dashed tag through it, so no bundler plugin is involved and the integration works outside Vite too.

    Synchronous element renderers only. React’s renderToString cannot await, so an asynchronous renderer degrades to client-only rendering for that element with a one-time warning rather than failing the page.

    @svebcomponents/ssr gains AsyncRendererError, thrown by renderCustomElementSync when the element’s renderer turns out to be asynchronous, so a non-awaiting host can degrade deliberately while genuine render errors still propagate.

    Documented on the site under Core Concepts → Framework Integrations, with a package reference page for the new integration.

  • ab7d1cd: Make the SSR runtime agnostic to how a custom element’s renderer is implemented, and make svebcomponents’ own renderers usable by other SSR pipelines.

    renderCustomElement previously rejected any renderer that was not a SvelteCustomElementRenderer, because it read host attributes through getSsrAttributes() — a svebcomponents-specific method added when the Svelte wrapper switched to <svelte:element {...attributes}> for hydration, which needs a name→value record rather than the serialized string Lit’s renderAttributes() returns.

    The renderer now keeps host attributes on Lit’s ElementRenderer.element (svelte’s generated custom element class is itself an SSR-shim element), so the record comes from element.attributes — the same standard surface @lit-labs/ssr-react reads. The guard and getSsrAttributes() are both gone, and any conforming ElementRenderer can be registered and rendered.

    Compatibility now runs in both directions.

    Generated SSR entry points implement Lit’s static matchesClass hook, so they can be passed straight to @lit-labs/ssr’s render() in elementRenderers with no adapter.

    And ElementRendererRegistry gains use(), which registers a renderer that selects its own elements through matchesClass — ElementRendererRegistry.use(LitElementRenderer) makes every LitElement in the app server-renderable through any svebcomponents host integration. Renderers now also receive a fully-formed Lit RenderInfo rather than a stub, which is what lets a renderer whose shadow content is itself a template (LitElementRenderer calls renderValue(value, renderInfo)) work at all, and lets nested custom elements resolve through the same registry.

    e2e/lit-ssr covers both directions: svebcomponents components rendered through Lit’s pipeline, and a plain Lit element rendered through svebcomponents’.

    Also adds a ./enable-async entry point. Svelte gates async SSR behind a module-global flag normally flipped by a Svelte app compiled with experimental.async; a non-Svelte host has to opt in explicitly or async components throw await_invalid.

    Breaking for anyone who called getSsrAttributes() or isSvelteCustomElementRenderer() directly; both are removed. Neither was part of a documented workflow.

  • 567aef3: Add @svebcomponents/ssr-vue, a Vue host integration for server-rendering Svelte-built custom elements.

    A Vite plugin rewrites custom element tags in SFC templates to a wrapper component, which drives the element’s registered ElementRenderer on the server and emits declarative shadow DOM. Vue’s renderToString awaits async setup(), so async element renderers work through the same wrapper as synchronous ones — no sync/async split.

    Documented on the site under Core Concepts → Framework Integrations, with a package reference page for the new integration.

    Supporting changes:

    • @svebcomponents/ssr gains shadowContent on RenderedCustomElement, for hosts that build the <template> element themselves rather than emitting raw markup.
    • The custom-element tag-name predicates move to @svebcomponents/utils so host integrations can share the detection without importing the Svelte-bound SSR runtime. @svebcomponents/ssr re-exports them from their previous path.
  • 0d1077f: Extract a host-framework-neutral renderCustomElement / renderCustomElementSync from the Svelte SSR wrappers.

    Both wrappers previously carried an identical copy of the “look up the renderer, apply props, collect declarative shadow DOM” logic, differing only in whether they collected the shadow result synchronously. That logic now lives in runtime/renderCustomElement.ts and is exported from the package root, so host integrations for other frameworks can reuse it.

    No behavior change for Svelte hosts.

Patch Changes

  • Updated dependencies [567aef3]
    • @svebcomponents/utils@0.2.0

0.3.3

Patch Changes

  • 117c5ba: Update the SSR runtime for Lit SSR 4’s thunked ElementRenderer contract.

0.3.2

Patch Changes

  • 1ca557a: Update the build integrations for tsdown 0.22 while preserving the existing .js and .d.ts output contract.

0.3.1

Patch Changes

  • 821e5df: SSR wrappers now recognize SvelteCustomElementRenderer subclasses across separate bundled and external module instances. A consuming app can leave a normally compiled svebcomponent external without the wrapper’s nominal instanceof check rejecting its registered renderer, so the component package no longer needs its own ssr.noExternal entry.

0.3.0

Minor Changes

  • fe3e191: - The generated SSR renderer entry now self-registers with ElementRendererRegistry when the component’s tag name can be determined at build time (read from its defineElement("tag", Component) call, which every component entry point already makes). Consuming apps no longer need to import ElementRendererRegistry and call .set() by hand — a bare import "my-component-package/ssr" is enough. Falls back to today’s manual registration when the tag can’t be determined statically (e.g. a dynamically computed tag).
    • Components with an SSR build now get a runtime-guarded shim install (if (typeof window === "undefined") { await import("@svebcomponents/ssr/shim"); }) prepended ahead of all bundled client code, so a custom element’s compiled class can never evaluate before the shim installs — regardless of which import path reaches it first (a generated SSR entry’s controlled dynamic import, or a consuming app’s own static import needed for browser registration, which frameworks like SvelteKit compile into the server bundle too). Scoped to SSR-enabled components only: referencing the optional @svebcomponents/ssr peer at all, even behind a runtime check, makes dev-server tooling (Vite’s import analysis) try to resolve it — so browser-only components never get this guard and are unaffected.
  • 6a8034f: @svebcomponents/ssr/vite now automatically adds @svebcomponents/ssr to Vite’s ssr.noExternal — it ships raw .svelte files under some export conditions, which Node’s SSR externalization can’t load directly, so consumers previously had to configure this by hand. A new noExternal plugin option lets consumers add their own component package(s) alongside it. The package’s vite peer range now also allows ^8.0.0 (not yet verified against a real Vite 8 install — no Vite 8 release is available to test against at time of writing).

Patch Changes

  • 7164bd3: Server.svelte/AsyncServer.svelte now install the DOM shim themselves as their first import, instead of depending on the consuming app happening to import @svebcomponents/ssr before anything else renders. These wrapper components — the ones Vite’s dev-time transform and the generated production SSR entry actually load on every custom-element render — previously imported runtime modules directly and never triggered the package’s own shim-install side effect, relying entirely on the consuming app’s own import order. installShim’s effects are idempotent, so this is safe to run alongside any existing manual shim import too.

  • fe3e191: Components can now declare their custom element tag with Svelte’s own string-shorthand syntax, and never need a manual registration call:

    <svelte:options customElement="my-component" />

    @svebcomponents/auto-options expands this into the object form, merging in the inferred props (previously this form was rejected outright — <svelte:options customElement="tag-name"/> bailed with a warning and skipped prop inference entirely). The object form (customElement={{ tag: "..." }}) is unaffected.

    @svebcomponents/build’s browser build now guards Svelte’s own auto-generated customElements.define(...) call against being run more than once — the actual reason component entrypoints previously had to hand-write a guarded registration via @svebcomponents/utils’s defineElement. That’s no longer necessary: a package entrypoint can simply re-export its component, with no registration call at all. defineElement remains available as a manual escape hatch for tags that can’t be a literal in <svelte:options> (e.g. computed at build time).

    @svebcomponents/ssr’s generated SSR entry now reads a component’s tag from its <svelte:options customElement> declaration (via svelte/compiler’s normalized parse() output, which resolves both syntax forms identically) instead of regexing a defineElement(...) call out of the entry file — no behavior change for consumers, just a more direct source now that the tag no longer needs to live in a separate manual call.

0.2.0

Minor Changes

  • bb1ca02: Add automatically discovered, server-only entry.ssr.ts preparation hooks for setting component properties before SSR and serializing the results for hydration.

0.1.0

Minor Changes

  • c2f1b6c: Auto-detect the async SSR wrapper from the host app’s svelte config.

    svebcomponents({ async: true }) duplicated a fact the build already knows: an app compiled with compilerOptions.experimental.async needs the async wrapper, and an app without it cannot compile the async wrapper. Keeping the two in sync manually was an easy way to produce wrapper-mismatch hydration bugs.

    The vite plugin now reads experimental.async from vite-plugin-svelte’s resolved options and picks the wrapper variant automatically. The explicit async option remains as an override.

  • c2f1b6c: Support Svelte’s $host() rune in hydratable components.

    Previously $host() was unusable in svebcomponents components: the SSR build compiles with customElement: false, where Svelte hard-errors on $host() (host_invalid_placement), and even on the client the hydrated path never supplied a host — Svelte compiles $host() to $props.$host, which only Svelte’s own fresh-mount custom-element wrapper passed. Component authors had to fall back to dispatching composed: true events from an inner node and hoping they retarget.

    Now:

    • Server: the SSR source transform replaces $host() calls with undefined — behavior-identical to Svelte’s own server transform ($host() → void 0). Components using $host() compile for SSR whether or not they declare <svelte:options customElement>.
    • Client: the hydration host passes the custom element through as $host, so $host() returns the upgraded element after hydration — exactly as it does in Svelte’s fresh-mount path.

    $host() returns undefined during SSR (there is no host element), so guard uses that can run server-side ($host()?.dispatchEvent(...)) — event handlers, onMount, and $effect bodies never run during SSR and need no guard.

    Note for editor tooling: Svelte’s language server still reports host_invalid_placement unless the component declares <svelte:options customElement> in source. Declaring it is safe — the SSR build strips it (and since the reflect-defaults change, a bare customElement="tag-name" needs no prop overrides).

  • c2f1b6c: Hydratable custom elements: server-rendered declarative shadow DOM is now hydrated instead of being wiped and re-rendered when the element upgrades.

    Previously, svelte’s generated custom element always called attachShadow (clearing the declarative shadow root per spec) and then mounted the component from scratch — losing the server-rendered DOM, transient state, and re-creating every node. Now, @svebcomponents/build compiles components as hydratable by default:

    • @svebcomponents/auto-options injects svelte’s official customElement.extend hook wired to the new hydratable wrapper from @svebcomponents/ssr/hydration.
    • The wrapper claims the declarative shadow root before svelte can clear it and hydrates it via svelte’s public hydrate() API — the server-rendered nodes are adopted in place, styles are deduped by svelte itself, and the component is fully reactive afterwards.
    • On the server, the generated SSR entry renders through a HydrationHost component (also used on the client) so the markup structure matches by construction.
    • Anything non-hydratable — no declarative shadow root, slotted components, reconnection after teardown — falls back to svelte’s untouched mount path, and svelte’s own hydration mismatch recovery re-mounts, so a failed hydration degrades to exactly the previous behavior.

    Opt out per package with defineConfig({ hydratable: false }) (or per component by declaring your own extend). Client custom-element bundles are now built with platform: "browser", so browser export conditions resolve correctly.

    Known limitations (fall back to mount): components with slots (expected to become hydratable with Svelte 6, when slots are no longer compiled through the legacy transformation — a dev-mode console.info makes the fallback visible); legacy createEventDispatcher events on hydrated elements (native $host() events are unaffected); component exports are not exposed on hydrated hosts. See the new Hydration docs for details.

Patch Changes

  • 8bceff0: Fix a race that could corrupt build output when several components share an output directory (e.g. multiple components inferred from package.json exports writing to dist/client): component configs are built in parallel and tsdown’s default per-build clean deleted sibling builds’ output. The config factories now set clean: false and the svebcomponents CLI cleans each distinct output directory once before building.

  • 8bceff0: Fix SSR breakage with svelte >= 5.36 and a chunk-ordering crash in production SvelteKit builds:

    • SvelteCustomElementRenderer.renderShadow now recognizes svelte’s lazily-evaluated RenderOutput (always thenable since svelte 5.36) and renders synchronous components through its sync head/body getters. This un-breaks the sync wrapper (collectResultSync previously threw Promises not supported in collectResultSync for every component); only genuinely asynchronous components now require the async wrapper.
    • The generated SSR entry (dist/server/ssr.js) now installs the DOM shim via the new @svebcomponents/ssr/shim subpath export before loading the client custom-element bundle, and loads that bundle with a dynamic import. Previously, bundlers that code-split (e.g. rollup in a SvelteKit adapter-node build) could hoist the client bundle into a shared chunk that evaluated before the shim installed, crashing at startup with Class extends value undefined is not a constructor or null (svelte’s SvelteElement captures HTMLElement at module-evaluation time). Dev mode was unaffected, which made the crash easy to miss.
  • c2f1b6c: Fix a flash of unstyled content (FOUC) when a component declares an explicit <svelte:options customElement={{ ... }}>.

    The SSR build compiles components with customElement: false (it renders the shadow content, not a custom element). But when the source declares <svelte:options customElement>, Svelte treats the component’s <style> as belonging to a custom element’s shadow root and drops it entirely from the server render — the compiler emits no css.add, so the server output carries scoped class names with no <style>. The result is server-rendered shadow DOM that stays unstyled until the client bundle injects the styles at hydration.

    A new build step (pluginStripCustomElementOptions) removes the customElement option from <svelte:options> before the server compile, so the CSS is emitted and placed inside the declarative shadow root — styled at first paint, no flash. The client build keeps customElement (where the element is defined), so only SSR output changes. Components without an explicit <svelte:options customElement> (the common case, where auto-options injects it into the client build only) were already unaffected.

  • c2f1b6c: Declare vite, tsdown, and rolldown as optional peer dependencies of @svebcomponents/ssr.

    The ./vite and ./tsdown entries type against these packages, but they were only devDependencies — under pnpm’s isolated layout a consumer’s TypeScript resolves the emitted declarations against a different installation than the consumer’s own, so the plugin’s Plugin type never unifies with the consumer’s PluginOption and every consumer needs an as unknown as PluginOption cast. Declaring them as optional peers makes the package resolve the consumer’s copies, so the types unify. Optional because the runtime entries (., /shim, /hydration) need none of them.

    @svebcomponents/build: createTsdownConfig now has an explicit Options return type — the inferred type referenced rollup’s plugin types through non-portable .pnpm paths (TS2742) in the emitted declarations.

  • Updated dependencies [c2f1b6c]

    • @svebcomponents/utils@0.1.0

0.0.8

Patch Changes

  • 257e5b0: Load package Svelte config during builds and support async SSR when that config enables Svelte’s experimental async compiler mode. Host apps can opt into the async Vite wrapper for Svelte async SSR.

  • 257e5b0: Share the Svelte build config helpers (SvelteBuildConfig, mergeCompilerOptions) from a single home in @svebcomponents/ssr via a new @svebcomponents/ssr/svelte-config export, instead of duplicating them in @svebcomponents/build. This removes the risk of the two copies drifting apart.

  • f02d6ee: document DOM-shim import-order requirement

  • 742c433: Fix installShim unconditionally overwriting existing Element, HTMLElement, and customElements globals, which silently dropped custom elements already registered by another DOM shim or jsdom-based test setup.

  • 2c2510b: Generate the SSR renderer entry filename from the declared package export instead of hardcoding ssr.js.

    Previously every SSR build wrote <ssrOutDir>/ssr.js regardless of the declared export. This meant the multi-component setup documented in the build README (e.g. "./button/ssr": "./dist/server/button-ssr.js") produced a dangling export, and two SSR components sharing an output directory overwrote each other’s generated entry.

    inferComponents now derives the entry basename from the declared ssr export path, and a new ssrEntryFileName option on defineConfig (defaulting to "ssr") threads it through svebcomponentsSsr into pluginGenerateSsrEntry. Single-component behavior is unchanged.

  • 724f00a: Declare supported Node versions (engines.node: ">=20.19.0") so consumers get a clear error instead of an opaque runtime failure on unsupported Node versions.

  • 303541d: Fix ElementRendererRegistry.has() to correctly walk the element prototype chain, matching the behavior of get().

  • 0d74921: Clean up SvelteCustomElementRenderer.setAttribute by removing the dead non-string branch (all callers honor the string contract; non-string values go through setProperty), and add a removeAttribute method that deletes the attribute from the internal SSR attribute map and notifies the client element via attributeChangedCallback(name, oldValue, null), mirroring browser semantics.

  • 94530d0: Pass the resolved tag name through generated SSR renderer entries so the base ElementRenderer receives a defined tagName.

  • 4ca91b2: Fix the Vite transform mistakenly wrapping spec-reserved SVG/MathML tag names (font-face, font-face-src, font-face-uri, font-face-format, font-face-name, annotation-xml, color-profile, missing-glyph) as custom elements just because they contain a dash. These names are explicitly excluded from valid custom element names by the HTML spec, and wrapping them corrupted otherwise-valid SVG/MathML markup.

  • 8913436: Drop test and build tooling from runtime dependencies: vitest, rolldown, typescript, and tslib are no longer installed when consuming @svebcomponents/ssr, and typescript/tslib are no longer installed when consuming @svebcomponents/build. These were only used for tests, type-only imports, or package builds and are now devDependencies (or removed entirely).

  • d51f92b: Fix the SSR Vite plugin corrupting self-closing custom elements (<my-widget />), which silently dropped the _tagName prop and broke SSR.

  • 2f11d81: Use attr from svelte/internal/server instead of svelte/internal/client in the SSR slot override virtual module, so server bundles no longer directly depend on Svelte’s client-internal entry point. Rendered output is unchanged: both entries re-export the same shared attr implementation.

  • f8970a8: Escape slot names via JSON.stringify in the vite plugin’s slot attribute transform, preventing syntax errors from slot names containing quotes.

  • 5c8d636: Export createElementRendererRegistry factory for creating isolated renderer registry instances (e.g. in tests), alongside the unchanged global ElementRendererRegistry accessor.

  • e4fe34f: Add a types condition to the ./wrapper-component export so TypeScript and svelte-check can resolve the wrapper component’s types in consuming projects.

  • 1e14cc5: Remove leftover debug markers (<p>server</p> / <p>client</p>) from the SSR wrapper components that were leaking into consumer apps’ SSR output and breaking hydration matching.

  • f75af70: Render SSR host attributes for Svelte custom elements, including reflected props, while using Svelte’s SSR attribute serializer for escaped attribute values and boolean attributes.

  • 3a4d68e: docs: make SSR security/limitations statements precise and current

  • Updated dependencies [9be6326]

  • Updated dependencies [724f00a]

  • Updated dependencies [e7e4adf]

  • Updated dependencies [d2094d2]

    • @svebcomponents/utils@0.0.3

0.0.7

Patch Changes

  • b282163: fix: migrate to tsdown to emit types again

0.0.6

Patch Changes

  • 1c5b92f: refactor!: migrate to rolldown

    since the minification logic of rolldown is different than rollup & rolldown is also still in beta, this is a breaking change

  • 1b1aea0: fix: imrpove faulty ctor lookup logic

0.0.5

Patch Changes

  • a1bc248: fix: consider that component children might be undefined

0.0.4

Patch Changes

  • 6fd10e7: fix: add @rollup/plugin-typescript peer deps

0.0.3

Patch Changes

  • 8aa8512: fix: publish private utils dependency
  • Updated dependencies [8aa8512]
    • @svebcomponents/utils@0.0.2

0.0.2

Patch Changes

  • 5cedd02: fix: set dependencies correctly