
A little bit of plain JavaScript can do a lot - ingve
https://jvns.ca/blog/2020/06/19/a-little-bit-of-plain-javascript-can-do-a-lot/
======
wolfgang42
I agree about the nuisance of creating DOM elements. innerHTML is OK if you’re
doing static content, but for anything that needs to be dynamic (untrusted
input, event handlers, etc.) I have a little tiny helper library that I carry
around in my head and write into projects that need it:

    
    
        const $T = text => document.createTextNode(text)
        
        function $E(tag, props, kids) {
            const elem = document.createElement(tag)
            for (const k in props) {
                elem[k] = props[k]
            }
            for (const kid of kids) {
                elem.appendChild(kid)
            }
            return elem
        }
    

(Add flourishes as necessary for things like optional arguments and allowing
falsey kids.)

This isn’t as nice as JSX, but it makes a reasonably ergonomic API for quick
little projects. Here’s what the example in the article would look like
(admittedly it’s a bit easier to follow with syntax highlighting):

    
    
        const button = $E('button', {}, [
            $E('i', {className: 'icon-lightbulb'}, []),
            $T('I learned something!'),
            $E('object', {data: '/confetti.svg', width: 30, height: 30}, []),
        ]);

~~~
pcr910303
A bit more minified/modern version of this that I'm using:

    
    
        function $e(t='div',p={},c=[]){
          let el=document.createElement(t);
          Object.assign(el,p);
          el.append(...c);
          return el;
        }
        
        var $t=document.createTextNode.bind(document);
    

That's 173 bytes not minified, might be useful for someone.

Interestingly, the function names are exactly the same - I guess people think
similarly :-)

~~~
PetahNZ
Isn't clarity better than bytes. We can always running through a minifier at
build time.

~~~
pcr910303
A minifier is better, but I find that snippet of code clear enough for
personal use (el is element, t is tag, p is props, and c is children).

The small bytes also mean that for simple sites I can just copy paste it
before transitioning to a real setup.

------
userbinator
_I’ve never worked as a professional frontend developer, so even though I’ve
been writing HTML /CSS/JS for 15 years for little side projects, all of the
projects have been pretty small_

I'm pretty much the same, and one thing I've noticed which continues to both
amuse and sadden me is the fact that those whose main focus is _not_ web
development often make better sites/pages than "professional" developers. I
once rewrote, in a few hours, an internal site that had been developed by
another team over a few weeks but with many problems (it was an enormous SPA),
and made it so much faster and easier to use that once my coworkers found out,
they all switched to using mine. That didn't go over well with the team who
built the original...

Another experience I've had with a dedicated frontend developer:
[https://news.ycombinator.com/item?id=18486124](https://news.ycombinator.com/item?id=18486124)

~~~
onion2k
The only way your comparison works is if your point is "Framework driven apps
are _never_ good". I've lost count of the number of times I've pointed out
that a good vanilla JS is _always_ going to be better than a bad framework
driven app. One is a good app and the other is a bad app. It's obvious, and
therefore not a useful comparison.

The valid, _useful_ test is whether or not a good vanilla JS app can be better
than a good framework driven app.

~~~
mjburgess
Yes, but the choice of approach isn't outcome-neutral.

There is also the question of whether going-vanilla more often results in good
apps.

I'd go so far as to say the "naive approach" for most things, whenever it
delivers on all the requirements, is always the best approach.

Be it relational (vs., nosql); grep vs elasticsearch; etc.

~~~
onion2k
The naive approach if you're a developer who knows React is to use React for
everything.

------
andrewstuart
I'm an experienced React developer.

I wanted to try out writing a plain vanilla JavaScript application - I enjoy
plain JavaScript, it feels close to the metal.

It wasn't long before I was craving an application framework that allowed me
to cleanly organise and structure my application instead of it rapidly
becoming a spaghetti.

I also craved the ability to write small simple functions for making
components.

And I wanted a sensible way to organise data flow through the application.

And I really, really wanted to be able to write in a declarative programming
style instead of imperative - this is something you don't come to appreciate
fully until you've lived it.

And I don't care what anyone else says - I love JSX - it's totally makes sense
to me.

So despite a solid effort to try to go to vanilla JavaScript I've decided that
a really good JavaScript application framework - whatever one you like - is a
good thing. I'll leave vanilla JavaScript to the birds.

~~~
jonny383
> it feels close to the metal.

Please tell me you're joking?

~~~
rasz
I mean he is a React developer after all, less than 3 levels of abstraction is
greased lightning.

~~~
andrewstuart
As a React developer I have the strictest policy to never go lower level than
the browser and I _never_ touch other fields of computing. I started my
computing career in 1986 knowing only React and I'll get to the end of it
knowing nothing else.

~~~
avery42
Damn, you must be the one scooping up all those 10+ years React experience
jobs :)

~~~
divbzero
I’m giving GP credit for the joke :) it’s hard to mistype 201X as 1986.

------
yesenadam
When starting to learn/write JS, I found a few of these basic name-shortening
functions from HN's own JS very useful, and well-named:

    
    
        function $(id) { return document.getElementById(id); }
        function byClass (el, cl) { return el ? el.getElementsByClassName(cl) : [] }
        function byTag (el, tg) { return el ? el.getElementsByTagName(tg) : [] }
        function allof (cl) { return byClass(document, cl) }
        function hasClass (el, cl) { var a = el.className.split(' '); return afind(cl, a) }
        function addClass (el, cl) { if (el) { var a = el.className.split(' '); if (!afind(cl, a)) { a.unshift(cl); el.className = a.join(' ')}} }
        function remClass (el, cl) { if (el) { var a = el.className.split(' '); arem(a, cl); el.className = a.join(' ') } }
        function html (el) { return el ? el.innerHTML : null; }
        function attr (el, name) { return el.getAttribute(name) }
        function tonum (x) { var n = parseFloat(x); return isNaN(n) ? null : n }
        function remEl (el) { el.parentNode.removeChild(el) }
        function posf (f, a) { for (var i=0; i < a.length; i++) { if (f(a[i])) return i; } return -1; }
        function apos (x, a) { return (typeof x == 'function') ? posf(x,a) : Array.prototype.indexOf.call(a,x) }
        function afind (x, a) { var i = apos(x, a); return (i >= 0) ? a[i] : null; }
        function acut (a, m, n) { return Array.prototype.slice.call(a, m, n) }
        function aeach (fn, a) { return Array.prototype.forEach.call(a, fn) }
        function arem (a, x) { var i = apos(x, a); if (i >= 0) { a.splice(i, 1); } return a; }
        function alast (a) { return a[a.length - 1] }
        function vis(el, on) { if (el) { (on ? remClass : addClass)(el, 'nosee') } }
        function noshow (el) { addClass(el, 'noshow') }
        function elShow (el) { remClass(el, 'noshow') }
        function ind (el) { return (byTag(el, 'img')[0] || {}).width }
    

[https://news.ycombinator.com/hn.js](https://news.ycombinator.com/hn.js)

~~~
ehnto
The class related functions have been made native by the classList property of
elements.

    
    
        element.classList.has('someClass') //true if present
        element.classList.add('someClass') //add to element
        element.classList.remove('someClass') //remove from element
        element.classList.toggle('someClass') //remove if present, add if not present
    

I would also argue element.querySelector and element.querySelectorAll are
plenty shorthand to replace the selector helpers!

Not saying HN should rewrite their JS obviously, just for anyone reading this
and wondering if there are native equivalents.

~~~
rasz
querySelectorAll is a lot slower than old school functions. You would be
tempted to say who cares, but then you load Gmail and wait 2 seconds for UI to
render.

~~~
kevincox
Do you have any references for this? I would be flabbergasted if the simple
case of #id and .class weren't optimized to be basically identical. (the only
difference being that they had to do a quick "parse" before jumping into the
optimized path)

~~~
rasz
[https://jsperf.com/getelementbyid-vs-
queryselector/25](https://jsperf.com/getelementbyid-vs-queryselector/25)

tldr just looking for class x10 slower.

~~~
kevincox
Trying that on Firefox mobile and they are all within 5%. The direct calls are
faster but that could conceivably be the time to check for special cases with
a DOM that small.

------
jjcm
Nice work! The only advice I’d give is don’t use patterns like

    
    
        div.parentElement.parentElement.foo()
    

It’s inflexible and will break if you ever make dom changes. Use the
.closest() selector instead. It’s like a reverse .querySelector (.closest
selects ancestors, .querySelector selects descendents) which means you’ll have
more specific code.

~~~
soylentgraham
TIL about .closest!

From the article.

------
identity0
It’s a little funny seeing vue/angular/react/whatever people discover vanilla
JS. I thought classList, innerHTML, and querySelectorAll were all extremely
common knowledge.

~~~
monoideism
If I'm not mistaken, she's a systems programmer. More low-level stuff. She's
definitely not a frontend/React/vue.js dev, as she says in the article.

She's written some excellent articles on lower-level programming.

But I agree that VanillaJS is not appreciated by a large number of web
developers.

~~~
bitwize
That's because in order to build anything of any remote sophistication, you
_need_ a framework, otherwise you are committing to maintaining an
unmaintainable spaghetti mess. Just like nobody actually built Windows
applications with just the Windows API -- they all used frameworks like MFC or
reimplemented those frameworks in-house.

~~~
bigiain
> in order to build anything of any remote sophistication, you need a
> framework

Sure. But does the project/requirement described in the original article sound
particularly sophisticated?

I note that HN loads less that 150 lines of js and no framework. While there's
an argument to be made that this place's frontend code is "not remotely
sophisticated", it without doubt provides a huge amount of value anyway.

While I agree with everybody here about the requirements for good frameworks
(and good development practices) for complex or sophisticated web
applications, to me at least it's abundantly clear the author of the article
made the correct choice for her problem domain.

Some things should resist every effort to make them more complex or more
sophisticated than they need to be. Most things probably.

"Perfection is achieved, not when there is nothing more to add, but when there
is nothing left to take away." \-- Antoine de Saint-Exupery

I strongly believe that, while he did not know it at the time, he was totally
talking there about lines of code.

Bill Atkinson clearly took that philosophy to heart:

"He was just putting the finishing touches on the optimization when it was
time to fill out the management form for the first time. When he got to the
lines of code part, he thought about it for a second, and then wrote in the
number: -2000."

from:
[https://www.folklore.org/StoryView.py?story=Negative_2000_Li...](https://www.folklore.org/StoryView.py?story=Negative_2000_Lines_Of_Code.txt)

~~~
spion
The issue is that on regular projects/products, you are trying to hone on
perfection by adding and removing a bunch of things and trying different
stuff. This repeats for a longer period of time, say 6 months to a year.

The codebase has got to be able to survive that churn in a sensible way, and
certain tools like (ES6) modules, types and declarative UI descriptions that
automatically update to reflect the changes really help with this process.

Why does this declarative approach help? Because the more features you add,
the more places can generate events that cause updates to the same DOM, and
the number of possible transitions between those DOM states grows a lot
larger. If the number of possible states that the DOM can end up in is N, the
number of possible transitions is N^2.

To a first approximation, declarative frameworks scale linearly with the
number of interacting features, while imperative (the vanilla DOM API) scale
quadratically.

------
bbx
Of course you can do quite a bit with JS. Some of the projects I’ve made

You can swap CSS classes in JS. But how do you know which classes are
available?

You can target HTML elements in JS. But how do you know which elements are
available?

You can set “.innerHTML”. But how do you save the state of the application?

As soon as the app grows a little bit in complexity, you’ll end up building
functions to keep track of things and functions to perform repeated actions.
You end up building a tiny framework that only you know how to use. When it’s
time to debug and grow, you’ll need a proper JS framework anyway.

The thing I would say about popular JS frameworks, and which I’m surprised
about, is how they favour single page app structures rather than “drop-ins” to
enhance a page. I’ve yet to see React or Angular be used as part of a page,
which is crazy because they would enhance the developer experience so much,
without disturbing the classic way of delivering pages server-side with
classic routing and classic user experience.

~~~
ehnto
There are other frameworks that do favour a "drop in" style of development,
with native Web Components being one of them. I used Riot for this for a
while, and tried native Web Components as well.

My current approach for zero dependency/build, one off UI components is:

    
    
        <div class="some-component" data-props="{}">
            ...more HTML...
            <button class="some-component__button">Click Me</button>
            <script type=text/javascript">
                (function() {
                    var component = document.currentScript.parentNode;
                    var props = component.dataset.props;
                    var button = component.querySelector('.some-component__button');
                     
                    button.addEventListener('click', function(){
                        ...doStuff...
                    });
                 })();
             </script>
        </div>
    

If it needs a global state, or is part of a larger more dynamic UI I'll start
looking at a framework. But for an otherwise static website, it's so much
easier to do it that way than it is to introduce an entire framework and build
pipeline in order to do a handful of JS components. It's also a somewhat
defensive approach, useful in legacy applications with lots of hodge podge JS
going on. Generally speaking, you don't cause any problems, and don't
encounter any conflicts.

I used to have a class based/web components way to do this, but I don't even
bother with that anymore. You don't gain anything from introducing complexity
in this circumstance.

------
elisee
Indeed! I swore off Web development a couple times because of the mess but now
my whole party games platform is made with plain JS, no bundling, no post-
processing, no big frameworks and it's pretty good. (Although I did recently
augment it with some JSDoc types, for long-term maintainability)

At least three devs have asked why I wasn't using some reactive framework or
big library. I know why: I'm much more productive without! Simple DOM helpers
go a long way:
[https://jklm.fun/common/dom.js](https://jklm.fun/common/dom.js)

(If you're curious: [https://jklm.fun](https://jklm.fun))

~~~
runawaybottle
Well isn’t that the economics of it? A new dev is much more productive in the
new thing they learned. A more seasoned dev is more productive in all the
things they already know.

The question is, who gets to dictate the economics here? The answer is always
Thanos, balance.

In a true meritocracy this is a legitimate fight, neither side has a one up.
We have to find the pros and cons of the old and new and iterate.

------
jv22222
@op Nice!

Might be good time to also mention these sites:

[http://microjs.com/](http://microjs.com/)

[http://youmightnotneedjquery.com/](http://youmightnotneedjquery.com/)

~~~
mqus
[http://vanilla-js.com/](http://vanilla-js.com/)

[http://vanillajs.net/](http://vanillajs.net/)

------
dhritzkiv
More specifically, how JavaScript can do a lot in browser by interacting
through the DOM API.

The modernization of the DOM API in the last 5 years has done a lot to remove
the need for jQuery et al, and has made building the View part of JavaScript
apps much more frictionless.

~~~
ed25519FUUU
Where can we learn about this “modern JS“? I feel like in some ways I’m
learning the old ways, similar to old c++.

~~~
bartread
The parent isn't talking about JS: they're talking about the DOM APIs. The
best resource I've found for learning about these is MDN:
[https://developer.mozilla.org/en-
US/docs/Web/API/Document_Ob...](https://developer.mozilla.org/en-
US/docs/Web/API/Document_Object_Model). I've found this to be an incredibly
helpful reference when I need to do something without using a library.

------
galaxyLogic
An interesting use of 'classList' is that it makes it easy to use css-classes
to indicate the state of a dom-element. Is the element selected or not? Well
does its classlist include the css-class 'selected'?

~~~
edwinjm
Do not use classes to mark something as selected. People using screen readers
won't know whether something is selected. Use aria attributes like aria-
selected instead.

[https://www.w3.org/WAI/PF/aria/states_and_properties#aria-
se...](https://www.w3.org/WAI/PF/aria/states_and_properties#aria-selected)

------
rasz
If little can do a lot just imagine what I can do with few megabytes: Google
Gmail/Youtube engineers

~~~
gear54rus
Probably a good batch of spaghetti code if you think vanilla can be used for
anything serious.

Or rewrite react yourself as another commenter already did.

~~~
hutzlibu
It is an old meme, that every js-developer, who does not use an existing
framework, ends up with his own framework. (also happened to me)

Depending on the scope of the work, this is not allways a bad thing. I like my
own framework. It does exactly what I want and I can tweak it to my needs
anytime.

But I work alone and don't have to share my code with different developers,
otherwise there would be problems ...

~~~
goatlover
Yeah, the one complaint with frameworks is they never do exactly what you
want, instead forcing you to do what generally works for everyone, which is
usually good enough. But also can be frustrating. Which is probably why there
are many frameworks.

------
xg15
So, question for those who are following the JS ecosystem trends more closely:
Is innerHTML now officially "ok to use" again?

I remember way back (when XHTML was still on the table) that innerHTML was
effectively deprecated: It was still around but you weren't supposed to use it
for anything new because support might be dropped at any point in "the
future". It was also non-standard. Instead, you were supposed to use the DOM
APIs (createElement etc) for any kind of DOM modifications. (Not even talking
about the risk of performance issues or XSS vulnerabilities if you used
innerHTML naively)

Now "the future" has come around and innerHTML is still supported basically
everywhere. It's clear that implementing innerHTML correctly adds substantial
complexity to browsers (they have to parse and serialise arbitrary HTML
fragments on the fly, synchronously, without stalling the event loop. The
result also has to interact well with all the other DOM
manipulation/inspection APIs. Not to mention if the HTML string contains its
own bits of JS...) - however, there seems to be so much "legacy" content that
dropping the API seems infeasible.

So, does anyone know what the official state of innerHTML is in terms of
standard compliance and future support?

~~~
IshKebab
Whether or not it is widely supported is academic - you shouldn't use
`innerHTML` for the same reasons you shouldn't use `eval`, the most important
being that it is a security risk.

------
irrational
I’ve been doing web development for more than 23 years. I’ve pretty much stuck
to vanilla JS that entire time (though I did use jquery during the worse of
the browser incompatibility years). I keep meaning to learn some of the newer
libraries but never get around to it since vanilla JS does everything I want
it to do and I already know it. I wonder if I would be as surprised by what
libraries like Vue can do as this person is about vanilla JS.

~~~
onion2k
Not a Vue example but have a look at
[https://github.com/drcmda/reactanoid](https://github.com/drcmda/reactanoid)

It's an Arkanoid clone (bat and ball game) written in React, with react-three-
fibre for WebGL, zustand for state management, and use-cannon for physics. The
full version is about 250 lines and there's also a simplified version that
weighs in at 60 lines (not including the library code obviously). It runs at a
solid 60FPS on a very basic laptop.

Doing the same thing in vanilla JS, using the same libraries (or alternatives)
would be _a lot_ more work, and would end up with the exact same result.
That's the point with a well written framework-driven app - when you use
something like React or Vue you should be aiming to get the same end result as
a vanilla JS app, but with _much_ less effort. The cost to the user should
really only be a slightly bigger download.

------
z3t4
Never set .innerHTML ! document.createElement is very tedious. But you can
hide them inside pure functions that returns your component. You could for
example put the confetti button example into a function and whenever you want
a confetti buttton const button = confettiButton("hurray");
body.appendChild(button); Or you could make a class and extend the Button into
a confetti button.

You can then use innerText to change values. And use other DOM methods instead
of redoing the innetHTML. When using function abstractions the DOM updates can
be private, and you don't have to worry about innerHTML elsewhere overwriting
your changes.

Basically React was invented to overcome spaghetti innerHTML, but you can
solve the problem with vanilla JS too, just stop thinking about the DOM as
HTML, think of it as a tree.

------
qubyte
I enjoyed this. It conveyed a little of the sense of wonder that can come with
just a touch of JS without bashing the frameworks. I also liked the emphasis
on making HTML and CSS do the heavy lifting by adding and removing CSS classes
as triggers.

------
blackrock
Instead of HTML, can’t JavaScript just be used to paint the browser canvas?

You can create your text boxes, your drop downs, buttons, etc., everything
that makes it a GUI application.

Then you fetch your data, per the page you display, via JSON, and fill in the
fields.

The initial JavaScript download is heavy, but the normal usage of the web
application should be quicker, as you’re only fetching the relevant data to
fill each of the required widgets.

This does sound resource intensive, for computers back in the 1990s, but
today’s iPad, iPhone, and modern laptop computers should be powerful enough to
handle it.

Or did I just describe some already popular JavaScript framework?

~~~
anonytrary
I think you just described the browser. While there are some efforts to do
canvas-only rendering in JS, they aren't mature and are meant for specific
use-cases like game boards and stock charts. For a typical website, this
approach would likely have negative implications for your site's accessibility
and usability; the site would also be harder to index.

~~~
blackrock
That’s correct. I was thinking more along the lines of video games and
interactive stock charts. But as an easier way to develop GUI applications via
JavaScript.

Perhaps where the widget definitions are described via an easier, LISP like
syntax, and rendered by the Framework. Kind of like Emacs Lisp.

Actually, it might have been amazing if the original Netscape browser shipped
with a Emacs Lisp like scripting tool to begin with. But instead, we got
JavaScript.

------
fefe23
Thank you for this. Really. Thank you.

I wish we had more postings like this.

No complaining, no whining, no "my framework is better than your framework".
Just what problems you had and how you solved them.

This actually gives me something to take away.

------
0xy
This is great. In legacy codebases for my employer I'll _always_ reach for
vanilla JS when solving a problem or implementing a new feature. Better than
tacking something on in jQuery.

------
afkqs
After a full year of only working with React, I started working on a few
projects a couple of days ago with Vanilla JS only and it is refreshing. It
feels like writing Haiku poems.

------
jb3689
Unfortunately the reason we have frameworks is because there are so many
gotchas. It's true - vanilla JS can do a lot however, there are also a lot of
strange edge cases that aren't handled well. If you don't mind failing on
these cases then it is fine, but this is why things like jQuery were built and
why your React project has hundreds of dependencies. Personally I prefer
vanilla JS, but for any serious project it is hard to choose it

------
patrec
Does anyone have a nice list of interesting, compact examples of sites or apps
that are written in vanilla javascript?

------
tekstar
I think if you make websites at any layer you owe it to yourself to learn
fundamental CSS and modern JavaScript (ES6).

------
afarrell
What sort of automated test framework do folks use while writing vanilla JS?

~~~
runarberg
I use AVA[1] and c8[2] for coverage. It only works in node so I have to use
JSDOM to mimic the browser environment.

1: [https://github.com/avajs/ava](https://github.com/avajs/ava)

2: [https://github.com/bcoe/c8](https://github.com/bcoe/c8)

------
feiss
In 2020, with our mature and cross browser compatible JavaScript, it surprises
me that many people are still discovering vanilla JavaScript. Is something
wrong here?

~~~
djKianoosh
I get the sense that there are a ton more beginner tutorials based on
frameworks. And those type of tutorials are underrated ways that lots of
people use to learn "js".

------
_pdp_
A little bit of C can do a lot, yet most 3A games are written with frameworks.

~~~
ben-schaaf
If all you need to do is display a native dialog, read some files, etc. you're
better off without the massive 300MB game engine. A lot of people making
native apps aren't game devs and a lot that are aren't making tripple-A games.

In the same way there's definitely web apps that are better off with a large
framework, but if all you're doing is toggling some classes you're better off
without them. Most websites don't need to be SPAs.

------
Nikaoto
It's as if using the technology the way it's designed to be used is a
reasonable idea. Who could've thought?

------
_hardwaregeek
This is super super minor but it's JavaScript not Javascript. IMO the camel
case makes it look so much better :D

~~~
dmitriid
If you really want to nitpick, it's ECMAScript

~~~
masswerk
Well, once it had been LiveScript…

------
anonytrary
Here's what a "little bit" of JavaScript can't do:

    
    
      1. Manage subtle browser differences
      2. Store and manage reactive state
      3. Manage declarative updates
    

You can build a simplified version of React in under 100 lines of code, but
that probably won't take care of subtle browser differences. A little bit of
JavaScript can do a lot, but it can't do everything you need these days.

~~~
andai
Does anyone know a 100 line react clone or tutorial? That sounds very
educational.

~~~
Tenemo
This one is amazing: [https://pomb.us/build-your-own-
react/](https://pomb.us/build-your-own-react/)

