As codebases have grown in complexity, the dynamic typing nature of JavaScript has become arguably suboptimal. TypeScript was introduced by Microsoft as a superset of JavaScript back in 2012, with version 1.0.0 out in 2014. It introduced a more strict and structured way of writing with data types and features that were missing altogether in JavaScript.
While either TypeScript or JavaScript is generally sufficient to solve today’s web development problems, JavaScript is limiting in extreme use cases such as AR, VR, and high fps 3D games. In such scenarios, we need native-like performance on the web, making WebAssembly perfect for this kind of problem.
WebAssembly is a relatively new concept for modern web browsers. It gives us the ability to run code from low-level languages, say C/C++ or Rust, easily. WebAssembly is fast because it’s distributed in a binary format (ie, already compiled), unlike high-level languages for the web such as TypeScript and JavaScript. The goal of WebAssembly is not to create a paradigm shift from the existing JavaScript ecosystem but to run side by side with JavaScript, supporting backward compatibility.
As WebAssembly requires writing low-level languages, web developers who are usually proficient in TypeScript or JavaScriptneed to learn a new syntax altogether. This is where AssemblyScript shines. It follows the very similar footsteps of TypeScript, so developers can work with a more familiar syntax.
Note: AssemblyScript is not a TypeScript-to-WebAssembly compiler. Neither data types are similar to that of TypeScript.
According to AssemblyScript’s home page, "Being a variant of TypeScript makes it easy to compile to WebAssembly without learning a new language." AssemblyScript uses the Binaryen compiler. Unlike V8’s JIT compiler, which is used to execute JavaScript, Binaryen allows AssemblyScript to be converted to machine code much faster without any performance overhead.
Behaviors to Watch Out For in AssemblyScript
Now, let’s walk through some of AssemblyScript’s unexpected behavior and features that might come as a complete surprise to TypeScript developers.
Numbers
Traditionally in JavaScript, there was just a single representation of the number
type with limits on its range. This issue was addressed later in ECMAScript standards by introducing a new number type called BigInt
. TypeScript version 3.2 released support for BigInt
, but the number types in AssemblyScript are more restrictive and type-safe with various floating points and specific number
types.
A few of them are:
- i8 - 8 bit signed integer
- i16 - 16 bit signed integer
- i32 - 32 bit signed integer
- i64 - 64 bit signed integer
- u8 - 8 bit unsigned integer
- u16 - 16 bit unsigned integer
- u32 - 32 bit unsigned integer
- u64 - 64 bit unsigned integer
- f32 - 32 bit float
- f64 - 64 bit float
In addition to these, you have two variable integer
types as well:
- isize - supports i32 and i64
- usize - supports u32 and u64
These leniencies for isize
and usize
will be uplifted in the upcoming release.
Array Sorting
Sorting an array of numbers in AssemblyScript can be fundamentally very different to TypeScript. Consider this example in TypeScript:
[3, 1, 2].sort();
> [1, 2, 3]
[20, 10, 3].sort()
> [10, 20, 3]
While the first array sorting behaves as expected, the second one doesn't make any sense. The reason for this is that TypeScript coerces the number to string
using the toString()
method, then compares its UTF-16
values alphabetically.
This mechanism of coercing has been improved in AssemblyScript. Instead of sorting alphabetically, it sorts numerically, and the results are as expected with no such weird quirks.
[20, 10, 3].sort()
> [3, 10, 20]
declare Keyword and Access Modifiers
The declare
keyword in TypeScript tells the compiler that a variable inside the declare
block already exists and TS doesn’t need to compile it to JavaScript.
So say you add a reference to your codebase from a third-party domain that the TS
compiler knows nothing about; it would immediately throw an error. The declare
block assures the compiler that the variable does exist and has a type but does not transpile as a JavaScript output.
However, when compiled, a declare
block in AssemblyScript
will result in WebAssembly imports that are added when the module is instantiated by JavaScript:
Creating an import namespace in AssemblyScript :
declare namespace Math {
function add(a: f64, b: f64): f64;
}
And when running it in JavaScript:
import { instantiateStreaming } from "assemblyscript/lib/loader";
const AssemblyScript = await instantiateStreaming(fetch("../build/output.wasm"), {
Math : {
add(a, b){
return a + b;
}
}
});
Many of the features in AssemblyScript are still in beta or planned for future releases on their product roadmap.
As of now, AssemblyScript does not enforce keyword class modifiers such as:
public
private
protected
(private
modifier does prevent it from be publically accessible)
WebAssembly Features
AssemblyScript features depend on the WebAssembly specification, and the future roadmap for implementation is highly ambitious. As mentioned earlier, WebAssembly is a new concept for the web ecosystem, and so is AssemblyScript. As of now, AssemblyScript provides enough to get you started on your WebAssembly journey.
Features that are already available include:
- Classes and Interfaces: Class-based syntax works as expected, however, there are few caveats. For example,
private
andprotected
are not enforced, but this leniency will get restrained in future releases. Similarly,interface
is nonexistent in AssemblyScript as of now, but the roadmap does includegetters
andsetters
. - Garbage Collection: The official WebAssembly proposal for garbage collection is still in the works, meanwhile, the garbage collector built into AssemblyScript works on top of Wasm’s linear memory with several variants available for different use cases.
- Interop with JavaScript: AssemblyScript provides a
loader
that translates JavaScript objects into WebAssembly memory (and vice versa), making working with AssemblyScript modules possible without running into performance issues. - Standard Library: The difference between TypeScript’s dynamic to AssemblyScript's static typing has resulted in a different standard library. At a very high level, AssembyScript does things as they were in TypeScript but is more restrictive in terms of assignments and memory allocation.
Features in TypeScript such as closures
and iterators
are not available yet. AssemblyScript also needs Exception Handling
; as of now, throwing an exception immediately terminates the program.
Handling Null
null
type in TypeScript can be daunting to work with.
TypeScript developers sometimes assign a data variable to null:
const data: number | null;
const foo: null
But this is discouraged in AssemblyScript, and it does throw an error whenever we try to assign null
to a basic data type. Only classes and functions can be nullable
.
function bar(value : i32) : null {
// logic
}
There’s no support for undefined
or void
or any
types. A few situations where you need compile-time/runtime type checking can be dealt with by the instanceof
keyword.
Comparison
==
and ===
are comparison operators in TypeScript. ==
coerces the comparison, which is why 2 == "2" -> true
in JavaScript, while ===
doesn’t. ECMAScript recommends ===
for comparison to avoid any unwanted quirks.
In AssemblyScript, there isn't a concept of "triple equals” for comparing data types. Interestingly, ===
in AS is used to compare objects and returns true only when two objects are strictly the same. The de facto method of comparison in AssemblyScript is just ==
.
Conclusion
As the ecosystem around WebAssembly matures, AssemblyScript has a roadmap that depends heavily on its evolving specification, narrowing the gap between TypeScript and WebAssembly. While AssemblyScript is somewhat new on the web, the whole idea around rethinking high-performance web development can have a huge impact. Shifting away from the dynamic characteristics of JavaScript can not only improve web performance by a huge margin, it also makes the web future-ready for 3D rendering, AR, VR, and compute-intensive tasks.
Suborbital builds frameworks and tools to help you ship software faster, without compromising on security or developer experience. It simplifies building web services in a number of languages by taking advantage of everything WebAssembly has to offer.
Cover photo by James Donovan on Unsplash