Should I switch from C# to JavaScript/TypeScript for scripting?

This is somewhat a boardly and personal question but i’ll try to cover it into inch.
I’ve firstly encouraged by the cleanness and simplicity by C# and written some scripts years ago. It DO make code clear and faster code written but there are some pitfalls when using for FiveM in my experience:

Most of the dependencies should be PCL/SL5

Due to the mono runtime of FiveM, it recommends use PCL/SL5 packages, it’s hard to use the rich-eco of C# and DotNet, eg: using Json, EntityFramework and IoC framework and etc. Those of course finally find the solutions, but it is not as straightforward as I assumed. More often, I can only get those errors on test/deploy in-game. And sometimes you need reinvent the wheel.

It only implement a subset of netframework/mono

Accroding to the semi-official Wiki, we use .net framework 4.5.2 for developing C# scripts. But in fact it only implemented a limited subset of .net. When deploying the dll which was compiled without error, it popup strange error from console, and finally found it has something to do with dependency with hours of debug time. And similar puzzle here
For me, I do enjoy write C# code but I’m not a very experienced developer so that I cannot foresee the right step to develop in C#. And as a result, time saved from C# code is suffered from debug.

About JS

I’m not familiar with FiveM scripting with JS/TS, however me and my teammate have a few experience with TypeScript(Angular). AFAIK, it has rich package support, and growing JS groups in FiveM. Here are some questions I wanna ask:

  1. Does JS also have a very limited of packages to use due to limitation of FiveM itself? It [includes a customized version of Node.js 12.x on the server](Scripting in JavaScript), and what’s for client-side?
  2. What’s the major issue we’re facing when scripting with JavaScript?

I have been writing all new modules in TypeScript, so I can give some feedback on the matter.

You can’t use ES6 modules as FiveM is using exports keyword to expose functions from module to other modules. This problem can be mitigated by using webpack, however webpacking server code leads to more complex error debugging.

You can’t expose promises & async functions via exports / client / server event callback (missing cfx_async_retval support in JavaScript implementation). Due to this limitation I couldn’t create a single shared database pool module. Right now I have multiple modules connecting to MariaDB via mariadb - npm. Connection string is taken from convar. Downside of it: might reach open connection limit, multiple connection pools scattered around.

When Promise::reject or error is thrown in the code, it will not log anything. Thus I suggest wrapping event handler code in try & catch block, where catch logs error. In C# server-side if exception is thrown, whole program is halted. And if i remember correctly, it forces server to restart. I have divided my application so events are received on controller level and passed to services, so there’s no big chunks of code between try & catch. I guess this could also be solved with decorators.

tsyringe works well as an IoC framework.

Sometimes in native API you would expect to receive a boolean, but number is returned instead. This can lead to some unexpected bugs in the code. For example I had a problem with this one https://runtime.fivem.net/doc/natives/?_0x01FEE67DB37F59B2. In web documentation it’s written to return boolean, whereas TypeScript bindings confirm that it returns number.

development preset in webpack will usually include source maps, a debug hook and won’t minify.

This one actually got fixed today, since someone bothered reporting it.

Not typically, but if you do so on a background thread and don’t catch it - yes.

Not sure why this was kept by @thers when implementing JS/TS codegen bindings in his PR, but this is a leftover from the days when the JS ScRT only existed for C++ compatibility using Emscripten.

You can however just wrap them in a ref call and unwrap into a promise on the other side. That’s exactly what the _cfx_async_retval ABI detail does as well.

Thanks for the detailed reply.
As @nta replied, most of the issues are fixed, or at least have workaround that is obvious to do.

For C#, the most annoying part for me is code gets compiled but failed to deploy because of dependency misuse. Say I have written medium-scale component with use of third-party packages, and then find some of the functionality cannot be used via FiveM. This is very cumbersome to test, find out and resolve. I know we can just reinvent the wheel or do some test before heavily use third-party package, but why on earth should do the fundamental job on almost every packages? It just cannot make use of the rich eco of DotNet easily.

Thanks for the explanations, it is very useful resources for better using JavaScript in FIveM.
Is there any issue/glitch we should taken care of when trying to use third-party package through npm?

Appreciate your response. Could you provide a small code snippet how to achieve this? I would very much like to try it out. I know it’s possible to wrap event callback into promises and it works well e.g.

public async getEsx(): Promise<any> {
    return new Promise((resolve, _) => {
        emit('esx:getSharedObject', (esx) => {
            resolve(esx);
        });
    });
}

However if the callback object has promise functions itself e.g. in case of mariadb db pool getConnection, connection::beginTransaction, it’s just missing.

As for using npm modules, I’ve used them without major hiccups. Had problems with using discordjs on the server module. ShardingManager: fetch is not a function · Issue #3563 · discordjs/discord.js · GitHub.

Sending a complex object over serialization is generally a bad idea already, for serialization only supports bare methods as closure. It’s usually suggested to make a specialized transfer object for your class containing only closures.