Show HN: Tweed – An OO JavaScript VDOM UI Library
Tweed — An OO JS UI Library - Emil Broman - Medium
First of all; look at all those abbreviations. I did it. Yay.Before showing Tweed, I need to explain the reasoning behind it, with a little bit of history. If you want, you can skip down to the heading Enter Tweed. You’re welcome.There has been a bit of a divide as of late, between two paradigms in the web space. Functional Reactive Programming—spearheaded by React—on one hand, and Object Oriented Programming—upheld by Angular and Web Components—on the other.The mindsets and traditions of these two have resulted in firm standpoints, from both sides, in questions seemingly unrelated to these paradigms.Virtual DOMOne of these controversies are the Document Object Model.The standard Web API’s of JavaScript in browsers have since the beginning used an object oriented representation of HTML. An HTMLButtonElement is an instance of HTMLElement which, in turn, is an instance of a basic DOM Node, and so on.Since these objects are directly tied to the layout and painting of the UI, operating on these objects can result in bad performance, flickering, lagging et cetera.The advent of the Virtual DOM—popularized by Elm and React—introduced the idea of a separate representation of the DOM which wasn’t connected to the UI. This JS-only data structure could be generated, manipulated, and replaced with a very small performance impact. The actual DOM was then intelligently manipulated in the smallest and quickest possible way, making it indistinguishable from carefully managing DOM mutations manually.This allowed developers to not worry too much about how the UI was manipulated, and instead focus on what the UI was. This declarative style replacing the old imperative style was a great pairing with Functional Reactive Programming, which led UI developers to slowly adopt a more functional mindset.JSX and the Platform DefenseTo make the transition from HTML to Virtual DOM, Facebook released React with a new language feature specifically designed to make constructing VDOM feel like writing HTML—JSX. It turned the non-familiar React.createElement(‘div’) into the much more comfortable <div />. Furthermore, it allowed developers to create their own components as elements in the XML-like tree, like <This />.This was initially considered blasphemous because at the time, the idea of combining the markup and behaviour of a UI had decidedly been deemed a “bad practice”.Gradually, as the community realized the appeal of coupling things that change for the same reasons—markup and behaviour—the argument of Working With the Platform instead of “on top” of it emerged in the OO crowd. After all, the web API’s are all object oriented.This divide made Virtual DOM exclusive to the FRP folks.The Elm ArchitectureAs you may or may not be aware of, React was actually inspired by another project called Elm, which is not a JS library but an entirely different programming language. It has a syntax similar to the ML-family, like Haskell, and is a strongly typed, purely functional programming language.Elm introduced the so called Elm Architecture, which replaced the dominating MVC Architecture. The idea is that you have a Model, a View function, and an Update function. These three work together like this:The View receives the Model and renders a UI (VDOM) from it. Actions from the user then trigger the Update function which, given the type of action, runs the View function again with a modified version of the Model, effectively updating the UI.The case against the Elm ArchitectureHowever, one issue with this whole approach that I have is the way that it handles source code dependencies.The View function only receives the Model, and since Elm is functional, that means that the entire UI must be concretely generated. If we need a “Sidebar”, View must choose an implementation to use for that sidebar. Thus, the View function has a source code dependency on the entire UI codebase.There are ways to manage this fact, primarily using function composition and currying, where one function generates the sidebar and receives another function as an argument, which can be called to generate the content of the sidebar. That way the sidebar function does not depend on the sidebar’s content in its source code.Finally, it should be mentioned that Elm is also a little bit limited in its implementation at the time of writing this. Specifically, it does not have Type Classes like Haskell. Type classes would enable the View function to call functions with unknown implementations, which would then be implemented differently depending on the data types in the Model.Object Orientation and Dependency InjectionSuffice it to say that source code dependency management is not very intuitive in functional programming, at least not for a developer like myself who is much more experienced with OOP than with FP.The Object Oriented community has been dealing with source code dependencies for a long time, and has established polymorphism as its primary mechanism for code reuse. Traditionally, it has been accomplished using inheritance. There are a lot of issues with that, however, so a better practice may be to use abstract composition using Dependency Injection. I’m referring to the pattern here, not Automatic Dependency Injection by libraries or frameworks.By injecting the implementation of an abstract dependency that a class has in its constructor, we not only maximizes the reusability of that class. We also make it easier to test, maintain, and replace.The Tweed LogoEnter TweedTweed is a reactive, virtual DOM based UI library, which uses the Object Oriented paradigm to solve the problem of maintaining a complex and stateful user interface.Here’s a simple Counter component:At first glance, it looks a lot like a React component, but without the setState and instead a (at)mutating decorator. I think you can probably imaging what it means (it means the UI will rerender when that property is mutated), so we’ll move on.Now let’s look at how this component is used:You’ll notice that we don’t see <Counter /> in the render method. Instead, we send in the instance that we already have. This is crucial. In React, each component is re-instantiated on re-render, unless otherwise expressed with shouldComponentUpdate callbacks and similar mechanisms. With Tweed, render methods simply delegate between persistent instances.JSX as a primitiveWith Tweed, JSX does not contain and manage instances of classes which should or shouldn’t be replaced. Instead, JSX is simply immutable objects representing HTML. Nothing else.With this mindset, you can think of the render method as a toString for HTML or toHTML. In fact, the name render is not at all required. It is simply a convention and, as seen in the example above, if the JSX factory receives an object, it will implicitly call a .render() method like that, with no arguments.MountingThe final step is to connect that App class to the browser, and that’s easy:As you might guess, it’s easy to use other renderers to perform an initial render on the server.Next to NativeSince the render method on the Engine takes an instance and not a class constructor, how you get that instance is entirely up to you. Up until that point, you’re in full control of your objects.Furthermore, Tweed doesn’t provide any dependency management facilities, like a DI/IoC Container. If you want to use one of those, you’ll have to choose one. You might even find it’s easier to just new up those classes yourself. If you stick to the Dependency Injection pattern, you will end up with a huge tree of instantiations, like this:While this may look unwieldy at first, you may actually find that not only does this give quite a comprehensive overview of what your site looks like; all the independent pieces are highly decoupled from the rest of the system, meaning they can easily be replaced or be independently tested and maintained. And everything can be monitored and composed in this top level file.Another benefit is that you can structure your app to inject different implementations for some interfaces; one for the client, one for the server. Imagine an interface EmployeeRepository, and its respective implementations APIEmployeeRepository and DatabaseEmployeeRepository. The component injecting the EmployeeRepository interface doesn’t need to know how the employees are fetched, only that they are.So that’s about it. Give Tweed a try if you’re an experienced OOP developer, or if you’re someone who finds the functional paradigm of React to not match your mental model of how to build apps.Check out the Tweed homepage for some more examples.