Skip to Content
We're rolling out the first batch of Game UIs right now, and it might take a few days to fully wrap up. If you're already testing the UI comps, we'd love to hear any feedback you've got! 🙏

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:

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);