<rip>HTML</rip>
JavaScriptUI — DevBlog #2
A couple of years ago I had a crazy idea: would it be possible to build websites without HTML?
No, I am not talking about building a new browser engine or using the canvas to display content, and no, I am not talking about WebAssembly or some new language that is compiled into HTML. I am talking about a way to write a fully functional website that instantly runs in any browser available today without writing a single line of HTML.
The thing is, it’s very much possible, and not even that difficult to do right now, we just need to abuse the sh*t out of the DOM API in JavaScript. Sure, it still generates some HTML inside the browser but that is just an implementation detail we can ignore for now. What we need is just the DOM. So let’s dive into the world of JavaScript generated websites and violate every single best practice possible.
Views and the View Tree
To build anything worthwhile we need objects. The default option is to use document.createElement()
to create HTML elements andElement.prototype.append()
or Node.prototype.appendChild()
to add them to the DOM later. Pretty basic, but it is cumbersome, ugly and confusing. We can do better.
Instead, let’s define some declarative constructors like Image
, Text
and Stack
and call them Views. They will still rely on the DOM API but can provide a much better declarative syntax. Take a look at this for example:
Stack(
Text("Hello World!"),
Stack(
Text("Everyone has a plumbus in their home."),
Image("plumbus.jpeg")
)
);
The beauty of this is that this is valid JavaScript. We just call some View constructors and supply their children as arguments. After all any View tree is just a nested list, regardless of the language we use.
Another benefit is that you can create your own Views, so you don’t have to wait for another decade for Safari to implement customized built-in elements. Or, if you are b*tthurt about losing semantics, you could create Views that map to proper HTML elements:
Div(
Div(
Div(
Div(),
Div(),
Div()
)
)
);
Never say new(er)
There are two reasons I chose not to use the new
keyword before the View constructors. First, our trees can become quite large quite fast, and having a shorter syntax helps readability. Second, JavaScript already provides constructors like Image and Text (and possibly others) and we need these names. Luckily, most of these built in constructors only work with the new
keyword, so in theory we can keep their functionality while adding our own.
new Image(width, height); //creates an HTMLImageElement (default behavior)
Image(url); //creates an Image View (JavaScriptUI)
The magic of logic
Now here comes the real fun part. We aren’t just mimicking HTML in JavaScript, we can go way beyond its functionality. We are now in the realm of a true programming language so we have access to variables, operators, conditionals, loops, first-class functions and many, many more to build literally anything we want. Even something as simple as a conditional assignment is well beyond the capabilities of HTML:
Stack(
yourChoice ? Text("red pill") : Text("blue pill")
);
Now, I understand that FizzBuzz can be traumatizing for some unfortunate CSS aficionados, but functions, operators, loops and conditionals are extremely simple, universal and powerful building blocks that enable us to go even further. With a simple callback, we can do things like this:
Stack(function* () {
let i = 1;
while (i <= 100) {
if ((i % 15) === 0) {
yield Text("FizzBuzz");
} else if ((i % 3) === 0) {
yield Text("Fizz");
} else if ((i % 5) === 0) {
yield Text("Buzz");
} else {
yield Text(i);
}
i += 1;
}
});
Don't worry about the generator syntax, I just use it here to return multiple values from a function in a declarative way. With some trickery in the View constructors we can do the same with a regular function, although now we need to pay attention that this callback works a bit different than what we would expect from vanilla JavaScript:
Stack(() => {
let i = 1;
while (i <= 100) {
if ((i % 15) === 0) {
Text("FizzBuzz"); //added to Stack
} else if ((i % 3) === 0) {
Text("Fizz"); //added to Stack
} else if ((i % 5) === 0) {
Text("Buzz"); //added to Stack
} else {
Text(i); //added to Stack
}
i += 1;
}
});
Or, if you are not scared of conditionals without blocks, you can write something like this:
Stack(() => {
let i = 1;
while (i <= 100) {
if (i % 15 === 0) Text("FizzBuzz");
else if (i % 3 === 0) Text("Fizz");
else if (i % 5 === 0) Text("Buzz");
else Text(i);
i += 1;
}
});
It doesn’t get much more declarative than that.
But it goes even further still. View constructors can supply arguments to their callbacks, set their this
context to itself or even implement a reactive system under the hood to abstract away many inconvenient aspects of UI building. For now, this is a quick overview of what’s possible currently and what isn’t:
Stack((argument) => {
//this; //accessing parent this way doesn't work
argument; //accessing parent as argument works (predefined inside View constructors)
//myView; //referencing view doesn't work
Text("foo"); //adding a regular child works
let view = Text("temp"); //variable assignment works, but any newly created view is automatically added to parent
evaluate ? Text("foo") : Text("bar"); //ternary works
if (
evaluate //conditionals work
) {
Text("foo");
} else {
Text("bar");
}
let i = 0;
while (i < 5) {
Text("foo"); //loops work
i += 1;
}
});
So this is JavaScriptUI
This is the basis of JavaScriptUI. My goal is to migrate all functionality from HTML and CSS directly into JavaScript, leveraging default browser APIs and the vast capabilities of general purpose scripting languages.
I am aiming to pump out a new post each week to keep you updated and share my progress (or just entertain myself if nobody reads them 😄). I’m hoping that I’ll be able to show you some working code in the near future so that you can start playing around with it.
In the meantime if you enjoy my stuff please clap, comment and share my DevBlog with anyone interested.
Thanks, and stay tuned.
⬅️ JavaScriptUI — DevBlog #1, a new web, without HTML and CSS
JavaScriptUI — DevBlog #3, Designing a new style system to replace CSS➡️