DEV Community

Cover image for WhatsUp - front-end framework based on ideas of streams and fractals. Part 2.
Dani Chu
Dani Chu

Posted on

WhatsUp - front-end framework based on ideas of streams and fractals. Part 2.

Hi guys!

My name is Dan. This is the second article about my project.

In the first article I talked about how state management can be organized using WhatsUp. Today I will tell you how easy and simple it is to organize the display of data in a react-like way - with jsx.

JSX

WhatsUp has its own jsx-renderer. There are two packages for this - @whatsup/jsx & @whatsup/babel-plugin-transform-jsx. And a simple HelloWorld would look like this.

import { cause } from 'whatsup'
import { render } from '@whatsup/jsx'

const app = cause(function*(){
    while(true){
        yield <div>Hello world</div>
    }
})

render(app)
Enter fullscreen mode Exit fullscreen mode

Example

His work is based on mutators. Unlike React, WhatsUp never creates the entire virtual house as a whole for further comparison and modification. It incrementally modifies the thread-specific fragment. In addition, WhatsUp can render directly to the body element (by default).

Fragments and their syntax (<></>) are also supported, and components are only defined as pure functions, have no internal state, and are only responsible for markup.

Fractals

This is perhaps the main component of my framework. Looking for inspiration, I looked on YouTube a lot of videos of traveling to fractals, for example one, two, three. Just take a look. Imagine that you are flying through the universe in a spaceship. It's incredibly beautiful!

There is mathematics behind all this beauty. If you look for a long time, you will notice some patterns. The fractal is designed in such a way that if we do not pay attention, then we do not notice the seams between the repeating patterns. They are not the same repeating pattern. Each time this is a new pattern, created in the context of the previous one according to the same rules.

Now imagine that our ship is not moving, it stands still, and the fractal gives us a stream of information towards us. This information reflects the internal state of the fractal. I really hope that I managed to describe it correctly. Let's move on to practice.

const app = fractal(function*(){
    while(true){
       yield <div>Hello world</div>
    }
})
Enter fullscreen mode Exit fullscreen mode

It looks like Cause, right? This is true, the only difference is that for each consumer, the fractal creates a personal generator and context. The contexts connect to each other as parent-child and form a context tree. This allows you to organize the transfer of data down the tree, as well as the bubbling of events up. A cause, unlike a fractal, creates one iterator for all consumers, and one context without a reference to the parent (root context).

Sharing

This mechanism allows you to move data down the context. Any data shared somewhere in parent fractals will be available in child fractals. To do this, in the context there are methods ctx.share(), ctx.get() and ctx.find().

import { fractal } from 'whatsup'
import { render } from '@whatsup/jsx'

class Theme {
    readonly name: string

    constructor(name: string) {
        this.name = name
    }
}

const app = fractal(function* (ctx) {
    const theme = new Theme('dark')

    ctx.share(theme)
    // share theme instance to children fractals

    while (true) {
        yield <div>{yield* page}</div>
    }
})

const page = fractal(function* (ctx) {
    const theme = ctx.get(Theme)
    // get theme instance from parent fractals

    while (true) {
        yield (
            <div>
                Theme name is <b>{theme.name}</b>
            </div>
        )
    }
})

render(app)
Enter fullscreen mode Exit fullscreen mode

Example

The ctx.get() method searches for data based on a strict constructor match, but ctx.find() method uses instanceof.

class User {}
class Guest extends User {}

const guest = new Guest()

// then in parent fractal
ctx.share(guest)

// and in child fractal
const user = ctx.find(User)
//    ^^^^
// the user constant will contain the Guest instance
Enter fullscreen mode Exit fullscreen mode

There is another way to share data - factors.

import { factor } from 'whatsup' 

const Token = factor(null)
//     default value ^^^^ if factor is not found 

// then in parent fractal
ctx.share(Token, 'i am a secret token')

// and in child fractal
const token = ctx.get(Token)
//> 'i am a secret token'
Enter fullscreen mode Exit fullscreen mode

A factor is a key by which you can share data in a context and get data out of a context.

Event system

This system allows sending events between fractals up the context. To work with events in context, there are ctx.on(),ctx.off() and ctx.dispatch() methods.

import { fractal, Event } from 'whatsup'
import { render } from '@whatsup/jsx'

class TimeEvent extends Event {
    readonly time: number

    constructor(time: number) {
        super()
        this.time = time
    }
}

const app = fractal(function* (ctx) {
    ctx.on(TimeEvent, (e) => console.log('Time', e.time))
    // attach listener for TimeEvent instances

    while (true) {
        yield <div>{yield* page}</div>
    }
})

const page = fractal(function* (ctx) {
    const onClick = () => {
        const event = new TimeEvent(Date.now())
        ctx.dispath(event)
        // trigger event bubbling
    }

    while (true) {
        yield (
            <div>
                <button onClick={onClick}>Print time</button>
            </div>
        )
    }
})

render(app)
Enter fullscreen mode Exit fullscreen mode

Example

Look at an example - when you click on the button, a message with the current time will appear in the console.

The ctx.off() method is needed to remove the event handler. In most cases, it does not need to be called manually - all handlers are automatically removed when the fractal is destroyed.

Extended example

The fractal function is shorthand for creating fractals. There is a base class Fractal available for extension. When extending, we need to implement a whatsUp method that returns a generator.

The following example demonstrates the use of the event system and data sharing.

import { Fractal, Conse, Event, Context } from 'whatsup'
import { render } from '@whatsup/jsx'

class Theme extends Conse<string> {}

class ChangeThemeEvent extends Event {
    readonly name: string

    constructor(name: string) {
        super()
        this.name = name
    }
}

class App extends Fractal<JSX.Element> {
    readonly theme = new Theme('light');
    readonly settings = new Settings()

    *whatsUp(ctx: Context) {
        // sharing this.theme for child fractals
        ctx.share(this.theme)

        // attach ChangeThemeEvent event listener
        ctx.on(ChangeThemeEvent, (e) => this.theme.set(e.name))

        while (true) {
            yield (<div>{yield* this.settings}</div>)
        }
    }
}

class Settings extends Fractal<JSX.Element> {
    *whatsUp(ctx: Context) {
        // get Theme from context
        const theme = ctx.get(Theme)
        // dispatch ChangeThemeEvent on click button
        const change = (name: string) => 
            ctx.dispatch(new ChangeThemeEvent(name))

        while (true) {
            yield (
                <div>
                    <h1>Current</h1>
                    <span>{yield* theme}</span>
                    <h1>Choose</h1>
                    <button onClick={() => change('light')}>light</button>
                    <button onClick={() => change('dark')}>dark</button>
                </div>
            )
        }
    }
}

const app = new App()

render(app)
Enter fullscreen mode Exit fullscreen mode

Example

Errors catching

Throwing an exception at the framework level is common. The error propagates along the streams, like any other data, and is handled by the standard try {} catch {} construction. In this case, the reactivity system preserves the state of dependencies in such a way that when the situation is corrected and the error disappears, the threads recalculate their data and return to their normal operating state.

import { conse, Fractal } from 'whatsup'
import { render } from '@whatsup/jsx'

class CounterMoreThan10Error extends Error {}

class App extends Fractal<JSX.Element> {
    *whatsUp() {
        const clicker = new Clicker()
        const reset = () => clicker.reset()

        while (true) {
            try {
                yield <div>{yield* clicker}</div>
            } catch (e) {
                // handle error  
                if (e instanceof CounterMoreThan10Error) {
                    yield (
                        <div>
                            <div>Counter more than 10, need reset</div>
                            <button onClick={reset}>Reset</button>
                        </div>
                    )
                } else {
                    throw e
                }
            }
        }
    }
}

class Clicker extends Fractal<JSX.Element> {
    readonly count = conse(0)

    reset() {
        this.count.set(0)
    }

    increment() {
        const value = this.count.get() + 1
        this.count.set(value)
    }

    *whatsUp() {
        while (true) {
            const count = yield* this.count

            if (count > 10) {
                throw new CounterMoreThan10Error()
            }

            yield (
                <div>
                    <div>Count: {count}</div>
                    <button onClick={() => this.increment()}>increment</button>
                </div>
            )
        }
    }
}

const app = new App()

render(app)
Enter fullscreen mode Exit fullscreen mode

Example

I tried to design the framework in such a way that it was easy and intuitive to use it.

Performance

Performance is very important. I added whatsapp to the krausest/js-framework-benchmark project. Below is an excerpt from the results table showing WhatsUp position against the background of the most popular libraries and frameworks such as Inferno, Preact, Vue, React and Angular.

In my opinion, the position is already quite worthy.

Conclusion

I hope you enjoyed the article. These are not all the features of the WhatsUp framework. In the next article I will talk about delegation, routing, asynchronous tasks. You can find more examples here, their sources.

If you liked the idea of my framework - leave your feedback or a star on the github, also contributing welcomed. I'll be very happy. Thanks!

GitHub logo whatsup / whatsup

A frontend framework for chillout-mode development 🥤 JSX components on generators, fast mobx-like state management and exclusive cssx style system

Top comments (0)