Understanding Web Dev for Systems Developers

A first-principles orientation and explanation for old-school system and scripting developers to understand web development and frameworks.

C developers and other system language developers have grown up with a clear understanding that code does not just run and must be converted from the programming language to machine language, with various intermediate forms of code, such as assembly language and object code, and Intermediate Representations (IRs). System development is simpler to understand and has a tolerable amount of rote memorization involved. You memorize the language, and run the tools, and you may memorize some patterns for writing code, or using libraries in that code. Web development, however, violates the KISS principles in spades. It has always felt foreign to me with its sprawling language requirements and poorly-defined terminology, like “framework”. The modern web development process requires knowledge of at least 1, 2, 3, languages…and 4 or 5 if you want to develop using a framework without shooting yourself in the foot, and 6 and 7, if you ever want to properly document your code. The full stack:

  • Basic languages: HTML, CSS, JavaScript. Note that JavaScript has gone through many revisions, so its dialects are widely varied.
  • Enhanced languages: JSX, TypeScript
  • Documentation languages: Markdown, Jekyll dialect of Markdown (or another static site generator’s dialect)
  • Want a performant back-end for highly complex calculations? Learn Rust/WASM too. That’ll add on language #8, and more tools to wrestle with and configure.

On top of the language learning overhead, these languages all have their own sets of tools. Every new tool provides a fresh set of potential bugs and dependency issues and integration requirements with the earlier tools.

Any app that targets the web must eventually compile its interface to JavaScript and deliver that JS to the end user. Other languages like Java, Rust, or C++ can be used to develop a web page, allowing developers to write their front-end web design code in another language, and then use a tool to compile it back down to JavaScript. However, this shifts the learning overhead from the basic web languages over to the nuanced behavior of a framework within that language, which can encourage awkward designs and reasoning.

Because learning multiple languages is a headache, developers decided to remake many back-end tools in JavaScript or some combination of JS and its enhanced language alternatives to make web apps. The result…Node.js, and JavaScript frameworks.

What is Node.js?

This one should be familiar enough for us old-school machine-centric developers. Node.js is a program that runs on a machine, converting JavaScript into machine language to access local resources on the machine and perform data processing, whether it’s responding with a page, or performing actions on request, such as writing to a database, or reading from a database and replying to the requester with a JSON document. It is called a “runtime environment” and it provides the functions and abilities in real-time for the language to interact with the machine. C on the other hand, is compiled, so it provides a Runtime library that gets included into the compiled program during compilation, instead of running separately from the program.

Node.js functions more like an interpreter and uses a Just-In-Time (JIT) compiler. Here’s the breakdown for a C developer:

  • No separate compile step: Unlike C, where you run gcc to produce an executable, you don’t have a distinct “Node.js compilation” phase before running your code. You just execute the JavaScript file with node your_script.js. This is more akin to how you’d execute a script with an interpreter directly.
  • Under the hood (JIT Compilation): However, Node.js uses Google’s V8 JavaScript engine. V8 doesn’t just interpret the JavaScript line by line. It actually performs Just-In-Time (JIT) compilation. This means that during execution, V8 dynamically compiles frequently used parts of your JavaScript code into highly optimized machine code.

So, while you interact with it as an interpreter, the underlying engine employs compilation techniques to achieve high performance, similar to how modern JVMs or .NET runtimes work.

Node.js isn’t a server?

No. It’s very commonly used to build servers, but it isn’t a server in and of itself. Unfortunately, the moment you start building an app, you’re going to start thinking of Node.js as “the server” because its true definition becomes obfuscated by what it’s most commonly used for.

Think of it this way: a C compiler isn’t a web server, but you can use C to write a web server. Similarly, Node.js provides the foundation and tools (like its built-in http module) that make it straightforward to create and run a web server using JavaScript. Many popular server-side frameworks, like Express.js, are built on top of Node.js specifically for this purpose.

What is a Framework?

A framework in software development is a collection of pre-written code, libraries, and tools that provides a structured foundation for building applications. It defines a skeletal structure and a set of conventions, offering common functionalities and patterns. This means a developer doesn’t start from an empty file; instead, they fill in the specific logic and data within the framework’s predefined architecture. The framework handles many low-level details, allowing developers to focus on higher-level application-specific problems.

What is React?

React is a JavaScript library for building user interfaces. While many people think of React as a framework, it is officially described as a library.

React is a JavaScript library for building user interfaces. Its principles are:

  • Component-Based Architecture: A user interface is broken down into independent, reusable pieces called components. Each component is a self-contained unit that encapsulates its own logic, state, and rendering instructions. This promotes modularity and makes complex UIs easier to manage.
  • Declarative Programming: Instead of explicitly telling the browser how to change the UI (e.g., “find this element, change its text, then add a class”), you describe what the UI should look like for a given state. React then efficiently updates the actual display to match your declarative description.
  • Virtual DOM: React maintains a lightweight, in-memory representation of the actual browser’s Document Object Model (DOM). When a component’s state changes, React first updates this “virtual DOM.” It then compares the new virtual DOM with the previous one (a process called “diffing”) to identify the minimal set of changes needed. Finally, it applies only these necessary changes to the real browser DOM, which is a computationally expensive operation. This optimization leads to faster and more efficient UI updates.
  • Unidirectional Data Flow: Data in React typically flows in one direction, from parent components to child components via properties (“props”). This predictable data flow makes it easier to understand how data changes affect the UI and helps in debugging.
  • State Management: Components can have their own internal data, called “state,” which determines how they are rendered and behave. When a component’s state changes, React automatically re-renders that component and its children to reflect the new state.

Here’s how to think about it, using first principles for a C developer:

Library vs. Framework Analogy for a C Developer:

  • Library (like a C utility library): Imagine you have a collection of well-defined C functions (e.g., for string manipulation, mathematical operations, or file I/O). You, the developer, decide when and where to call these functions within your program’s control flow. You are in charge of the overall program structure, and you pull in library functions as needed. This is how React, at its core, operates. You import React, and then you call its functions (like useState, useEffect, or JSX elements) to define and render your UI components. You dictate the application’s flow.
  • Framework (like a C application skeleton): Now, imagine a C application that provides a pre-defined main loop, a specific directory structure, and “hooks” or predefined functions (like init(), update(), render()) that it calls at specific points in its lifecycle. You, the developer, fill in the blanks by implementing your specific logic within those hooks. The framework dictates the overall application structure and flow, and it calls your code. This is known as “inversion of control.”

Where React gets “Frameworky” (and the relation to being a Library):

React, while a library, has characteristics that make it feel like a framework:

  1. Component-Based Paradigm: React strongly encourages (and essentially requires) you to break your UI into small, reusable components. This component-based architecture is a powerful paradigm that influences how you structure your entire front-end application. While not strictly enforced like a rigid framework, it’s a very opinionated way of building UIs.
  2. Virtual DOM and Reconciliation: React takes control over how your UI updates. You describe the desired state of your UI, and React (using its Virtual DOM and reconciliation algorithm) efficiently figures out the minimal changes to make to the actual browser’s DOM. This “inversion of control” over the rendering process is a framework-like characteristic.
  3. Ecosystem and Best Practices: The React community has developed a vast ecosystem of companion libraries (like React Router for navigation, Redux for state management, Next.js for full-stack React applications) and strong conventions. When you combine React with these popular tools, you end up with a fairly complete and opinionated stack that collectively acts much like a full-fledged framework.

The “Library” aspect is still true because:

  • Focused Scope: At its core, React’s primary concern is efficient UI rendering. It doesn’t dictate how you handle routing, data fetching, or global state management. You have the flexibility to choose other libraries for these concerns.
  • Flexibility: You can integrate React into existing projects incrementally, or use it for only a small part of a larger application, something that’s harder to do with a strict, all-encompassing framework.

Essentially, React is a powerful UI library that, due to its strong component model, internal control over rendering, and the surrounding ecosystem, often enables a framework-like development experience without imposing the rigid structure of a traditional framework.

React is developed in JavaScript. While you can use it with TypeScript (which is a superset of JavaScript), the core library itself is JavaScript.

What are TypeScript and JSX? Where do they fit in the ecosystem?

What are TypeScript and JSX? How does TypeScript compare to JSX? Where do they fit in the ecosystem?

TypeScript and JSX serve different, complementary purposes in web development, especially in the context of building user interfaces with libraries like React. They are not competing technologies, but rather work together.

TypeScript

  • What it is: TypeScript is a superset of JavaScript. This means all valid JavaScript code is also valid TypeScript code. Its primary addition is optional static typing.
  • Purpose: TypeScript aims to improve code quality, readability, and maintainability, especially in large and complex applications. By adding types, it allows developers to catch a significant class of errors (type-related errors) during development (compile-time) rather than at runtime. This leads to more robust and reliable code, and also provides better tooling support in IDEs (like autocompletion, refactoring, and code navigation).
  • Where it fits: TypeScript fits into the “language” layer. You write your application logic and data structures in TypeScript, which then gets “transpiled” (converted) into plain JavaScript that web browsers can understand.

JSX

  • What it is: JSX (JavaScript XML) is a syntax extension for JavaScript that allows you to write HTML-like markup directly within your JavaScript or TypeScript code. It can be thought of as “JavaScript Extension” even if it’s inaccurate.
  • Purpose: JSX is primarily used in UI libraries like React to describe what the user interface should look like. It provides a more intuitive and declarative way to define UI components compared to manually creating DOM elements with JavaScript. It makes your UI code more readable and easier to reason about.
  • Where it fits: JSX fits into the “templating” or “UI description” layer. It’s a syntactic sugar that gets transformed into regular JavaScript function calls (e.g., React.createElement()) that create the UI elements.

You may recall that typically in a webpage, you create an HTML file and embed your JavaScript in the HTML page, either by directly embedding it or including a .js file containing your JavaScript. JSX inverts this relationship, allowing you to insert HTML inside of your JavaScript. PHP does the same thing. PHP is an older web development language, which stands for PHP Hypertext Preprocessor. As you can guess, the language produces HTML by processing a PHP script that has an HTML template as well as conditional logic that produces different HTML at specific points in the template, which is then shipped to the user as a static HTML page. It “pre”-processes the HTML. This inverted relationship between HTML and the programming language is a common pattern in web development tooling, indicating a deeper flaw in the design of web development technologies that all of these tools attempt to compensate for.

How they compare and fit together:

  • Complementary: You can use JSX without TypeScript (just with plain JavaScript), and you can use TypeScript without JSX (for general-purpose JavaScript development). However, in modern web development, especially with React, they are often used together. To use JSX, you’ll need a compiler like Babel that can transpile JSX into JavaScript.
  • File extensions: When using TypeScript with JSX, your files typically have a .tsx extension (instead of .ts for plain TypeScript or .jsx for plain JavaScript with JSX). This signals to the TypeScript compiler that the file contains both TypeScript and JSX syntax.
  • Type Safety for UI: When combined, TypeScript provides type safety for your JavaScript logic, while JSX provides a concise way to describe your UI. TypeScript can then apply its type-checking to the props and state used within your JSX, helping to prevent errors when passing data between components.

Essentially, TypeScript enhances the JavaScript language by adding static types, while JSX extends JavaScript’s syntax to make UI creation more intuitive. They work in tandem to create more robust, maintainable, and readable front-end applications.

What is WASM?

WebAssembly (Wasm or WASM) is a next-generation technology that is a final delivery format for web code, similar to JavaScript. It is JIT-compiled, just like JavaScript. At the moment, it is intended to complement, rather than replace JavaScript, providing a way to offload parts of a web page’s calculations to another programming language. It does this by providing a low-level bytecode format for high performance languages like C, Rust, and C++ to compile to. Remember how we compiled to assembly language as an intermediate format in normal system dev processes, before it gets compiled to machine code? This is the same idea here. It is designed as a portable compilation target for high-level languages like C, C++, and Rust. It runs in a sandboxed environment in web browsers and other runtimes, offering near-native performance.

The goal of WebAssembly was initially to circumvent the performance flaws in JavaScript, particularly relating to its design choice to make every computation a floating-point operation, which is slow, and to enable high-performance applications on the web that were previously impractical with JavaScript alone. This includes computationally intensive tasks like 3D graphics, video editing, scientific simulations, and complex game engines. While JavaScript is very efficient for many tasks, it has certain characteristics (like its dynamic nature and garbage collection) that can sometimes lead to performance bottlenecks in highly demanding scenarios. Wasm provides a way to offload these performance-critical parts of a web page’s calculations to code written in languages optimized for raw speed.

Furthermore, one of the potential benefits of Wasm replacing JavaScript would be allowing developers to stay with their chosen languages and tools without ever knowing JavaScript. On a similar note, at present, Wasm facilitates significant code reuse. Developers can leverage existing codebases and libraries written in languages such as C, C++, or Rust, and compile them to Wasm, bringing powerful, battle-tested software to the web. This can reduce development time and cost for complex applications.

JavaScript was originally intended to be a human-readable language, but its role has transitioned over time to a conflicting set of roles, ultimately baiting new developers into learning a language that isn’t appropriate for hand-coding, wasting their time and energy on memorizing its syntax and weeding out the outdated teaching resources, and the painful lessons of its behavioral nuances due to its architectural oddities. In the words of the creator of JSON, Douglas Crockford, JavaScript has “congenital defects”. JavaScript provides a minefield of technical debt, and WASM is a sane path forward for deleting all of that debt. You can see more about its flaws in the Further Criticisms of JavaScript section at the end.

With Wasm, if we were to give it full control over the web rendering, it would pave the way forward for letting developers of any language create web pages and stick to their tooling.

While Wasm is designed to complement JavaScript, rather than entirely replace it, here’s how it could potentially impact JavaScript’s role in the future:

  • Performance-critical tasks: Wasm excels at computationally intensive operations, such as video editing, 3D graphics, scientific simulations, and game development. For these tasks, Wasm can significantly outperform JavaScript due to its compiled nature and efficient execution. This means developers can write these parts of their applications in languages like C++ or Rust and compile them to Wasm for a much faster user experience.
  • Code reuse: Wasm allows developers to bring existing codebases written in languages other than JavaScript (e.g., C, C++, Rust) to the web. This can reduce development time and cost, especially for complex applications or those with a large existing codebase.
  • New types of web applications: Wasm opens the door for web applications that were previously impractical to run in the browser due to performance limitations, such as full-fledged desktop applications ported to the web.
  • Interoperability: Wasm can serve as an interoperability layer, allowing different languages to work together on the web, including with JavaScript.

However, Wasm currently doesn’t directly access the DOM (Document Object Model) or have its own built-in garbage collection. It relies on JavaScript to interact with the web page’s structure and browser APIs. This means that for general web development, especially tasks involving direct DOM manipulation and user interface logic, JavaScript will likely remain essential for the foreseeable future.

Essentially, Wasm is poised to handle the “heavy lifting” of web applications, while JavaScript continues to manage the user interface and interact with browser functionalities. Over time, as Wasm evolves with features like direct DOM access and garbage collection support, its role might expand, but a complete replacement of JavaScript is unlikely due to JavaScript’s pervasive nature and extensive ecosystem. What we can hope for is that when Wasm achieves feature parity with JavaScript, JavaScript’s prevalence will erode.

Further Criticisms of JavaScript

Douglas Crockford, the creator of JSON, has been a prominent voice on JavaScript’s shortcomings. He wrote a book called “JavaScript: The Good Parts”, which has gained cult classic status, that highlights the more reliable aspects of the language while advising against its “bad parts.”

Crockford describes JavaScript as a “smelly language” filled with “congenital defects.” He argues that while it may still be the best option for certain applications, this is not sufficient justification for its continued use. The accumulation of new features over the years has only exacerbated existing problems, leading to a complex and often unreliable programming environment.

The Case Against JavaScript: Insights from Douglas Crockford | Galaxy.ai

His main criticisms include:

  • Design Errors: JavaScript has fundamental design flaws, such as the + operator being overloaded for both addition and string concatenation with type coercion, and the error-prone with statement. He also criticizes semicolon insertion and the notation for literal regular expressions.
  • “Moving Target” / Too Many Versions: The language has evolved, but this has also led to confusion and too many versions, with new features sometimes aggravating existing problems.
  • Poor Naming: The name “JavaScript” itself is problematic, suggesting a relation to Java that doesn’t exist and the “-Script” suffix implying it’s not a “real” programming language.
  • Bad Implementations and Books: Early implementations were buggy, and many books about the language were poorly written, promoting bad practices.
  • Lack of Discipline/Bloat: Crockford believes that while he initially advocated for JavaScript and tried to correct its flaws (e.g., with ES5), there has been a strong interest in “bloating” the language rather than making it better. He sees this as a barrier to progress.
  • Stuck in the Past: He argues that JavaScript, like other “dinosaur languages,” was designed for a paradigm where programs run as a single process on a single machine. In today’s distributed world, he believes we need new languages built for secure, distributed programming that focus on communication rather than just computation. He has stated, “The best thing we can do today to JavaScript is to retire it.”

Leave a Reply