├── .prettierignore ├── example ├── .prettierignore ├── src │ ├── fonts │ │ └── pricedown.ttf │ ├── globalStyles.js │ ├── reducers.js │ ├── containers │ │ ├── App │ │ │ ├── reducer.js │ │ │ └── index.js │ │ ├── CharacterSelect │ │ │ ├── reducer.js │ │ │ └── index.js │ │ └── WindowListener │ │ │ └── index.js │ ├── index.html │ ├── util │ │ └── Nui.js │ ├── configureStore.js │ ├── components │ │ └── character │ │ │ └── index.js │ └── app.js ├── .prettierrc ├── __resource.lua ├── .editorconfig ├── .babelrc ├── webpack │ ├── webpack.dev.js │ ├── webpack.prod.js │ └── webpack.common.js ├── LICENSE.md ├── gitignore ├── .eslintrc.js ├── package.json └── client.lua ├── src ├── fonts │ └── pricedown.ttf ├── reducers.js ├── globalStyles.js ├── containers │ ├── App │ │ ├── reducer.js │ │ └── index.js │ └── WindowListener │ │ └── index.js ├── index.html ├── util │ └── Nui.js ├── configureStore.js └── app.js ├── .prettierrc ├── .editorconfig ├── .babelrc ├── README.md ├── webpack ├── webpack.dev.js ├── webpack.prod.js └── webpack.common.js ├── LICENSE.md ├── .gitignore ├── .eslintrc.js └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /example/.prettierignore: -------------------------------------------------------------------------------- 1 | build/ 2 | node_modules/ 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /src/fonts/pricedown.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calumari/fivem-react-boilerplate/HEAD/src/fonts/pricedown.ttf -------------------------------------------------------------------------------- /example/src/fonts/pricedown.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/calumari/fivem-react-boilerplate/HEAD/example/src/fonts/pricedown.ttf -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all" 8 | } 9 | -------------------------------------------------------------------------------- /src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import appReducer from 'containers/App/reducer'; 4 | 5 | export default () => 6 | combineReducers({ 7 | app: appReducer, 8 | }); 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /example/__resource.lua: -------------------------------------------------------------------------------- 1 | resource_manifest_version '44febabe-d386-4d18-afbe-5e627f4af937' 2 | 3 | client_script "client.lua" 4 | 5 | ui_page "html/index.html" 6 | 7 | files { 8 | "html/96c342ee6d5d50fc5b3fa76a6f6932b9.tff", 9 | "html/index.html", 10 | "html/main.js" 11 | } 12 | -------------------------------------------------------------------------------- /example/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/globalStyles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | import Pricedown from 'fonts/pricedown.ttf'; 3 | 4 | const GlobalStyle = createGlobalStyle` 5 | @font-face { 6 | font-family: 'Pricedown'; 7 | src: url(${Pricedown}); 8 | } 9 | `; 10 | 11 | export default GlobalStyle; 12 | -------------------------------------------------------------------------------- /example/src/globalStyles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | import Pricedown from 'fonts/pricedown.ttf'; 3 | 4 | const GlobalStyle = createGlobalStyle` 5 | @font-face { 6 | font-family: 'Pricedown'; 7 | src: url(${Pricedown}); 8 | } 9 | `; 10 | 11 | export default GlobalStyle; 12 | -------------------------------------------------------------------------------- /example/src/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import appReducer from 'containers/App/reducer'; 4 | import characterReducer from 'containers/CharacterSelect/reducer'; 5 | 6 | export default () => 7 | combineReducers({ 8 | app: appReducer, 9 | characters: characterReducer, 10 | }); 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "@babel/preset-react" 10 | ], 11 | "plugins": [ 12 | "styled-components", 13 | "@babel/plugin-proposal-class-properties", 14 | "@babel/plugin-syntax-dynamic-import" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "@babel/preset-react" 10 | ], 11 | "plugins": [ 12 | "styled-components", 13 | "@babel/plugin-proposal-class-properties", 14 | "@babel/plugin-syntax-dynamic-import" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/containers/App/reducer.js: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | hidden: true, 3 | }; 4 | 5 | const appReducer = (state = initialState, action) => { 6 | switch (action.type) { 7 | case 'APP_SHOW': 8 | return Object.assign({}, state, { hidden: false }); 9 | case 'APP_HIDE': 10 | return Object.assign({}, state, { hidden: true }); 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default appReducer; 17 | -------------------------------------------------------------------------------- /example/src/containers/App/reducer.js: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | hidden: true, 3 | }; 4 | 5 | const appReducer = (state = initialState, action) => { 6 | switch (action.type) { 7 | case 'APP_SHOW': 8 | return Object.assign({}, state, { hidden: false }); 9 | case 'APP_HIDE': 10 | return Object.assign({}, state, { hidden: true }); 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default appReducer; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fivem-react-boilerplate 2 | ## Notice 3 | I am no longer involved in FiveM and apart from the occasional dependency bump this project is unmaintained. PRs highly encouraged! 4 | 5 | # Quick start 6 | ### 1. Clone repo 7 | ``` 8 | git clone https://github.com/calumari/fivem-react-boilerplate.git html 9 | ``` 10 | 11 | ### 2. Install dependencies 12 | ``` 13 | cd html 14 | npm install 15 | ``` 16 | 17 | ### 3. Build 18 | ``` 19 | npm run build 20 | ``` 21 | 22 | ### 4. Copy the contents of the generated manifest to your resource manifest! 23 | 24 | # Commands 25 | ### Run locally for development 26 | ``` 27 | npm run start 28 | ``` 29 | -------------------------------------------------------------------------------- /example/src/containers/CharacterSelect/reducer.js: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | hidden: true, 3 | characters: [], 4 | }; 5 | 6 | const appReducer = (state = initialState, action) => { 7 | switch (action.type) { 8 | case 'RECIEVE_CHARACTERS': 9 | return Object.assign({}, state, { 10 | characters: action.payload.characters, 11 | }); 12 | case 'CHARACTERS_SHOW': 13 | return Object.assign({}, state, { hidden: false }); 14 | case 'CHARACTERS_HIDE': 15 | return Object.assign({}, state, { hidden: true }); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default appReducer; 22 | -------------------------------------------------------------------------------- /src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import GlobalStyle from '../../globalStyles'; 8 | 9 | const H1 = styled('h1')` 10 | font-family: Pricedown; 11 | visibility: ${props => props.hidden}; 12 | `; 13 | 14 | const App = ({ hidden }) => ( 15 |
16 |

Hello world

17 | 18 |
19 | ); 20 | 21 | App.propTypes = { 22 | hidden: PropTypes.bool.isRequired, 23 | }; 24 | 25 | const mapStateToProps = state => ({ hidden: state.app.hidden }); 26 | 27 | export default connect(mapStateToProps)(App); 28 | -------------------------------------------------------------------------------- /example/src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import styled from 'styled-components'; 6 | 7 | import GlobalStyle from '../../globalStyles'; 8 | 9 | const H1 = styled('h1')` 10 | font-family: Pricedown; 11 | visibility: ${props => props.hidden}; 12 | `; 13 | 14 | const App = ({ hidden }) => ( 15 |
16 |

Hello world

17 | 18 |
19 | ); 20 | 21 | App.propTypes = { 22 | hidden: PropTypes.bool.isRequired, 23 | }; 24 | 25 | const mapStateToProps = state => ({ hidden: state.app.hidden }); 26 | 27 | export default connect(mapStateToProps)(App); 28 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FiveM Boilerplate 8 | 9 | 10 | 11 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/util/Nui.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async send(event, data = {}) { 3 | /// #if DEBUG 4 | return new Promise(resolve => setTimeout(resolve, 100)); 5 | /// #endif 6 | 7 | /* eslint-disable no-unreachable */ 8 | return fetch(`http:///${event}`, { 9 | method: 'post', 10 | headers: { 11 | 'Content-type': 'application/json; charset=UTF-8', 12 | }, 13 | body: JSON.stringify(data), 14 | }); 15 | /* eslint-enable no-unreachable */ 16 | }, 17 | emulate(type, data = null) { 18 | window.dispatchEvent( 19 | new MessageEvent('message', { 20 | data: { 21 | type, 22 | data, 23 | }, 24 | }), 25 | ); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FiveM Boilerplate 8 | 9 | 10 | 11 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /example/src/util/Nui.js: -------------------------------------------------------------------------------- 1 | export default { 2 | async send(event, data = {}) { 3 | // /// #if DEBUG 4 | // return new Promise(resolve => setTimeout(resolve, 100)); 5 | // /// #endif 6 | 7 | /* eslint-disable no-unreachable */ 8 | return fetch(`http://example/${event}`, { 9 | method: 'post', 10 | headers: { 11 | 'Content-type': 'application/json; charset=UTF-8', 12 | }, 13 | body: JSON.stringify(data), 14 | }); 15 | /* eslint-enable no-unreachable */ 16 | }, 17 | emulate(type, data = null) { 18 | window.dispatchEvent( 19 | new MessageEvent('message', { 20 | data: { 21 | type, 22 | data, 23 | }, 24 | }), 25 | ); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux'; 2 | import createReducer from './reducers'; 3 | 4 | export default function configureStore(initialState) { 5 | let composeEnhancers = compose; 6 | 7 | if (process.env.NODE_ENV !== 'production' && typeof window === 'object') { 8 | if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { 9 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}); 10 | } 11 | } 12 | 13 | const enhancers = []; 14 | 15 | const store = createStore( 16 | createReducer(), 17 | initialState, 18 | composeEnhancers(...enhancers), 19 | ); 20 | 21 | if (module.hot) { 22 | module.hot.accept('./reducers', () => { 23 | store.replaceReducer(createReducer(store.injectedReducers)); 24 | }); 25 | } 26 | 27 | return store; 28 | } 29 | -------------------------------------------------------------------------------- /example/src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux'; 2 | import createReducer from './reducers'; 3 | 4 | export default function configureStore(initialState) { 5 | let composeEnhancers = compose; 6 | 7 | if (process.env.NODE_ENV !== 'production' && typeof window === 'object') { 8 | if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { 9 | composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}); 10 | } 11 | } 12 | 13 | const enhancers = []; 14 | 15 | const store = createStore( 16 | createReducer(), 17 | initialState, 18 | composeEnhancers(...enhancers), 19 | ); 20 | 21 | if (module.hot) { 22 | module.hot.accept('./reducers', () => { 23 | store.replaceReducer(createReducer(store.injectedReducers)); 24 | }); 25 | } 26 | 27 | return store; 28 | } 29 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | 7 | import App from 'containers/App'; 8 | 9 | import WindowListener from 'containers/WindowListener'; 10 | 11 | import configureStore from './configureStore'; 12 | 13 | const initialState = {}; 14 | const store = configureStore(initialState); 15 | const MOUNT_NODE = document.getElementById('app'); 16 | 17 | const render = () => { 18 | ReactDOM.render( 19 | 20 | 21 | 22 | 23 | , 24 | MOUNT_NODE, 25 | ); 26 | }; 27 | 28 | if (module.hot) { 29 | module.hot.accept(['containers/App'], () => { 30 | ReactDOM.unmountComponentAtNode(MOUNT_NODE); 31 | render(); 32 | }); 33 | } 34 | 35 | render(); 36 | -------------------------------------------------------------------------------- /src/containers/WindowListener/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | class WindowListener extends React.Component { 6 | componentWillMount() { 7 | window.addEventListener('message', this.handleEvent); 8 | } 9 | 10 | componentWillUnmount() { 11 | window.removeEventListener('message', this.handleEvent); 12 | } 13 | 14 | handleEvent = event => { 15 | const { dispatch } = this.props; 16 | const { type, data } = event.data; 17 | dispatch({ type, payload: { ...data } }); 18 | }; 19 | 20 | render() { 21 | return React.Children.only(this.props.children); 22 | } 23 | } 24 | 25 | WindowListener.propTypes = { 26 | dispatch: PropTypes.func.isRequired, 27 | children: PropTypes.element.isRequired, 28 | }; 29 | 30 | export default connect( 31 | null, 32 | null, 33 | )(WindowListener); 34 | -------------------------------------------------------------------------------- /example/src/containers/WindowListener/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | class WindowListener extends React.Component { 6 | componentWillMount() { 7 | window.addEventListener('message', this.handleEvent); 8 | } 9 | 10 | componentWillUnmount() { 11 | window.removeEventListener('message', this.handleEvent); 12 | } 13 | 14 | handleEvent = event => { 15 | const { dispatch } = this.props; 16 | const { type, data } = event.data; 17 | dispatch({ type, payload: { ...data } }); 18 | }; 19 | 20 | render() { 21 | return React.Children.only(this.props.children); 22 | } 23 | } 24 | 25 | WindowListener.propTypes = { 26 | dispatch: PropTypes.func.isRequired, 27 | children: PropTypes.element.isRequired, 28 | }; 29 | 30 | export default connect( 31 | null, 32 | null, 33 | )(WindowListener); 34 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = require('./webpack.common')({ 6 | mode: 'development', 7 | 8 | // Add hot reloading in development 9 | entry: [ 10 | path.join(process.cwd(), 'src/app.js'), // Start with js/app.js 11 | ], 12 | 13 | // Add development plugins 14 | plugins: [ 15 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading 16 | new HtmlWebpackPlugin({ 17 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js 18 | template: 'src/index.html', 19 | }), 20 | ], 21 | 22 | // Emit a source map for easier debugging 23 | // See https://webpack.js.org/configuration/devtool/#devtool 24 | // devtool: 'eval-source-map', 25 | 26 | performance: { 27 | hints: false, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /example/webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = require('./webpack.common')({ 6 | mode: 'development', 7 | 8 | // Add hot reloading in development 9 | entry: [ 10 | path.join(process.cwd(), 'src/app.js'), // Start with js/app.js 11 | ], 12 | 13 | // Add development plugins 14 | plugins: [ 15 | new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading 16 | new HtmlWebpackPlugin({ 17 | inject: true, // Inject all files that are generated by webpack, e.g. bundle.js 18 | template: 'src/index.html', 19 | }), 20 | ], 21 | 22 | // Emit a source map for easier debugging 23 | // See https://webpack.js.org/configuration/devtool/#devtool 24 | // devtool: 'eval-source-map', 25 | 26 | performance: { 27 | hints: false, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /example/src/components/character/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Box } from '@rebass/grid'; 5 | import styled from 'styled-components'; 6 | 7 | const Background = styled(Box)` 8 | background-color: white; 9 | border: 1px solid black; 10 | margin: 5px; 11 | padding: 5px; 12 | `; 13 | 14 | const Character = ({ forename, surname, description, balance, onSelect }) => ( 15 | 16 |

17 | {forename} {surname} 18 |

19 |

{description}

20 |

${balance}

21 | 22 |
23 | ); 24 | 25 | Character.propTypes = { 26 | forename: PropTypes.string.isRequired, 27 | surname: PropTypes.string.isRequired, 28 | description: PropTypes.string, 29 | balance: PropTypes.number.isRequired, 30 | onSelect: PropTypes.func.isRequired, 31 | }; 32 | 33 | export default Character; 34 | -------------------------------------------------------------------------------- /example/src/app.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom'; 5 | import { Provider } from 'react-redux'; 6 | 7 | import App from 'containers/App'; 8 | 9 | import WindowListener from 'containers/WindowListener'; 10 | 11 | import configureStore from './configureStore'; 12 | import CharacterSelect from './containers/CharacterSelect'; 13 | 14 | const initialState = {}; 15 | const store = configureStore(initialState); 16 | const MOUNT_NODE = document.getElementById('app'); 17 | 18 | const render = () => { 19 | ReactDOM.render( 20 | 21 | 22 | <> 23 | 24 | 25 | 26 | 27 | , 28 | MOUNT_NODE, 29 | ); 30 | }; 31 | 32 | if (module.hot) { 33 | module.hot.accept(['containers/App'], () => { 34 | ReactDOM.unmountComponentAtNode(MOUNT_NODE); 35 | render(); 36 | }); 37 | } 38 | 39 | render(); 40 | -------------------------------------------------------------------------------- /webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ResourceManifestPlugin = require('webpack-fivem-manifest'); 4 | 5 | module.exports = require('./webpack.common')({ 6 | mode: 'production', 7 | 8 | // In production, we skip all hot-reloading stuff 9 | entry: [path.join(process.cwd(), 'src/app.js')], 10 | 11 | plugins: [ 12 | new HtmlWebpackPlugin({ 13 | template: 'src/index.html', 14 | minify: { 15 | removeComments: true, 16 | collapseWhitespace: true, 17 | removeRedundantAttributes: true, 18 | useShortDoctype: true, 19 | removeEmptyAttributes: true, 20 | removeStyleLinkTypeAttributes: true, 21 | keepClosingSlash: true, 22 | minifyJS: true, 23 | minifyCSS: true, 24 | minifyURLs: true, 25 | }, 26 | inject: true, 27 | }), 28 | new ResourceManifestPlugin(), 29 | ], 30 | 31 | performance: { 32 | assetFilter: assetFilename => 33 | !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename), 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /example/webpack/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ResourceManifestPlugin = require('webpack-fivem-manifest'); 4 | 5 | module.exports = require('./webpack.common')({ 6 | mode: 'production', 7 | 8 | // In production, we skip all hot-reloading stuff 9 | entry: [path.join(process.cwd(), 'src/app.js')], 10 | 11 | plugins: [ 12 | new HtmlWebpackPlugin({ 13 | template: 'src/index.html', 14 | minify: { 15 | removeComments: true, 16 | collapseWhitespace: true, 17 | removeRedundantAttributes: true, 18 | useShortDoctype: true, 19 | removeEmptyAttributes: true, 20 | removeStyleLinkTypeAttributes: true, 21 | keepClosingSlash: true, 22 | minifyJS: true, 23 | minifyCSS: true, 24 | minifyURLs: true, 25 | }, 26 | inject: true, 27 | }), 28 | // new ResourceManifestPlugin(), 29 | ], 30 | 31 | performance: { 32 | assetFilter: assetFilename => 33 | !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename), 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Callum Night 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Callum Night 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/src/containers/CharacterSelect/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import { Flex } from '@rebass/grid'; 6 | 7 | import Character from 'components/character'; 8 | import Nui from '../../util/Nui'; 9 | 10 | class CharacterSelect extends React.Component { 11 | selectCharacter = id => () => { 12 | Nui.send('SELECT_CHARACTER', { id }); 13 | }; 14 | 15 | render() { 16 | const { hidden, characters } = this.props; 17 | if (hidden) { 18 | return null; 19 | } 20 | return ( 21 | 22 | {characters.map(c => ( 23 | 24 | ))} 25 | 26 | ); 27 | } 28 | } 29 | 30 | CharacterSelect.propTypes = { 31 | characters: PropTypes.arrayOf(PropTypes.object), 32 | }; 33 | 34 | const mapStateToProps = state => ({ 35 | hidden: state.characters.hidden, 36 | characters: state.characters.characters, 37 | }); 38 | 39 | export default connect(mapStateToProps)(CharacterSelect); 40 | 41 | /// #if DEBUG 42 | 43 | // Emulate a client event during local testing. 44 | 45 | setTimeout(() => { 46 | Nui.emulate('RECIEVE_CHARACTERS', { 47 | characters: [ 48 | { 49 | id: 1, 50 | forename: 'Joe', 51 | surname: 'Bloggs', 52 | description: 'Recently signed a Mixer contract.', 53 | balance: 1000, 54 | }, 55 | { 56 | id: 2, 57 | forename: 'John', 58 | surname: 'Everyman', 59 | description: 'Author of Roleplaying 101.', 60 | balance: 20320732, 61 | }, 62 | ], 63 | }); 64 | }, 100); 65 | 66 | /// #endif 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | html 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # Cruft 94 | .DS_Store 95 | .idea 96 | -------------------------------------------------------------------------------- /example/gitignore: -------------------------------------------------------------------------------- 1 | build 2 | html 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | 12 | # Diagnostic reports (https://nodejs.org/api/report.html) 13 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | *.lcov 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # Cruft 94 | .DS_Store 95 | .idea 96 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | 4 | const prettierOptions = JSON.parse( 5 | fs.readFileSync(resolve(__dirname, '.prettierrc'), 'utf8'), 6 | ); 7 | 8 | module.exports = { 9 | parser: 'babel-eslint', 10 | extends: ['airbnb', 'prettier', 'prettier/react'], 11 | plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y'], 12 | parserOptions: { 13 | ecmaVersion: 6, 14 | sourceType: 'module', 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | env: { 20 | jest: true, 21 | browser: true, 22 | node: true, 23 | es6: true, 24 | }, 25 | rules: { 26 | 'prettier/prettier': ['error', prettierOptions], 27 | 'arrow-body-style': [2, 'as-needed'], 28 | 'class-methods-use-this': 0, 29 | 'import/imports-first': 0, 30 | 'import/newline-after-import': 0, 31 | 'import/no-dynamic-require': 0, 32 | 'import/no-extraneous-dependencies': 0, 33 | 'import/no-named-as-default': 0, 34 | 'import/no-unresolved': 2, 35 | 'import/no-webpack-loader-syntax': 0, 36 | 'import/prefer-default-export': 0, 37 | indent: [ 38 | 2, 39 | 2, 40 | { 41 | SwitchCase: 1, 42 | }, 43 | ], 44 | 'jsx-a11y/aria-props': 2, 45 | 'jsx-a11y/heading-has-content': 0, 46 | 'jsx-a11y/label-has-associated-control': [ 47 | 2, 48 | { 49 | // NOTE: If this error triggers, either disable it or add 50 | // your custom components, labels and attributes via these options 51 | // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md 52 | controlComponents: ['Input'], 53 | }, 54 | ], 55 | 'jsx-a11y/label-has-for': 0, 56 | 'jsx-a11y/mouse-events-have-key-events': 2, 57 | 'jsx-a11y/role-has-required-aria-props': 2, 58 | 'jsx-a11y/role-supports-aria-props': 2, 59 | 'max-len': 0, 60 | 'newline-per-chained-call': 0, 61 | 'no-confusing-arrow': 0, 62 | 'no-console': 1, 63 | 'no-unused-vars': 2, 64 | 'no-use-before-define': 0, 65 | 'prefer-template': 2, 66 | 'react/destructuring-assignment': 0, 67 | 'react-hooks/rules-of-hooks': 'error', 68 | 'react/jsx-closing-tag-location': 0, 69 | 'react/forbid-prop-types': 0, 70 | 'react/jsx-first-prop-new-line': [2, 'multiline'], 71 | 'react/jsx-filename-extension': 0, 72 | 'react/jsx-no-target-blank': 0, 73 | 'react/jsx-uses-vars': 2, 74 | 'react/require-default-props': 0, 75 | 'react/require-extension': 0, 76 | 'react/self-closing-comp': 0, 77 | 'react/sort-comp': 0, 78 | 'require-yield': 0, 79 | 'no-underscore-dangle': [ 80 | 'error', 81 | { allow: ['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] }, 82 | ], 83 | }, 84 | settings: { 85 | 'import/resolver': { 86 | webpack: { 87 | config: 'webpack/webpack.prod.js', 88 | }, 89 | }, 90 | }, 91 | }; 92 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { resolve } = require('path'); 3 | 4 | const prettierOptions = JSON.parse( 5 | fs.readFileSync(resolve(__dirname, '.prettierrc'), 'utf8'), 6 | ); 7 | 8 | module.exports = { 9 | parser: 'babel-eslint', 10 | extends: ['airbnb', 'prettier', 'prettier/react'], 11 | plugins: ['prettier', 'react', 'react-hooks', 'jsx-a11y'], 12 | parserOptions: { 13 | ecmaVersion: 6, 14 | sourceType: 'module', 15 | ecmaFeatures: { 16 | jsx: true, 17 | }, 18 | }, 19 | env: { 20 | jest: true, 21 | browser: true, 22 | node: true, 23 | es6: true, 24 | }, 25 | rules: { 26 | 'prettier/prettier': ['error', prettierOptions], 27 | 'arrow-body-style': [2, 'as-needed'], 28 | 'class-methods-use-this': 0, 29 | 'import/imports-first': 0, 30 | 'import/newline-after-import': 0, 31 | 'import/no-dynamic-require': 0, 32 | 'import/no-extraneous-dependencies': 0, 33 | 'import/no-named-as-default': 0, 34 | 'import/no-unresolved': 2, 35 | 'import/no-webpack-loader-syntax': 0, 36 | 'import/prefer-default-export': 0, 37 | indent: [ 38 | 2, 39 | 2, 40 | { 41 | SwitchCase: 1, 42 | }, 43 | ], 44 | 'jsx-a11y/aria-props': 2, 45 | 'jsx-a11y/heading-has-content': 0, 46 | 'jsx-a11y/label-has-associated-control': [ 47 | 2, 48 | { 49 | // NOTE: If this error triggers, either disable it or add 50 | // your custom components, labels and attributes via these options 51 | // See https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/label-has-associated-control.md 52 | controlComponents: ['Input'], 53 | }, 54 | ], 55 | 'jsx-a11y/label-has-for': 0, 56 | 'jsx-a11y/mouse-events-have-key-events': 2, 57 | 'jsx-a11y/role-has-required-aria-props': 2, 58 | 'jsx-a11y/role-supports-aria-props': 2, 59 | 'max-len': 0, 60 | 'newline-per-chained-call': 0, 61 | 'no-confusing-arrow': 0, 62 | 'no-console': 1, 63 | 'no-unused-vars': 2, 64 | 'no-use-before-define': 0, 65 | 'prefer-template': 2, 66 | 'react/destructuring-assignment': 0, 67 | 'react-hooks/rules-of-hooks': 'error', 68 | 'react/jsx-closing-tag-location': 0, 69 | 'react/forbid-prop-types': 0, 70 | 'react/jsx-first-prop-new-line': [2, 'multiline'], 71 | 'react/jsx-filename-extension': 0, 72 | 'react/jsx-no-target-blank': 0, 73 | 'react/jsx-uses-vars': 2, 74 | 'react/require-default-props': 0, 75 | 'react/require-extension': 0, 76 | 'react/self-closing-comp': 0, 77 | 'react/sort-comp': 0, 78 | 'require-yield': 0, 79 | 'no-underscore-dangle': [ 80 | 'error', 81 | { allow: ['__REDUX_DEVTOOLS_EXTENSION_COMPOSE__'] }, 82 | ], 83 | }, 84 | settings: { 85 | 'import/resolver': { 86 | webpack: { 87 | config: 'webpack/webpack.prod.js', 88 | }, 89 | }, 90 | }, 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-fivem", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.js --color -p --progress --hide-modules --display-optimization-bailout", 8 | "build:clean": "rimraf ./build", 9 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.dev.js --open", 10 | "lint": "eslint src/**/**.js", 11 | "lint:fix": "eslint src/**/**.js --fix", 12 | "lint:staged": "lint-staged", 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "prettify": "prettier --write **/*.js" 15 | }, 16 | "lint-staged": { 17 | "*.js": [ 18 | "npm run lint:fix", 19 | "git add --force" 20 | ], 21 | "*.json": [ 22 | "prettier --write", 23 | "git add --force" 24 | ] 25 | }, 26 | "pre-commit": "lint:staged", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/2277/react-fivem.git" 30 | }, 31 | "keywords": [ 32 | "fivem", 33 | "react" 34 | ], 35 | "author": "2277", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/2277/react-fivem/issues" 39 | }, 40 | "homepage": "https://github.com/2277/react-fivem#readme", 41 | "dependencies": { 42 | "@babel/polyfill": "^7.4.4", 43 | "cross-env": "^5.2.0", 44 | "prop-types": "^15.7.2", 45 | "react": "^16.8.6", 46 | "react-dom": "^16.8.6", 47 | "react-redux": "^7.0.3", 48 | "redux": "^4.0.1", 49 | "styled-components": "^4.2.0" 50 | }, 51 | "devDependencies": { 52 | "@babel/cli": "^7.4.4", 53 | "@babel/core": "^7.4.5", 54 | "@babel/plugin-proposal-class-properties": "^7.4.4", 55 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 56 | "@babel/preset-env": "^7.4.5", 57 | "@babel/preset-react": "^7.0.0", 58 | "babel-eslint": "^10.0.1", 59 | "babel-loader": "^8.0.6", 60 | "babel-plugin-styled-components": "^1.10.0", 61 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 62 | "css-loader": "^2.1.1", 63 | "eslint": "^5.16.0", 64 | "eslint-config-airbnb": "^17.1.0", 65 | "eslint-config-prettier": "^4.3.0", 66 | "eslint-import-resolver-webpack": "^0.11.1", 67 | "eslint-loader": "^2.1.2", 68 | "eslint-plugin-babel": "^5.3.0", 69 | "eslint-plugin-import": "^2.17.3", 70 | "eslint-plugin-jsx-a11y": "^6.2.1", 71 | "eslint-plugin-prettier": "^3.1.0", 72 | "eslint-plugin-react": "^7.13.0", 73 | "eslint-plugin-react-hooks": "^1.6.0", 74 | "file-loader": "^3.0.1", 75 | "html-loader": "^0.5.5", 76 | "html-webpack-plugin": "^3.2.0", 77 | "ifdef-loader": "^2.1.4", 78 | "image-webpack-loader": "^4.6.0", 79 | "lint-staged": "^8.1.7", 80 | "prettier": "^1.17.1", 81 | "rimraf": "^2.6.3", 82 | "style-loader": "^0.23.1", 83 | "svg-url-loader": "^2.3.2", 84 | "terser-webpack-plugin": "^1.3.0", 85 | "url-loader": "^1.1.2", 86 | "webpack": "^4.32.2", 87 | "webpack-cli": "^3.3.11", 88 | "webpack-dev-server": "^3.11.0", 89 | "webpack-fivem-manifest": "git+https://github.com/2277/webpack-fivem-manifest.git" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-fivem", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.js --color -p --progress --hide-modules --display-optimization-bailout", 8 | "build:clean": "rimraf ./build", 9 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack/webpack.dev.js --open", 10 | "lint": "eslint src/**/**.js", 11 | "lint:fix": "eslint src/**/**.js --fix", 12 | "lint:staged": "lint-staged", 13 | "test": "echo \"Error: no test specified\" && exit 1", 14 | "prettify": "prettier --write **/*.js" 15 | }, 16 | "lint-staged": { 17 | "*.js": [ 18 | "npm run lint:fix", 19 | "git add --force" 20 | ], 21 | "*.json": [ 22 | "prettier --write", 23 | "git add --force" 24 | ] 25 | }, 26 | "pre-commit": "lint:staged", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/2277/react-fivem.git" 30 | }, 31 | "keywords": [ 32 | "fivem", 33 | "react" 34 | ], 35 | "author": "2277", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/2277/react-fivem/issues" 39 | }, 40 | "homepage": "https://github.com/2277/react-fivem#readme", 41 | "dependencies": { 42 | "@babel/polyfill": "^7.4.4", 43 | "@rebass/grid": "^6.1.0", 44 | "cross-env": "^5.2.0", 45 | "prop-types": "^15.7.2", 46 | "react": "^16.8.6", 47 | "react-dom": "^16.8.6", 48 | "react-redux": "^7.0.3", 49 | "redux": "^4.0.1", 50 | "styled-components": "^4.2.0" 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.4.4", 54 | "@babel/core": "^7.4.5", 55 | "@babel/plugin-proposal-class-properties": "^7.4.4", 56 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 57 | "@babel/preset-env": "^7.4.5", 58 | "@babel/preset-react": "^7.0.0", 59 | "babel-eslint": "^10.0.1", 60 | "babel-loader": "^8.0.6", 61 | "babel-plugin-styled-components": "^1.10.0", 62 | "babel-plugin-transform-react-remove-prop-types": "^0.4.24", 63 | "css-loader": "^2.1.1", 64 | "eslint": "^5.16.0", 65 | "eslint-config-airbnb": "^17.1.0", 66 | "eslint-config-prettier": "^4.3.0", 67 | "eslint-import-resolver-webpack": "^0.11.1", 68 | "eslint-loader": "^2.1.2", 69 | "eslint-plugin-babel": "^5.3.0", 70 | "eslint-plugin-import": "^2.17.3", 71 | "eslint-plugin-jsx-a11y": "^6.2.1", 72 | "eslint-plugin-prettier": "^3.1.0", 73 | "eslint-plugin-react": "^7.13.0", 74 | "eslint-plugin-react-hooks": "^1.6.0", 75 | "file-loader": "^3.0.1", 76 | "html-loader": "^0.5.5", 77 | "html-webpack-plugin": "^3.2.0", 78 | "ifdef-loader": "^2.1.4", 79 | "image-webpack-loader": "^4.6.0", 80 | "lint-staged": "^8.1.7", 81 | "prettier": "^1.17.1", 82 | "rimraf": "^2.6.3", 83 | "style-loader": "^0.23.1", 84 | "svg-url-loader": "^2.3.2", 85 | "terser-webpack-plugin": "^1.3.0", 86 | "url-loader": "^1.1.2", 87 | "webpack": "^4.32.2", 88 | "webpack-cli": "^3.3.11", 89 | "webpack-dev-server": "^3.11.0", 90 | "webpack-fivem-manifest": "git+https://github.com/2277/webpack-fivem-manifest.git" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = options => ({ 5 | mode: options.mode, 6 | entry: options.entry, 7 | output: { 8 | path: path.resolve(process.cwd(), 'html'), 9 | filename: '[name].js', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | }, 19 | }, 20 | { 21 | // Preprocess our own .css files 22 | // This is the place to add your own loaders (e.g. sass/less etc.) 23 | // for a list of loaders, see https://webpack.js.org/loaders/#styling 24 | test: /\.css$/, 25 | exclude: /node_modules/, 26 | use: ['style-loader', 'css-loader'], 27 | }, 28 | { 29 | // Preprocess 3rd party .css files located in node_modules 30 | test: /\.css$/, 31 | include: /node_modules/, 32 | use: ['style-loader', 'css-loader'], 33 | }, 34 | { 35 | test: /\.(eot|otf|ttf|woff|woff2)$/, 36 | use: 'file-loader', 37 | }, 38 | { 39 | test: /\.svg$/, 40 | use: [ 41 | { 42 | loader: 'svg-url-loader', 43 | options: { 44 | // Inline files smaller than 10 kB 45 | limit: 10 * 1024, 46 | noquotes: true, 47 | }, 48 | }, 49 | ], 50 | }, 51 | { 52 | test: /\.(jpg|png|gif)$/, 53 | use: [ 54 | { 55 | loader: 'url-loader', 56 | options: { 57 | // Inline files smaller than 10 kB 58 | limit: 10 * 1024, 59 | }, 60 | }, 61 | { 62 | loader: 'image-webpack-loader', 63 | options: { 64 | mozjpeg: { 65 | enabled: false, 66 | // NOTE: mozjpeg is disabled as it causes errors in some Linux environments 67 | // Try enabling it in your environment by switching the config to: 68 | // enabled: true, 69 | // progressive: true, 70 | }, 71 | gifsicle: { 72 | interlaced: false, 73 | }, 74 | optipng: { 75 | optimizationLevel: 7, 76 | }, 77 | pngquant: { 78 | quality: '65-90', 79 | speed: 4, 80 | }, 81 | }, 82 | }, 83 | ], 84 | }, 85 | { 86 | test: /\.html$/, 87 | use: 'html-loader', 88 | }, 89 | { 90 | test: /\.(mp4|webm)$/, 91 | use: { 92 | loader: 'url-loader', 93 | options: { 94 | limit: 10000, 95 | }, 96 | }, 97 | }, 98 | { 99 | test: /\.js$/, 100 | exclude: /node_modules/, 101 | use: [ 102 | { 103 | loader: `ifdef-loader`, 104 | options: { 105 | DEBUG: options.mode !== 'production', 106 | version: 3, 107 | 'ifdef-verbose': true, // add this for verbose output 108 | 'ifdef-triple-slash': true, // add this to use double slash comment instead of default triple slash 109 | }, 110 | }, 111 | ], 112 | }, 113 | ], 114 | }, 115 | plugins: options.plugins.concat([ 116 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV` 117 | // inside your code for any environment checks; Terser will automatically 118 | // drop any unreachable code. 119 | new webpack.EnvironmentPlugin({ 120 | NODE_ENV: 'development', 121 | }), 122 | ]), 123 | resolve: { 124 | modules: ['src', 'node_modules'], 125 | extensions: ['.js', '.jsx', '.react.js'], 126 | }, 127 | }); 128 | -------------------------------------------------------------------------------- /example/webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = options => ({ 5 | mode: options.mode, 6 | entry: options.entry, 7 | output: { 8 | path: path.resolve(process.cwd(), 'html'), 9 | filename: '[name].js', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | }, 19 | }, 20 | { 21 | // Preprocess our own .css files 22 | // This is the place to add your own loaders (e.g. sass/less etc.) 23 | // for a list of loaders, see https://webpack.js.org/loaders/#styling 24 | test: /\.css$/, 25 | exclude: /node_modules/, 26 | use: ['style-loader', 'css-loader'], 27 | }, 28 | { 29 | // Preprocess 3rd party .css files located in node_modules 30 | test: /\.css$/, 31 | include: /node_modules/, 32 | use: ['style-loader', 'css-loader'], 33 | }, 34 | { 35 | test: /\.(eot|otf|ttf|woff|woff2)$/, 36 | use: 'file-loader', 37 | }, 38 | { 39 | test: /\.svg$/, 40 | use: [ 41 | { 42 | loader: 'svg-url-loader', 43 | options: { 44 | // Inline files smaller than 10 kB 45 | limit: 10 * 1024, 46 | noquotes: true, 47 | }, 48 | }, 49 | ], 50 | }, 51 | { 52 | test: /\.(jpg|png|gif)$/, 53 | use: [ 54 | { 55 | loader: 'url-loader', 56 | options: { 57 | // Inline files smaller than 10 kB 58 | limit: 10 * 1024, 59 | }, 60 | }, 61 | { 62 | loader: 'image-webpack-loader', 63 | options: { 64 | mozjpeg: { 65 | enabled: false, 66 | // NOTE: mozjpeg is disabled as it causes errors in some Linux environments 67 | // Try enabling it in your environment by switching the config to: 68 | // enabled: true, 69 | // progressive: true, 70 | }, 71 | gifsicle: { 72 | interlaced: false, 73 | }, 74 | optipng: { 75 | optimizationLevel: 7, 76 | }, 77 | pngquant: { 78 | quality: '65-90', 79 | speed: 4, 80 | }, 81 | }, 82 | }, 83 | ], 84 | }, 85 | { 86 | test: /\.html$/, 87 | use: 'html-loader', 88 | }, 89 | { 90 | test: /\.(mp4|webm)$/, 91 | use: { 92 | loader: 'url-loader', 93 | options: { 94 | limit: 10000, 95 | }, 96 | }, 97 | }, 98 | { 99 | test: /\.js$/, 100 | exclude: /node_modules/, 101 | use: [ 102 | { 103 | loader: `ifdef-loader`, 104 | options: { 105 | DEBUG: options.mode !== 'production', 106 | version: 3, 107 | 'ifdef-verbose': true, // add this for verbose output 108 | 'ifdef-triple-slash': true, // add this to use double slash comment instead of default triple slash 109 | }, 110 | }, 111 | ], 112 | }, 113 | ], 114 | }, 115 | plugins: options.plugins.concat([ 116 | // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV` 117 | // inside your code for any environment checks; Terser will automatically 118 | // drop any unreachable code. 119 | new webpack.EnvironmentPlugin({ 120 | NODE_ENV: 'development', 121 | }), 122 | ]), 123 | resolve: { 124 | modules: ['src', 'node_modules'], 125 | extensions: ['.js', '.jsx', '.react.js'], 126 | }, 127 | }); 128 | -------------------------------------------------------------------------------- /example/client.lua: -------------------------------------------------------------------------------- 1 | -- Copyright © Vespura 2018 2 | -- https://github.com/TomGrobbe/JoinTransition/blob/master 3 | 4 | -- Arboratory character data. Likely fetched from a database. 5 | Characters = { 6 | { 7 | id = 1, 8 | forename = "Joe", 9 | surname = "Bloggs", 10 | description = "Recently signed a Mixer contract.", 11 | balance = 1000, 12 | position = {x = -1038.121, y = -2738.279, z = 20.16929} -- Los Santos International Airport 13 | }, { 14 | id = 2, 15 | forename = "John", 16 | surname = "Everyman", 17 | description = "Author of Roleplaying 101.", 18 | balance = 20320732, 19 | position = {x = 892.55, y = -182.25, z = 73.72} -- Downtown Cab Co. 20 | } 21 | } 22 | 23 | ------- Configurable options ------- 24 | 25 | -- set the opacity of the clouds 26 | local cloudOpacity = 0.01 -- (default: 0.01) 27 | 28 | -- setting this to false will NOT mute the sound as soon as the game loads 29 | -- (you will hear background noises while on the loading screen, so not recommended) 30 | local muteSound = true -- (default: true) 31 | 32 | -- Register NUI callback 33 | RegisterNUICallback('SELECT_CHARACTER', function(data, cb) 34 | if Characters[data.id] then 35 | local pos = Characters[data.id].position 36 | -- Set player's location 37 | SetEntityCoords(PlayerPedId(), pos.x, pos.y, pos.z, false, false, false, 38 | true) 39 | -- Hide CharacterSelect NUI 40 | SendNUIMessage({type = "CHARACTERS_HIDE"}) 41 | -- Remove NUI Focus 42 | SetNuiFocus(false, false) 43 | 44 | Citizen.CreateThread(function() 45 | local timer = GetGameTimer() 46 | 47 | -- Re-enable the sound in case it was muted. 48 | ToggleSound(false) 49 | 50 | SwitchInPlayer(PlayerPedId()) 51 | 52 | ClearScreen() 53 | 54 | -- Wait for the player switch to be completed (state 12). 55 | while GetPlayerSwitchState() ~= 12 do 56 | Citizen.Wait(0) 57 | ClearScreen() 58 | end 59 | -- Reset the draw origin, just in case (allowing HUD elements to re-appear correctly) 60 | ClearDrawOrigin() 61 | end) 62 | end 63 | end) 64 | 65 | ------- Code ------- 66 | 67 | -- Mutes or un-mutes the game's sound using a short fade in/out transition. 68 | function ToggleSound(state) 69 | if state then 70 | StartAudioScene("MP_LEADERBOARD_SCENE") 71 | else 72 | StopAudioScene("MP_LEADERBOARD_SCENE") 73 | end 74 | end 75 | 76 | -- Runs the initial setup whenever the script is loaded. 77 | function InitialSetup() 78 | -- Stopping the loading screen from automatically being dismissed. 79 | SetManualShutdownLoadingScreenNui(true) 80 | -- Disable sound (if configured) 81 | ToggleSound(muteSound) 82 | -- Switch out the player if it isn't already in a switch state. 83 | if not IsPlayerSwitchInProgress() then 84 | SwitchOutPlayer(PlayerPedId(), 0, 1) 85 | end 86 | end 87 | 88 | -- Hide radar & HUD, set cloud opacity, and use a hacky way of removing third party resource HUD elements. 89 | function ClearScreen() 90 | -- SetCloudHatOpacity(cloudOpacity) 91 | HideHudAndRadarThisFrame() 92 | 93 | -- nice hack to 'hide' HUD elements from other resources/scripts. kinda buggy though. 94 | SetDrawOrigin(0.0, 0.0, 0.0, 0) 95 | end 96 | 97 | -- Sometimes this gets called too early, but sometimes it's perfectly timed, 98 | -- we need this to be as early as possible, without it being TOO early, it's a gamble! 99 | InitialSetup() 100 | 101 | Citizen.CreateThread(function() 102 | SendNUIMessage({ 103 | type = "RECIEVE_CHARACTERS", 104 | data = {characters = Characters} 105 | }) 106 | 107 | -- In case it was called too early before, call it again just in case. 108 | InitialSetup() 109 | 110 | -- Wait for the switch cam to be in the sky in the 'waiting' state (5). 111 | while GetPlayerSwitchState() ~= 5 do 112 | Citizen.Wait(0) 113 | ClearScreen() 114 | end 115 | 116 | -- Shut down the game's loading screen (this is NOT the NUI loading screen). 117 | ShutdownLoadingScreen() 118 | 119 | ClearScreen() 120 | Citizen.Wait(0) 121 | DoScreenFadeOut(0) 122 | 123 | -- Shut down the NUI loading screen. 124 | ShutdownLoadingScreenNui() 125 | 126 | ClearScreen() 127 | Citizen.Wait(0) 128 | ClearScreen() 129 | DoScreenFadeIn(500) 130 | while not IsScreenFadedIn() do 131 | Citizen.Wait(0) 132 | ClearScreen() 133 | end 134 | 135 | -- Show CharacterSelect NUI 136 | SendNUIMessage({type = "CHARACTERS_SHOW"}) 137 | SetNuiFocus(true, true) 138 | end) 139 | --------------------------------------------------------------------------------