Let’s build a realworld.io for brookjs!

I’ve been working on brookjs for the better part of a year, and it’s maturing into a solid framework. In order to help make brookjs real for people, I started building a realworld.io app with it. The intention is to make this a testbed for brookjs, hammering on all the features and hitting as many weird corner cases as possible. WP-Gistpen has been doing that for me up to this point; implementing a syntax highlighting text editor on top of brookjs has been an interesting challenge, one still pretty fraught with problems, but it’s allowed me to abuse every layer of the stack, sussing out bugs and designing patterns.

In order for the framework to really progress, it has to be stable, and there are areas of it that aren’t tested enough to really be considered production-ready. brookjs-realworld.io can test some of those areas–specifically, animations and the rendering cycle API–and bring them the "real world" (cwidt?) implementations they need to iron out their warts. I also need to see what the development experience looks like to those outside the bubble.

To assist with that, I’ve also started a cli for brookjs, which can also be informed by realworld.io development. beaver will be able to scaffold new reducers/deltas/etc., manage and run the build and test scripts, and provide a opinionated means of building brookjs applications. I’m basically stealing the best ideas from create-react-app and ember-cli and implementing them for brookjs.

Even if we’re stable now, we’ve got a number of improvements we can make to brookjs, the first of which is precompiling the Handlebars templates to vdom-returning functions, rather than parsing the string output of a Handlebars function. We currently recommend using handlebars-loader, but if the cli manages the build process, when the time is right, we can swap handlebars-loader for a brookjs-loader (or similar) without breaking anyone’s app (🙏).

Anyone can contribute to the realworld.io app, and I’m working on a Vagrant setup so we can provide the entire environment without needing to install anything on your development machine (besides Vagrant and Virtualbox). It doesn’t do anything yet but provide scaffolding for the front-end and back-end bootstrapping.

If you’re interested in getting to know brookjs and seeing how it works in a real app, or want to help define best practices for brookjs applications, check out the realworld.io app on GitHub. This is a great opportunity to find something you’re interested in learning (API, caching, offline apps, cli apps, observables…) and work on it collaboratively with other people. If you want something to work on, comment in or open an issue on GitHub.

Looking forward to working with you!

This post is part of the following threads: brookjs, Project: WP-Gistpen - ongoing stories on this site. View the thread timelines for more context on this post.

Quick Vue tip: lifecycle methods update & beforeUpdate will not be called if the props that changed for a component aren’t being used to render. In my case, I was using a prop not used by the render method to trigger some side effect, and couldn’t get the callback to trigger the side effect. Use this.$watch to watch prop changes unrelated to a Vue component’s rendering.

Don’t let JS turmoil stop you from learning

Alex Standiford:

I have been reluctant to learn a modern JS framework because the network has felt too volatile for me to make a jump, and I really don’t see a point in learning something until I decide to use it in my daily workflow. I invest my time in getting really good at the tools I use instead of pretty good at a bunch of different tools.

Once the community makes this decision, I have a solid reason to learn one of these frameworks. Until then, I’ll just stick with jQuery, even though I dislike it.

This is not an uncommon sentiment, so I thought it would be useful to share my response:

The counter to this is that a number of the frameworks share concepts that are broadly applicable. Understanding what a component is applies to React, Vue, Angular and others. The knowledge transfers, even if the specific implementations are different between them, so there isn’t a reason to avoid learning one. Understanding React makes learning Vue easier, and gives you a better sense of what trade-offs are made when choosing one over the other.

Also, many of these frameworks follow a similar Flux-y pattern (actions up, data down), which is additional knowledge that again transfers regardless of framework. The volatility in the JS community has settled down since a few years ago, and these concepts are broadly applicable, so I’d highly recommend picking one (usually React) and learning it as much for its concepts/approaches as its specific implementation.

Using Observables to Control Render Scheduling

When I talk to other developers about Observables, it’s often hard to explain their benefits because on the surface, they just look like glorified event emitters, but it’s how they compose that make handling dependencies between async events such a breeze. While working on the code snippet editor in WP-Gistpen, I came across an example that shows how easily Observables handle async behavior.

So here’s the setup: building a snippet editor on top of PrismJS and borrowing some code from Dabblet, both from Lea Verou, and using Redux as an Observable of states, we need to rerender the editor as the text changes, keeping the syntax highlighting up to date. We keep the position of the cursor and the value of the editor in the Store, allowing the reducer to be responsible for the complex logic that handling special text editor keystrokes, like enter, tab, etc.

The issue is rendering the editor requires us to track & reset the cursor, as we have to reset the text with the latest from the store and rehighlight it. If we do that while the user is typing, we have the potential to interrupt her, so we need to delay the render until the user is no longer typing. The render itself is scheduled in a requestAnimationFrame, so if the user types before the next frame, we need to cancel the render request and wait until the user stops typing again. Otherwise, when the frame renders, the cursor will jump back to the position it was at when the render was scheduled.

So this is the problem we’re trying to solve.

In brookjs, a component is just a function that takes a DOM element & a stream of props$ and returns a stream of (usually Flux Standard) Actions from the element. Since we’re given an Observable of state, we’re able to get pretty fine control over exactly when the editor renders.

I’m using Kefir, but the same concepts apply any other Observable implementation. Here’s how I solved this:

import Kefir from 'kefir';

export default function EditorComponent(el, props$) {
    const keyup$ = Kefir.fromEvents(el, 'keyup');
    const keydown$ = Kefir.fromEvents(el, 'keydown');

    return props$.sampledBy(keyup$.debounce(150))
        .flatMapLatest(props =>
            createRenderStream(props).takeUntil(keydown$)
        );

    function createRenderStream(props) {
        return Kefir.stream(emitter => {
            const loop = requestAnimationFrame(() => {
                // Update the element
                emitter.end();
            });

            return () => cancelAnimationFrame(loop);
        });
    }
};

This problem is obviously solvable in a stateful way, with the view being responsible for holding onto a reference to the render loop, scheduling it in one event callback if a render isn’t scheduled and cancelling the render on the other if it is. We’re not talking about a huge component here, so there’s no reason to think managing this state would be difficult.

But as this component grows in complexity (and it will), colocating the temporal dependencies makes it very easy to reason about how the component changes over time. In the above example, how keyup and keydown events interact with the render cycle is explicit, rather than bouncing between callbacks or methods to see how animations are scheduled and cancelled. There’s zero chance of accidental cancellations or double renders.

Side effects, like updating the DOM, are wrapped in a stream, so they can be handled asynchonrously and cancelled, if needed. Because an Observable comes with its own cleanup code (the function returned at the end of stream‘s callback), when the active Observable is switched, the animation frame request for the previous Observable is cancelled, ensuring the user isn’t interrupted as she types. The props$ stream has control over when the render happens, and flatMapLatest ensures only the newest Observable is being observed at a time, so as the props$ emits new state, previous renders are also cancelled.

brookjs provides some structure around this paradigm, but underneath, this is all that’s happening, and it provides some elegant solutions to thorny async problems. The canonical example is the autocomplete box, sending an AJAX request as a user types and cancelling the previous request. This is explained by Jafar Husain in this great talk.

Even after writing this article, the editor rendering continued to get more complicated, and handling it with Observables allowed me to focus on how particular events interacted with the render cycle, without worrying about how to manage what was actually happening at a given time. Let me know if you think the current implementation is easy to understand.

This post is part of the thread: Project: WP-Gistpen - an ongoing story on this site. View the thread timeline for more context on this post.