├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── scripts ├── compiler.js ├── copy.js ├── shared.js ├── transform.js └── watch.js ├── server.toml ├── src-webviews ├── index.html ├── public │ └── vite.svg ├── resource.toml ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── main.tsx │ ├── shims-react.d.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── src └── core │ ├── client │ ├── startup.ts │ └── webview │ │ └── index.ts │ ├── resource.toml │ ├── server │ ├── startup.ts │ └── utility │ │ ├── ipc.ts │ │ └── reconnect.ts │ └── shared │ └── webviewEvents.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [stuyk] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | altv-server 3 | resources 4 | .vscode 5 | *.dll 6 | *.exe 7 | update.json 8 | modules/ 9 | data/ 10 | *.log 11 | cache/ 12 | crashdumps/ 13 | package-lock.json 14 | ipc.txt -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 120, 4 | "singleQuote": true, 5 | "semi": true, 6 | "arrowParens": "always" 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Typescript Boilerplate for alt:V with React - v5.0.1 3 |
4 |
5 |
6 |
9 | Super Fast Compilation 10 |
11 | 12 | > [!IMPORTANT] 13 | > This boilerplate will no longer be updated. 14 | > Consider using [Rebar Framework](https://rebarv.com/) for more up-to-date practices, and a plugin system. 15 | 16 | # Features 17 | 18 | A simple Typescript Boilerplate that builds incredibly fast using the [SWC Library](https://github.com/swc-project/swc). 19 | 20 | - Auto Refresh Server 21 | - Auto Compile TypeScript Files 22 | - Auto Download Resources 23 | - Single Resource Code Support 24 | - Fastest Auto Reconnect Time on Recompile 25 | - Built-in React for WebViews 26 | 27 | # Installation 28 | 29 | * [Install NodeJS 18+](https://nodejs.org/en/download/current/) 30 | * [Install GIT](https://git-scm.com/downloads) 31 | 32 | ## Clone the Repository 33 | 34 | Use the command below in any terminal, command prompt, etc. 35 | 36 | ```sh 37 | git clone https://github.com/Stuyk/altv-typescript-react 38 | ``` 39 | 40 | ## Install the Repository 41 | 42 | Use the command below in any terminal, command prompt, etc. 43 | 44 | ```sh 45 | cd altv-typescript-react 46 | npm install 47 | ``` 48 | 49 | ## Download Server Files 50 | 51 | Use the command below in any terminal, command prompt, etc. This will download all necessary server files from an additional package used by this project. 52 | 53 | ```sh 54 | npm run update 55 | ``` 56 | 57 | ## Start Production Server (Windows) 58 | 59 | Run this command to run the server in production mode. 60 | 61 | ``` 62 | npm run windows 63 | ``` 64 | 65 | ## Start Production Server (Linux) 66 | 67 | Run this command to run the server in production mode. 68 | 69 | ``` 70 | npm run linux 71 | ``` 72 | 73 | ## Start Developer Server 74 | 75 | Run this command to run the server in development mode. 76 | 77 | This will automatically reconnect your alt:V Client if you have `debug` mode turned on. 78 | 79 | ``` 80 | npm run dev 81 | ``` 82 | 83 | ## WebView Previews 84 | If you need to modify the WebView and want to work out of the browser, use the following command. 85 | 86 | ``` 87 | npm run react-dev 88 | ``` 89 | 90 | ## End Server Runtime 91 | 92 | Use the key combination `ctrl + c` to kill your server in your terminal, command prompt, etc. 93 | 94 | ## How to Add Mods, and New Resources 95 | 96 | Always add your already compiled resources & mods into the `resources` folder. 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "altv-quickstart-ts", 3 | "version": "5.0.1", 4 | "description": "TypeScript with alt:V Made Easy", 5 | "scripts": { 6 | "[-] Server Deployment Commands (They All Do Different Things)": "", 7 | "build": "node ./scripts/compiler.js && npx vite build ./src-webviews && node ./scripts/copy.js && node ./scripts/transform.js", 8 | "windows": "npm run build && altv-server.exe", 9 | "linux": "npm run build && ./altv-server", 10 | "dev": "node ./scripts/watch.js", 11 | "[-] Utility": "", 12 | "react-dev": "npx vite ./src-webviews --clearScreen=false --host=localhost --port=3000", 13 | "update": "altv-pkg d release" 14 | }, 15 | "author": "stuyk", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@altv/types-client": "^2.5.3", 19 | "@altv/types-natives": "^1.5.3", 20 | "@altv/types-server": "^2.6.6", 21 | "@altv/types-shared": "^1.4.2", 22 | "@altv/types-webview": "^1.0.5", 23 | "@swc/cli": "^0.1.62", 24 | "@swc/core": "^1.3.52", 25 | "@types/node": "^18.15.11", 26 | "@types/react": "^18.0.28", 27 | "@types/react-dom": "^18.0.11", 28 | "@vitejs/plugin-react-swc": "^3.0.0", 29 | "altv-pkg": "^2.0.11", 30 | "fs-extra": "^11.1.1", 31 | "glob": "^10.2.1", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "typescript": "latest", 35 | "watcher": "^2.2.2" 36 | }, 37 | "prettier": { 38 | "tabWidth": 4, 39 | "semi": true, 40 | "printWidth": 120, 41 | "arrowParens": "always", 42 | "singleQuote": true 43 | }, 44 | "type": "module", 45 | "dependencies": { 46 | "vite": "^4.3.1" 47 | }, 48 | "engines": { 49 | "node": ">=18" 50 | } 51 | } -------------------------------------------------------------------------------- /scripts/compiler.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import * as glob from 'glob'; 3 | import swc from '@swc/core' 4 | import { normalizeFilePath } from './shared.js'; 5 | 6 | const SWC_CONFIG = { 7 | jsc: { 8 | parser: { 9 | syntax: 'typescript', 10 | dynamicImport: true, 11 | decorators: true, 12 | }, 13 | transform: { 14 | legacyDecorator: true, 15 | decoratorMetadata: true, 16 | }, 17 | target: 'es2020', 18 | }, 19 | sourceMaps: false, 20 | }; 21 | 22 | const startTime = Date.now(); 23 | const filesToCompile = glob.sync('./src/core/**/*.ts'); 24 | 25 | if (fs.existsSync('resources/core')) { 26 | fs.rmSync('resources/core', { force: true, recursive: true }); 27 | } 28 | 29 | 30 | let compileCount = 0; 31 | for (let i = 0; i < filesToCompile.length; i++) { 32 | const filePath = normalizeFilePath(filesToCompile[i]); 33 | const finalPath = filePath.replace('src/', 'resources/').replace('.ts', '.js'); 34 | const compiled = swc.transformFileSync(filePath, SWC_CONFIG); 35 | fs.outputFileSync(finalPath, compiled.code, { encoding: 'utf-8' }); 36 | compileCount += 1; 37 | } 38 | 39 | console.log(`${compileCount} Files Built | ${Date.now() - startTime}ms`);; 40 | -------------------------------------------------------------------------------- /scripts/copy.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | import * as glob from 'glob'; 3 | import { normalizeFilePath } from './shared.js'; 4 | 5 | const startTime = Date.now(); 6 | const files = glob.sync(['src/core/**/*.!(ts)', 'src-webviews/**/*.toml']) 7 | 8 | let filesCopied = 0; 9 | for (let file of files) { 10 | const filePath = normalizeFilePath(file); 11 | 12 | if (filePath.includes('src/')) { 13 | const finalPath = filePath.replace('src/', 'resources/'); 14 | fs.copySync(filePath, finalPath, { overwrite: true }); 15 | } 16 | 17 | if (filePath.includes('src-webviews')) { 18 | const finalPath = filePath.replace('src-webviews/', 'resources/webviews/'); 19 | fs.copySync(filePath, finalPath, { overwrite: true }); 20 | } 21 | 22 | filesCopied += 1; 23 | } 24 | 25 | console.log(`${filesCopied} Files Moved | ${Date.now() - startTime}ms`); -------------------------------------------------------------------------------- /scripts/shared.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra'; 2 | 3 | export function normalizeFilePath(filePath) { 4 | return filePath.replace(/\\/gm, '/'); 5 | } 6 | 7 | export function writeToIpc(command) { 8 | fs.appendFileSync('ipc.txt', `\r\n${command}`); 9 | } 10 | 11 | export async function sleep(ms) { 12 | return new Promise((resolve) => { 13 | setTimeout(resolve, ms); 14 | }) 15 | } -------------------------------------------------------------------------------- /scripts/transform.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import * as glob from 'glob'; 3 | import fs from 'fs'; 4 | 5 | /** 6 | * What does this do? 7 | * Appends `.js` to the end of all imports that are not 'nodejs' imports. 8 | * This is required for alt:V 9 | */ 10 | 11 | const resourcePath = path.join(process.cwd(), 'resources/core/**/*.js').replace(/\\/gm, '/'); 12 | const filePaths = glob.sync(resourcePath); 13 | 14 | const funcsToIgnore = [ 15 | // 16 | 'export function', 17 | 'export const', 18 | 'export let', 19 | 'export async function', 20 | '=>', 21 | ]; 22 | 23 | for (let filePath of filePaths) { 24 | const fileContents = fs.readFileSync(filePath, { encoding: 'utf-8' }); 25 | const splitContents = fileContents.split(/\r?\n/); 26 | 27 | const filePathing = filePath.split('/'); 28 | filePathing.pop(); 29 | const directoryPath = filePathing.join('/'); 30 | 31 | let wasModified = false; 32 | for (let i = 0; i < splitContents.length; i++) { 33 | if (!splitContents[i].includes('import') && !splitContents[i].includes('export')) { 34 | continue; 35 | } 36 | 37 | let shouldSkip = false; 38 | for (let funcToIgnore of funcsToIgnore) { 39 | if (splitContents[i].includes(funcToIgnore)) { 40 | shouldSkip = true; 41 | break; 42 | } 43 | } 44 | 45 | if (shouldSkip) { 46 | continue; 47 | } 48 | 49 | const filePathReg = new RegExp(/('|").*.('|")/g); 50 | const extractions = splitContents[i].match(filePathReg); 51 | if (extractions === null || !extractions) { 52 | continue; 53 | } 54 | 55 | const relativeFilePath = extractions[0].replace(/'/gm, '').replace(/"/gm, ''); 56 | if (relativeFilePath.charAt(0) !== '.' && relativeFilePath.charAt(0) !== '/') { 57 | continue; 58 | } 59 | 60 | const actualFilePath = path.join(directoryPath, relativeFilePath).replace(/\\/gm, '/'); 61 | if (fs.existsSync(actualFilePath)) { 62 | const barrelFileTest = fs.statSync(actualFilePath); 63 | if (barrelFileTest.isDirectory()) { 64 | splitContents[i] = splitContents[i].replace(relativeFilePath, `${relativeFilePath}/index.js`); 65 | wasModified = true; 66 | continue; 67 | } 68 | } 69 | 70 | if (!splitContents[i].includes('.js')) { 71 | splitContents[i] = splitContents[i].replace(relativeFilePath, `${relativeFilePath}.js`); 72 | wasModified = true; 73 | } 74 | } 75 | 76 | if (!wasModified) { 77 | continue; 78 | } 79 | 80 | const finalFile = splitContents.join('\r\n'); 81 | fs.writeFileSync(filePath, finalFile, { encoding: 'utf-8' }); 82 | } -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | 2 | import { spawnSync, spawn, ChildProcess } from 'node:child_process' 3 | import Watcher from 'watcher'; 4 | import { writeToIpc, sleep } from './shared.js'; 5 | 6 | const fileWatcher = new Watcher(['./src', './src-webviews'], { recursive: true, renameDetection: true }); 7 | const isWindows = process.platform === "win32"; 8 | const altvProcessName = isWindows ? './altv-server.exe' : './altv-server' 9 | 10 | /** @type {ChildProcess} */ 11 | let childProcess = undefined 12 | let rebootDebounce = Date.now() + 0; 13 | 14 | async function compiler() { 15 | console.log(`Starting Compile`) 16 | const webviewProcess = spawn(isWindows ? 'npx.cmd' : 'npx', ['vite', 'build', './src-webviews'], { stdio: 'inherit' }) 17 | 18 | spawnSync('node', ['./scripts/compiler.js'], { stdio: 'inherit' }) 19 | spawnSync('node', ['./scripts/transform.js'], { stdio: 'inherit' }) 20 | 21 | await new Promise((resolve) => { 22 | webviewProcess.on('exit', resolve); 23 | }) 24 | 25 | spawnSync('node', ['./scripts/copy.js'], { stdio: 'inherit' }) 26 | console.log(`Compile Complete`) 27 | } 28 | 29 | async function reboot() { 30 | if (rebootDebounce > Date.now()) { 31 | return; 32 | } 33 | 34 | rebootDebounce = Date.now() + 1000; 35 | writeToIpc('kick-all'); 36 | await sleep(250); 37 | if (childProcess) { 38 | try { 39 | childProcess.kill(); 40 | } catch (err) { } 41 | 42 | await new Promise((resolve) => { 43 | const interval = setInterval(() => { 44 | if (!childProcess.killed) { 45 | childProcess.kill(); 46 | return; 47 | } 48 | 49 | childProcess = undefined 50 | clearInterval(interval); 51 | resolve(); 52 | }, 100); 53 | }) 54 | } 55 | 56 | await compiler(); 57 | childProcess = spawn(altvProcessName, { stdio: 'inherit' }) 58 | } 59 | 60 | function start() { 61 | fileWatcher.on('change', reboot); 62 | reboot(); 63 | } 64 | 65 | start(); -------------------------------------------------------------------------------- /server.toml: -------------------------------------------------------------------------------- 1 | announce = false 2 | 3 | description = 'test' 4 | website = 'test.com' 5 | gamemode = 'Freeroam' 6 | 7 | language = 'en' 8 | modules = [ 'js-module' ] 9 | name = 'TestServer' 10 | players = 1024 11 | port = 7788 12 | tags = [ 'customTag1', 'customTag2', 'customTag3', 'customTag4' ] 13 | 14 | resources = [ 15 | # Add mods here 16 | 'webviews', 17 | 'core' 18 | ] 19 | 20 | # Turn this off in production mode 21 | debug = true 22 | 23 | # Leave host as 0.0.0.0, don't change it. 24 | host = '0.0.0.0' 25 | 26 | [js-module] 27 | global-webcrypto = true 28 | global-fetch = true 29 | 30 | [voice] 31 | bitrate = 64000 -------------------------------------------------------------------------------- /src-webviews/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
44 | Edit src/App.tsx
and save to test HMR
45 |
Click on the Vite and React logos to learn more
48 |