Building Web Applications the Old-Fashioned Way

Web development on the frontend has come a long way from writing scripts and putting them directly into a Web page. Build tools and frameworks make it much easier to build entire applications that run in the browser, far beyond anything thought possible when JavaScript was first created.

Even so, it’s still possible to do it the old-fashioned way, and it turns out we don’t actually have to give up everything that modern Web development has brought us.

Vanilla JS

“Vanilla JS” refers to JavaScript that doesn’t depend on any external libraries or tools, and it’s quite capable. There’s a great parody website that advertises many of the features of the Vanilla JS “framework” (and another one, complete with NPM package).

To demonstrate the capabilities of vanilla JS, I’ll build a simple example application. This example has a form with a text input and a button. When the user enters their name in the text input and clicks the button, a greeting will appear on the page.

The HTML might be something like:

<form>
	<label>Your name: <input type="text" id="name-input"/></label>
	<button type="submit" id="name-submit">Go</button>
</form>
<p id="message"></p>

The script would be:

'use strict';
(function () {
	var submitButton = document.getElementById('name-submit');
	submitButton.addEventListener('click', function (e) {
		var name = document.getElementById('name-input').value;
		document.getElementById('message').textContent = formatGreeting(name);
		e.preventDefault();
	});

	function formatGreeting(name) {
		return name ? 'Hello, ' + name + '!' : 'Hello!';
	}
})();

Now, you may be wondering: Why am I still using var instead of const? Why am I using an IIFE when JavaScript now has native module support? These are good points: The ECMAScript 6 specification, which introduced these language features (among many others) is now well supported in major browsers. But we’re doing things the old-fashioned way, so I’m going to stick to ECMAScript 5. (This provides support as far back as Internet Explorer 9, which is about as far as we can go before we start losing the standard DOM functions.)

Component Framework with No Build Tools

Writing a script with vanilla JS is okay for simple things, but what about more complex applications? This is where frameworks come in.

My preferred framework for this use case is Preact, which is a lightweight alternative to React that provides much of the same functionality and API. Its documentation demonstrates how to use it without build tools.

But there’s a catch: The examples use ECMAScript 6 module loading, which is not what I’m looking for. Fortunately, Preact provides a script that exports its functionality as a global variable, the old-fashioned way. We can add the script from a CDN like jsDelivr:

<!-- 10.19.3 is the latest version of Preact at the time of writing -->
<!-- Note that hooks are provided in a separate script -->
<script src="https://cdn.jsdelivr.net/npm/preact@10.19.3/dist/preact.min.js" integrity="sha256-AVEFjvsovrKLreXG31cYrJM5sE86/sU1BifN9fLvcVM=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/preact@10.19.3/hooks/dist/hooks.umd.js" integrity="sha256-JpZEsutmP7dKktW7O461+saCi7ah+Nvj1IPElSsx9ng=" crossorigin="anonymous"></script>

Here’s the same example application written using Preact:

'use strict';
(function () {
	var h = preact.h; // Or preact.createElement, similar to React

	function App() {
		var nameState = preactHooks.useState('');
		var greetingState = preactHooks.useState('');
		return [
			h(
				'form',
				null,
				h(
					'label',
					{ onInput: function (e) { nameState[1](e.target.value); } },
					'Your name:',
					h('input', { type: 'text' })
				),
				h(
					'button',
					{
						type: 'submit',
						onClick: function (e) {
							greetingState[1](formatGreeting(nameState[0]));
							e.preventDefault();
						}
					},
					'Go'
				)
			),
			h('p', null, greetingState[0])
		];
	}

	function formatGreeting(name) {
		return name ? 'Hello, ' + name + '!' : 'Hello!';
	}

	preact.render(h(App), document.body);
})();

A few things may seem a bit strange, and they have to do with the fact that the code doesn’t use ECMAScript 6 features:

  • Preact’s documentation recommends using HTM, a way to add JSX-like markup without build tools. However, that uses ES6 tagged templates so it’s not going to work for this example. Without any JSX or similar markup, the code declares the elements using h/createElement function calls.
  • The useState hook is usually used like: const [foo, setFoo] = useState(''); However, destructuring assignment is an ES6 feature, so the code here stores the array as is.

Conclusion

This was a fun exercise more than anything else, and I’m not suggesting that we all go back to this old style of Web development. Even so, I think it has some value for smaller applications, where the benefits of setting up a build process don’t necessarily outweigh the costs of added complexity. I built a visualization of IP address space using Preact without build tools, and it wasn’t much of a problem at this scale.

Reply to this post on: LinkedIn, Twitter
Philip Chung
Philip Chung
Software Developer