Not a New Language
Compile existing JavaScript and npm packages without rewriting them into a new language or restricted subset. Stay compatible with the JavaScript ecosystem.
The JS 2 compiler is a Loopdive project focused on ahead-of-time compilation of real JavaScript to WebAssembly.
The mission of the JS² compiler project is to make WebAssembly a drop-in deployment target for existing JavaScript and npm packages, running modules without embedding a JS runtime and enabling more secure, flexible dependency management.
Compile existing JavaScript and npm packages without rewriting them into a new language or restricted subset. Stay compatible with the JavaScript ecosystem.
Deploy compiled modules across JavaScript hosts and standalone WebAssembly runtimes without embedding a JS engine in the module or hand-writing glue code.
Reduce supply chain attack surface by isolating JavaScript modules from each other inside the same application and keeping filesystem, network, DOM, and globals behind explicit imports.
Link modules through explicit imports so consumers can choose implementations, control upgrades, and swap dependencies for each environment.
Which JS language features compile to WebAssembly today, which still rely on host support, and which are still on the roadmap. Status is derived from ECMAScript Test262 pass rates and known compiler limitations.
const s = "hello"; const n = 42; const b = true; const x = null;
(func $__module_init string.const "hello" global.set $s f64.const 42 global.set $n i32.const 1 ;; true global.set $b)
const sum = a + b; const eq = x === y; const ok = a > 0 && b > 0; const bits = n | 0xFF;
(func $example (param f64 f64)
(result f64)
local.get 0
local.get 1
f64.add ;; a + b
)
;; x === y → f64.eq (or ref.eq)
;; n | 0xFF → i32.or
typeof x === "string"; obj instanceof Array;
;; typeof x === "string" ;; compiles to tag check on struct local.get $x ref.test (ref $String) ;; instanceof → ref.test
const obj = { a: 1, b: 2 };
delete obj.b;
;; delete obj.b local.get $obj ref.null extern struct.set $Obj $b
const x = (1, 2, 3); // x === 3
f64.const 1 drop f64.const 2 drop f64.const 3
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 1) continue outer;
}
}
(block $outer
(loop $outer_loop
(block $inner
(loop $inner_loop
;; if j === 1
br $outer_loop
))
br $outer_loop
))
for (const key in obj) {
console.log(key);
}
local.get $obj call $Object.keys local.set $keys ;; iterate keys array
function legacy() {
return arguments.length;
}
// prefer rest params: (...args)
eval("1 + 2"); // not supported
with (obj) { x; } // not supported
let count = 1; const name = "hello"; var legacy = true;
(func $__module_init (local $count f64) (local $name externref) (local $legacy i32) f64.const 1 local.set $count string.const "hello" local.set $name i32.const 1 local.set $legacy)
function greet(name) {
return "Hi " + name;
}
const add = (a, b) => a + b;
(func $greet (param (ref null $str))
(result (ref null $str))
string.const "Hi "
local.get 0 ;; name
string.concat
return)
if (x > 0) {
handle();
} else {
fallback();
}
for (let i = 0; i < 10; i++) {
process(i);
}
(func $test (param f64)
(result f64)
local.get 0
f64.const 0
f64.gt
(if (result f64)
(then local.get 0 return)
(else local.get 0 f64.neg return)))
try {
riskyOp();
} catch (e) {
handle(e);
} finally {
cleanup();
}
(func $safe (result f64)
(try
(do call $riskyOp)
(catch 0
local.set $e
f64.const -1
return)))
throw new Error("something went wrong");
(func $__module_init i32.const 1 string.const "something went wrong" struct.new $Error throw 0)
const obj = {
name: "js2wasm",
version: 1,
greet() { return this.name; }
};
(type $obj (struct
(field $__tag i32)
(field $name externref)
(field $version f64)))
(func $obj_greet (param (ref null $obj))
(result externref)
local.get 0
struct.get $obj $name
return)
"hello".toUpperCase(); // "HELLO"
"a,b,c".split(",");
"hello".slice(1, 3); // "el"
(func $__module_init ;; "hello".toUpperCase() string.const "hello" call $string.toUpperCase ;; host ;; "hello".slice(1, 3) string.const "hello" i32.const 1 i32.const 3 string.substring)
Math.max(1, 2);
parseInt("42");
(3.14).toFixed(1);
(func $__module_init (local $a f64) (local $b f64) f64.const 1 local.set $a f64.const 2 local.set $b local.get $a local.get $b f64.max drop)
const obj = JSON.parse('{"a": 1}');
const str = JSON.stringify({ a: 1 });
(func $__module_init global.get 0 call $JSON.parse global.set $obj)
throw new TypeError("expected string");
throw new RangeError("out of bounds");
(func $__module_init i32.const 2 string.const "expected string" struct.new $TypeError throw 0)
const doubled = [1, 2, 3].map(x => x * 2); const sum = arr.reduce((a, b) => a + b, 0); const found = arr.find(x => x > 5);
(func $__module_init ;; [1, 2, 3].map(x => x... f64.const 1 f64.const 2 f64.const 3 array.new_fixed 3 ref.func $callback call $__arr_map
/hello/i.test("Hello World");
"abc123".match(/\d+/);
(func $__module_init global.get $re string.const "Hello World" call $regexp_test)
const obj = {
_v: 0,
get value() { return this._v; },
set value(v) { this._v = v; }
};
(func $get_value (param (ref null $obj))
(result f64)
local.get 0
struct.get $obj $_v
)
(func $set_value (param (ref null $obj)) (param f64)
local.get 0
local.get 1
struct.set $obj $_v)
Object.defineProperty(obj, "x", {
enumerable: false,
writable: false
});
const add = (a, b) => a + b; const square = x => x * x; const greet = () => "hello";
(func $add (param f64 f64)
(result f64)
local.get 0
local.get 1
f64.add)
(func $square (param f64)
(result f64)
local.get 0
local.get 0
f64.mul)
const msg = `Hello ${name}, you are ${age}!`;
(func $__module_init string.const "Hello " local.get $name string.concat string.const "!" string.concat)
const { name, age } = person;
const [first, ...rest] = items;
const { a: x = 0 } = opts;
;; typed object → direct struct ... (func $__module_init local.get $obj struct.get $Obj $a ;; a local.get $obj struct.get $Obj $b) ;; b
const merged = [...a, ...b];
const clone = { ...original };
function sum(...args) { }
(func $__module_init ;; [...arr, 4] f64.const 1 f64.const 2 f64.const 3 f64.const 4 array.new_fixed $f64arr 4)
function greet(name = "world") { }
(func $greet (param externref)
(result externref)
local.get 0
ref.is_null
(if (then
global.get 1
local.set 0))
local.get 0
return)
const key = "id";
const obj = { [key]: 42 };
(func $__module_init
;; { [key]: 42 }
global.get $key ;; "id"
f64.const 42
struct.new $obj)
for (const item of iterable) { }
(func $__module_init
;; for (const x of arr)
local.get $arr
struct.get $Vec $data
struct.get $Vec $len
(loop $for_of
local.get $i
array.get $data
;; ... process x
br_if $for_of))
function* range(n) {
for (let i = 0; i < n; i++) {
yield i;
}
}
(func $range (param f64)
(result (ref $Gen))
;; yield values into WasmGC array
array.new_default $f64arr 0
local.set $buf
(loop $loop
local.get $i
local.get 0 ;; n
i32.lt_s
(if (then
;; yield i → push to buffer
local.get $i
;; i++, br $loop
)))
local.get $buf
struct.new $Generator)
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return this.name + " speaks";
}
}
class Dog extends Animal { }
(type $Animal (struct
(field $__tag i32)
(field $name externref)))
(func $Animal_new (param externref)
(result (ref null $Animal))
struct.new $Animal
local.get 0
struct.set $Animal $name)
const m = new Map();
m.set("key", 42);
const s = new Set([1, 2, 3]);
s.has(2); // true
(func $__module_init call $Map_new global.set $m global.get $m string.const "key" f64.const 42 call $Map_set)
const id = Symbol("id");
obj[id] = 42;
(func $__module_init global.get $nextId i32.const 1 i32.add global.set $nextId global.get $nextId global.set $id)
const buf = new ArrayBuffer(16); const view = new Int32Array(buf); view[0] = 42;
(func $__module_init i32.const 16 call $ArrayBuffer_new call $Int32Array_from global.set $view global.get $view i32.const 0 f64.const 42 call $TypedArray_set)
import { foo } from "./mod";
export function bar() { return 1; }
(func $bar (export "bar")
(result f64)
f64.const 1
return)
new Proxy(target, handler); // not supported
promise.then(v => v + 1); // not yet
const mod = await import("./module"); // not yet
(func $fetchData (result f64) f64.const 42 call $Promise.resolve call $__await return)
Object.entries({ a: 1, b: 2 });
// [["a", 1], ["b", 2]]
Object.values({ x: 10 });
// [10]
(func $__module_init f64.const 1 f64.const 2 struct.new $obj extern.convert_any call $Object.entries)
new SharedArrayBuffer(1024); // not supported
const clone = { ...original,
extra: 1 };
const { a, ...rest } = obj;
(func $__module_init
;; { ...original, extra: 2 }
local.get $original
struct.get $Obj $x
f64.const 2
struct.new $Clone)
for await (const chunk of stream) { }
(func $process (param externref)
local.get 0
call $__iterator
local.set $iter
(loop $for_await
local.get $iter
call $__iterator_next
;; ... process chunk
br_if $for_await))
const name = user?.profile?.name;
(func $test (param externref)
(result externref)
local.get 0
ref.is_null
(if (result externref)
(then ref.null extern)
(else local.get 0
call $__extern_get_name)))
const val = input ?? "default";
(func $test (param f64)
(result f64)
local.get 0 ;; x
call $__is_nullish
(if (result f64)
(then f64.const 0)
(else local.get 0)))
globalThis.console;
(func $__module_init call $__get_globalThis global.set $g)
const big = 9007199254740993n; const sum = big + 1n;
(func $__module_init i64.const 9007199254740993 global.set $big global.get $big i64.const 1 i64.add global.set $sum)
const mod = await import("./module"); // not yet
new WeakRef(obj); // not supported
class Counter {
count = 0;
#secret = 42;
static instances = 0;
}
(type $Counter (struct
(field $__tag i32)
(field $count f64)))
(func $Counter_new
(result (ref null $Counter))
i32.const 0 ;; __tag
f64.const 0 ;; count = 0
struct.new $Counter)
throw new Error("fail", { cause: origErr });
(func $fail i32.const 1 string.const "root" struct.new $Error ;; cause i32.const 1 string.const "fail" struct.new $ErrorCause throw 0)
[1, 2, 3].at(-1); // 3 "hello".at(0); // "h"
(func $__module_init ;; [1,2,3].at(-1) local.get $arr i32.const -1 local.get $len i32.add array.get $data) ;; → 3
const data = await fetch(url);
export { data };
[1, 2, 3].includes(2); // true
;; linear scan over WasmGC array local.get $arr struct.get $Vec $data (loop $scan array.get $data local.get $target f64.eq br_if $found br $scan)
const sq = 2 ** 10; // 1024
f64.const 2 f64.const 10 call $Math.pow
try { riskyOp(); }
catch { handleError(); }
(try
(do call $riskyOp)
(catch_all
call $handleError))
[[1, 2], [3]].flat(); // → [1, 2, 3]
const obj = Object.fromEntries( [["a", 1], ["b", 2]] );
[1, 2, 3, 2].findLast(x => x === 2); // → 2 (last match)
const sorted = arr.toSorted(); const rev = arr.toReversed();
#!/usr/bin/env node
console.log("hello");
;; hashbang stripped at parse time ;; no Wasm output for comments
const { promise, resolve, reject }
= Promise.withResolvers();
const buf = new ArrayBuffer(8,
{ maxByteLength: 16 });
buf.resize(16);
/[\p{Letter}--[a-z]]/v.test("A");
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
a.union(b); // {1,2,3,4}
function* nums() { yield 1; yield 2; yield 3; }
nums().filter(x => x > 1)
.take(1)
.toArray();
/(?<v>a)|(?<v>b)/.exec("b");
// v === "b"
console.log(x); // undefined var x = 5;
;; var hoisted to function scope (local $x f64) f64.const 0 local.set $x ;; ... later f64.const 5 local.set $x
function f() {
return arguments.callee;
}
obj.__proto__ = parent;
"hello".substr(1, 3); // "ell" // use .slice() or .substring()
const n = 0777; const ok = 0o777;
escape("hello world");
// use encodeURIComponent()
function f() {
return f.caller;
}
"text".bold(); // deprecated
"text".anchor("name");
/(\d+)/.exec("abc123");
RegExp.$1; // "123" — deprecated
const now = Temporal.Now.instant();
const date = Temporal.PlainDate
.from("2026-04-06");
@logged
class MyClass {
@bound method() { }
}
match (value) {
when ({ x, y }): return x + y;
when (String): return value.length;
default: return 0;
}
Write regular JavaScript. The compiler produces a .wasm binary that runs anywhere WebAssembly runs — browser, server, edge. No interpreter embedded, no garbage collector shipped. When deployed with explicit imports, the module boundary can reduce ambient access and limit supply-chain attack surface.
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1)
+ fibonacci(n - 2);
}
export function run() {
return fibonacci(10);
}
app.js
(func $fibonacci (param f64) (result f64)
local.get 0
f64.const 1
f64.le
(if (result f64)
(then local.get 0)
(else
local.get 0 f64.const 1 f64.sub
call $fibonacci
local.get 0 f64.const 2 f64.sub
call $fibonacci
f64.add)))
(func $run (export "run") (result f64)
f64.const 10
call $fibonacci)
app.wat
DOM access and browser APIs compile to typed host imports. The compiler generates the bindings — you just write normal JavaScript.
const el = document.createElement("div");
el.textContent = "Hello from Wasm";
el.style.color = "blue";
document.body.appendChild(el);
dom.js
(import "env" "global_document"
(func $doc (result externref)))
(import "env" "__extern_method_call"
(func $call (param externref externref)
(result externref)))
(func $__module_init
call $doc
string.const "div"
call $call ;; createElement
local.tee $el
string.const "textContent"
string.const "Hello from Wasm"
call $__extern_set ;; el.textContent = ...
local.get $el
string.const "color"
string.const "blue"
call $__style_set ;; el.style.color = ...
call $doc
string.const "body"
call $__extern_get ;; document.body
local.get $el
call $call) ;; .appendChild(el)
dom.wat
import { js2 } from "js2wasm";
// Pure computation — runs standalone
const app = await js2.import("./app.js");
console.log(app.run()); // → 55
// DOM example — pass browser globals
const dom = await js2.import("./dom.js", { document });
// div is already appended to document.body
run.ts
js2.import()
instantiates the cached compiled module or compiles on demand if needed, builds host imports, instantiates,
and returns typed exports. Pass browser globals as dependencies for DOM access.
The roadmap is to first establish compatibility with existing code, then expand host support, optimize performance, and strengthen security.