Zasoby TypeScript w FiveM: fxmanifest, typy @citizenfx i esbuild
Napisz zasoby FiveM w TypeScript lub JavaScript: fxmanifest z plikami dist, typy @citizenfx, zbiór esbuild, zdarzenia, setTick i Wait, oraz wywoływanie eksportów Lua.
Chcesz typów, async/await i pakietów npm w swoim skrypcie FiveM, ale twój zasób to tylko pliki .lua. FiveM uruchamia JavaScript we własnym runtime, więc TypeScript działa tak długo, jak jest skompilowany do JavaScript najpierw. Ten przewodnik daje działający układ: manifest, typy, bundler, zdarzenia i ticki, i jak mieszać go z Lua.
Układ projektu
my_resource/
fxmanifest.lua
package.json
tsconfig.json
src/
client.ts
server.ts
dist/ (build output, loaded by FiveM)Edytujesz src/, budujesz w dist/, a manifest zna tylko dist/.
fxmanifest
fx_version 'cerulean'
game 'gta5'
client_script 'dist/client.js'
server_script 'dist/server.js'Oba to zwykłe pliki JavaScript. Jeśli twój zasób potrzebuje konkretnej wersji runtime JavaScript lub funkcji Node na serwerze, sprawdź bieżące dokumenty Cfx.re dla opcji manifestu, ponieważ wspierane runtime zmieniały się w czasie.
Porada: jeśli pominiesz kompilację, FiveM mówi, że nie może znaleźć
dist/client.js. Zawsze uruchom kompilację przedensurelubrestart.
Zainstaluj typy i bundler
npm init -y
npm install --save-dev typescript esbuild @citizenfx/client @citizenfx/server@citizenfx/client i @citizenfx/server to pakiety typów dla natives i funkcji Cfx.re po każdej stronie. tsconfig.json, który je używa:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noEmit": true,
"skipLibCheck": true
},
"include": ["src"]
}TypeScript tylko sprawdza typy tutaj (noEmit). esbuild robi faktyczną zbiorkę:
{
"scripts": {
"build": "esbuild src/client.ts --bundle --outfile=dist/client.js --target=es2020 && esbuild src/server.ts --bundle --outfile=dist/server.js --target=es2020 --platform=node",
"watch": "npm run build -- --watch",
"typecheck": "tsc"
}
}Zbiórka oznacza, że pakiety npm, które importujesz, są kopiowane do jednego pliku wyjściowego, więc serwer nie potrzebuje node_modules w runtime. Sprawdź dokumenty Cfx.re dla właściwego --platform i --target dla runtime, które używają twoje artefakty.
Zdarzenia
JavaScript ma te same cztery bloki budowlane co Lua, z innymi nazwami:
| Lua | JavaScript |
|---|---|
AddEventHandler |
on |
RegisterNetEvent + handler |
onNet |
TriggerEvent |
emit |
TriggerServerEvent / TriggerClientEvent |
emitNet |
// src/server.ts
onNet('my_resource:requestData', () => {
const src = (global as any).source as number
emitNet('my_resource:data', src, { hello: 'world' })
})// src/client.ts
on('onClientResourceStart', (resourceName: string) => {
if (GetCurrentResourceName() !== resourceName) return
emitNet('my_resource:requestData')
})
onNet('my_resource:data', (data: { hello: string }) => {
console.log(data.hello)
})Na serwerze gracz, który wyzwolił zdarzenie, jest w globalnym source, jak w Lua. Przeczytaj go do stałej na początku handlera: po await, global może wskazywać na innego gracza. Te same zasady bezpieczeństwa z zabezpieczania zdarzeń serwerowych mają zastosowanie, ponieważ gracz może wyzwolić twój onNet z zewnątrz. Więcej o modelu zdarzeń w zdarzeniach klient i serwer.
Ticki i czekanie
Nie ma domyślnie pętli ramek. setTick uruchamia funkcję każdą ramkę, a Wait z Lua staje się obietnicą wokół setTimeout:
const Delay = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
setTick(async () => {
const ped = PlayerPedId()
const [x, y, z] = GetEntityCoords(ped, true)
// ... check something about the position
await Delay(500) // without this, it runs every frame
})Bez opóźnienia, handler uruchamia się każdą ramkę, ten sam problem co pętla Lua bez Wait(0): zobacz Wait i wątki wyjaśnione. Użyj dłuższego opóźnienia, gdy nic nie musi być sprawdzane każdą ramkę.
Dla jednorazowego opóźnienia pomiń setTick i po prostu await Delay(1000) wewnątrz funkcji async.
Mieszanie z eksportami Lua
Eksporty pracują między językami. Zarejestruj jeden w JavaScript:
exports('getGreeting', (name: string) => `Hello ${name}`)I wołaj go z zasobu Lua:
local text = exports['my_resource']:getGreeting('Mic')Drugi kierunek, wywoływanie eksportu Lua z TypeScript, przechodzi przez obiekt exports:
const ok = exports['ox_inventory'].GetItemCount(source, 'bread')Zadeklaruj kształt sam, jeśli typy nie znają innego zasobu, na przykład za pomocą declare const exports: Record<string, any>. Przeczytaj eksporty w Lua, aby dowiedzieć się, jak zachowują się, gdy docelowy zasób się restartuje.
Buduj, zanim zaczniesz
Uruchom obserwatora podczas pracy i uruchom ponownie zasób w konsoli po każdej zbiórce:
npm run watch
restart my_resourceJeśli restart nic nowego nie robi, sprawdź, czy plik wyjściowy się zmienił, czy manifest na niego wskazuje, i czy zasób nie jest serwowany ze starej kopii w cache/.
Lista kontrolna
| Objaw | Naprawa |
|---|---|
FiveM nie może znaleźć dist/client.js |
Uruchom kompilację; manifest ładuje wyjście, nie src/ |
| Natives pokazują się jako nieznane w edytorze | Zainstaluj @citizenfx/client i @citizenfx/server |
import zawodzi w runtime |
Zbórka z esbuild, aby importy były wstawiane |
| Handler używa niewłaściwego gracza | Przeczytaj source do stałej przed dowolnym await |
| Wysoka resmon z ticka | Dodaj await Delay(ms) do handlera setTick |
| Lua nie widzi twojej funkcji | Zarejestruj ją za pomocą exports('name', fn) i wołaj ją po nazwie zasobu |
Szybkie odpowiedzi
Czy mogę używać TypeScript bezpośrednio w zasobie FiveM?
Nie. FiveM uruchamia JavaScript. Piszesz TypeScript, zbiórka go do czystego JavaScript za pomocą narzędzia takiego jak esbuild, i wskazujesz client_script i server_script na pliki wyjściowe.
Czy zasób TypeScript może wywoływać eksporty Lua?
Tak. Eksporty są wspólne między językami: wołaj exports['my_resource'].functionName() z JavaScript, a Lua może wywoływać eksporty, które rejestrujesz za pomocą exports('name', fn) w JavaScript.
Co zastępuje Wait() w JavaScript?
Użyj obietnic wokół setTimeout wewnątrz funkcji async lub handlera setTick. Sama pętla ticków to setTick, a każdy tick powinien czekać opóźnienie, aby nie uruchamiał się każdą ramkę.
Skrypty bez tego problemu
Quest CreatorWizualny edytor questów i dialogów z NPC, budowanych węzeł po węźle w grze.Zobacz skrypt →
Item Creator V2Twórz używalne przedmioty z animacjami, propami, efektami i nie tylko — bez pisania kodu.Zobacz skrypt →
Shop CreatorZbuduj sklep w niecałą minutę — właściciele, pracownicy, sejfy i napady w zestawie.Zobacz skrypt →