JavaScript: the right tool for the job?
Tags: javascript, webdev
Published: 2021-08-01 19:18:46

Hi there, fellow programmers!

Lately I've been making improvements to my JS-free web asset bundler (to be more precise, to the ES6 module bundler of the toolchain) and apropos of this activity, I had to re-realize how fundamentally broken the "programming language of the web" (and, more and more, the language of the backend and desktop/mobile apps) is.

A nice-to-have step of the ES6 module bundling process is detecting variable name collisions (redeclarations) because without that the bundler may turn a syntactically incorrect program to a syntactically correct one with possibly unwanted behavior. But I won't delve more deeply into the details of this as it's not relevant to this post.

What's relevant is the quirks which are getting unearthed as I'm trying to decipher JS's variable redeclaration detection logic.

For example, let's have a look at the following code:

```
(() => {
  try { throw 'error'; }
  catch (err) {
    var err = { err };
    console.log(err);
  }
  console.log(err);
})()
```

Try to guess the output of this snippet.

**Spoiler alert!** The first message is just exactly what's expected (the newly created object instance) but the second one (which is printed in the outer block) is `undefined`. Up to the point that the script executes without error, there's no surprise since `var` declarations are function-scoped and hoisted to the beginning of the function.

But how on earth becomes our `var` variable `undefined` when we assign it an object instance right at its declaration???

The thing is that it doesn't actually get initialized at all. The first part of the statement `var err = { err };` declares the variable as expected but the second part assigns the catch clause's error parameter! (Even strict mode makes no difference in this case.)

Well, that's that. Let's get over it and check out another one:

```
(() => {
  try { throw 'error'; }
  catch (err) {
    var e = err;
    function err() { return e; }
  }
  console.log(err());
})()
```

This is a redeclaration error. But if we move the function declaration into a nested block:

```
(() => {
  try { throw 'error'; }
  catch (err) {
    var e = err;
    {
      function err() { return e; }
    }
  }
  console.log(err());
})()
```

Ta-da! No error and prints the expected message! (At least in this case we can stop the confusion if we enable strict mode because then it's guaranteed that functions are not hoisted out of the declaring block.)

Let's check out one more.

```
(() => {
  {
    const f = 0; 
    {
      {
        var f = function() { return 1 }       
      } 
      console.log(f);
    }
  }
  console.log(f);
})()
```

This is a redeclaration error, too. The moral of the story is that  block-scoped and function-scoped variables don't like each other.

```
(() => {
  {
    const f = 0; 
    {
      {
        function f() { return 1 }       
      } 
      console.log(f);
    }
  }
  console.log(f);
})()
```

Now, it's getting confusing. First, it prints `0` (looks like `const` is the stronger one in that intermediate no-man's-land block), then throws an error saying "f is not defined". It seems the `const` declaration somehow blocks the hoisting of the function.

However, if we replace `const` with `var`, everything works as expected:

```
(() => {
  {
    var f = 0; 
    {
      {
        function f() { return 1 }       
      } 
      console.log(f);
    }
  }
  console.log(f);
})()
```

Prior to ES6, the behavior of function declarations in nested blocks was undefined (mostly, an error), then, with ES6, it changed to "[complicated](https://stackoverflow.com/questions/31419897/what-are-the-precise-semantics-of-block-level-functions-in-es6#answer-31461615)". Yay!

And all this is just a tiny portion of the can of worms which we call JS quirks. Isn't it intimidating that a large portion of the software we use these days are built on this inconsistent and confusing technology? As we say, always use the right tool for the job. But JS seems like a hammer whose head is taped to its handle. You can hit with it but you never know when it will fall apart. How could such a thing become a ubiquitous tool when there are so many mature tools which was carefully designed by seasoned engineers?

Of course, it's a rhetorical question. We all know that the reasons are mainly historical and political. Anyhow, this situation is not alright. Today, in web development, we use a markup language (HTML) which was designed for describing rich text documents as a rich GUI markup language for applications which moved from the OS into the browser (mainly because of easy deployment and multi-platform support). We use a scripting language (JS) which was meant for enabling some basic interactions and animations in our web pages by adding several lines of code as a general-purpose, application programming language. In order to make this work, that is, to provide the rich GUI-feeling in the browser and to keep it maintainable at the same time, we need hacks on top of hacks on top of hacks.

I think we should have separated this two use cases long ago. HTML and JS is perfect for what they were designed for but we need something else for rich GUI applications, some kind of technology which was designed specifically for that purpose because the current web stack is a poor tool concerning that, no matter how strong we try to wind more bands of tape around it.

As a matter of fact, we already have technologies which would meet the requirements of rich GUI application development much better. In my opinion, Silverlight is (was) maybe the closest to this achievement. It's a shame that it got killed because of Microsoft's (then) bad attitude towards free and open source software. If they hadn't wanted monopoly on the web, chances are that we wouldn't have to wrestle with the utter clusterfuck also known as the JS ecosystem now. But that ship has sailed and the plugin model is also such a concept that we need to avoid. The new programming model should be supported by browsers out-of-the-box.

Luckily, by the emergence of WebAssembly a standardized, free and open source rich GUI application programming model is not something completely unimaginable any more. Just the idea would need some love from the big players.

Ironically, Microsoft's Blazor is our best hope for now, but it will always be halfway to the real solution as it builds on HTML. But even so, it's a far better programming model because C# is a real application programming language with great tooling. However, there is still much work to do: among others, WebAssembly should get a GC and direct access to DOM and bundle size must be reduced significantly. The solution to the latter looks pretty simple: browsers should ship with the standard library so that it's available right away instead of being downloaded all the time. For example, this is what I meant by the love from the big ones. This would need some co-operation. And this is just as in their interest as in ours because using subpar tools is in nobody's interest since it hurts productivity and in the end it hurts business.