DEV Community

Cover image for Browsers now have Observables and I created a framework for it
Valery Zinchenko
Valery Zinchenko

Posted on • Originally published at Medium

Browsers now have Observables and I created a framework for it

Introduction

Browsers are embracing Observables as a native primitive for handling asynchronous streams. This shift marks a pivotal moment in web development. Luckily I was working on creating Proton — a "framework" built around native Observables. This article examines the new browser support, the implications for developers, and how Proton leverages native Observables to offer isolated components with reactivity at their core.

Native Observable Adoption in Browsers

Native Observable support has arrived in major Chromium-based browsers. According to Can I use, the Observable API support is

Browser Version Supporting Observable Status Notes
Chrome (Desktop) 135+ ✅ Supported Enabled by default
Edge 135+ ✅ Supported Chromium-based
Chrome for Android 137+ ✅ Supported Matches desktop behavior
Opera TBD (Chromium-based) ⚠ Experimental Likely to follow Chrome
Firefox ❌ Not Supported No implementation yet
Safari ❌ Not Supported No known plans or flags
Brave 135+ ✅ Supported Chromium-based
Vivaldi 135+ ✅ Supported Chromium-based
Samsung Internet ❌ Not Supported Behind mainline Chromium
Node.js ✗ (under --harmony flag) ⚠ Behind Flag Experimental in V8; not available by default

This adoption signals that push-based asynchronous streams are moving from external libraries into the platform itself.

Support remains uneven: Chromium-derived browsers lead, whereas others lag. Developers must plan for environments where native Observable is absent or behind a flag. Yet the trajectory is clear: Observables are on the path to becoming a first-class citizen in JavaScript. It is prudent to embrace this change while remaining vigilant about cross-browser compatibility.

Why Observables Matter

Observables model push-based data streams with subscription semantics. They are lazy: they do not emit until subscribed. They can compose elegantly via higher-order operations. Native Observables integrate with AbortSignal for cancellation, simplifying cleanup for long-lived subscriptions. They can interoperate with Promises, providing familiar async patterns while offering more powerful composition for event streams, data feeds, or real-time updates. The native API reduces dependency on libraries such as RxJS for basic use cases, yielding leaner bundles and tighter integration with platform APIs.

Nonetheless, the ecosystem of operators in RxJS remains richer. Native Observables provide the core primitive; advanced combinators and complex transformations may still benefit from libraries or user-defined utilities. The prudent approach is to use native Observables for straightforward streams (DOM events, fetch-based updates, etc.) and layer additional utilities when necessary.

The Shift in the Landscape

  • Observable is now first-class in Chrome, Edge, and Android browsers—nearly 66 % global reach .
  • This isn't RxJS nor a framework add-on; this is the API baked into EventTarget:
  document.when('mousemove').subscribe(console.log);
Enter fullscreen mode Exit fullscreen mode

A shift from imperative listeners to declarative, composable streams (wicg.github.io).

  • Patterns like filter, map, takeUntil, and promise-returning operators come standard - no extra weight.

Proton: A non-Framework for Native Observables

Proton is designed from the ground up to leverage native Observables in browsers. It adheres to principles of isolation, relocatability, and asynchronous lifecycle handling. Proton components return DOM Nodes that uses Observables under the hood. This aligns with the Denshya movement’s ethos of minimal coupling and rootless architecture.

Core Principles

  • No Framework
  • Open Internals
  • Fault Tolerance
  • Customization
  • Observable-based
  • No coupled state manager

Example: Simple Proton Component with Native Observable

function ColorApp() {
  const pointerMoveX$ = window.when("pointermove").map(event => event.x)
  const background = pointerMoveX$.map(x => x > 500 ? "red" : "green")

  return (
    <div style={{ background }}>{pointerMoveX$}</div>
  )
}

const inflator = new WebInflator
const AppView = inflator.inflate(<App />)

document.getElementById("root").replaceChildren(AppView)
Enter fullscreen mode Exit fullscreen mode

This pattern demonstrates how Proton binds Observables directly within components without reliance on a virtual DOM or framework-managed render loop. It is lean and declarative: the component reflects the data stream.

Practical Considerations

Detect native Observable support at runtime. If missing, load a polyfill that matches the TC39 Observable proposal API. Example:

  if (typeof Observable === 'undefined') {
    // Load polyfill, e.g., import '@w3c/observable-polyfill';
  }
Enter fullscreen mode Exit fullscreen mode

Or you custom state managers, which are based on observables - just like the one I created - Reactive.

Skeptical View

The proposal remains in an incubation stage (WICG/Community Group) and is not yet a W3C Recommendation or a de facto standard in browsers
wicg.github.io

Implementers have not shipped it broadly in engines at time of writing (June 9, 2025). Thus any production reliance on “native Observables” is premature. One must adopt a skeptical stance: the specification could evolve, stall, or be rejected in final form.

Learning Curve - Many developers are unfamiliar with Observables or comfortable with Promises/async await. Introducing native Observables adds another paradigm to master.

Framework Integration - Some frameworks already integrate their own Observables (e.g., Angular with RxJS, Svelte supports certain observable patterns). If native Observables differ subtly from RxJS, framework authors must decide whether to adapt or continue using RxJS, potentially causing fragmentation. Conversely, frameworks may ignore native Observables, undermining their value.

Community Momentum - While WICG sees interest, actual adoption depends on community enthusiasm. If major libraries and frameworks rally behind the native API, it could gain traction; if they remain cautious, the spec may stagnate.

Forward-Looking Thoughts

As more browsers adopt native Observable, developers can shift patterns to rely less on external libraries for basic streams. Proton showcases how to design component systems around native primitives, avoiding centralized framework control. This aligns with the Denshya movement’s vision: empower developers to compose UI and logic in pure JavaScript, leveraging standard features as they emerge.

Call to Action

Developers should experiment with native Observable today. Install Proton early, test patterns, and provide feedback on edge cases, performance, and ergonomics. Advocate for wider browser support (e.g., Safari alignment). Write blog posts, share code examples, and help shape best practices.

Proton is open to evolution: the Denshya movement thrives on innovation and critical questioning. Test assumptions: do isolated components built on Observable offer tangible benefits? What limits emerge in large-scale applications? Share findings. Build prototypes that mix Proton with existing frameworks to assess integration complexity if possible at all.

Conclusion

Browsers are beginning to adopt native Observables. Proton harnesses this shift to provide an isolated, rootless framework for UI components powered by observables. This approach is practical: it reduces bundle size, aligns with platform features, and encourages modular design. It is skeptical: it does not discard proven libraries but complements them where native primitives suffice. It is forward-thinking: it anticipates a future where reactivity is built into the platform and frameworks yield to simpler, composable patterns. Embrace native Observables today, explore Proton capabilities.

Denshya Movement
Pinely International

Top comments (7)

Collapse
 
dariomannu profile image
Dario Mannu

Hi @framemuse, it's nice to discover other people working on frameworks/no-frameworks for native observables!

I noticed your example suffers from an annoying problem these cause when you use them inside components, which is that you can't call .when() on a node you're creating because... it doesn't exist yet.
Obviously you can still call document.when() as you did in your example, but that's a different thing.

If you use components as building blocks of your app, what you end up having to do is create some sort of future reference to the Observable in order to be able to call its instance methods (.map, .filter, etc).

A Subject serves this purpose, but not only there is no Subject in the WICG spec, but it would also add an extra unnecessary processing step in the pipeline (I wrote an article about it a while back).

So, if you use a Subject in Proton it could look something like this (mousemove over the div, not the whole document):

const pointerMoveX$ = (new Subject<Event>()).map(event => event.x)

<div style={{ background }} on={{ pointermove: pointerMoveX$ }}>{pointerMoveX$}</div>
Enter fullscreen mode Exit fullscreen mode

In order to avoid this extra Subject, in Rimmel.js we introduced the (highly experimental) Observature, waiting to see if any better idea emerged.

A (1) few (2) examples (3) on Stackblitz (4) illustrate how Observatures can be used to address the uninstantiated Observable problem so we don't have to use an intermediate Subject.

I thought a pattern like this might benefit Proton, as well (but will it not conflict with the no-framework priciples?), unless of course we find a better way to address the problem? Happy to brainstorm on it if you have different opinions...

BTW: there is observable-polyfill that can be used now.

Collapse
 
framemuse profile image
Valery Zinchenko • Edited

Yeah, I see, my example was just to quickly demonstrate it's possible.

To fix the problem you're referring to I created @denshya/reactive library, which usage looks like this

function ColorApp() {
  const x = new State(0)
  const background = new State("gray")

  const pointerMoveX$ = window.when("pointermove").map(event => event.x)
  pointerMoveX$.subscribe(value => x.set(value))
  pointerMoveX$.subscribe(value => background.set(value > 500 ? "red" : "green"))

  return (
    <div style={{ background }}>{x}</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

or in more compact way

function ColorApp() {
  const x = State.from(window.when("pointermove").map(event => event.x))
  const background = x.to(x => x > 500 ? "red" : "green")

  return (
    <div style={{ background }}>{x}</div>
  )
}
Enter fullscreen mode Exit fullscreen mode

So the source of data can be changed on fly just like elements you want to subscribe to that doesn't exist for now.

Collapse
 
dariomannu profile image
Dario Mannu

You're still using window.when(). What if we need to call div.when()? That's challenging because the div doesn't exist yet while you're still creating a template for it.

This is not our fault, but rather a drawback of the WICG design and the choice of using operators as instance methods, not really designed to work with components the way we use them today.

This is probably something for both Proton and Reactive to sort together. Maybe Proton should parse the following and connect the input of pointerMoveX$ to div.when() after it's mounted.

<div on={{ pointermove: pointerMoveX$ }}>{pointerMoveX$}</div>

Reactive should be able to provide just a pipeline (in other words, a Stream), like this, if it makes sense:

const x = FutureState.map(event => event.x))
Enter fullscreen mode Exit fullscreen mode
Thread Thread
 
framemuse profile image
Valery Zinchenko • Edited

I don't really see a problem since Proton creates elements disregardless if they're going to be mounted and subscribes the attributes immediately.

This is where github.com/whatwg/dom/issues/533 issue comes in, I have a solution for that, I will working on it as a next goal. The problem in short is "if you subscribe things, you need to unsubscribe when element is unmounted to allow Garbage Collecting, but you can't listen for disconnection events since there are none."

However, you may be seeing it from a different angle, I'm waiting to exchange our knowledge.

Collapse
 
framemuse profile image
Valery Zinchenko • Edited

That's cool I'm not the only one who thought combining UI Building and Observables is a great idea. I'm open to share thoughts/ideas, so if you want to communicate in voice - a Discord would be cool (if you have one).

I'm going to add your library in Proton README.

Collapse
 
dariomannu profile image
Dario Mannu

Sure, let's... I'll DM you

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more