WebAssembly is a simple thing: it’s a bytecode, which is independent of a particular language and particular runtime. The idea of the bytecode is, of course, not new, but this particular implementation is quite good.
Then, we have a few increasingly important use cases for good bytecode:
As the web becomes more and more of a full operating system, it becomes important to run CPU heavy computations in the browser.
Edge compute is the current trendy thing, and it too needs some sort of bytecode to run on the said edge
And of course various blockchain-shaped things need a bytecode for smartcontracts.
Finally, while not new or recent, there’s a background humming of various plugin architectures, where, ideally, you need some abstract way to specify computation as data (bytecode!) to plug into the larger application.
So, there’s a coincidence of various wants and a well-funded and well-designed technology which covers those wants.
As maybe an additional factor, WebAssembly doesn’t have GC (bc it’s hard to do GC without compromising on language/runtime independence and performance), and we have some interesting recent GC-less languages, which make targeting Wasm-shaped bytecode easier.
Finally, while this is more in the future of Wasm at this point, I get a feeling that the holy grail here is a component-based software architecture, where you get re-usable components without getting vendor locked into one particular ecosystem. So, COM, but done right.
Not sure if that’ll fly (there were quite a few iterations on the relevant module linking proposal already), it looks like it might, given the high quality of implementation of what’s already there.
WASI is also interesting in that it allows compiled programs (ie. Rust, C, etc.) to target WASM + WASI ABIs and effectively become JVM-like semi-interpreted languages. While I think server-side WASM is mostly silly, the ability to have shared language + modules in backend and frontend without having to use Node/JS is nice.
I’d like to see something on the other side of the spectrum - a microkernel WASM system. Just a bare WASM runtime, scheduler, and memory manager, with everything else built on top as WASM modules.
Not for any practical purpose, just for fun. Though it would be cool to have a system that actually seriously abstracts away the hardware almost entirely (including CPU architecture)
I think server-side WASM will be non-silly the same place the JVM is useful for letting users send servers .jar files and asking them to execute them (things like Apache Spark and other things where users provide the logic to be run in a larger system, where the logic has to be executed fast yet safely). Similarly, desktop apps can allow for plugins and run them in a WASM sandbox. You can dynamically create and execute WASM on an iPhone or iPad (unlike native code - App store doesn’t allow native compilation). Etc.
Yeah, that’s definitely a major use-case for WASM + WASI. I think WASM as a target for FaaS and microVM-based services will be an interesting topic too. Theoretically stronger sandboxing than traditional virtualization and containerization, so maybe we’ll see it start to supplant part of those markets.
Well, sounds like for most of the benefits you listed (besides browser applications) proven, established solutions are already available (e.g. ECMA-335), featuring even the parts still missing in WASM today (like the mentioned GC). Ironically for the original WASM use case (browser applications) the progress seems to be very slow, and people recently seem more interested to use WASM in non-brower applications.
I think quality of implementation really matters. While CLI and WebAssembly try to solve the same problem on the first glance, closer look at then reveals quite significant technical differences, which I think are enough to explain the fact that CLI didn’t fill WebAssembly niche despite having a considerable headstart.
I have only a passing familiarity with CLI, but there’s quite a few things which jump at me as liabilities:
Built-in OOP
names, overloads, coercions (Wasm has just mechanically-sympathetic indexes)
unstructured control flow
no dual text/binary view into code
a boat load of already existing implementations of WebAssembly vs a relatively few for CLI
It sort of like x86 vs RISCV — definitely, x86 has been around for longer, is deployed wider, and sort of works. But there’s clearly a design space for something more MIT and less New Jersey, and maybe even a market niche.
No need to use it; you can perfectly well generate bytecode which doesn’t use the dotnet framework nor OOP features. You can even do pointer arithmetics if you really want (a WASM index by the end of the day is just a pointer into the memory array).
unstructured control flow
Not sure whether this is supposed to be an advantage or disadvantage; in any case you can generate CIL code for both structured and unstructured control flow, and there is a verifier for the applications which want to be verifiable
no dual text/binary view into code
ILASM? Writing CLI assemblies in IL assembler is well possible; there are even several books about it
vs a relatively few for CLI
Well, there is the Mono and CoreCLR VM; Mono 5 is more than enough for all of my purposes; I’m looking forward to using the WASM micro runtime as soon as WASM has reached an essential feature set (e.g. GC and FFI).
Even if you don’t use it, there are still plenty of users who want, so it’s worthwhile the spend the design effort; it’s already spent anyway. With my Oberon+ compiler (see https://github.com/rochus-keller/Oberon/) I e.g. only use a fraction of the mscorlib.dll and the Mono binary which together are less than 10 MB, but nothing from the non-core .NET framework; and I can do everything I need including FFI and GC; none of the WASM engines today would support all required features to do an efficient implementations, even though my requirements are very minimal.
This boils down to “WebAssembly is simpler”; that’s not quality of implementation. In fact, CLI has better quality of implementation, like having a GC integration. C and C++ compile to CLI well, built-in OOP is not a problem in practice.
Edit: CLI has standard text form (it is specified in ECMA-335, together with binary form), so you are completely mistaken about text/binary issue.
It seems to me that simplicity is the main QoI of an intermediate language itself. Number of different implementations is probably the definitive metrics for measuring success of “protocols”, and simplicity is a significant contribution to that.
CLI is probably better at “the thing you can compile C++ to”, but clearly worse at serving as an common intermediate infrastructure between independent actors.
I argue that CLI is “clearly worse” at serving independent actors because independent actors refused. Even if it got a well done international standard. It is mostly politically worse, not technically worse.
CLI had interesting implementations like JSIL. If you are thinking CLI had only two implementations, .NET and Mono, because it is so complex, you are mistaken. In fact CLI is of quite reasonable size (and WebAssembly, currently, is a bit too small), and I think number of implementations mostly reflects popularity, not simplicity.
If you are thinking CLI had only two implementations, .NET and Mono, because it is so complex, you are mistaken.
Thanks, this would be an important update for me! What would be the best thing read to understand what CLI is? A the moment, I indeed think that it is way to complex, but that ‘s base on background info and skimming the standard’s toc, not the actual knowledge.
Hm, does it? Reading from VI.Annex CCIL assembler implementation
This clause provides information about a particular assembler for CIL, called ilasm. It supports a superset of the syntax defined normatively in Partition II, and provides a concrete syntax for the CIL instructions specified in Partition III.
Which sounds to me like an implementation defined, non-canonical encoding of a particular assembler. This seems close, but also importantly different from WASM, which specifies implementation independent text format, together with required syntactic sugar which makes hand-writing Wasm quite OK.
Again, this is a difference in degree/quality, both technologies has text format, but Wasm seems way more upfront about code being actually human-readable and human editable.
Yes it does. Partition II is standard, and it defines text form with BNF and all. Annex C is not, but there is no non-canonicality going on. Standard binary form can be canonically converted to standard text form.
In fact, this is exactly the same situation with WebAssembly. WAT is standard and WAST is not. See WAST vs WAT for an explanation.
Hm, I am still not seeing it. Partition II delegates textual encoding of instructions to Partition VI (annex), see the table in II.15.4.1 Method body.
The situation in Wasm seems different, WAT (not WAST) is the thing that defines helpful abbreviations and symbolic $names (see, eg, abbreviations section from https://webassembly.github.io/spec/core/text/modules.html#text-func). WAST I think is just minor extensions to specify test directives (basically, a bunch of asserts at the top-level). That is, it doesn’t seem that the linked page entirely correctly explains what WAT is.
Quoting the spec,
Except for a few exceptions, the core of the text grammar closely mirrors the grammar of the abstract syntax. However, it also defines a number of abbreviations that are “syntactic sugar” over the core syntax.
My take is that it’s because it is (IIUC) an open standard of a bytecode that is not controlled by a single major player, while at the same time many major players have a serious stake in it. As such, people and companies feel relatively safe from being captured by a monopolist when using it, while also at the same time relatively safe that it won’t be abandoned soon, so an investment in it (money, time) won’t suddenly become wasted resources. Due to its evolution path, it was also immediately useful from the beginning and compatible with a huge target platform (“Da Web”) with a relatively clear path to quick performance improvements with low opportunity cost for browser makers (basically, “make some corner-case areas of JS run faster is still mostly a win for us even if this newfangled wasm thing shows up to be a fad”), which gave it big credibility and a promising future from the start (at least that’s what I thought when I first learned about asm.js).
Good question; the most excitement at the moment seems even to be in non-browser or even embedded applications, where there are already good established solutions; even more since there is no GC (yet) one can fairly ask why one should compile a non-VM language to WASM and not directly to machine code. Personally I still prefer LuaJIT for dynamic (and even some statically typed) languages, and CIL/Mono for statically typed VM languages. The WASM micro runtime looks interesting though and I’ll keep an eye on it.
CIL/ECMA-335 or the JVM is also “sandboxed”; but why anyway should there be a benefit for a sandbox when you run code on your own desktop, server or embedded device? For browser apps run from anywhere on the web sandboxing is essential, but for the others mentioned?
I agree C++/CLI is great and browsers should have embedded Mono and C++ libraries should have been compiled with C++/CLI instead of reinventing the wheel that is WebAssembly. As you pointed out, C++/CLI already solved GC integration! Alas, browser vendors disagreed, and we are powerless to stop them. (One concrete problem is that as far as I know there is no open source implementation of C++/CLI, but if this superior technical choice was taken, it would have been easy enough to add support to GCC and Clang.)
Ok, I see. But even without CLI or WASM you can isolate plugins today (if you really think it’s worthwhile) by running them in a separate process; in contrast to a WASM based solution this even works for all kinds of applications and languages; and if you want to run non-trustable applications you can use virtualization.
Well, then apparently something like CIL is preferable; but even without CIL, there is still CORBA or similar technologies (e.g. Protocol Buffers, or even web services, i.e. all kinds of technologies where you can generate stub functions in your host language from given IDL specifications).
WASM would be a great alternative to CIL for VM languages/applications which want to do without managed types, but it still has a long way to go before it is sufficiently useable.
There are plenty of existing, proven solutions for exactly this purpose. WASM doesn’t bring anything to the table which we didn’t have already. It’s just yet another intermediate representation.
It certainly brings something new to the table: it runs in a web browser. Web browsers are great, because they are completely ubiquitous, and are able to fetch resources on-demand. They also allow you to deploy your server-side code whenever you want, so you can update your application and with one page refresh your users have the up to date version.
Can you name another binary format / intermediate representation that has that capability?
Can you name another binary format / intermediate representation that has that capability?
Java applets.
I think Webassembly has plenty of technical advantages; Java applets have a very different execution model that is much more naive. But let’s not fool ourselves into thinking this has never been done before.
Yeah, it feels like wasm was made by the browser people as “let’s make a replacement for Java applets that is better-behaved for our use cases” - no expansive stdlib, the program only gets the interfaces that the client allows for.
That’s a strange way to look at it. Java is amazingly efficient; no language prior to it was so incredicbly effective at trading off RAM usage to achieve better CPU throughput.
I’d suggest a different take than yours, having implemented a JVM, a Java compiler, some prototype WASM compilers, etc. (i.e. barely knowledgeable enough to believe my own B.S.), and here it is:
WASM is just like Java, without any language with it, and without dragging any language assumptions with it.
Java byte code may be claimed to be language agnostic, but it is not. It is designed to support exactly one language: Java. You can compile other languages to Java byte code, but in doing so, those languages all become Java, because the Java language assumptions (including the Java type system, such as java/lang/String and java/lang/Object and java/lang/Exception and so on) fully permeate the design of Java byte code.
WASM is more like “whatever … there’s no such thing as a type”.
Java is amazingly efficient; no language prior to it was so incredicbly effective at trading off RAM usage to achieve better CPU throughput.
That’s fine, but that’s rarely something I consider when evaluating efficiency. I can’t actually remember the last time I had a nontrivial system bottlenecked by CPU before it was bottlenecked by memory.
It’s all a bit moot, though. Current browser-oriented Wasm use cases are more or less equivalent to the stuff that Java Applets etc. were meant to solve. The difference is the overhead, and that difference dominates the cost calculus.
That’s fine, but that’s rarely something I consider when evaluating efficiency.
I’m going to have to start adding the “/s” tag when I write stuff like that, I guess. I thought that my sarcasm was pretty obvious, but apparently my sense of humor is only appreciated inside of my own skull.
Because web browsers have support for it out-of-the-box, and it enables writing webapps in rust/c++/other languages, instead of JavaScript like it’s done now.
Why is there all this excitement about wasm? I assume I’m missing something.
Here’s how I see it.
WebAssembly is a simple thing: it’s a bytecode, which is independent of a particular language and particular runtime. The idea of the bytecode is, of course, not new, but this particular implementation is quite good.
Then, we have a few increasingly important use cases for good bytecode:
So, there’s a coincidence of various wants and a well-funded and well-designed technology which covers those wants.
As maybe an additional factor, WebAssembly doesn’t have GC (bc it’s hard to do GC without compromising on language/runtime independence and performance), and we have some interesting recent GC-less languages, which make targeting Wasm-shaped bytecode easier.
Finally, while this is more in the future of Wasm at this point, I get a feeling that the holy grail here is a component-based software architecture, where you get re-usable components without getting vendor locked into one particular ecosystem. So, COM, but done right.
Not sure if that’ll fly (there were quite a few iterations on the relevant module linking proposal already), it looks like it might, given the high quality of implementation of what’s already there.
WASI is also interesting in that it allows compiled programs (ie. Rust, C, etc.) to target WASM + WASI ABIs and effectively become JVM-like semi-interpreted languages. While I think server-side WASM is mostly silly, the ability to have shared language + modules in backend and frontend without having to use Node/JS is nice.
I’ve heard it has nice sandboxing properties that are convenient for security on server
I would really like to see a unikernel WASM runtime. No OS, filesystem, etc. Just networking stack and object storage.
There were things like Cervus and Nebulet.
I’d like to see something on the other side of the spectrum - a microkernel WASM system. Just a bare WASM runtime, scheduler, and memory manager, with everything else built on top as WASM modules.
Not for any practical purpose, just for fun. Though it would be cool to have a system that actually seriously abstracts away the hardware almost entirely (including CPU architecture)
You are looking for redshirt. Everything under programs, like e1000 device driver, are WASM modules.
Yes - in particular, it omits a bunch of the insecure-by-default APIs you find in things like the Java and Lua stdlibs.
I think server-side WASM will be non-silly the same place the JVM is useful for letting users send servers .jar files and asking them to execute them (things like Apache Spark and other things where users provide the logic to be run in a larger system, where the logic has to be executed fast yet safely). Similarly, desktop apps can allow for plugins and run them in a WASM sandbox. You can dynamically create and execute WASM on an iPhone or iPad (unlike native code - App store doesn’t allow native compilation). Etc.
Yeah, that’s definitely a major use-case for WASM + WASI. I think WASM as a target for FaaS and microVM-based services will be an interesting topic too. Theoretically stronger sandboxing than traditional virtualization and containerization, so maybe we’ll see it start to supplant part of those markets.
Well, sounds like for most of the benefits you listed (besides browser applications) proven, established solutions are already available (e.g. ECMA-335), featuring even the parts still missing in WASM today (like the mentioned GC). Ironically for the original WASM use case (browser applications) the progress seems to be very slow, and people recently seem more interested to use WASM in non-brower applications.
I think quality of implementation really matters. While CLI and WebAssembly try to solve the same problem on the first glance, closer look at then reveals quite significant technical differences, which I think are enough to explain the fact that CLI didn’t fill WebAssembly niche despite having a considerable headstart.
I have only a passing familiarity with CLI, but there’s quite a few things which jump at me as liabilities:
It sort of like x86 vs RISCV — definitely, x86 has been around for longer, is deployed wider, and sort of works. But there’s clearly a design space for something more MIT and less New Jersey, and maybe even a market niche.
No need to use it; you can perfectly well generate bytecode which doesn’t use the dotnet framework nor OOP features. You can even do pointer arithmetics if you really want (a WASM index by the end of the day is just a pointer into the memory array).
Not sure whether this is supposed to be an advantage or disadvantage; in any case you can generate CIL code for both structured and unstructured control flow, and there is a verifier for the applications which want to be verifiable
ILASM? Writing CLI assemblies in IL assembler is well possible; there are even several books about it
Well, there is the Mono and CoreCLR VM; Mono 5 is more than enough for all of my purposes; I’m looking forward to using the WASM micro runtime as soon as WASM has reached an essential feature set (e.g. GC and FFI).
The runtime still needs to implement it. That takes work, and requires design tradeoffs.
Even if you don’t use it, there are still plenty of users who want, so it’s worthwhile the spend the design effort; it’s already spent anyway. With my Oberon+ compiler (see https://github.com/rochus-keller/Oberon/) I e.g. only use a fraction of the mscorlib.dll and the Mono binary which together are less than 10 MB, but nothing from the non-core .NET framework; and I can do everything I need including FFI and GC; none of the WASM engines today would support all required features to do an efficient implementations, even though my requirements are very minimal.
This boils down to “WebAssembly is simpler”; that’s not quality of implementation. In fact, CLI has better quality of implementation, like having a GC integration. C and C++ compile to CLI well, built-in OOP is not a problem in practice.
Edit: CLI has standard text form (it is specified in ECMA-335, together with binary form), so you are completely mistaken about text/binary issue.
It seems to me that simplicity is the main QoI of an intermediate language itself. Number of different implementations is probably the definitive metrics for measuring success of “protocols”, and simplicity is a significant contribution to that.
CLI is probably better at “the thing you can compile C++ to”, but clearly worse at serving as an common intermediate infrastructure between independent actors.
I argue that CLI is “clearly worse” at serving independent actors because independent actors refused. Even if it got a well done international standard. It is mostly politically worse, not technically worse.
CLI had interesting implementations like JSIL. If you are thinking CLI had only two implementations, .NET and Mono, because it is so complex, you are mistaken. In fact CLI is of quite reasonable size (and WebAssembly, currently, is a bit too small), and I think number of implementations mostly reflects popularity, not simplicity.
Thanks, this would be an important update for me! What would be the best thing read to understand what CLI is? A the moment, I indeed think that it is way to complex, but that ‘s base on background info and skimming the standard’s toc, not the actual knowledge.
I guess the answer might be “skim the standard”?
Hm, does it? Reading from VI.Annex CCIL assembler implementation
Which sounds to me like an implementation defined, non-canonical encoding of a particular assembler. This seems close, but also importantly different from WASM, which specifies implementation independent text format, together with required syntactic sugar which makes hand-writing Wasm quite OK.
Again, this is a difference in degree/quality, both technologies has text format, but Wasm seems way more upfront about code being actually human-readable and human editable.
All specifications and examples in the standard use the text form of IL.
Yes it does. Partition II is standard, and it defines text form with BNF and all. Annex C is not, but there is no non-canonicality going on. Standard binary form can be canonically converted to standard text form.
In fact, this is exactly the same situation with WebAssembly. WAT is standard and WAST is not. See WAST vs WAT for an explanation.
Hm, I am still not seeing it. Partition II delegates textual encoding of instructions to Partition VI (annex), see the table in II.15.4.1 Method body.
The situation in Wasm seems different, WAT (not WAST) is the thing that defines helpful abbreviations and symbolic $names (see, eg, abbreviations section from https://webassembly.github.io/spec/core/text/modules.html#text-func). WAST I think is just minor extensions to specify test directives (basically, a bunch of asserts at the top-level). That is, it doesn’t seem that the linked page entirely correctly explains what WAT is.
Quoting the spec,
Syntactic sugar for hand-authoring is specified.
Note for people as ignorant as me, ECMA 335 refers to the Common Language Infrastructure (CLI), discussed downthread.
My take is that it’s because it is (IIUC) an open standard of a bytecode that is not controlled by a single major player, while at the same time many major players have a serious stake in it. As such, people and companies feel relatively safe from being captured by a monopolist when using it, while also at the same time relatively safe that it won’t be abandoned soon, so an investment in it (money, time) won’t suddenly become wasted resources. Due to its evolution path, it was also immediately useful from the beginning and compatible with a huge target platform (“Da Web”) with a relatively clear path to quick performance improvements with low opportunity cost for browser makers (basically, “make some corner-case areas of JS run faster is still mostly a win for us even if this newfangled wasm thing shows up to be a fad”), which gave it big credibility and a promising future from the start (at least that’s what I thought when I first learned about asm.js).
Because it gets us away from using JS, plain and simple.
Good question; the most excitement at the moment seems even to be in non-browser or even embedded applications, where there are already good established solutions; even more since there is no GC (yet) one can fairly ask why one should compile a non-VM language to WASM and not directly to machine code. Personally I still prefer LuaJIT for dynamic (and even some statically typed) languages, and CIL/Mono for statically typed VM languages. The WASM micro runtime looks interesting though and I’ll keep an eye on it.
WebAssembly is sandboxed, while machine code is not. WebAssembly is a good way to provide SFI(Software Fault Isolation), see RLBox for example.
CIL/ECMA-335 or the JVM is also “sandboxed”; but why anyway should there be a benefit for a sandbox when you run code on your own desktop, server or embedded device? For browser apps run from anywhere on the web sandboxing is essential, but for the others mentioned?
Software fault isolation is useful for the same reason process isolation is useful. For example, editor may want to recover from crash of plugin. In addition to RLBox, have a look at How to build a plugin system on the web and also sleep well at night by Figma.
I agree C++/CLI is great and browsers should have embedded Mono and C++ libraries should have been compiled with C++/CLI instead of reinventing the wheel that is WebAssembly. As you pointed out, C++/CLI already solved GC integration! Alas, browser vendors disagreed, and we are powerless to stop them. (One concrete problem is that as far as I know there is no open source implementation of C++/CLI, but if this superior technical choice was taken, it would have been easy enough to add support to GCC and Clang.)
Ok, I see. But even without CLI or WASM you can isolate plugins today (if you really think it’s worthwhile) by running them in a separate process; in contrast to a WASM based solution this even works for all kinds of applications and languages; and if you want to run non-trustable applications you can use virtualization.
It boils down to “function call is easier and simpler than IPC”.
Well, then apparently something like CIL is preferable; but even without CIL, there is still CORBA or similar technologies (e.g. Protocol Buffers, or even web services, i.e. all kinds of technologies where you can generate stub functions in your host language from given IDL specifications).
WASM would be a great alternative to CIL for VM languages/applications which want to do without managed types, but it still has a long way to go before it is sufficiently useable.
Portable binaries seem like another reason to prefer it to machine code
There are plenty of existing, proven solutions for exactly this purpose. WASM doesn’t bring anything to the table which we didn’t have already. It’s just yet another intermediate representation.
It certainly brings something new to the table: it runs in a web browser. Web browsers are great, because they are completely ubiquitous, and are able to fetch resources on-demand. They also allow you to deploy your server-side code whenever you want, so you can update your application and with one page refresh your users have the up to date version.
Can you name another binary format / intermediate representation that has that capability?
Java applets.
I think Webassembly has plenty of technical advantages; Java applets have a very different execution model that is much more naive. But let’s not fool ourselves into thinking this has never been done before.
Yeah, it feels like wasm was made by the browser people as “let’s make a replacement for Java applets that is better-behaved for our use cases” - no expansive stdlib, the program only gets the interfaces that the client allows for.
Sure, but the argument was because of the current excitment for non-browser WASM applications.
One way to think about WASM is that it’s basically Java, but efficient enough to actually be usable.
That’s a strange way to look at it. Java is amazingly efficient; no language prior to it was so incredicbly effective at trading off RAM usage to achieve better CPU throughput.
I’d suggest a different take than yours, having implemented a JVM, a Java compiler, some prototype WASM compilers, etc. (i.e. barely knowledgeable enough to believe my own B.S.), and here it is:
WASM is just like Java, without any language with it, and without dragging any language assumptions with it.
Java byte code may be claimed to be language agnostic, but it is not. It is designed to support exactly one language: Java. You can compile other languages to Java byte code, but in doing so, those languages all become Java, because the Java language assumptions (including the Java type system, such as java/lang/String and java/lang/Object and java/lang/Exception and so on) fully permeate the design of Java byte code.
WASM is more like “whatever … there’s no such thing as a type”.
That’s fine, but that’s rarely something I consider when evaluating efficiency. I can’t actually remember the last time I had a nontrivial system bottlenecked by CPU before it was bottlenecked by memory.
It’s all a bit moot, though. Current browser-oriented Wasm use cases are more or less equivalent to the stuff that Java Applets etc. were meant to solve. The difference is the overhead, and that difference dominates the cost calculus.
I’m going to have to start adding the “/s” tag when I write stuff like that, I guess. I thought that my sarcasm was pretty obvious, but apparently my sense of humor is only appreciated inside of my own skull.
Wow! I have the dryest sense of humor among everyone I’ve ever met, by a wide margin, and I didn’t catch this at all. Well done? 😉
Because web browsers have support for it out-of-the-box, and it enables writing webapps in rust/c++/other languages, instead of JavaScript like it’s done now.