Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Building a front end framework – Reactivity, composability with no dependencies (18alan.space)
171 points by 18al on May 14, 2023 | hide | past | favorite | 78 comments


Enjoy the process of building your framework. I had similar goals when I started my no tooling / no dependencies Reactive framework https://reken.dev, 2 years ago. And I loved every bit of it. My most complicated problems were reactive DOM elements based on nested loops and recursive components. Even though Reken does pretty much what I need and grew to 7kb compressed, I am not 100% happy with the scope of the application state, and it is simple but sometimes confusing. Perhaps your proxy approach can help me here.... I'll have to think about it more.


> My most complicated problems were reactive DOM elements based on nested loops and recursive components

Agreed, I've tried solving it by setting an attribute `sb-mark` which allows syncing just the branch of DOM elements that maps to that particular key in the reactive object.

This removes the need for VDOM diffing, but unless I use a `MutationObserver` external updates to marked branches will probably mess it up.

Haven't yet tested it for recursive components, it should work for nested loops.

> and it is simple but sometimes confusing

I understand what you mean, my approach has the aforementioned `sb-mark` attribute/directive which syncs primitives, lists, and objects.

I've started feeling that the convenience of having just one attribute to remember is supplanted by the confusion of its implications not being immediately apparent from context.


> This removes the need for VDOM diffing, but unless I use a `MutationObserver` external updates to marked branches will probably mess it up.

Similar in Reken. It controls all the DOM; DOM updates outside Reken will get stuff out of sync. After a model change, all managed DOM gets directly updated by a generated controller. It does check the DOM first if a textContent or attribute change is necessary. Most DOM state checks are cheap. Another optimization is that all hidden DOM trees get skipped; Great in SPA apps with multiple pages.


Your arrays and buttons example appears broken - after the first item is added to the todo list, typing new items in the input field leaves the add button looking disabled (although it works when you click it).

I think it only gets enabled when focus leaves the input field.


Thanks for pointing that out. Yes had rewritten it a will need to use a different event to enable the button if there is an update in the input field.


Fixed :-)


I made a similar library [1] using data-* attributes. It also supports nesting, looping and conditions. For event handling, I use function in object (a.k.a. method) while you support writing them inline.

Your way to support inline logic in the text and style is interesting.

[1] https://github.com/beenotung/data-template


Very cool, it seems we almost came up with the same approach of components/templates. In your framework, it is referenced with a data-template attribute. In Reken, I use a data-component attribute.

One of the design goals for Reken was to not have to context-switch while coding, to not lose my train of thought (Guess my short-term memory is limited). Hence try to add everything inline in the HTML file. Also I'm working on a tailwind-like css inlining framework (compatible with Reken). Together these give me dynamic DOM and styling inline.


Wish you'll keep up and see more adoption over time :)


New front end frameworks that claim leaps in simplicity feel like the violate some kind of no free lunch principle to me. If they’re that simple, then I’m willing to bet they make some use cases very difficult or impossible


Depends on the definition of simplicity. People say they want simple, but then really want easy. The most easy is always somebody doing the work for you. I got tired of hearing people mention easy when really they probably mean some combination of fearful and/or lazy, so I chose to define easiness:

https://github.com/prettydiff/wisdom/blob/master/Easiness.md

If developers really wanted simplicity or to be done with work faster they would just learn the primitives of their environment: DOM, functions, and events. Most of the frameworks have APIs that are huge, so clearly simplicity isn't what's wanted.


Thanks for sharing this. I’ve been recently into the concept of Digital Gardens where you evolve concepts and expand them over time (instead of the traditional chronological blogging style) and this is exactly the result that I imagine a page should look like when matured enough.


I would argue rather that the amount of complexity that frontend web devs put up with is more of a Stockholm Syndrome situation. You can be much simpler than most mainstream frameworks, using only standard JS, CSS, and HTML, and acheive better results.

Using custom elements and shadow DOM like this post is a big part of that. Custom elements give you a built-in component module, shadow DOM gives you compositions and style scoping.

I think using proxies like this post is a challenging because proxies are very hard to get correct when dealing with methods, collections, object identity, privacy, etc. but it turns out that many applications do just fine with a simple Redux-like store / action / subscribe system for data.

I personally think the project I work on (https://lit.dev) hits a sweet-spot of simplicity vs complexity because it also gives component reactivity, declarative templates, embedded CSS, with standard syntax and no build tools required. In more than 400 LoC, but only by ~3x.


Rather I feel like there's a now fairly well known set of requirements, and every so often, a new framework comes around and says: "It's simple, we just don't consider this."

For instance, the controllers introduced in Lit 2.0 feel like an admission that you forgot to consider the reasons why React moved away from classes in the first place. The example [1] could be written in less than half the amount of lines with hooks, and isn't any easier to understand. Also, I can't find a solution for skipping expensive computations when re-rendering the component.

Admittedly, I only skimmed Lit's documentation. But with the frameworks that I have tried, I came across cases where I had to either implement my own workaround, or the framework began introducing more and more concepts to fix their fundamentally broken approach (cough Aurelia).

[1]: https://lit.dev/docs/composition/controllers/


Controllers are a much simpler way to hook a component's lifecycle than hooks, and I'm referring to the implementation and conceptual load.

A hook system takes some doing to build, controllers work like:

    connectedCallback() {
      this.controllers.forEach((c) => c.hostConnected());
    }
References from host to controller and controller to host are simple JS references that you can directly see in the debugger.

The React custom hook is a bit shorter, but it's only 13 lines vs 17, and I think hooks are harder to understand because of how much is happening under the hood and the fact that for some reason you need two separate useEffect() calls.


In terms of implementation, I can believe you that they're simpler.

In terms of conceptual load I disagree. In React I'd have to know two concepts: `useEffect` and `useState`. In Lit I have to know the API of LitElement; that it implements a host; the four methods of the controller; the boilerplate for registering my controller and triggering updates; plus all the conceptual baggage of classes.

Further, the hooks implementation considers when an effect should replace itself. For example, if you wanted to have the timeout change dynamically based on the component input, you'd get this for free in react by including `[timeout]` in the dependency array of `useEffect`, whereas with the controller you'd have to do the diffing and replacement yourself.

Then there's propagation of errors from controllers through the tree, where Lit just gives up [1].

Granted, you may not need these features for whatever project you're building, so a simpler framework may be perfectly fine. But don't call it Stockholm-syndrome when other projects _do_ need solutions to these problems.

I want to make clear that I'm not just picking these concepts because they're in React. I've used frameworks before React and I found myself naturally wishing for similar solutions. And React is by no means the only framework with such solutions.

---

[1]: https://lit.dev/docs/components/lifecycle/#errors-in-the-upd...

PS: I was counting 10 Lines in React vs 23 in Lit on the ClockController. Although I would of course be fine with both, if I thought it improved on readability.


Do you have an example of a complex widget being implemented in a simpler way with better results? I see this sentiment often here, but most examples are toy examples or frivolous.


I've found that if they brag about how easy it is to get started, it usually ends up messy relatively quickly. On the other hand, if they brag about being easy to order when you have a lot of code, it's usually a bit harder to get started with.

It's like you get to chose one of "get started quickly" or "remain quick enough on the medium/long term"


Not always, some can hit the sweet spot of being simple in all phases of development and quick to learn. They are super rare though and their lessons are forgotten; industry often standardizes on inferior things with better marketing.


> industry often standardizes on inferior things with better marketing.

It standardizes to what’s cheaper on their eyes, sometimes in a very short-sided way, but also companies that work for profit never know if a project will still be running in the next quarter, right? It makes me think that open source and for profit companies should have very different considerations when it comes to choosing frameworks or technologies for their solutions. I should probably gather more info on that and expand it into a blog article.


What are some examples?


I think Alpine.JS hits this spot pretty well.

https://alpinejs.dev/


jQuery


I’d argue it’s evident large jQuery apps are a pain in the ass to maintain


[deleted]


The law of leaky abstractions - as systems become more complex eventually you rely on more abstractions that try to hide complexity.

What makes it worse in the front-end framework world is that either:

1-Projects become convoluted with 3rd party libs to solve a problem

2-The framework maintainers eventually introduce APIs that aren't backwards compatible and existing ones stranded or deprecated


When discussing this problem with ChatGPT it indeed said the best solution for this is for frameworks to be designed in a way that encourages extensibility: so a framework should stick to its core principles but allow “plugins” to extend its functionality for very specific use-cases. But I understand there’s always a trade off here, as simplicity in the framework shifts the complexity to the application.


One example of a framework that takes this approach is Apache Royale. It’s a reboot of the AS3 language and component library that works like Adobe/Apache Flex but compiles to JS targeting the browser runtime. This refactored Flex was rebuilt from the ground up with a Pay As You Go (PAYG) philosophy where components are composed of „beads“ on a strand that enable composition of the functionality you need rather than the kitchen sink.


I feel like the only things that provide “ leaps in simplicity” as far as web dev goes are basic css frameworks.

Generally they really can save a lot of time / needed structure.


Sure but they make certain things very hard or impossible, usually as things get more dynamic. But CSS has gotten a lot more dynamic capability in recent years.


Web components weren't as widely available as they are now when most web frameworks were written.


I think a lot of stuff is taken wrongly in this article. First of all, React.js is a library that just provides a handful of hooks. "magical HTML and JavaScript" code is called JSX, and that is transpiled by Babel into not-so-magical nested function walls. Imperatively applying mutations to an application state inside a proxy object setter is, in fact, proactive and not reactive. Reactive implies that the renderers are pure declarative functions that are derived from data, so changing the data would result in a different renderer output based on a composition of these pure functions. So, in the end, React.js is just a library that brings a pure functional renderer to the web, and you even need a separate library, called react-dom, in order to manipulate the DOM itself with the outputs of React function. If you want to approach something similar to React, try to abstract away from DOM and HTML, and think in terms of a single pure data function, which returns a tree that can be then traversed into a set of UI elements.


This is extremely similar to https://jhuddle.github.io/ponys/ which I've been using quite happily. The `data-mark="foo"` convention is interesting, similar in spirit to ng:bind or knockout attributes.


This is really neat! I'm going to take it for a spin in a simple CRUD internal app.


Nice write-up, I look forward to seeing how Strawberry progresses. I keep a list of JS front end frameworks where no build step is required at https://unsuckjs.com/. I'll add this there (and a few of the others mentioned in the comments here).


Would you also add these js library to the list? They don't require build step as well.

https://github.com/beenotung/html-template-lite

https://github.com/beenotung/data-template

https://github.com/beenotung/dom-proxy


I added `dom-proxy` because that looked like the closest to a complete solution from a quick skim. Let me know if you want to swap for another one, though.


dom-proxy is the most recent work, I'm glad it stands out to you


Lit 2.x is compatible with IE11, btw.

We're actually removing that in the upcoming 3.0 branch though.


Thanks for the correction. Just updated it to be accurate... for now. :)


As someone who started out in this profession in their early 30s, it was made clear very quickly, both by observation and shared wisdom, that it was completely possible to go one's entire career never getting below the level of gluing business code together with the adhesive of some framework, i.e. never learning how to design a system or think from the 'bottom' of a solution.

While its arguably more accessible than ever, you can quickly find yourself swimming in a shallow pool if you're not careful where your first job is, unless you have the free time and comportment to swim towards the deeper end on your own. And the larger magnitude that this occurs at, the greater the stranglehold of most devs to some well-funded framework and tooling.

So I applaud reminders like this that ask to just take a step back from time-to-time and maybe provide ourselves a new opportunity before reaching for $BIG_FRAMEWORK when the project is a few views w/ some buttons and an input on them. There has to be some balance between the utter pragmatism and curiosity and exploration that builds skills to have a healthy demo of devs.

I routinely see this second-hand classist complaint about 'JavaScript devs ruining software' (particularly w/ Electron in hand, even though the most used Electron apps are probably worked on by high-tier devs) but really the source of that concern is the market desiring that devs be more or less replaceable for the most part and the skillsets follow that. You can't break that without some disruption to the Framework-Industrial Complex.


Nice writeup. We're working on a framework with similar goals, here: https://github.com/frameable/el

Reactivity, composability, templates, etc with no dependencies, in ~150 SLOC.


They literally just described Svelte with that headline.

Front end framework: check

Reactivity: $check

Composability: check

No dependencies: once compiled, check

And pretty sure Svelte (or Qwik or Solid or even React) will perform better than the "dependency-free" custom components. The open secret in the front end world is that custom components as baked into browsers is slower and a major pain in the ass as an API. That's why it wasn't adopted widely and why it will likely never be adopted widely.

The funny thing about stories like these is that once you write the first general use code that isn't based on your specific task, you've created a dependency. Only this dependency isn't improved and maintained by a community or company; its maintenance is handled by you and your team. Maybe you take it on because the benefits outweigh the costs for your team. You improve and refine "just a few functions". Other folks like what you've done and ask to use it. Now they have a dependency on the "no dependencies" framework. Eventually you have to give it a name, and it gets popular.

A few years later, a developer decides they doesn't want any dependencies in their front end code anymore…


Kind of bending definitions to say svelte doesn't have any dependencies once compiled. Svelte itself is a dependency, and though complex and bundled there are svelte utilities included in the app that aren't code directly written by the user. Anecdotally, if also be very interested to see any example of a production Svelte project that doesn't pull in any other dependencies.

The article also calls out no build step, though it's not in the title. That's an important factor for the kind of project that isn't updated regularly and needs to work without any fuss after a year or two of going stale.

I'm a big fan of svelte by the way, been using it since pre-release 2.0 and still reach for it whenever I need a more complex state management or don't have the time to roll my own animation trigger utilities.


Fair points.


No, it is more Vue than svelte. Vue use proxies while svelte doesn’t. That being said I wonder if tying up with web components is a good idea. Cause if it was, other js framework author would have applied it


I love Svelte for all these reasons. But in the end, was turned off by the required tooling.


On a recent project, I’ve just been using JS template strings and .innerHTML all over the place.

I don’t necessarily recommend it, but it’s been a good reminder to me that most of the value React provides me is literally just html-in-JS. In many cases the complexity that comes from React effects and state is unnecessary, and directly mutating DOM nodes is sometimes a lot less painful. Sometimes.


This definitely works until you hit one of a few cases I can think of:

* Adding animations to elements. By blowing away the DOM and inserting new elements each time, you'll trigger any css `animation` for new elements entering.

* Stale data. If your template isn't re-run when some data changes for whatever reason, you'll continue to render the old data. You've got to manage the lifecycle of state updates yourself.

* To counter that, you might just re-run your templates when _anything_ changes. This works until you have a significant amount of data, then performance starts to become an issue.

This won't come up for many cases though, so for simpler apps it's definitely more than enough!


Very interesting. I think this explains a lot of the magic that happens under the surface in the frontend frameworks. I quite like the approach you are using to avoid a build step and also zero dependencies.


Very thin frameworks can avoid the complexities of React and hooks and useEffect and whatnot. Here's an app written with a 500-line "framework", notice how readable and maintainable it is: https://github.com/wisercoder/eureka


Less Framework and more Library it feels like.

Also, wondering if there are other tools for searching code and other internal Corp stuff on external services (GitHub/Lab, Google Drives, etc)


I'm experiencing the trade off between simplicity and expressiveness for a lightweight frontend library that doesn't involve VDOM.

On one hand, a library [1] can be very concise (update dom from object, with looping and nesting supported).

On another hand, a library [2] can be very flexible and reactive (update dom from dom events).

However, when double-binding (a.k.a. bi-directional binding) is required (update dom from object and update object from dom), it seems more complex than I would consider it lightweight.

When double-binding is preferred, I'd rather go for angular / vue. Still exploring alternatives.

[1] https://github.com/beenotung/data-template

[2] https://github.com/beenotung/dom-proxy


Why do we need a frontend framework? I am genuinely interested. The last project we used SSR and the sire ended up fast and snappy using a lot less energy than the previous similar project we used a JS based framework and we had all the functionality we needed.


I think the common argument is "complex interactivity". If you have sufficiently complex custom client-side interactivity (e.g. sending, receiving, manipulating and displaying data in the DOM without constantly reloading the page) then something like React or Vue is much easier and more maintainable than a bunch of custom JS. Logic for mutating and displaying data can also live on the backend and use SSR, but it has to live somewhere. I think people like SPAs because you can draw a convenient boundary at the server level by exposing a JSON API, and folks working in HTML, CSS and JS like the component model offered by frontend frameworks (I know I do).

There's also something to be said for consistency. When I walk into a React or Vue app, I can figure out what's going on and build on top of it quickly. Even if they are using a mish-mash of libraries (as JS apps do) the majority of the time you will see similar libraries and patterns used.

All that said, there are many monstrosities built upon SPA frameworks with poor performance that would likely provide better user experience if they were using SSR. But there were also many SSR monstrosities built before SPAs were in vogue.


This is the most useful comment so far. I tend to agree. However, I still think that most frontend project are perfectly fine with mostly HTML + CSS and a tiny bit of JS.


Projects that don't need a lot of interactivity after rendering definitely don't need one and you can get away with rendering everything in the backend.

Other projects need some additional features that must be implemented in the frontend, but still don't need more than vanilla JS or jQuery.

Others might need more complex components, such as datepickers, carousels, interactive charts, interactive tables, accordions. But even those can be consumed from third-party components without a framework. A middle ground is writing your own encapsulated components.

However there are more complex apps that do benefit from frameworks. It's often because they have a lot of custom components and a framework really helps; and/or because they're not really divided into pages in a traditional web way, so rendering on the backend is significantly harder; and/or they have a lot of shared state between multiple areas of the screen, and not refreshing is easy than caching or re-fetching. All those among other reasons. Slack Web can benefit from this. Your daily CRUD not so much.

Whether people are using the right tools for each job is up for debate. And sometimes you'll have incorrect requirements. But there are definitely reasons to use more complex/flexible tools.


They were extremely useful before browsers had certain features, like web components of the Proxy api mentioned in the article.

Today the argument is usually based on highly complex apps, think complex dashboards accessible behind a login or browser-based apps for recording a podcast. Those are reasonable uses for client-side frameworks - the problem is they are often used for much more basic sites that just need a mobile menu, accordion component, or a dialog modal. All of these can either use entirely browser native HTML/CSS or easily built in JS without any dependencies.


I've worked on quite a few projects that would have had significantly worse user experiences if they were done in a purely SSR driven way:

* Chat applications, or anything where you need to have the UI react to incoming events from a socket.

* Applications where sensitive data lives in the client and you don't want to be liable for that passing through your servers.

* Anything dealing with video or audio (e.g. video chat, screen recordings.)

* Applications that are driven by peer to peer data.

* Applications with high interactivity (e.g. spreadsheets, heavy form validation, drag and drop UIs, graphic manipulation.)

It really depends on what your application is doing. If you're just a blog or an eCommerce site, it's definitely worth asking if you need a frontend framework. But for some applications it's absolutely worth it.


* Applications where sensitive data lives in the client

Isn't JS the worst kind of solution for security related things?


E-commerce of any sort involves sensitive data (PII, credit cards, etc). Plenty of third party payment processors let you build a very nice custom looking check-out flow via provided libraries that interact with popular frameworks. Your server never sees any sensitive info.

None of them require using those frameworks, of course, but they can make life easier.


In what way?



> The second main reason is the ability to define a component and reuse it without having to redefine it every time we need to use it. This is called composability.

Uhm, this isn’t strictly composability. Its reusability. Composability is distinct in that you’re architecting or designing components to be composed with one another. That is, they only know what they need to know and isolate domain. Also, their composition interface is the same as their output interface. A likely outcome is reusable components, but it’s not the goal necessarily.


This is great! I've been looking for a framework with exactly these goals. I've been using vanilla JS and custom elements and feeling like I'm writing too much boilerplate, but all the frameworks I've tried are too heavyweight.


Have you tried Lit yet? It gives you a reactive base class and declarative templates. Plain JS and no build step (or you can use TypeScript).


I played with it a little bit when you told me a few months ago it supports buildless.[0]

The thing that dissuaded me is that it seems like it forces me to write my template HTML in strings, so I lose VS Code syntax highlighting and Prettier auto-formatting. I tried looking for VS Code / Prettier plugins but didn't see anything.

Is there a way in Lit to write the templates in regular HTML rather than a string?

[0] https://news.ycombinator.com/item?id=34828992


The lit-plugin in for VS Code offers syntax highlighting, jumpt-to-definition, etc: https://marketplace.visualstudio.com/items?itemName=runem.li...

Prettier already supports HTML in html`` strings, likewise, CSS.

> Is there a way in Lit to write the templates in regular HTML rather than a string?

This would require a compiler. You would need to load the HTML into the JS module graph and JS can't do that yet, though there is a proposal for it: https://github.com/WICG/webcomponents/blob/gh-pages/proposal...

Template in HTML also have the problem of the data not being in scope as it is in JS, and there not being an expression language. So you ned up having to re-implement a lot of JS embedded into the HTML syntax, which then preferences a compiler-based approach to make fast. It turns out to be a lot simpler to embed HTML in JS.


This is good progress. I hate that i can't do progressive enhancement on pure html form with Frontend Framework.

Example, RoR already provides powerful good library like SimpleForm. Now i want to turn this html form into a React form, how ?


This is certainly new to me and something I'm going to play with a bit. Thank you for sharing this here.


Lost me at web components. What a miserable DX and inconsistent mess that is.


What's inconsistent about them?


Please don't make websites dark mode only. This is terrible for accessibility. I have astigmatism and can't read more than a few paragraphs.

https://medium.com/@h_locke/why-dark-mode-causes-more-access...

If the issue is not wanting to spend a bit of effort to implement prefers-color-scheme then light mode is a much better default option.


I do not have astigmatism (or at least I think I don’t) but I notice that the rows of white letters stay burned in my vision, the contrast was so high it’s as if I am staring at small strong light sources.

So I prefer black letters on light background.


Maybe this is why I cannot read dark mode websites at all. Even coworkers with dark mode themes on their IDE are completely inscrutable to me.


I have astigmatism with the opposite effects. I wish more sites had dark modes. I have to use dark reader to ensure I can have a dark page but that doesn't always work.


Do you use your devices in a well lit room?


Yet another people denying React Hooks has won...




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: