Jump to content

Typescript - a very helpful technology nowadays


LeonardSSH

Recommended Posts

UPDATE: The types are public! See: ragempcommunity/ragemp-types + mp.game v2 fully integrated

 

https://github.com/LeonardSSH/ragemp-typescript
https://github.com/LeonardSSH/ragemp-javascript

Advanced types: https://github.com/LeonardSSH/ragemp-types/wiki/Advanced

 

Spoiler

https://i.imgur.com/NO4NgyW.png

 

Typescript, a JavaScript superset that adds optional static typing to the language. (in a nutshell)

 

How can we use it now, in a RAGE:MP project?

In order to benefit from the functionality of the language, we need some type definitions. 
Currently, these are not generated by the platform, requiring the community to create their own definitions.

CocaColaBear/types-ragemp-c for client-side.
CocaColaBear/types-ragemp-s for server-side.

Disadvantages of these definitions:

1) Requires a special setup, each side needing its own package.json to work properly, as they are just static definitions, not extensible in the true sense of the word.
2) Their extensibility is not the most user-friendly. Take for example, the Player object, add a custom function and a property to it.

// https://github.com/CocaColaBear/types-ragemp-s/blob/v2.0/index.d.ts#L129
interface PlayerMp extends EntityMp {
  armour: number;
  eyeColor: number;
  gameType: string;

  ...
}
// player.interface.ts
export interface CustomPlayer extends PlayerMp {
 customFunction(): void; // Extend the PlayerMp interface by adding our custom function.
}
  
// ready.event.ts
// To be able to use the new interface containing our custom function, we need to import it everywhere we need it.
import { CustomPlayer } from './player.interface';
  
mp.events.add('playerReady', (player: CustomPlayer) => {
   // Declare our custom function to be usable.
   player.customFunction = function() {
     // our custom logic 
   }
});

This is a simple example, but when your project reaches several thousand lines, and you have to declare hundreds of interfaces, it is not so easy to maintain.

 

What is the solution?
I kept trying to find the best solution, both to ease the developer's unnecessary work and to keep the structure as modular as possible. But after dozens of hours of testing, I found a method that is very user-friendly, easy to maintain and easy for anyone to use.

My solution:
To be able to use modular style definitions, we need to declare a module, which we can extend only where needed and without the need to import interfaces whenever we need them.

// @ragemp/types-server
declare module "rage-server" {
  export class Entity {
    readonly id: number;
  }

  export class Player extends Entity {
    name: string;
  }

  export class EntityPool<T> {
    readonly length: number;
    readonly size: number;

    at(index: number): T;
    exists(entity: T | number): boolean;
    forEach(fn: (entity: T) => void): void;
    toArray(): T[];
  }

  export class PlayerPool extends EntityPool<Player> {
    broadcast(text: string): void;
  }

  export interface IServerEvents {
    playerReady: (player: Player) => void;
    packagesLoaded: () => void;
  }

  export class EventPool {
    delayShutdown: boolean;
    delayInitialization: boolean;

    add<K extends keyof IServerEvents>(eventName: K, callback: IServerEvents[K]): void;
    add(eventName: string, callback: (...args: any[]) => void): void;
  }

  export class Event<K extends keyof IServerEvents> {
    constructor(eventName: K, callback: IServerEvents[K]);
    constructor(eventName: string, callback: (...args: any[]) => void);

    destroy(): void;
  }

  export const players: PlayerPool;
  export const events: EventPool;
}

Usage:

// another .ts file
declare module 'rage-server' {
  export interface Player {
    customProperty: number;
  }
}

// another .ts file
declare module 'rage-server' {
  export interface Player {
    anotherProperty: string;
  }
}

// ready.event.ts
import * as mp from 'rage-server';
  
declare module 'rage-server' {
  export interface Player {
    customFunction: () => void;
  }
}

mp.events.add('playerReady', (player: mp.Player) => {
  // Declare our custom function to be usable.
  player.customFunction = function() {
    // our custom logic 
  }
  
  player.customProperty = 1;
  player.anotherProperty = "anotherProperty";
});

const packagesLoaded = new mp.Event('packagesLoaded', () => {
  console.log('packagesLoaded called from constructor');
});

setTimeout(() => {
  console.log('packagesLoaded destroyed');
  packagesLoaded.destroy();
}, 2000);

Advantages:
1) Modularity in extending definitions and using them in a more user-friendly way
2) No need to declare new interfaces that extend static ones
3) Doesn't require a special setup, it can be used in monorepo mode, modules are installed in package.json in the project root, and types are added in tsconfig.json.

Disadvantages:
Since we are importing a module that contains only some definitions, it cannot be resolved by the server, and this limits our use of this solution, because we have to create a rollup environment with a special plugin that removes the 'rage-server' module from the project files.

rollup-plugin-external-globals

// rollup.config.js

// your imports
import externalGlobalsPlugin from 'rollup-plugin-external-globals';

export default {
  // your config
  plugins: [
    // your plugins
    externalGlobalsPlugin({
      'rage-server': 'mp'
    })
  ]
},

 

There are 2 ways to solve this problem which makes a rollup setup necessary:
1) Extract parts of the code from the above plugin, and create an extractor to be run by the developers before starting the server, in order to replace the module in the server files.
2) These modules to be resolved by the platform (I don't know if it's possible, only the platform developer can confirm)

 

My opinion is that my method is quite advantageous, I use it for my server, and it makes my days better.
If you want to see a preview, we have made the definitions public with the rollup method.

Link: https://github.com/rysemultiplayer/ragemp-types
 

What do you think about it? Would it motivate more the developers who want to use typescript in their projects?

Edited by LeonardSSH
update types links
  • Like 8
  • Mask 1
Link to comment
Share on other sites

  • 2 weeks later...
  • 3 months later...

The types are now public on NPM, you can use them:

# With npm
> npm i --save-dev @ragempcommunity/types-server
> npm i --save-dev @ragempcommunity/types-client
> npm i --save-dev @ragempcommunity/types-cef

# With yarn
> yarn add -D @ragempcommunity/types-server
> yarn add -D @ragempcommunity/types-client
> yarn add -D @ragempcommunity/types-cef

# With pnpm
> pnpm add -D @ragempcommunity/types-server
> pnpm add -D @ragempcommunity/types-client
> pnpm add -D @ragempcommunity/types-cef

For more details, please see: https://github.com/ragempcommunity/ragemp-types

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...