DEV Community

Cover image for How we tried to create a frontend framework which can be learned in 5 minutes and which is the result
Vasille
Vasille

Posted on

How we tried to create a frontend framework which can be learned in 5 minutes and which is the result

Hi!

I will tell you about my experience of creating a front-end framework. The goal was to create a framework that can be learned in 5 minutes by a front-end developer who has experience with React, Vue, or Angular.

How to create a component

In React, a component is a function; in Vue, it is a file; and in Angular, a component consists of several files. Personally, I prefer the possibility to create a helper component in a component file. Because of this, I decided that a component will be a function declared in the following way:

export const MyComponent = component(() => {
    // component logic here
});
Enter fullscreen mode Exit fullscreen mode

Reactive states

To save states, there are variables in Javascript, so we will it.

The rules are simple:

  1. If the variable name starts with $ than it will be reactive.
  2. If the variable name does not start with $ than it will be just a variable.

If a derived/computed state is needed, then a constant is declared and initialized with the required value. When let is used, the linter can switch automatically to const, so constant is canonical.

Example of code:

export const MyComponent = component(() => {
    let $a = 2;
    let $b = 3;
    const $sum = $a + $b;
    const $sum2 = sum($a, $b);
});
Enter fullscreen mode Exit fullscreen mode

Side effects

The component function is executed once, so the following side effects are present in the next functions:

  • watch executes a function each time when a reactive value from the body is updated.
  • beforeMount executes a function after data initialization and before DOM nodes mount.
  • afterMount executes a function after the DOM nodes mount.
  • beforeDestroy executes a function before the DOM nodes removing.

The next code

export const MyComponent = component(() => {
    let $state = 'init';
    watch(() => { console.log($state) });
    beforeMount(() => { $state = "before" });
    afterMount(() => { $state = "after" });
});
Enter fullscreen mode Exit fullscreen mode

Will print in console:

init
before
after
Enter fullscreen mode Exit fullscreen mode

DOM

To declare DOM nodes, HTML code is used; it is written directly to the component function body, and it works via JSX. The event handlers like onclick/onpress accept functions as value.

Nodes declarations

Example of button with counter:

export const MyComponent = component(() => {
    let $count = 'init';
    function inc() {
        $count++;
    }
    <button class="btn" onclick={inc}>
        You clicked {$count} times
    </button>;
});
Enter fullscreen mode Exit fullscreen mode

The class attribute accepts a string array as a value; the style one accepts objects as a value.

Callbacks

To connect third-party libraries or to access DOM nodes directly, the callbacks are used. A callback is a function that is called when the element and all its children are mounted in DOM.

Example of callback usage:

export const MyComponent = component(() => {
    function sideEffect(input: HTMLInputElement) {
        input.showPicker();
    }
    <input type="date" callback={sideEffect}/>;
});
Enter fullscreen mode Exit fullscreen mode

Share data between compoents

The data can be shared between components in the next ways:

  • from parent to children via properties;
  • from children to parent via callbacks;
  • from children to parent and from parent to children via slots;

Sharing data via properties

Properties are an object; the field names can match the same rules as variable names, so if the field name starts with $ then the field is reactive; otherwise, it is not reactive.

Example of sharing data via properties:

interface Props {
    userId: string;
    $userName: string;
}
const Child = component(({userId, $userName}: Props) => {
    <div>{userId} is named {$userName}</div>;
});
const Parent = component(() => {
    const id = 1;
    let $name = "First";

    // When the name is updated here
    // it will be automatically updated in the child component
   <Child userId={id} $userName={$name}/>;
});
Enter fullscreen mode Exit fullscreen mode

Sharing data via callbacks

The component function can return data; the returned data can be accessed in the parent component via callback.

Example of using callbacks as an alternative for forwardRef from React:

const Child = component(() => {
    let input: HTMLInputElement|null = null;
    <input callback={element => input = element}/>;
    return input;
});
const Parent = component(() => {
   <Child callback={input => { console.log(input) }}/>;
});
Enter fullscreen mode Exit fullscreen mode

Sharing data via slots

The slots are sending data to the parent component data via properties, and the parent is sharing a DOM description with the child component:

interface Props {
    $title: string;
    slot?(props: { $name: string }): void;
}
const Child = component(({$title, slot}: Props) => {
    <div>
        <Slot model={slot} $name={`${$title} is amazing`}/>
    </div>;
});
const Parent = component(() => {
    let $title = "MyApp";
    <Child $title={$title} slot=(($name) => {
        <span>{$name}</span>;
    })/>;
});
Enter fullscreen mode Exit fullscreen mode

When the child is not sharing data with the parent, the DOM description can be included in the child tag: <Child><span>Text</span></Child>.

Also, inside the Slot tag can be present a DOM description, which will be used if the parent does not fill the slot.

The slot is not just a function; it is a component. The reactive states, derived/computed value, and watch, beforeMount, afterMount and beforeDestroy can be used in a slot.

Logic and loops

There are building components that operate the logic and loops: If, Else ElseIf and For.

Example of conditional text:

const MyComponent = component(() => {
    let $count = 0;
    <If $codition={$count > 2}>
        Count is too big!
    </If>;
});
Enter fullscreen mode Exit fullscreen mode

Example of loop;

const MyComponent = component(() => {
    const arr = [1, 2, 3];
    <For model={arr} slot={number => {
        <div>Number is {number}</div>
    }}/>;
});
Enter fullscreen mode Exit fullscreen mode

The property name rules are applied to built-in components. The If will react to updates of $condition, but For will not react to updates of the model; the model must be updated via push, pull and similar methods.

Conclusions

There is a minimal set of functions to create an SPA. All this is already working; the component styling library is already ready, and scripts for building component libraries and applications are ready. The last updates added the SSG compiling option and a React-like condition for conditional tags as an alternative to If/Else; it fixes some TypeScript errors.

The project is open-source. For volunteers who want to help, we have a Google Form for Customer Development Interview: https://docs.google.com/forms/d/e/1FAIpQLScvoUvCRlbONheW1lu_ZnerXLz1pcGxhKV_q3EKs3d40csXTw/viewform?usp=header

Thanks for your attention!

Top comments (0)