Super Mario, JSX, and the destruction of the web development learning curve
Luke LaValva
Posted on January 25, 2023
Super Mario Bros is a triumph of accessibility: in the 1980s, millions of people new to video games booted up the NES and immediately learned how to run, jump, dodge enemies, break bricks, and collect coins. A focus on approachability was a keystone of its design, and was carried over its many sequels. Luckily, the design strategies used in Mario games are no secret.
Nintendo's family-friendly empire is built with a pattern they've stuck to for almost 40 years. Players begin in a safe and friendly environment, free to explore the game mechanics and controls. After this introduction, new concepts are introduced one-by-one and challenges are presented that let players demonstrate what they've learned.
With the standard Web languages of HTML, CSS, and JavaScript, learning to make websites feels a lot like playing a Nintendo game. HTML empowers its authors to publish webpages of all shapes and sizes, while its low barrier to entry makes it available to people of all experience levels. Designing simple websites is easy and exploratory, and complex behavior is additive.
The Approachability of HTML
When Mario becomes Fire Mario, his existing controls don’t change. Instead, he throws a bouncy flame in addition to running when the player presses B. Adding CSS to HTML is similar; nothing about writing HTML changes (except probably more class
attributes), but the site's style is enhanced. Just as Mario doesn’t need to know how to triple jump or wall kick before tossing fireballs, a developer doesn’t need to know the details of form submission before aligning elements with flexbox; even though CSS layers on top of HTML, the two can be learned in parallel.
Traditionally, JavaScript was similar to CSS. It let developers enhance sites with interactivity by dynamically updating HTML and CSS, without first requiring mastery of them. As such, the web development learning curve was smooth and approachable.
However, many web engineers assert that vanilla HTML, CSS, and JavaScript are insufficient for large projects, especially when sites heavily manipulate the DOM. Even for a basic client-side shopping list, most code is likely to be written in JavaScript, which is harder to understand and maintain than HTML. Here's one example of how a shopping list app may be written:
To understand the code above, developers are faced with the concepts of querySelector
, template cloning, and hardcoded element manipulation. Information is fragmented, and multiple places must be touched to make changes.
Those maintainability complications are especially apparent when many developers work on the same codebase, or frequently switch teams and projects. Since JavaScript has no “best” way to manipulate the DOM, vanilla JS projects tend to have different sets of opinionated code. Libraries like jQuery (which is still widely used) weren't the complete solution that developers were looking for. This may be why JavaScript frameworks have overtaken traditional web development in recent years. Here's an equivalent shopping list in React, the leading JavaScript framework:
This React example has more code than its vanilla equivalent, but the logic is presented with a consistency that appeals to experienced software engineers. This consistency makes it easier to switch across React codebases than switching between vanilla projects. Also, separating UI into components offers benefits like letting engineers work in parallel without conflicts (at least in theory).
This shopping list is a simple example of an app with DOM manipulation. As an application grows, it becomes harder to argue that the vanilla solution is more maintainable or easier to understand. This is why common advice for aspiring web engineers is to "learn React".
Unfortunately, beginners are quickly faced with a large front-loading of complexity inherent to a JSX project.
The Approachability of React
React’s official documentation instructs newcomers to begin by downloading VSCode, installing Node.js, and navigating a terminal to type npx create-react-app my-app
, after which they encounter this mound of files:
To create their "Hello World", beginners are told to find the src/App.js
file and replace its lightly-confusing contents with:
import React from "react";
function App() {
return (
<p>Hello World</p>
);
}
export default App;
Compare all of that to opening a text editor and typing some explainable HTML, which create-react-app
also introduces:
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<p>Hello World</p>
</body>
</html>
With React, the difficulty curve becomes a difficulty cliff. To fit with the analogy, learning web development in React is like starting Mario in this level:
To demonstrate this front-loading of complexity, compare this HTML+JS counter and its React equivalent:
<!DOCTYPE html>
<html>
<head><title>Counter</title></head>
<body>
<button id="counter" onclick="increment()">0</button>
<script>
let count = 0;
function increment() {
count = count + 1;
counter.innerHTML = count;
}
</script>
</body>
</html>
import React, { useState } from "react";
function App(props) {
const [count, setCount] = useState(0);
const increment = () => {
setCount((curr) => curr + 1);
}
return (
<button onClick={increment}>{count}</button>
);
}
export default App;
For the vanilla example, a new developer must learn
- General page and tag structure
- Buttons and the
onclick
attribute - The
<script>
tag - Variables with
const
andlet
- Functions and function calls
In addition to those, the React example requires knowledge of
- ES6
import
/export
- Object destructing
- Anonymous/arrow functions
- Functional programming concepts (can't set
count
directly) - JSX templating
For beginners who often already struggle with basics like variables, React raises further barriers to entry. For useState
alone, a new programmer has know
- The rules of hooks
- That functions can be stored in variables
- That even though
count
is aconst
, its value updates withsetCount
- However, if they reference
count
in the same function they callsetCount
, they'll find that its value did not change.
- However, if they reference
That’s all routine for seasoned React developers, but it's a lot for beginners to take in.
The Future of Web Frameworks
Despite its learning curve, React was a hit because it addressed problems that plagued web developers in the mid-2010s. Many companies were experimenting with client-side interactivity, but their attempts with vanilla JavaScript scaled poorly as teams grew and more state management was required. React seemed like a perfect solution, and developers used it to build millions of sites. However, other options have appeared, and some developers are growing disillusioned with React and JSX.
The problem highlighted in this post is that JSX doesn't build on top of the vanilla experience as much as it replaces it, while still requiring knowledge of HTML and JavaScript. As a result, the learning curve for React and its successors, like Solid and Qwik, is steeper than HTML+CSS+JS.
My criticism of React and JSX isn't to disregard the value that frameworks add. Instead, it's an aspiration for creators of future web technologies. Along with improving developer experience for large projects, frameworks should preserve the web development learning curve. I hope that the next era of web development will be built with tools that encourage hobbyists and experienced developers alike.
Frameworks like Astro and Svelte are fairly effective at preserving web development’s learning curve, but my money is (quite literally) on Marko 6’s new Tags API. In addition to all of its performance benefits, Marko's syntax brings the modern developer experience much closer to that of vanilla HTML. To me, adding Marko to the vanilla web languages feels a whole lot like collecting a power-up.
Appendix: examples rewritten with Marko
-
<p>Hello world</p>
-
<let/count = 0/> <button onClick() { count++ }> ${count} </button>
-
<let/list = []/> <form onSubmit(e) { const input = nextItem(); list = list.concat(input.value); input.value = ""; e.preventDefault(); }> <label>New item: <input/nextItem></label> <button>Add to list</button> </form> <ul> <for|item, itemIndex| of=list> <li>${item} <button onClick() { list = list.filter((_, i) => i !== itemIndex) }> Delete </button> </li> </for> </ul>
Posted on January 25, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.