Vanilla-todo: A case study on viable techniques for vanilla web development
github.com
github.com
That said, I don't hate it. For quite some time, I've taken the stance that a web development team needs an opinionated framework, but it's fine for it to be a bespoke creation rather than off-the-shelf. The biggest value of choosing React, Vue, Svelte, etc is, in my opinion, less about it doing the heavy lifting for you with the DOM and more about adopting an established valid opinion to guide the team's development.
With React and particulary svelte (for now) it's a mishmash of possible choices.
Where that bites is when you have 3 choices to make with 4 options.
4^3 === 64 - so any project you pick up/come onto has a 1/64 chance of using a stack you've seen before.
All of the code written with React 15 is compatible with React 16. And React 17. And React 18. And so on probably.
React project in the wild seems to exist in two modes from what I can see, the constantly tended garden, or the write and move on and leave it to someone else to rewrite in future (otherwise known as write only, or abbreviated to perl). /s
IMO that's just poor engineering and not an inherent problem with React. The app I inherited at my current job had many such packages. And we have indeed had to update/replace some of them. However most of them were implementing functionality which could be trivially replicated in "plain react" so we've mostly replaced them with simple internal components and are not anticipating having the same problem in future.
Regardless, the diaspora of packages is just common culture in Javascript in the wild. As a reference, I site leftpad. As a remedy I offer the saying 'its better to laugh than to cry'.. :)
Taking the somewhat-arbitrary but reasonable standpoint that "stable" means that there aren't any major architectural changes required to bring a codebase up to modern standards, I think the best you can say is that React has been stable since hooks were introduced in Feb 2019. So about a year and a half of stability, which isn't horrible but it's a far cry from stable since 2015.
At $DAYJOB we have no intention to rewrite our older class based code to hooks (except where we're otherwise making significant changes to that bit of code), and indeed we're still writing some new code in the class style.
If you feel like you need to upgrade then I think that on you, not the framework.
Another reason is the ease and speed of online documentation/resources. We all use StackOverflow to get answers and insight into problems we face on a daily basis, and as the framework (or library) progresses, the majority of Q&As adopt it, giving us a wealth of resources at our disposal.
While React apps in 2015 probably still work with the newest versions of React, you can't put a 2015 React dev in a 2020 project and vice-versa as if nothing's changed.
Our app is still using Redux, so I guess that one hasn't hit us yet (but there's also no real reason for us to update to a newer method).
This is often the case in practice. But it's a pretty easy problem to avoid. Usually those packages simply aren't necessary in the first place.
https://blog.isquaredsoftware.com/2018/03/redux-not-dead-yet...
https://blog.isquaredsoftware.com/2020/10/presentation-state...
Sure, it's definitely _peaked_, because there's a lot of other great options in the React ecosystem these days. But, there's still plenty of good reasons to use Redux. And, with our new Redux Toolkit package and the React-Redux hooks API, "modern Redux" code is a lot different than what you've seen in the past, as shown in our new "Redux Essentials" tutorial:
https://redux.js.org/tutorials/essentials/part-1-overview-co...
Admittedly those are trivial renames, but they've been renamed as such because they're very likely to be buggy with async rendering.
Even setting that aside, there's nothing keeping older developers from switching to a newer technology; it might be a bit bothersome to adapt to ultimately inconsequential changes again and again, but that applies to younger devs just as much.
It's also not like web development is old tech in any way. There's constant improvements being made and things are often changing for the better.
Well, for the most part at least. Looking at the situation with serverside resource compilation I have to wonder when the world of web will re-invent make and call it a revolutionary achievement.
However, if your "micro framework" follows language idioms and is thin enough you get the best of both worlds, whilst releasing yourself from some of the downsides that come with using a general purpose framework.
I'm amazed at the number of developers who can't seem to cope with working with custom in house frameworks. I'm talking about fully working systems, with full source code and being walked through the code by the author / maintainer.
They fall apart, constantly complaining that the approach is non standard, deprecated, dangerous, unprofessional, untestable.
So we are trying more and more to use frameworks, just to be able to hire more easily.
Ideally you could hire senior developers with the skills to work inside boutique software, but I have found "legacy" code turns a lot of people off a project, and most boutique software eventually gets called "legacy" even if it's well architected and running perfectly fine.
The skill which I think new devs could differentiate themselves with, is debugging. Familiarising yourself with software patterns definitely puts you ahead, but being able to use debuggers to understand code that has no pattern, that's when you become the kind of bug-squasher/problem-solver that projects like that require. If you have good debugging skills, you can work on any project, because you can find out all the information you need by stepping through the code.
Now IE11 isn't even an excuse because $current_year ES version can be transpiled to older ES versions.
Often a custom micro-framework better suits the needs of a particular project.
I've written micro-frameworks for specific projects intentionally, because they did a few things that the established frameworks either didn't do, or it was very difficult to get them to do.
An additional benefit was that using the micro frameworks ended up being simpler, and the startup time was much, much faster.
Whether or not you use an existing framework depends on how much effort it is to write and test a custom framework vs the amount of effort you'd need to put in to use an existing framework.
The ones I've written have been pretty quick to develop, and were also intended to be used for a few different projects that had similar needs.
Edit: Just for clarification, the micro frameworks I've written are server-side, if that makes any difference.
Fitting the needs of a particular project is frequently a local optimum however. Often, it's much more optimal to focus on the needs of a whole team or even whole company. You can hire people who already know React/Vue/whatever, but there is no one in the world who knows your micro-framework.
Rails vs Sinatra is a great example.
> I've written micro-frameworks for specific projects intentionally, because they did a few things that the established frameworks either didn't do, or it was very difficult to get them to do.
Is a false statement in most projects. I'd even argue in all projects except those that have a very strict limit on time or memory usage (or a legal one).
Can you share an example?
I have this strong suspicion that most projects that use something like React don't really need it. Like, if you're creating something that actually acts like an application and data might be shown in many places it makes a lot of sense, but for something that's just displaying data from a db on a page hit it can be massive overkill.
Different requirements allow for different settings. Coming from an Enterprise Application background, I found this being more often the case or the only case. Working with different teams, developer fluctuation, different time zones, different skill sets, a framework guides the development process far better. Vanilla JS is something that should be avoided in team setups. There is too much mental overload that is usually not justified by the benefits. It is like deliberately choosing assembler language when Java or C# would do the job better. Frameworks got a bad rep. For me, they are tools that should be used.
Assembly languages come in many flavors whereas C is a single, standard abstraction over these.
DOM is a single, standard API whereas frameworks/libraries come in many, ever-changing flavors and provide different abstractions over the DOM.
It's a very different situation. Also, the level of abstraction that C provides over assembly is amazing. The level of abstraction that React provides over the DOM is comparably low (they are based on the same programming language).
It became obvious it would become a side project of its own and not a smart use of my time. I settled down for Svelte and I love it.
I would question the choice to use ES5, as it makes it significantly more complicated to handle dependencies between modules in a scalable way. I understand the point of avoiding a build step, but if the code can run in modern clients without it I don’t think it breaks the spirit of the project to use a bundler to support certain older browsers. It’s a lot like using polyfills when needed.
I love the motivation behind the repo though. The author’s write-up is fantastic and refreshingly reasonable in the dogmatic webdev world. It gives great insight into the places where real value _is_ provided by frameworks and build tools
> It’s a lot like using polyfills when needed.
Never thought of it this way, thanks for that! As is state in the conclusion, the study would likely be more convincing with ES6 and build steps. So yeah, ES5 is questionable :)
As for ES5 support, would you consider using something like Babel to be close enough to using polyfills to consider them? Transpiling ES6 into ES5 fixes your compatibility issue with ES6 just like I would argue adding a polyfill for WebP images would.
However, this number will only continue to decrease as time goes on, and it also has to be questioned for any specific product whether that number is bigger or smaller on average.
Both for the (near?) future when ES6 is near-universal and for projects that have the luxury of just ignoring older browsers even today, it would be nice to have a proof of concept project like this that uses modern ES6 features, since many of them address precisely the types of problems that many pre-processors also fix.
Template-strings, custom elements, etc. add a huge amount of possibilities for web-application development and imo should get way more attention outside of a small circle of excited people.
The only pain point of not building during development is when I need to pull in a third party dependency (since node and browsers pull from different sources; and import-maps is not is still not a standard), but if your are not using any third party dependencies, you should be fine.
Using ES5 is pretty much always a mistake in 2020.
That being said, the page still didn't load properly for me when I actually tried it in IE11, but it's not because of Object.assign.
Nobody writes ES5 because they want to; many developers are still forced to support Internet Explorer for one reason or another.
I think this statement deserves some serious scrutiny. In many cases the performance hit may not be relevant or noticeable. I know this because I've been very productively using the following approach to build dynamic webapps for my personal use:
1. Compose HTML strings using Javascript, especially with string interpolation
2. Slap the resulting strings into div/span elements with innerHTML= statements.
This approach results in extremely clean and simple code - much cleaner than the OP's code in my view. I have never noticed any kind of performance issues, the updates are always instantaneous. I don't know what the author means by breaking functionality like text selection and focus, but it's never been relevant for me.
For an example of the coding style, see the reDispActiveTable in this code, which draws a table of TODO list items with some operations like edit/delete/mark complete. https://github.com/comperical/WebWidgets/blob/main/gallery/m...
And that's why reconciliation algorithms became so popular. You can't be serious when you say that losing input focus and text selection have never been relevant to you. Basically, dealing with the internal state of components down the tree becomes a nightmare.
https://codesandbox.io/s/zealous-vaughan-qd3gg?file=/src/ind...
A couple possible problems off the top of my head:
1. CSS transitions won't work if you re-render a complete chunk of HTML instead of toggling a class.
2. <a>, <button>, <input>, etc. may lose focus or even data on re-render (e.g. while filling out a form).
3. Text selection may be reset on re-render.
4. If you don't use event delegation, event listeners may need to be reattached.
I agree that for raw display .innerHTML may be sufficient, but it's surely not in general. That would make React almost irrelevant, by the way, which would be a huge surprise.
I'm not sure I personally agree with this blanket statement. Connection times can be, for a vast majority of users, measured in tens of milliseconds, giving us a pretty good budget for processing and rendering while still appearing to be "instantaneous" (100ms) or "fluid" (1s). Even the worst case connection times (satellite & mobile) are still measurable using hundreds of milliseconds.
The worst ranked countries still average in excess of 1.5 Mbps transmission rates - more than enough for compressed text.
And, given how unresponsive so many "top 100" SSA pages are (such as blogs that take whole seconds to display their initial content), I can't agree that doing that processing on the server would actually be less interactive or responsive.
Even the test application from this article can load from scratch in under a second, most of that time being the DNS resolution and the server processing for serving the page.
You cannot hope to get enough uptime and guaranteed response time ranges for an app's interactions from any server; you will always get inferior UX to client-side rendering (which, in a way, has 100% uptime and guaranteed response times only bound by CPU/RAM/bugs in your code).
Again, the performance bar for SPAs has been set so low by top-100 sites (like Medium and other SPA blogs) that a server powered forms would have to work very hard to be worse.
As a side note, we can’t forget that even these “100% uptime” SPAs in the real world largely still rely on requests and responses from a server backend; still rely on prompt responses to their ‘XMLHttpRequest’ calls (hello, animated spinners!).
A major result of the study is greatly reduced bandwidth (and consequently, shorter parse time) compared to the original TeuxDeux, so I'm working towards respecting these resources, for what it's worth.
To be clear, the study does not care about doing SPAs or not. The results are applicable to server-rendered HTML as well (write a function that enable some behavior on an element, mount by class name, done). I agree that many use cases (e.g. blogs) should mostly be server-rendered and only progressively enhanced with JS for some UX improvements.
But highly interactive apps (drag & drop just being one example) will not have comparable UX without client-side rendering. I do not want a 100-1000ms delay (or an error message) after dropping an item in some list because of a server-roundtrip. This is not good UX.
Also, when filling out a form, I do not want to lose data or context when clicking submit while I'm in a tunnel. I'd rather have a client-side rendered UI that keeps my context and tells me "Sorry, try again when you're connected again".
Even better if it works fully offline and syncs the transactions I've done with a backend once I'm connected again.
WRT losing data by filling out a form, that hasn’t been an issue for years now (except when the “smart client renderer” decides that it is). Most browsers will not lose data in an interrupted form transmission.
Plus, most browsers (especially mobile ones) handle interruptions like a tunnel fairly well, waiting for the connection to return without losing data. And without having to think about that usecase as a web developer.
It’s funny to me that you mentioned slack above; some of my favorite old-school chat experiences were server-side rendered pages. They worked remarkably well for the limitations they faced.
All this said, the proper compromise is probably doing both client and server-side rendering. I’m reflexively against client side rendering, because of how they’re typically implemented: slow to download up front, each SPA creates its own interaction primitives, and finally the interactivity of an SPA is only rarely required and yet they’re used everywhere (read-only SPAs are the worst).
Javascript - and client-side rendering - is the power hammer of the frontend development world.
But totally agree with the last parts - currently, typical SPA implementations are often misguided and create more problems than they solve (if any) compared to a server-side approach. That does not mean that pure server-side is always enough to provide good UX, especially with interactive/offline apps.
In the real world my crusty old-fashioned drag-and-drop works way better than pretty much all "SPA"s (who are only trying to display plain text) on low/spotty connection. SPAs usually fail to display anything but a blank screen when there is a slow connection.
Losing form data hasn't been an issue in forever... The browser saves it when you go back or forward. You don't even need to press "back" - you can even just refresh the error page and as long as you click "yes" to the pop-up telling you you're resubmitting a POST, the form will submit with the original data entered, no problem.... Of course, SPAs like to break this for no reason, stop doing that.
I remain convinced that for 90% of websites, using React is shooting a sparrow with a cannon.
I hope to leave productive feedback later.
I do hope people don't miss the point that I think OP was trying to be modest about. This study is not for or against frameworks, it is a detailed and informative reflection of the current state of client JavaScript.
so yeah, props to the author for making a proposition for something that would work.
but nah, frontend work this days is about making everything complex from getting the project running to the build steps and even deploying the project.
though one area, I will say frontend is now better on is testing: cypress, jest and react-testing library are nice things to work with.
no framework js = topic of this link
vanilla = a bean, or a flavor
Like a "vanilla kernel" is a Linux kernel without (distributor) patches. Or a "vanilla debian" is a Debian system without third party repositories or software.
Regarding parent, I could say... you cannot make a permanent To-Do web app, with HTML+CSS... drag and drop? data persistence? you need a backend (and JS for event handling) or as in this case local storage via JS.
OK, you can make a simple To-Do with static forms being sent to a backend... but then you have HTML+CSS+something (java, python, php, ruby, perl, golang, whatever), not only HTML+CSS.
Because unless I'm misremembering things, HTML 2.0 (literally the first version intended to be a standard going forwards) came out exactly the same year that JS dropped in a commercial browser (1995), and superseded the previous HTML and HTML+ markups.
So surely you don't mean HTML with things like file uploads, or GIF support. Because that's not "vanilla" in your world.
/s
I know I'm nitpicking here, but I always find this phrase confusing, and I think this way of explaining unidirectional data-flow is problematic for those new to the concept.
If you want to persist something from a child component, the message that represents the action contains the data needed to make that state change, which sort of exposes the flaws in the up/down analogy.
Unidirectional data flow doesn't have an "up" and a "down". It doesn't even follow a single path. It's more like a ladder with several water slides connected to it. The pool is where the state lives. The slides are the child components. The people are the data. Climbers are performing state propagation. People who are sliding are creating actions. People who are landing back in the pool are updating the state.
Edit: no, it's not lost on me that the example I ended up using involves up and down motion--it's just not a very easy concept to convey using real-life analogies (which I think also contributes to the learning curve).
A major weakness seems to be my choice of ES5. I wanted an almost absolute minimum, which ES5 seemed to be at the time. I was lead by the fact that most bundlers produce ES5 by default, which may very well have been a mistake.
Interestingly, if ES5 is really dead (which it might be, I'm not sure) and ES6 is the minimum target, the study's results would actually improve drastically (less verbosity, actual modules, etc.) and further support the claim that vanilla can be maintainable (even without build steps). For anyone interested, let's continue the discussion here: https://github.com/morris/vanilla-todo/issues/6
I do my wordsandbuttons.online in similar spirit: no dependencies, and all the pages are kept below 64KB. However, as the code base grows, I'm starting to employ scripts to do the grunt work for me. So while I don't have dependencies per se, code patterns become dependency it its own right.
el.innerHTML = [
'<div class="items"></div>',
'<div class="todo-item-input"></div>',
].join('\n');
I wonder why aren't they using `DOM.createElement` instead of innerHTML since they will query for those nodes later anyway.The neat thing is that it starts from scratch and adds things, like pub/sub, then models, controllers and so on.
It seems that you are leveraging the built-in event system for observers and using the dom for referencing your components. Personally, I don't feel that mvc for the web is inventing a whole lot more, just patterns and some utilities.
Having events that bubble from the child component up to the app component is an interesting approach. It simplifies the code by not having to pass controllers into views, but makes the child components less reusable in other contexts.
Btw, this structure also reminds me of backbone(1) and that riotjs 1kb blog post from ages ago(2).
I love the effort and thank you for making it.
(1) https://backbonejs.org/#View
(2) https://muut.com/blog/technology/riotjs-the-1kb-mvp-framewor....
And that makes "vanilla" browser apps easier to write too.
I was looking for a minimal, simple and user-friendly app for daily task management, so I developed Renoj.
Fast to-do task management in Desktop for ultimate productivity.
Website: https://ribal.dev/renoj
If not have you considered trying to update the repo to use them
This is probably thin ice, especially since I've never built anything with WC, but they always seemed slightly over-engineered. I will try to learn about WC a bit more and maybe elaborate on my reasoning here.
Web components are supported in all current browsers. Unless you support IE11 you don't need to worry about polyfills.
First write the app in spaghetti code, then turn it into pure functions.
var app = todoList(data);
document.documentElement.appendChild(app);(M) Model is your `data`. (V) View is what your function returns. (C) Controller is the functions your elements may use to mutate the data.
I saw a draggable package on mom that is less than 2k. Does Vanilla mean we can't use npm or we pack or browserify or anything?
Remember that 44K is unminified, unoptimized, with considerable duplication, and includes HTML, CSS, JS, and SVG icons.
Proceeds to literally invent a custom framework.
It’s interesting, but I’ll stick with Vue/React.
What definition do you have of “framework” if you say that? I just see a bunch of procedures that create and manipulate DOM, coordinating through regular events and selectors. There are patterns and loose organizing principles, but no framework code that I can see.