PuerTS  is an open-source toolkit that embeds high-performance JavaScript runtimes (V8, QuickJS, Node) directly inside Unity and Unreal, auto-generating TypeScript bindings so your scripts can invoke native C++ or C# APIs with minimal glue code.
After testing several JS runtimes during OneJS development, PuerTS came out on top for stability and speed in Unity. OneJS now relies on PuerTS to tap into its three backend options (V8, QuickJS, and NodeJS) giving developers the freedom to choose the engine that best fits their project.
PuerTS Documentation for Unity 
Switching Backends
Having 3 backends to choose from is nice. Use QuickJS for its small build size (~20MB). Use V8 for raw speed and WebAssembly. Use NodeJS for its robust ecosystem.
To check which backend you’re currently using, go to Tools > OneJS > Backend
in the Unity menu.
If you want to switch to a different backend, you’ll need to close the Unity Editor first. Then in your terminal or VSCode, run:
QuickJS
npm run switch quickjs
Unity doesn’t have a reliable way to unload and replace native plugins while it’s running, so the editor needs to be closed for the switch to work properly.
PuerTS Primer
Source: chexiongsheng/puerts_unity_demo 
// You can access all C# namespaces and classes from the CS. global namespace in JS
const { $ref, $unref, $generic, $promise, $typeof } = puer;
// Static function
CS.UnityEngine.Debug.Log('hello world');
// Object construction
let obj = new CS.PuertsTest.DerivedClass();
// Instance member access
obj.BMFunc(); // Parent class method
obj.DMFunc(CS.PuertsTest.MyEnum.E1); // Subclass method
console.log(obj.BMF, obj.DMF);
obj.BMF = 10; // Parent class property
obj.DMF = 30; // Subclass property
console.log(obj.BMF, obj.DMF);
// Static members
console.log(CS.PuertsTest.BaseClass.BSF, CS.PuertsTest.DerivedClass.DSF, CS.PuertsTest.DerivedClass.BSF);
// Delegates, events
// If you don't need -= later, you can pass a function as a delegate directly like this
obj.MyCallback = msg => console.log("do not need remove, msg=" + msg);
// Delegates created via new can later use this reference for -=
let delegate = new CS.PuertsTest.MyCallback(msg => console.log('can be removed, msg=' + msg));
// Since TS doesn't support operator overloading, Delegate.Combine is equivalent to obj.myCallback += delegate in C#
obj.MyCallback = CS.System.Delegate.Combine(obj.MyCallback, delegate);
obj.Trigger();
// Delegate.Remove is equivalent to obj.myCallback -= delegate in C#
obj.MyCallback = CS.System.Delegate.Remove(obj.MyCallback, delegate);
obj.Trigger();
// Events
obj.add_MyEvent(delegate);
obj.Trigger();
obj.remove_MyEvent(delegate);
obj.Trigger();
// Static events
CS.PuertsTest.DerivedClass.add_MyStaticEvent(delegate);
obj.Trigger();
CS.PuertsTest.DerivedClass.remove_MyStaticEvent(delegate);
obj.Trigger();
// Variable arguments
obj.ParamsFunc(1024, 'haha', 'hehe', 'heihei');
// in out parameters
let p1 = $ref(1);
let p2 = $ref(10);
let ret = obj.InOutArgFunc(100, p1, p2);
console.log('ret=' + ret + ', out=' + $unref(p1) + ', ref=' + $unref(p2));
// Generics
// First, instantiate generic parameters through $generic
let List = $generic(CS.System.Collections.Generic.List$1, CS.System.Int32); //$generic invocation performance isn't great, so it's recommended to use it only once per project or at least per file for the same generic parameter
let Dictionary = $generic(CS.System.Collections.Generic.Dictionary$2, CS.System.String, List);
let lst = new List();
lst.Add(1);
lst.Add(0);
lst.Add(2);
lst.Add(4);
obj.PrintList(lst);
let dic = new Dictionary();
dic.Add("aaa", lst);
obj.PrintList(dic.get_Item("aaa"));
let Arr = CS.System.Array.CreateInstance(puer.$typeof(CS.System.String), 3);
Arr.SetValue("aaa", 0);
Arr.SetValue("bbb", 1);
Arr.SetValue("ccc", 2);
obj.PrintArray(Arr);
// arraybuffer
let ab = obj.GetAb(5);
let u8a0 = new Uint8Array(ab);
console.log(obj.SumOfAb(u8a0));
let u8a1 = new Uint8Array(2);
u8a1[0] = 123;
u8a1[1] = 101;
console.log(obj.SumOfAb(u8a1));
// Engine API
let go = new CS.UnityEngine.GameObject("testObject");
go.AddComponent($typeof(CS.UnityEngine.ParticleSystem));
go.transform.position = new CS.UnityEngine.Vector3(7, 8, 9);
// Extension methods
puer.$extension(CS.PuertsTest.BaseClass, CS.PuertsTest.BaseClassExtension);
obj.PlainExtension();
obj.Extension1();
obj.Extension2(go);
let obj1 = new CS.PuertsTest.BaseClass1();
obj.Extension2(obj1);