Hey there! Hope you're having a great week!
I'm building Cardboard, a straightforward, yet powerful reactive web framework. You can look at the repo, or the other articles in the series if you don't know what Cardboard is about!
This post is a devlog/changelog about what's happened with Cardboard in the last update. What's been added, changed, and improved.
Changes
State
I've re-written the state to use Consumables
. I decided to do this as the previous implementation of the state was unstable, hacky and not very intuitive to be honest.
Consumables
are custom objects that hold a value, can be listened for changes, and can dispatch new values.
Another inconvenience was that you could only create object/array states, not primitive values. With the new implementation, you can have both.
By using Consumables the new state is simplified. It's also a lot more stable and intuitive.
Before:
let st = state({ count: 0 });
st.count++;
Now:
let count = state(0);
count.value++;
Now we must modify the
value
of the consumable.
There are many more changes, but not worth mentioning as they're not that interesting :P
Additions
States can now be children
To be able to create reactive text before, you had to use the text
function or tag.text
method. While that did the job, it was a pain if you just wanted to add the Consumable value without interpolating. Let's look at an example to better understand what I mean:
Before:
const hours = state(0);
const minutes = state(0);
div(text('$hours', { hours }), ':', text('$minutes', { minutes }));
Now:
const hours = state(0);
const minutes = state(0);
div(hours, ':', minutes);
Consumable Intersecting
When using Consumables, you might want to check if it has a value, if it's empty, if it's a certain value, etc...
For this, Consumables can be intercepted, which means that you can create a new Consumable that updates its value based on another Consumable.
Look at this example:
const temperature = createConsumable(32);
const isTooHot = greaterThanOr(temperature, 30);
div().showIf(isTooHot);
As you can see this can be very powerful.
isTooHot
will be true if the temperature is higher or equal to 30, and false if less.
Thediv
will be shown or hidden based on the value ofisTooHot
.
Let's look at another example:
const TodoItem = (todo: IConsumable<TodoItem>) => {
const isComplete = grab(todo, 'complete', false);
const todoText = grab(todo, 'text', 'Empty Todo');
return div(
input().setAttrs({ type: 'checkbox' })
.changed((self) => {
todo.value.complete = self.checked;
}),
h4(todoText)
.stylesIf(isComplete, { textDecoration: 'line-through' }),
);
}
grab
will set its value to a specified value from the original consumable. Whenevertodo
changes, the values ofisComplete
andtodoText
will also update.
If the input checkbox is changed, it will set the complete property. When this happens theh4
will react and set the styles accordingly.
I'm interested in knowing what you think about this. Do you think the approach I've taken is good? Or would you suggest going another route?
each
The biggest change has been the addition of the each
function. This has taken most of my time and has made me rethink how some aspects of the project work. Most other changes have been made to make each
work properly.
each
renders lists that update whenever the state changes. The interesting and hard thing is that it's smart and will only update the things that need to be updated. If a value is added, only that value is added to the dom. If a value changes places, only that operation is done.
This was a bit of a challenge, not only creating the actions to be performed but also handling memory.
This is how it works:
const colors = state(['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']);
const selectedColor = state('red');
div(
each(colors, (color) =>
button(color)
.addStyle('color', color)
.stylesIf(equalTo(selectedColor, color), { fontWeight: 'bold' });
)
);
- If a colour is added, removed, or changed places,
each
will react and update the list accordingly.
If you want me to write a more detailed and technical article about how
each
works, let me know in the comments and I will get to it. It's pretty interesting!
Improvements
After working on all of the changes mentioned above, and adding more tests a lot of bugs have been squashed, naming has improved, and the overall stability of the project has increased.
Additionally, I've spent a lot of time improving the wiki and docs, as well as the README. I think having a decent Wiki and documentation is key from the get-go.
Summary
It's been a productive couple of weeks. I don't think I've ever spent this amount of time working on a single project (outside of work). But I like how Cardboard is turning out! I think that with a bit more work it can be very powerful and competent.
I'm aiming to have the first stable version finished before the end of the year!
If you find the project interesting please consider helping out!
nombrekeff
/
cardboard-js
A very simple, yet powerful reactive framework, to create web applications. All of this with, no HTML, CSS, JSX, and no required build.
📦 Carboard.js
Welcome to Cardboard. An extremely light (around 18kb), performant, and very simple reactive framework. It offers almost everything you'd expect from a complete framework. Like managing state, components, logic, and the rest. But with a twist, you don't need to write any HTML, CSS, or JSX if you don't want to. See what it can do.
It's similar in philosophy to VanJS, if that rings a bell, but with many more features, and a more extensive API.
!NOTE!: Cardboard is in development, use it with caution.
You can check the v1.0.0 milestone for a view on the development state - help is much appreciated!
const Counter = () => {
const count = state(0);
return button()
.text(`Clicked $count times`, { count })
.addStyle('color', 'gray')
.
…
Top comments (8)
Congrats you invented Vue/Solid.js
Not really, main point of cardboard was to remove the need to write html tags. That was the main point. I hate writing html. I don't know what solid.js is, but Vue requires you to write html tags or jsx. Also cardboard is surprisingly fast, and lightweight at around 20kb. Of course other frameworks offer much more stuff, but most things can already be done with cardboard.
Might seem pointless, but I'm convinced you can learn a LOT doing this (even if you end up being the only user of the framework) ...
Oh, I totally agree with that one.. It's very similar to something I made for a static SSR generator... (just not the state tracking/changing) with everything being chainable. (Which I actually like)..
Not a massive fan of the
each()
part, but I can completely understand WHY it's that way.. But, to be fair, I hate the way React does the same thing with JSX.. I am obviously a very hateful person.. LOL..As a learning endeavour though, this will be invaluable.
I'm also quite hateful if I'm honest, that's why I built it.
each
is simple, something I value a lot. Cardboard is simple. Might seem weird at first but it's very simple in essence.It was more than a learning endeavour, it was an experiment to see if it could be done in another way.
I explain all of this and why I did what I did in some other post if you're interested.
I always thought that a performant vanilla js Framework could be made without all the fuf that most frameworks have. Jsx always seemed excessive to me. And html extremely redundant. So that's why I tried to build this.
I don't know if JSX is "excessive", but what I do know is that React isn't performant, at all - unless as an application developer you make it so ... :D
Yup, most frameworks aren't performant without the developer's help. They add to much overhead and features not used in many scenarios. It can be fixed in some ways using tree shaking and so on, but even then.
Well not excessive per-se, but I've always thought there could be a better way. For me it feels wrong I'm some weird way.