@tsrx/ripple@0.2.2

Ripple-TS/ripple@tsrx/ripple@0.2.2Sep 17, 2026by github-actions[bot]

Upgrading?

AI Summary

This release introduces 'build shapes' for client-only and server-side prerendering, along with significant bundle size optimizations to reduce production bundle weight.

Key Highlights

  • Smaller production bundles: Tree-shaking optimizations for event handlers, class expressions, and unused runtime modules.
  • New build shapes: Support for `ripple({ ssr: false })` to create client-only builds and `prerender()` functionality for static HTML generation.
  • Optimized `@for` loops and event delegation for better performance.
  • Removed dependency on `clsx` in favor of a shared internal joiner.

New Features

  • Client-only build support
  • Server-side prerendering capabilities
  • Bundle size optimization and tree-shaking improvements

Full Release Notes

### Patch Changes

- [#1486](https://github.com/Ripple-TS/ripple/pull/1486)
  [`304ba6c`](https://github.com/Ripple-TS/ripple/commit/304ba6c2d9098752396fff6c2545e3ef2546381c)
  Thanks [@leonidaz](https://github.com/leonidaz)! - perf: a static component,
  template `@if`, or template `@for` followed by a static element sibling inserts
  before that element instead of before a `<!>` placeholder of its own: the
  element is part of the template and never moves, so the compiled template holds
  no comment node for it and the client reuses the element's variable where
  hydration steps to the sibling. The `RippleArray` statics (`from`, `of`,
  `fromAsync`) lower to standalone runtime functions, so a bundle that never
  constructs a `RippleArray` drops the array proxy instead of keeping it through
  property assignments on the constructor's compiled form (the server's
  `fromAsync` lowering previously named a function that did not exist).

- [#1487](https://github.com/Ripple-TS/ripple/pull/1487)
  [`b4abea0`](https://github.com/Ripple-TS/ripple/commit/b4abea0ab4830eed99adf14dc95a0ef214b24af1)
  Thanks [@leonidaz](https://github.com/leonidaz)! - perf: smaller production
  bundles without a runtime cost. The compiler lowers a static `onBlur`-style
  handler (an event its name keeps off delegation) to a direct `listen` call with
  the DOM name and phase resolved at compile time, packs the locals a hoisted
  `@if`/`@switch` captures under positional keys, writes template strings without
  attribute quotes or implied end tags where the parser reads them back the same,
  gives a plain function no `_$_.scope()` call when its body never reads the
  block, and omits `set_class`'s default trailing arguments. The runtime keeps its
  hydration paths (template adoption, expression text, append cursor, streamed
  boundaries) in a module `hydrate()` installs and its `trackAsync` machinery in a
  module that registers with the runtime on first use, and the catch routing is
  registered by the first boundary, so a client-only mount without `trackAsync`
  ships neither and the core runtime no longer imports the boundary module; the
  try boundary state, the root event ref and the template cache use short keys,
  the keyed and reference list diffs share one patch helper, and symbol constants
  carry no descriptions. The runtime's errors are thrown as in Svelte: the message
  and a link to `https://ripple-ts.com/e/<code>` in development, the link alone in
  production, so the message text stays out of production bundles; `set_class`
  composes non-string values with its own clsx-style joiner instead of importing
  `clsx`; hoisted render blocks store captured locals under positional keys; a
  `template()` with no flags passes none; and the hash a `track()` call carries
  encodes the same 32 bits in base 36. The Vite plugin leaves Vite's modulepreload
  polyfill out of production builds unless `build.modulePreload` is configured.

- [#1487](https://github.com/Ripple-TS/ripple/pull/1487)
  [`b4abea0`](https://github.com/Ripple-TS/ripple/commit/b4abea0ab4830eed99adf14dc95a0ef214b24af1)
  Thanks [@leonidaz](https://github.com/leonidaz)! - feat: build shapes.
  `ripple({ ssr: false })` builds a client-only app: components compile without
  the hydration cursor or `track()` serialization hashes (`hydration: false` on
  the compiler), the runtime's hydration paths are compiled out through its
  `HYDRATION` build constant, and `hydrate()` throws; `ssr: true` compiles every
  module for the server. `prerender()` on `ripple/server` renders a component to
  static HTML with every boundary settled and the CSS as text, and a `RenderRoute`
  marked `prerender: true` is rendered through the built server entry at build
  time into `<outDir>/client<path>/index.html`, which the node and bun adapters
  now serve for a directory request before the server renders. The client build's
  `index.html` template moves to the server output so a static handler never
  serves its placeholders.

- [#1487](https://github.com/Ripple-TS/ripple/pull/1487)
  [`b4abea0`](https://github.com/Ripple-TS/ripple/commit/b4abea0ab4830eed99adf14dc95a0ef214b24af1)
  Thanks [@leonidaz](https://github.com/leonidaz)! - perf: a `class` expression
  the compiler can prove to be a string writes through a string-only `set_class`,
  so the clsx-style joiner ships only with a bundle that composes classes from
  arrays or objects; templates in the SVG or MathML namespace parse through a
  `template_ns` entry that an HTML-only bundle never loads; and the class joiner
  is one module shared by the client and server renderers, so `ripple` no longer
  depends on `clsx`. `ripple({ rootBoundary: false })` builds an app that renders
  without the default root try/pending/catch boundary and leaves the boundary
  runtime out of the bundle; a `rootBoundary` option on `mount()` or `hydrate()`
  then throws. The `ripple` package declares `sideEffects: false`, and dynamic
  attribute, style and spread helpers live in their own runtime module, so a
  bundle that never sets an attribute dynamically drops them along with the
  attribute tables they consult. The plugin's `ssr: false` and
  `rootBoundary: false` rewrite the runtime's build-constant imports to literals
  at transform time, which every bundler folds while tree-shaking.

- [#1484](https://github.com/Ripple-TS/ripple/pull/1484)
  [`1ea71f1`](https://github.com/Ripple-TS/ripple/commit/1ea71f14fcee5a976ea81e2bedf5010251eaa70c)
  Thanks [@leonidaz](https://github.com/leonidaz)! - perf: a template `@if` that
  closes the trailing run of an element's children (nothing rendered follows it)
  never materializes an anchor node: the element's tail is its position for its
  whole life. An `@if (outer === item.key)` in a `@for` body lowers to the loop's
  selector like an attribute comparison, and a comparison against the item's key
  reads the block's key (fixed for its life) rather than the item, so a change of
  the outer value re-runs the ifs of its previous and next key alone and a
  replaced item leaves its ifs untouched; comparisons against the same outer value
  share one selector per loop. Event delegation reads the composed path only for a
  nested root and looks the handler property up by one string, and `checked` is
  compared against the element instead of a cached write.

- [#1487](https://github.com/Ripple-TS/ripple/pull/1487)
  [`b4abea0`](https://github.com/Ripple-TS/ripple/commit/b4abea0ab4830eed99adf14dc95a0ef214b24af1)
  Thanks [@leonidaz](https://github.com/leonidaz)! - `trackReadOnly(value)`
  replaces the `readOnly()` method on tracked and derived values: it returns a
  derived that follows a tracked or writable derived, or a read-only derived as it
  is. The method is removed, so `count.readOnly()` becomes `trackReadOnly(count)`,
  imported from `ripple`. Dropping the prototype methods lets a bundle that never
  takes a read-only view leave the code out.

Related Repositories