Close search results
Close search results
KnockoffJS - Because the world needs another JS framework

Rolling Your Own Frontend JS Framework

For fun and (no) profit

(TL;DR I built a framework just for fun, please do not use, check it out here!) There is no shortage of frontend JS frameworks, and they seemlingly never keep coming. Trying to keep up with every new framework on the block for the last decade or so has proven to be an exercise in futility, resulting in what can be best described as framework fatigue. But instead of trying to fight about which of the frameworks released this week is the best (and why everyone should make the switch), it could be a fun experience to approach the subject from a different angle by building your own framework. Let's dive in.

Why?

I found building a simple JS framework is good for several reasons:

  • You will learn how a framework actually works (because magic is not real)
  • You will understand some of the tradeoffs that frameworks have, why they exist, and how to deal with them
  • You will learn that building a simple framework might actually be easier than you think!

After you built your own thing, you should have a good understanding of how a framework gets the job done, and what design decisions need to be made when building one - and this should also answer the question older than time itself: Why are there so many frameworks, and why are they all sort of the same but all a little different??

So a framework has several building blocks and concepts. But what we generally need is:

  • Templating - so that there will be HTML to show on the page.
  • Reactivity/data binding - so that when we interact with the page, the framework picks up what we do and can update itself accordingly.
  • Some general rules/guidelines around structure - Such as: What is a component, how do components interact, how is CSS handled, etc.

The ideal framework

Different frameworks take different approaches to all this. For example, Angular has templates in separate files and offers two-way binding. React on the other hand, has the component and the template in the same file, and uses one-way binding. Also, Angular uses HTML with some special "template language" syntax thrown in (such as [(size)]="fontSizePx" AKA "Banana in a box") while React sticks to JSX.

So how would we like the ideal framework to behave? And this is where you mileage may vary! But in my case, I think it would be great if we could have:

  • Native technologies as far as possible, but flexibility when required: Plain HTML, plain JS, plain CSS should all work. Generally, if you know the web, you're good to go.
  • Minimal tooling. Webpack and time consuming builds should not be needed.
  • Ease of use. No weird JS trickeries, no new template syntax to learn. Especially, updates to the state should "just work" and not require the developer to understand new concepts (yes, I'm looking at you React, with your useEffect and other hooks)
  • Good performance.
  • Lightweight and small.

So that would be ideal in my world.

How it turned out

As I pretty much expected, I immediately ran into problems when I tried the above.

  • With just plain Javascript, I kept running into runtime errors because there is no compiler that tells you that you are making simple errors (using wrong variable names, types, etc.). Also, concepts such as abstract classes or generics don't exist in Javascript (yet). We can solve these problems by switching to TypeScript, ensuring a better developer experience - but it also requires at least building using the TypeScript compiler. So, not really plain JS, and some tooling, but a reasonable price to pay in my opinion.
  • Avoiding special templating syntax turned out to be tricky. We could just render the HTML per component as a plain string, and on each change, replace the HTML. I found that this would lead to problems such as child components being re-rendered and inputs loosing focus in re-render. By using a basic template syntax, we can make sure to re-render only what is needed.

So I ended up with this:

  • TypeScript instead of plain Javascript.
  • A component is simply a web component - the "native" idea of a component that both a browser, as well as a developer, can understand.
  • Each components extends an abstract base class and needs to define a state model, that can be accessed using this.state.
  • A simple templating syntax that is inspired by KnockoutJS. The basic idea is that we simply use the attribute data-bind on any element, and then set the property, just like you would in the DOM. For example, if your state model contains a property called name, then you display it in a span by: <span data-bind="innerText: name"></span>

Using the framework, I then set up a page that explains how the framework works, and also contains a few demos: KnockoffJS.

If this sounds a bit like Lit, then - yes, there are certainly similarities!

When it comes to reacting to changes, I opted for using Proxy (MDN documentation). Using Proxy allows us to detect when we are setting or getting a value from a "proxied" object. So after setting the state model, the object is wrapped in a Proxy so that assigning a new value to anything in the state can be detected and trigger a re-render.

For good measure, I added a simple service locator, a registry that can be used to share styles, and a (very) simple router. This all weighs in at less than 5kb (gzipped, non-minimized).

Can I use it?

I want to point out once more that this was not much more than an exercise, and while the framework could be used for building something, I would not recommend it. It has several obvious flaws that I don't plan to address at the moment. Here are some examples:

  • Every time something in the state is updated, any child components rendered in for-loops are destroyed and re-rendered, which might be slow and also the state of each child might get lost.
  • Updates are not batched, so changing the state several times e.g. in a loop will re-render on each change.
  • Basic functionality is missing, such as some sort of if construct in the template syntax. Instead, elements must be shown/hidden by CSS.

Conclusion

Now, this is hardly a framework I would recommend for use in production. But as a learning experience, I found it quite inspiring. And if you try something similar, maybe you have other priorities than I did? Maybe you would rather use React-style hooks, such as useState, for state handling? Or separate JS and HTML? Or a virtual DOM with diffing? The sky is really the limit here, and once you start thinking about these things, it will soon be obvious why there are so many frameworks and why they all have different solutions to what seems to be the same problem.

Happy coding!

Leave a comment





This will just take a second.

Submitting your comment...
Page Theme: Dark / Light
Erik Moberg  2024