LeonardSSH Posted November 17, 2021 Posted November 17, 2021 (edited) 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 June 28, 2023 by LeonardSSH update types links 8 1
LeonardSSH Posted November 26, 2021 Author Posted November 26, 2021 The types are public! See: https://github.com/LeonardSSH/ragemp-types + mp.game v2 fully integrated. 1
LeonardSSH Posted November 27, 2021 Author Posted November 27, 2021 I made 2 boilerplates using typescript and javascript, with support for the new types: https://github.com/LeonardSSH/ragemp-typescripthttps://github.com/LeonardSSH/ragemp-javascript 1
voltration Posted November 27, 2021 Posted November 27, 2021 4 hours ago, LeonardArabu said: I made 2 boilerplates using typescript and javascript, with support for the new types: https://github.com/LeonardSSH/ragemp-typescripthttps://github.com/LeonardSSH/ragemp-javascript Tried it and it works flawlessly. Thank you for your contribution. 🙂 1
LeonardSSH Posted November 27, 2021 Author Posted November 27, 2021 Advanced types: https://github.com/LeonardSSH/ragemp-types/wiki/Advanced 1
LeonardSSH Posted December 9, 2021 Author Posted December 9, 2021 (edited) ragemp-typescript is now using SWC as default compiler (x70 faster than typescript compiler) Edited December 9, 2021 by LeonardSSH 3
LeonardSSH Posted March 31, 2022 Author Posted March 31, 2022 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 1
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now