Repository navigation
release: v4.4.0 — client-side rendering - #78
Open
theetherGit wants to merge 56 commits into
Open
theetherGit wants to merge 56 commits into
theetherGit wants to merge 56 commits into
Conversation
Routes cover png/svg/takumi x default/cog/prerendered, built with @ethercorps/svelte-adapter-universal for node, bun, deno and workers.
ultrahtml rebuilds its pseudo-class regex by string-matching "(?<argument>¶*)" inside another regex's .source. Minifiers that escape non-ASCII (e.g. terser ascii_only emits \xb6) break the match, so :not() and :nth-child() throw. 1.7.0 still has the bug. Match the argument group regardless of escaping: /\(\?<argument>[^)]*\)/. Replaces the 1.6.0 patch, which only covered ¶ and \u00B6.
dev + -next.x -> next, main + clean version -> latest. -next.x no longer publishes from main, and the beta channel is removed.
New `@ethercorps/sveltekit-og/client` export for generating OG images
in the browser or a worker, no server request. Single surface:
`ImageResponse` (extends Response) + `createImage`, with the engine
chosen in config: `{ engine: 'takumi' | 'satori', ...opts }` (default
takumi), typed as a per-engine discriminated union.
Fully self-contained under src/lib/client/ — own browser wasm providers
(satori/yoga + resvg via bundler `?url` + fetch(new URL), same-origin so
no CORS, worker-safe) and own satori/resvg render path that reuses only
the engine-agnostic shared helpers. Server render code stays untouched.
- Svelte components rendered client-side via mount into a detached
shadow root (not svelte/server), isolating page CSS from the image
and the component's styles from the page.
- Playground demo route at /client (ssr=false).
- Free resvg wasm objects after render; clamp satori raster formats to
png so Content-Type matches the bytes; revoke object URLs only once
the replacement is ready.
…yBuffer()/text() in browsers Browsers reject Response.arrayBuffer()/blob()/text() with a bare "TypeError: Failed to fetch" when the body stream errors, dropping the ImageResponseError and its code. Read the stream directly instead, and unwrap the shared builder's UNKNOWN_ERROR wrapper so callers see the inner code (COMPONENT_IN_WORKER, FONT_LOAD_FAILED, ...).
…e-split chunks) Vite's default iife worker format can't code-split, and the client entry lazy-imports its engines, so `vite build` failed for any worker using it. The demo worker also stops importing a .svelte file (the worker sub-build has no Svelte plugin); a non-string stand-in exercises the same guard.
…ts, fix docs Browsers can't set the User-Agent, so Google Fonts serves woff2 and loadGoogleFont rejects it. Satori needs resolved font data, so the docs example now goes through resolveFonts with CustomFont. Docs also state that takumi-js is required with either engine (the takumi chunk is resolved at build time) and that workers need worker.format "es".
sveltekitOG() added unwasm to every build. On the client build it rewrote the /client entry's takumi wasm into something WebAssembly.instantiate can't load. Vite computes env.isSsrBuild before SvelteKit sets build.ssr, so check the merged config and enforce: post to run after sveltekit().
Shared ClientDemo component (@examples/shared/client) renders one PNG in the browser with the default engine; satori-only passes engine="satori". Index pages gain a Client-side section. Examples now use sveltekitOG() instead of a raw rollupWasm in build.rollupOptions so the plugin can stay off the client build. satori-only gains takumi-js: the client entry resolves the takumi chunk at build time regardless of engine.
…/client page layout New shared ClientImage component renders the PNG on mount; the index card shows it as a real thumbnail (same height as the other cards — the old 'open page' placeholder broke the aspect ratio) and /client reuses it. The /client page's dark background moves to body so no white gutters appear, and its header matches the index hero.
Index pages show two live client cards (Takumi default, Satori · resvg); /client picks the engine from ?engine=satori and offers a Takumi/Satori switch. satori-only and takumi-only keep their single card.
feat(client): browser/worker rendering via /client entry
✅ Deploy Preview for sveltekit-og canceled.
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
sveltekit-og | f628d1e | Oct 08 2026, 06:10 AM |
✅ Deploy Preview for sveltekit-og-dev canceled.
|
Requirements (Kit 3, Bun >= 1.4, build must run under Bun), vite.config
with sveltekit({ adapter }) + sveltekitOG({ esmImport: false }), build/run
scripts, and the examples/bun-build self-test. The package-manager
installer tabs gain a bun tab, with a bun add line in every adapter
snippet.
Single-stage node:24 + Bun 1.4.2 image built from the repo root (pnpm workspace install filtered to bun-build), vite build under Bun, bun ./build at runtime. render.yaml deploys it as a free Docker web service from dev. Verified locally: all image routes 200, /client renders with both engines.
Components using css="injected" emit their <style> verbatim into head; a multi-line CSS value (e.g. linear-gradient split across lines) makes satori 0.25's css-gradient-parser throw "object null is not iterable". HTML strings were already newline-stripped; the component body now gets the same, and the injected head has its whitespace collapsed.
Renders OG images in the browser with @ethercorps/sveltekit-og/client: five templates (docs, blog post, release, profile, Svelte component), engine/format/size/quality controls, live preview with size and timing, download, and a copy-ready createImage(...) snippet. Built on the docs' own svecodocs tokens and components; responsive from 320 px up. Bumps the docs' package dependency to 4.4.0-next.1 (first version with the /client entry) and adds a Playground anchor to the sidebar.
…lighting Pick a template (live thumbnails) → Tune it → Preview (sticky on wide screens, in sequence on narrow) → Take the code. The createImage snippet is highlighted with @twinkleplop/typescript and the GitHub theme, which follows the docs' .dark toggle. Thumbnails render once at the template's native 1200×630 and scale down.
Stage 02 is now the editor: a transparent textarea over a twinkleplop HTML paint layer with matching metrics and scroll sync, Tab inserts two spaces, Reset to template. The preview re-renders as you type. The component template shows Card.svelte's markup read-only beside its props. Output controls move to the preview stage.
Toolbar (template picker popover, engine/format segments, size, quality), editor beside the live render, collapsible code drawer; Edit/Preview/Code tabs below lg. Cmd/Ctrl+Enter renders immediately. HtmlEditor gains a fill mode so it can stretch inside a pane.
…yout Preview pane gets an engine / browser / both toggle. The browser view lays the same markup out in a sandboxed iframe (or mounts Card.svelte) at the OG size and scales it to fit; frames size from the pane height so two stack without cropping.
… side paneforge splits editor | preview horizontally and the preview into engine / browser shells vertically; every divider drags (keyboard too), layout persists via autoSaveId. Each render gets its own header with its own facts. Below lg the shells become Edit / engine / Browser / Code tabs.
satori-html emits children: [] for every empty element and satori treats any array of children on a non-flex <div> as more than one child, so an empty decorative <div> (a dot, a rule) always failed with SATORI_RENDER_FAILED. Drop empty children arrays in createVNode; covers the server and client paths.
…template - Format pretty-prints the markup: one element per line, long style attributes split into one declaration per line (the engines strip newlines and trim, so the render is unchanged). - Tailwind template using the tw attribute, rendered by both engines; the browser shell maps tw to class and loads the Tailwind browser build for it (sandbox allow-scripts, opaque origin). - Docs template's dot <div> gets display:flex so it renders on Satori with the published package too (the real fix is in createVNode).
Each HTML template now ships three variants: satori (flex only, the subset both engines share), takumi (CSS grid, box-shadow, gradients — Satori rejects grid, and the playground says so), tailwind (tw attribute, both engines). A Variant control sits next to the template; picking takumi follows with the engine. The standalone Tailwind template folds into the variants.
…ngine The three-way satori / takumi / tailwind variant read like a second engine picker. Now the toolbar has Engine and Styling; the vanilla template is chosen per engine (grid on Takumi, flex on Satori) and Tailwind is shared. Switching engine reloads the template unless the markup was edited.
…footer 100dvh minus the sticky header and the footer, and no min-height so short viewports fit too.
Engines and formats show as Takumi / Satori / PNG / JPEG / WebP / SVG (the snippet keeps the API values). Below lg the toolbar is Template · Engine · Options; styling, format and size live in an Options popover, Download moves into the render head, Copy into the Code tab, Reset into the editor head. The workbench is one screen tall on every size, so panes scroll and the page never does.
The drawer slides open/closed (svelte slide, 180 ms, cubicOut) with the caret rotating; template and options popovers fade and drop in and out via @starting-style + allow-discrete. Both collapse to instant under prefers-reduced-motion.
The passive '⌘↩ renders now' hint becomes a Render button (fires the immediate render; keycap shows the shortcut on lg) next to Format, both with icons in the small bordered head-button style shared by Reset, Copy and Download. Icon-only below sm so the head never clips.
dirty compared raw text, so Format → Satori kept the formatted Takumi grid markup and Satori rejected it. Compare formatted (the formatter is idempotent); engine, style and template switches carry the pretty-printed state over.
The Takumi grid version (title, author row, series card) is now also the flex version for Satori and the tw version; the Tailwind card is a solid rose since Satori's tw has no gradients.
…styling The Takumi grid designs (tiles, shadows, gradients) are rebuilt with flex for Satori and as tw for Tailwind, so switching engine or styling changes the markup, not the picture. Tailwind uses solid fills where Satori's tw has no gradients.
Pages' v2 build reads .node-version from the project root directory (apps/docs), not the repository root, so it kept picking 22.16 and failed SvelteKit 3's engines check (>=22.17).
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Promotes
devtomainfor the 4.4.0 release. Currently published as4.4.0-next.0on thenexttag.Added
@ethercorps/sveltekit-og/client(feat(client): browser/worker rendering via /client entry #74): render OG images in the browser or a web worker with Takumi (default) or Satori + ReSVG — no server request.import(); only the one you pick is downloaded.COMPONENT_IN_WORKER, HTML strings work everywhere..blob()/.arrayBuffer()/.text()asImageResponseErrorwith acode(browsers otherwise collapse an errored body intoTypeError: Failed to fetch).CustomFont+resolveFonts;GoogleFontis not exported (browsers can't set the UA, Google serves woff2).usage/client.md. Requirestakumi-jswith either engine; workers needworker: { format: "es" }./clientpage and index cards for both engines.Fixed
sveltekitOG()Vite plugin now applies the wasm rollup plugin to the SSR build only — on the client build it broke the/cliententry's Takumi wasm.ultrahtmlupgraded to 1.7.0 with a repo patch for the pseudo-selector regex (breaks under ascii-escaping minifiers).Chores
dev+-next.x→next;main+ clean version →latest; beta channel dropped.srvx-buildexample removed.Verification
pnpm test: 34 passed, 1 skipped (satori render is browser-only).node-buildandsatori-only-buildexamples: production builds pass and/clientrenders in the built apps.4.4.0-next.0published and smoke-installed from the registry.Before merging
mainonly publishes a clean version. Bump ondevfirst, then merge this PR:Merging as-is (version
4.4.0-next.0) is a no-op publish.🤖 Generated with Claude Code