A little bit of plain JavaScript can do a lot
jvns.ca
jvns.ca
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}, []),
]); <esc>vi`:edit-indirect-region<ret>:html-mode
And edit the string contents as HTML.See “tagged templates” here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
For event handlers, only the event name is in the string. The handler function is passed directly in and we add it to elements with addEventListener().
The only "magic" is that we wrap the user's handler call it with the host component as the `this` value so that you don't have to create closures like in Reach. You can just do:
class MyElement extends LitElement {
render() {
return html`<button @click=${this._onClick}`>Click Ma</button>
}
_onClick(e) {
console.log('this is', this);
}
} // create an element with 'tag'
const elem = document.createElement(tag)
// copy properties into the new element one at a time
for (const k in props) {
elem[k] = props[k]
}
// append child elements one at a time
for (const kid of kids) {
elem.appendChild(kid)
}
// and that's it
return elem
Applied recursively, you can make dom elements with very little boilerplate. // data driven HTMLElement creation
var $element = function(type, p={}) {
let h, elem = document.createElement(type);
if (!p || (typeof(p) !== "object")) {
elem.innerHTML = p || '';
return(elem);
}
h = p.attributes; delete p.attributes; if (h) for (let e of Object.entries(h)) { elem.setAttribute(e[0],e[1]) }
h = p.classList; delete p.classList; if (h) for (let c of h) { elem.classList.add(c) }
h = p.dataset; delete p.dataset; if (h) Object.assign(elem.dataset, h);
h = p.style; delete p.style; if (h) Object.assign(elem.style, h);
h = p.innerHTML; delete p.innerHTML; if (h) elem.innerHTML = h;
h = p.children; delete p.children; if (h) for (let ch of h) { if (ch) elem.appendChild(ch) }
h = p.parentNode; delete p.parentNode; if (h) h.appendChild(elem);
h = p.event; delete p.event; if (h) for (let e of Object.entries(h)) { elem.addEventListener(e[0],e[1]) }
return(Object.assign(elem, p));
};This can be accomplished non-destructively with destructuring.
const {
attributes,
classList,
style,
...rest
} = p
// elem = ...consume attributes, classList, style...
return Object.assign(elem, rest)You protect against someone else abusing it on other users.
This mindset right here is exactly why XSS is still an issue.
If you pull user generated content and put it in the DOM like this, you will open your users to XSS from other users. Basing your personal use DOM APIs on setting `el.innerHTML` will lead to a slip-up. Use `textContent` by default.
I don't think it says anything about you, it's a very easy mistake to make. But it should say something to you, which is to stay vigilant and to try very hard to not dismiss security concerns without giving them some thought.
And of course always assume code will be misused if you let it, by others who don't know what you're going for and by yourself when you're trying to make a deadline. So always design your interfaces to be secure by default. Obviously easier said than done...
Dynamically setting text is very common. While dynamically setting innerHTML is a rare and dangerous operation which should be explicit in the code. The alternative syntax also supported by this function $element("span", { innerHTML: html }) is much better.
* getNodesByType - allows me to get things like attributes, text, and comments directly
* getAncestor - allows me to get a specified element somewhere between a target element and the document.documentElement
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 :-)
The small bytes also mean that for simple sites I can just copy paste it before transitioning to a real setup.
You’re not wrong about clarity; it wouldn’t hurt here at all, but it wouldn’t hinder without, so I imagine it’s to reduce cognitive load and make it abundantly clear the function is logicless glue.
It looks like append() also accepts strings directly. This eliminates the need for $T, which makes things even nicer.
https://preactjs.com/guide/v10/getting-started#no-build-tool...
I personally use preact over react because it's tiny - 3kb for the entire library.
> Preact is packaged to be used directly in the browser, and doesn't require any build or tools
I suppose I could rephrase my question to something like "What is the point of this library?". Every other page on that site that includes examples uses JSX... which means there needs to be a build step.
I guess if someone really just wants to include a 3rd party library to programatically create elements, but get none of the benefits of using them this would be great! Then again there is like 3 examples in this thread of tiny functions (< 10 LoC) that appear to do the same thing.
I guess 3.5kb is about 10x smaller than React + React DOM, but 35kb isn't exactly breaking the bank either. I can't tell specifically by glancing over their examples, but I also suspect feature parity is not quite there. Specifically, I develop with Typescript so being able to type hint `React.ChangeEvent<HTMLInputElement>` etc. is sometimes necessary. It's not clear if Preact exposes a sufficient API. Maybe you know?
It's just a lightweight, no-build-steps-required library that's similar to React. If you already like using React there's nothing much compelling to make you switch.
I like using it with HTM for a nice alternative to JSX which doesn't need any build steps.
I can't speak on its interaction with TypeScript, I've only used this for simple pages where I want to add some interactivity without involving the rest of the nightmarish JS ecosystem.
I wonder how many XSS vulns this decision has caused in the wild.
You can grep for {{{ without false positives matching {{. Conversely, it's harder to find places where == was used when === should have been used (although I'll concede it's not much harder). In general I'd prefer to go the extra mile to be unsafe than accidentally miss something out.
You shouldn't have to squint at your templating code to see if there's an XSS vector or not, or defensively/neurotically grep for "{{{" just in case you didn't trust your team to squint sufficiently.
For comparison, here's JSX: <div dangerouslySetInnerHTML={{__html: username}} />.
vs. Mustache: {{{username}}} in a file of 1000 other { and } glyphs.
Mustache's hey-dey is long over thankfully.
I've never seen JSX before (I haven't learned anything new in front-end later than ES5). In your example, it looks more like an attribute than an inner tag, the former of which I'd almost always escape. Is that how you set inner HTML as well?
May I suggest a repo. Github is great, I hear. :)
https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/...
When will we ever get that jQuery’s thing wasn’t only cross-browser compatibility. It’s that the DOM’s native API is terrible and jQuery is a much terser, powerful, chainable alternative.
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
Second problem is that buyers are not result focused. Few companies understand that they pay for these cool frameworks every time a user visit their website. They pay with 15% less conversion, or lower user satisfaction.
The valid, useful test is whether or not a good vanilla JS app can be better than a good framework driven app.
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.
A much more useful test is to look at the average vanilla and framework app. Obviously the best framework app or vanilla app is gunna be pretty good regardless.
Do frameworks encourage bad/lazy coding? Do they make it very easy to half baked something but very hard to create something polished? Does the constant churn of frameworks mean that a project can never be mature and stable?
Is plain JS too restrictive? Do you have to reimplement react yourself if you want to make a good website?
I think these are the relevant questions.
None of the max 30-user SPAs I wrote during my time at a certain large pharmaceutical company made sense as SPAs, because the scale at which that becomes beneficial just wasn't there.
As for your other experience: I get where you both are coming from. Thing is, we as front-end developers are kind of forced to use that guy's approach, because over the years stakeholders got used to a level of flexibility that's just not achievable using yours.
I for one would love to use minimal amounts of JS instead of frameworks that make me wait 2 minutes on my sad little work laptop until they compile the project, but that would make any event in which some stakeholder changes their mind a disaster requiring me to re-write the whole application every now and then.
Anyway there's light at the end of this tunnel in the form of compiler-frameworks:
Writing in this feels just like old school vanilla JS, but with additional language features like reactivity.
>In one place I wanted to change a button’s HTML contents.
if you know what the hidden elm is going to contain you can just add the html to the page as a hidden elm. if it can have 2 states you can also add both nodes as html. just show only one at a time.
the <i> and the <object> are always children of <button> i would target them as such, no need to have a class. unless you use <i> 2x. (one for each state) but i think the icon should be the button bg.
onclick gives you a reference to the button with which you can change it's class. things inside (now styled by the classname of their parent) will change along. (swap bg, hide/show etc)
Because it doesn't scale for large projects and is impossible to work on with larger teams. Rails may seem bloated to some people, but if you are creating a set of complex business rules that constantly change and cost too much to rewrite with the framework flavor of the day, it makes a lot of sense.
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.
Reimplementing gmail or slack in html/css/vanilla-javascript, without an "application frameowrk is no doubt an exercise in futility.
At the same time, reaching for React/Fluttr/Angular/frontend-framework-de-jour when all you need is to show and hide divs and maybe do css or scroll animations between them is, in my mind, an equally bad decision.
(I can totally see why the framework-de-jour option gets chosen so often though - because no matter how written-in-stone the original specs saying "There wil be no functinality required beyond showing/hiding divs and css animations between them" are, we've all seen projects where a last minute critical/showstopper requirement for the web page to include something insane like a fully functional email client gets dropped in the FE dev's lap the Friday afternoon before launch Monday. Sure, the FE dev made the choice to use React, and while some (perhaps a lot?) of that is "resume driven development", at least some of the blame is on bad sales people and project managers as well...)
Do people really do that? To me, that reflects bad frontend engineering practice. Even so for something like that, I'd use jQuery or something similar, because it's a much nicer API than the DOM.
I'm pretty sure gmail started out without a framework.
I'm pretty sure you're right.
I'm also pretty sure it's not only one of the big reasons frameworks exists as they do today (because it's perhaps one of the earliest and probably the most famous example of a web app that 100% _needs_ a framework for all the reasons discussed everywhere in this thread), but that it's also the poster child case of why every ambitious/opinionated/inexperienced FE dev builds their own framework (Of _course_ I can't use jQuery/Angular/React/Fluttr! _My_ ToDo list app is _groundbreaking and special_ - just like gmail was in 2004, I'll need to write my own framework for this. Stand back! <flexes>") ;-)
There is nothing preventing you to write nice and clean code in plain JS.
Please tell me you're joking?
I of course don't mean close to the CPU that would be silly.
> never go lower level than the browser
So maybe "close to the DOM" rather than "close to the metal"? :-)
On the other hand, if you ever want a project to get you out of your comfort-zone(/rut?) - check this out:
You _can_ go "bare metal" and still use your hard won javascript skills...
> I started my computing career in 1986 knowing only React
That's a big gap in your memory between 1986 and when React was first released in 2013. Or _maybe_ 2010 or 2011 if you were working inside Facebook back before they released it publicly...
You do realize why you're being downvoted, right? :D
There are many approaches you can take. The following is an MVC approach I'm trying out in a personal project. It's organized as follows:
There's a subscribe/emit system, where you can link functions to run when an object sends an emit signal. This can be done in a couple dozen lines of code. Example: https://gist.github.com/stefano/32cbe9bef5a68ecb3286113369d8...
Models are JavaScript objects. They have methods to read their state and to update it. The methods that update the state need to call emit() so all subscribers are notified.
Views are functions. They return an object with 2 properties: a DOM node (the root of the view), and an update function. The update function takes a model instance, and updates the DOM structure accordingly. If a view supports user interaction, it'll take a delegate object as a parameter. The view will add event handlers which call the delegate object. Example of a text input with a label and optional validation error: https://gist.github.com/stefano/2135cc15470cceba4ac2d344fe2e...
The controller is a function or class. It returns an object with its root view and a function to run when the controller is destroyed for cleanup. It can either take a model as a parameter or instantiate it. It also instantiates the root view, passing itself (or another object) as the delegate. In the delegate methods, it calls the model update methods. The controller subscribes the view to model changes, and unsubscribes them when it's destroyed. Alternatively, the view can subscribe/unsubscribe itself.
With this approach, views can be composed by calling the view functions from inside another view, passing down a delegate and adding the root DOM node to the parent view DOM structure.
The controller and the model are completely independent of the DOM. The model can be tested independently. The controller can be tested with a mock view. The view can be tested by passing in a mock model.
Views only depend on their initial state and the state of the model passed to the update function.
Never thought I'd hear the day...
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 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.
tldr just looking for class x10 slower.
The slowest and most convoluted selector took 21ms when tested against 50,000 elements using a 2014 MacBook Pro.
I was similarly inspired:
if (undefined === window.$) {
window.$ = document.querySelectorAll.bind(document);
[EventTarget.prototype, window, XMLHttpRequest.prototype].forEach(function setOnOff(p) {
Object.defineProperty(p, "on", {
get() {
return function onElement(t, f) {
this.addEventListener(t, f);
return this;
};
}
})
Object.defineProperty(p, "off", {
get() {
return function offElement(t, f) {
this.removeEventListener(t, f);
return this;
};
}
});
});
[NodeList.prototype, HTMLCollection.prototype].forEach(function setOnArray(p) {
Object.setPrototypeOf(p, Array.prototype);
Object.defineProperty(p, "on", {
get() {
return function onArray(t, f) {
this.forEach(function onEach(e) {
e.addEventListener(t, f);
});
return this;
};
}
});
Object.defineProperty(p, "off", {
get() {
return function offArray(t, f) {
this.forEach(function offEach(e) {
e.removeEventListener(t, f);
});
return this;
};
}
});
});
}They look useful to me, but I personally think they aren't so well-named; excessively concise for my taste.
Wm Little came to church & heard my sermon against minding trifles. He told me, had he preached he should have taken the other side. Probably not one hearer besides thought so far on the subject.
https://gist.github.com/nilsandrey/c1f66819de40b26eed4bf71ac...
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.From 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.
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...
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.
However, if you're a frontend dev, you should understand VanillaJS. Just like a systems programmer should understand assembly and C. Also, there are some situations where you won't need a framework.
Knowing VanillaJS and using frameworks are obviously not mutually exclusive.
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.
> You can target HTML elements in JS. But how do you know which elements are available?
This assumes someone else is creating the CSS and HTML for you? Have rules or coding and naming conventions like BEM for example. That's what a framework basically is, a set of rules and conventions about how to structure an App. What you need is proper architecture, regardless of whether or not it is based on a popular framework. That's how the popular frameworks started out to begin with.
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.
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
(If you're curious: https://jklm.fun)
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.
Might be good time to also mention these sites:
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.
If you don't need to support older browsers, you don't even need to use any packaging software (although if you're building a serious app, it's still wise to do so). You can import ES6 modules in vanilla JS, and a modern browser will do the loading for you. Great for quick starts.
but it's nice that jquery (as convenient as it is) is no longer a must-have library for dom manipulation.
`NodeList` can be a live list (not `querySelectorAll` though) which has some benefits over arrays.
`NodeList` also implements `Symbol.iterator` so you can use your favorite iterator library if you need to map, filter or reduce it. And with the future pipeline operator, you’ll really don’t see a different between a standard array and a fancy structure like `NodeList`.
https://www.w3.org/WAI/PF/aria/states_and_properties#aria-se...
Or rewrite react yourself as another commenter already did.
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 ...
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?
It would be nice to have this (via regular or template strings) compile to DOM objects, wrapped with a couple of pointers indicating where strings or elements are placed. I guess for now the best course would be to budge that into transpilation, raising an error if it does anything kooky like work at the boundaries of elements.
(.innerHTML setting is sugar for a HTML parser being initialised on the element, so such a transpiler wouldn’t be removing functionality per se)
[1] https://w3c.github.io/DOM-Parsing/#dom-innerhtml-innerhtml
I had heard that document.write is discouraged but not innerHTML. In fact, browsers have doubled down, by adding insertAdjacentHTML (originally only in Internet Explorer).
> Instead, you were supposed to use the DOM APIs
I tried that in an app. It turned out that IE 6 was faster with innerHTML, like literally 1,000 times faster. This was surprising because I assumed the DOM APIs were closer to the metal. Not so, at least with Internet Explorer. (With Chrome, the DOM APIs and innerHTML were about the same speed).
> standard compliance and future support
It will be supported forever, because browsers refuse to break the web. You can see them saying so when they discuss syntax for new features.
Mozilla's documentation tells you when a feature is deprecated (again, usually for a feature that was experimental and never widespread). It carries no such notice on its page for innerHTML. It does, however, carry a warning: "Warning: If your project is one that will undergo any form of security review, using innerHTML most likely will result in your code being rejected." --- https://developer.mozilla.org/en-US/docs/Web/API/Element/inn...
To me, innerHTML is sometimes useful and no more dangerous than server-side rendering. Rather than deprecating it, I wonder why browser vendors have not added a native escapeHTML function (or even better, a very short syntax) to make innerHTML safer.
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.
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.
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?
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.
React pretty much is just a fast way to determine what to re-render on a page as a result of a state change.
The browser gives you all that for free, just by writing some HTML. And allows your website to be accessible and indexable. It seems like a lot of work to re-implement that, for little benefit. It doesn't seem worth the hassle, but maybe there's something I've overlooked.
> normal usage of the web application should be quicker, as you’re only fetching the relevant data to fill each of the required widgets...Or did I just describe some already popular JavaScript framework?
Every popular JS framework already does all of this.
Is 64 kb heavy?
There is a graphic libary, that (partly) tries to accomplish this. (but did not really take off)
lib.ivank.net/
Basically the idea was to replace the DOM with a canvas and then create elements in it, like you did in flash.
If it would have matured, you probably could have then used Flex, which is a flash libary, similar to the DOM, but much cleaner. (but flex is heavy, though)
But apparently, no one thought it is worth it. Because, it would mean quite some work, to reimplement the DOM in a faster way ...
Flutter does this for web support.
Yes it can. Qt when compiled to web-assembly does this. It just uses a canvas as a framebuffer basically. [0]
Going off on a tangent here. This is something I've been thinking about a lot lately. I wanted to build a cross platform app that also works in the browser. Especially as single developer maintaining multiple codebases just sucks. So the only real option seems to be to write it as a webapp. So I started, but personally, I just really can't stand the JS ecosystem.
I basically see two ways here this could go. Either the we go the use-canvas-as-framebuffer route. Or we'll need small embeddable html/css engines so you can write your code once in your preferred language, where for native apps you access the engines DOM directly, and when compiled to WASM it 'syscall's it out to JS see e.g. go [1]
Basically something like sciter[2].
[0] https://www.qt.io/web-assembly-example-slate
But, JavaScript is such a poor language, that it’s a poor choice for rigorous software development. However, it is a good candidate for transpiling code to.
I was thinking of bolting on an Lisp type of scripting language, which can transpile down to a rigorous subset of JavaScript, which then controls how the widgets behave in the browser.
This way, the Lisp script can be used to control how the widgets are organized in the browser screen. How each widget gets its data, and how it interacts with other widgets on the screen. Basically, it becomes its own little GUI development environment.
Then over time, a public library of Lisp functions can be made available, and easily integrated into your code, with recommendations on commonly accepted techniques to do something. But if you don’t like that, then you’re still free to reinvent your own new technique.
Then over time, the superior nature of this system, becomes popular enough that the major browser vendors begin integrating the Lisp scripting engine as a first class library. And to improve efficiency, it can cut out JavaScript entirely, and just interact directly with the browser. So the Lisp engine should have the ability to both, transpile down to JavaScript for backwards compatibility, as well as to compile down to bytecodes that can be executed by the browser. This way, all the variant JavaScript ecosystems gets neutralized, since this new system, just sidesteps JavaScript completely. (Wait, I think I just described WebAssembly.)
And then, a new variant of web-Lisp takes over the world.
Excellent..
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.
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.
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.The tree things you mentioned might be important to you for a specific project, but these are not requirements for every project. So instead, do what makes sense to you. And remember that you don’t always need a framework to achieve what you actually need.