DEV Community

Cover image for I created a framework with JSX components on generators*, a fast mobx-like state management and an exclusive cssx styling system
Dani Chu
Dani Chu

Posted on

I created a framework with JSX components on generators*, a fast mobx-like state management and an exclusive cssx styling system

Hi guys!

My name is Dani. For several years now, I have been experimenting with different approaches to front-end development. I tried many different ideas, the best of which I collected in my framework Whatsup. In this article, I want to briefly tell you about architectural decisions and discuss them with you in the comments.

Short features list

  • πŸŽ‰ easy to use: simple api, just write code
  • πŸš€ own reactivity system with high performance
  • 🌈 cool styling system based on css modules
  • β›“ glitch free, autotracking and updating of dependencies
  • πŸ₯— written in typescript, type support out of the box
  • πŸ—œ small size: ~7kB gzipped (state + jsx + cssx)

JSX components on generators

It seems to me that one day the React team really wanted to get rid of class components in order to leave only functional ones. But there was a problem - the functional components are called every time when rendering and you need to somehow transfer the state from render to render. They came up with hooks... Now we all use hooks... When javascript has generators... Just take a look at how using native language constructs you can describe the life cycle of a component.

function* App() {
    // componentWillMount
    try { 
        while (true) { 
            // main life cycle
            yield <div>Hello</div>
        }
    } catch (e) {
        // componentDidCatch
    } finally {
        // componentDidUnmount
    }
}
Enter fullscreen mode Exit fullscreen mode

It may seem unusual at first glance, but believe me - it's very simple. All variables you declare in the componentWillMount phase will be available from render to render, no magic - that's the nature of generators.

With try{}catch{} you can easily handle errors. And it's native javascript capabilities, isn't that great?

However, you are not required to write try{}catch{}finally{} in each component, only where it's really needed. For example, we only need to control the componentWillMount and componentDidUnmount phases:

function* App() {
    // componentWillMount
    try { 
        while (true) { 
            // main life cycle
            yield <div>Hello</div>
        }
    } finally {
        // componentDidUnmount
    }
}
Enter fullscreen mode Exit fullscreen mode

Or we only need the componentWillMount phase:

function* App() {
    // componentWillMount 
    while (true) { 
        // main life cycle
        yield <div>Hello</div>
    }
}
Enter fullscreen mode Exit fullscreen mode

And if we don’t need to control any phases at all, then we just use a regular functional component:

function App() {
    return <div>Hello</div>
}
Enter fullscreen mode Exit fullscreen mode

Mobx-like state management

I have been using React + Mobx for many years. I love that Mobx allows you to write intuitive code that is easy to read and maintain. But I always lacked the ability to use generators to create computed atoms.

const timer = computed(function*(){
    const count = observable(0)
    const intervalId = setInterval(()=> count(count() + 1), 1000)

    try {
        while(true){
            yield count()
        }
    } finally {
        clearInterval(intervalId)
    }
})

autorun(()=> console.log(timer())
//> 1
//> 2
//> 3
Enter fullscreen mode Exit fullscreen mode

In this example, all the components necessary for the timer to work are encapsulated in the body of the generator. I find this to be a very slick solution. Mobx does not provide us with such opportunities.

A computed atom can also be created from a normal function

const count = observable(0)
const text = computed(()=> `Count is: ${count()}`)

autorun(()=> console.log(text())
//> Count is: 0
count(1)
//> Count is: 1
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, getting a value from a computed or an observable is done with a call without arguments (count() text()), and setting a value in an observable is a call with an argument (count(1)).

In all other respects, the state management API is very similar to Mobx and includes the following components:

  • observable - creates a trackable atom
  • array, map, set - creates a trackable array, map, set
  • computed - creates a derived atom
  • action, runInAction - allows multiple updates in one operation
  • autorun, reaction - trigger side effects when observed values change
  • mutator - allows you to create new data based on previous

CSSX styling system

This is a hybrid of css-modules and jsx namespaces, that uses the sass language to describe the styles of the components.
Consider this example:

// styles.scss

.box {
    width: 50px;
    height: 50px;
}
Enter fullscreen mode Exit fullscreen mode

We can use it like regular css modules

import styles from './styles.scss'

function Box(){
    return <div className={styles.box} />
}
Enter fullscreen mode Exit fullscreen mode

We can bind styles to a component using the cssx function and then apply the .box class to an element using a namespaced property css:box

import styles from './styles.scss'
import { cssx } from 'whatsup/cssx'

const Div = cssx('div', styles)

function Box(){
    return <Div css:box />
}
Enter fullscreen mode Exit fullscreen mode

Or we can immediately import the component with binded styles from the css file (this way we can import any standard html tag)

import { Div } from './styles.scss' 

function Box(){
    return <Div css:box />
}
Enter fullscreen mode Exit fullscreen mode

Among other things, you can import style files and access their styles, for example:

// styles.scss
@import 'grid.scss';

.box {
    width: 50px;
    height: 50px;
}
Enter fullscreen mode Exit fullscreen mode

And now we can arrange our box according to the grid rules

import { Div } from './styles.scss' 

function Box(){
    return <Div css:box css:sm_col_2 css:md_col_3 />
}
Enter fullscreen mode Exit fullscreen mode

And for all this, Whatsup provides intellisense

Image description

Our first component

Well, let's summarize our knowledge and write our first component. Let's make a box that changes color on click.

// styles.scss 
.box {
    width: 50px;
    height: 50px;
}
.coral {
    background-color: coral;
}
.green {
    background-color: green;
}
Enter fullscreen mode Exit fullscreen mode
import { observable } from 'whatsup'
import { render } from 'whatsup/jsx'
import { Div } from './styles.scss'

export function Box() {
    const color = observable('coral') 
    const onClick = () => color(color() === 'coral' ? 'green' : 'coral')

    while (true) { 
        yield (
            <Div 
                css:box
                css:coral={color() === 'coral'} 
                css:green={color() === 'green'} 
                onClick={onClick}
            /> 
        )
    }
}

render(<Box />)
Enter fullscreen mode Exit fullscreen mode

And we can look at the result and sources

Want to try?

Just run this in your terminal

npx @whatsup/cli create project
Enter fullscreen mode Exit fullscreen mode

Conclusion

The frontend world is rapidly evolving. New projects come to replace the old ones. Only the most courageous and ambitious survive.I like to search and find original ideas, I hope that my article was useful to you.
Thank you for reading, and let's connect!

Links

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

What is it?

Whatsup is a modern frontend framework with own reactivity system and JSX components based on pure functions and generators.

Features

  • πŸŽ‰ easy to use: simple api, just write code
  • πŸš€ own reactivity system with high performance
  • 🌈 cool styling system based on css modules
  • 🚦 built-in router with intuitive api
  • β›“ glitch free, autotracking and updating of dependencies
  • πŸ₯— written in typescript, type support out of the box
  • πŸ—œ small size: ~7kB gzipped (state + jsx + cssx)

Example

import { observable } from 'whatsup'
import { render } from 'whatsup/jsx'
function* App() {
    const counter = observable(0)
    const increment = () => counter(counter() + 1)

    while (true) {
        yield (
            <div>
                <p>You click {counter()} times</p>
                <button onClick=
…
Enter fullscreen mode Exit fullscreen mode

Top comments (5)

Collapse
 
jrmarqueshd profile image
Junior Marques

Woow, congratulations my friend!
I saw the documentation, and I thought it was amazing!

Collapse
 
iminside profile image
Dani Chu

Thank you! Glad you liked it. Do you have a desire to try it in one of your projects?

Collapse
 
jrmarqueshd profile image
Junior Marques

In my personal projects, yep. πŸ˜„

Collapse
 
noah55 profile image
Ndacyayisenga-noah • Edited

cool stuff bro

Any youtube tutorial for this?

Collapse
 
iminside profile image
Dani Chu

My spoken English is too bad to voice the video. But I'll try to come up with something.