├── .editorconfig ├── .eslintrc ├── .github └── workflows │ └── code-quality-checks.yml ├── .gitignore ├── .istanbul.yml ├── .npmrc ├── .travis.yml ├── .watchmanconfig ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── Components │ ├── App.js │ ├── CategoryDivider.js │ ├── Footer.js │ ├── MainMenu.js │ └── Root.js ├── Config │ └── AppConfig.js ├── Containers │ ├── Docs.js │ ├── Home.js │ ├── NotFoundPage.js │ └── Posts.js ├── Images │ ├── favicon.png │ ├── logo.png │ └── logo.svg ├── Navigation │ └── Routes.js ├── Redux │ ├── Posts.js │ └── index.js ├── Sagas │ ├── PostsSaga.js │ └── index.js ├── Services │ └── Api.js ├── Store │ └── CreateStore.js ├── Styles │ ├── Images.js │ ├── app.scss │ ├── bootstrap.scss │ └── home.scss ├── Tests │ ├── Home.test.js │ └── MathUtilis.spec.js ├── Utils │ └── math.js ├── favicon.png ├── index.ejs ├── index.js └── webpack-public-path.js ├── tools ├── .yarnclean ├── analyzeBundle.js ├── assetsTransformer.js ├── build.js ├── chalkConfig.js ├── distServer.js ├── fileMock.js ├── setup │ ├── setup.js │ ├── setupMessage.js │ └── setupPrompts.js ├── srcServer.js ├── startMessage.js └── testCi.js ├── webpack.config.dev.js └── webpack.config.prod.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parser": "@babel/eslint-parser", 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:react/recommended" 11 | ], 12 | "rules": { 13 | "no-console": 0, 14 | "no-undef": 0 15 | }, 16 | "overrides": [ 17 | { 18 | "files": ["src/index.js"], 19 | "rules": { 20 | "react/no-deprecated": "off" 21 | } 22 | } 23 | ], 24 | "parserOptions": { 25 | "requireConfigFile": false, 26 | "babelOptions": { 27 | "presets": ["@babel/preset-env", "@babel/preset-react"] 28 | } 29 | }, 30 | "settings": { 31 | "react": { 32 | "version": "detect" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/code-quality-checks.yml: -------------------------------------------------------------------------------- 1 | name: Code Quality Checks 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | code_quality_checks: 10 | name: Code Quality Checks 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | 17 | - name: Setup Node.js 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: 20.x 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | - name: Run lint 26 | run: npm run lint 27 | 28 | - name: Run tests 29 | run: npm test 30 | 31 | - name: Upload coverage report 32 | if: success() 33 | uses: actions/upload-artifact@v3 34 | with: 35 | name: coverage 36 | path: coverage/lcov-report 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log* 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | #dist folder 30 | dist 31 | 32 | # IDEA/Webstorm project files 33 | .idea 34 | *.iml 35 | 36 | #VSCode metadata 37 | .vscode 38 | 39 | # Mac files 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | excludes: ['*.spec.js'] 3 | extensions: ['.js'] 4 | include-all-sources: true 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | node_js: 4 | - "8" 5 | script: yarn run test:CI 6 | after_success: 7 | # Send coverage data to coveralls. 8 | - yarn run test:cover:CI 9 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theqdev/react-big-bang/b09a8d6ae7d80e1d2ec125f4c3eae64b3f297f3d/.watchmanconfig -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Cory House 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | A ready-to-go, production ready React.js boilerplate, backed by reliant technologies and tools to get your React projects up and running in no time. The boilerplate includes basic examples of API calls and UI elements usage. 4 | 5 | --- 6 | 7 | ##### 2024 Update 8 | 9 | While researching different frameworks and tools to assist with building new SPAs, I realized that this older boilerplate had some neat development and production tools that facilitate fast development. 10 | 11 | All deprecated libraries have been updated, and those no longer supported have been replaced with alternatives that offer long-term support. 12 | 13 | Compatibility with the latest versions of Node.js, npm, and operating systems has also been improved. 14 | 15 | ## Getting Started 16 | 17 | You can also check out the official page of the boilerplate [here](https://rbb.qdev.tech). 18 | 19 | #### Installation and Development Mode 20 | 21 | * `git clone https://github.com/theqdev/react-big-bang` 22 | * `npm run setup` or `npm install` 23 | * `npm start` 24 | 25 | ### Creating a Production Build 26 | 27 | * `npm run clean-dist` - _Cleans the build directory_ 28 | * `npm run prebuild` - _Cleans the build directory and runs linting and tests_ 29 | * `npm run build` - _Generates the production build_ 30 | * `npm run analyze-bundle` - _Analyzes bundle size and its libraries_ 31 | 32 | ### Linting and Tests 33 | 34 | * `npm run lint` or `npm run lint:watch` 35 | * `npm run test` 36 | 37 | ## Technologies 38 | 39 | Under the hood, the kit is powered by: 40 | 41 | | **Tech** | **Description** | 42 | |----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| 43 | | [React](https://facebook.github.io/react/) | Fast, composable client-side components. | 44 | | [react-bootstrap](https://react-bootstrap.github.io/) | React Bootstrap UI kit for React.js. | 45 | | [Redux](http://redux.js.org) - [Redux Sauce](https://github.com/infinitered/reduxsauce) - [Redux Saga](https://github.com/redux-saga/redux-saga) | Redux store implementation. Enforces unidirectional data flows and an immutable, hot-reloadable store that supports time-travel debugging. | 46 | | [APISauce](https://github.com/infinitered/apisauce) | Axios-based API wrapper for mapping your app's backend. | 47 | | [React Router](https://github.com/reactjs/react-router) | A complete routing library for React. | 48 | | [Babel](http://babeljs.io) | Compiles code to any desired presets. | 49 | | [Webpack](https://webpack.js.org) | Bundles npm packages and our JS into a single file. Includes hot reloading. | 50 | | [Browsersync](https://www.browsersync.io/) | Lightweight development HTTP server that supports synchronized testing and debugging on multiple devices. | 51 | | [Jest](https://facebook.github.io/jest/) | Automated tests with built-in expect assertions and react-testing-library for DOM testing without a browser using Node. | 52 | | [TrackJS](https://trackjs.com/) | JavaScript error tracking. | 53 | | [ESLint](http://eslint.org/) | Lint JS. Reports syntax and style issues. Using [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react) for additional React-specific linting rules. | 54 | | [SASS](http://sass-lang.com/) | Compiled CSS styles with variables, functions, and more. | 55 | | [PostCSS](https://github.com/postcss/postcss) | Transforms styles with JS plugins. Used to autoprefix CSS. | 56 | | [EditorConfig](http://editorconfig.org) | Enforces consistent editor settings (spaces vs tabs, etc). | 57 | | [npm Scripts](https://docs.npmjs.com/misc/scripts) | Glues all this together in a handy automated build. | 58 | 59 | ## Contributing 60 | 61 | If you ever end up using this and find a fix or an improvement, don't hesitate to open a [PR of your changes](https://docs.github.com/en/get-started/exploring-projects-on-github/contributing-to-a-project). 62 | 63 | --- 64 | 65 | Check out our website at [Qdev](https://qdev.tech) and our other projects at [Envato Market](https://codecanyon.net/user/ic0de) for more cool stuff. 66 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = function(api) { 3 | api.cache(false); 4 | return { 5 | "env": { 6 | "development": { 7 | "presets": [ 8 | "@babel/preset-env", 9 | "@babel/preset-react" 10 | ], 11 | "plugins": [ 12 | "react-refresh/babel" 13 | ] 14 | }, 15 | "production": { 16 | "presets": [ 17 | [ 18 | "@babel/preset-env", 19 | { 20 | "modules": false, 21 | "targets": { 22 | "ie": 9 23 | }, 24 | "useBuiltIns": "entry", 25 | "corejs": 3 26 | } 27 | ], 28 | "@babel/preset-react" 29 | ], 30 | "plugins": [ 31 | "@babel/plugin-transform-react-constant-elements", 32 | "babel-plugin-transform-react-remove-prop-types" 33 | ] 34 | }, 35 | "test": { 36 | "presets": [ 37 | "@babel/preset-env", 38 | "@babel/preset-react" 39 | ] 40 | } 41 | } 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // jest.config.js 2 | module.exports = { 3 | moduleNameMapper: { 4 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/tools/assetsTransformer.js", 5 | "\\.(css|scss|sass)$": "/tools/assetsTransformer.js" 6 | }, 7 | setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"], 8 | testEnvironment: "jsdom", 9 | testEnvironmentOptions: { 10 | url: "http://localhost" 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React-Big-Bang", 3 | "version": "0.0.2", 4 | "description": "React-Big-Bang", 5 | "engines": { 6 | "npm": ">=6" 7 | }, 8 | "scripts": { 9 | "setup": "node tools/setup/setupMessage.js && npm install && node tools/setup/setup.js", 10 | "start": "concurrently -k -r -s first \"npm run open:src\" \"npm run lint:watch\"", 11 | "open:src": "babel-node tools/srcServer.js", 12 | "open:dev": "babel-node tools/srcServer.js", 13 | "open:build": "babel-node tools/distServer.js", 14 | "lint": "esw webpack.config.* src tools --color", 15 | "lint:watch": "npm run lint -- --watch", 16 | "open:dist": "babel-node tools/distServer.js", 17 | "clean-dist": "npm run remove-dist && mkdir dist", 18 | "remove-dist": "rimraf ./dist", 19 | "prebuild": "npm run lint && npm run test && npm run clean-dist", 20 | "build": "babel-node tools/build.js && npm run open:dist", 21 | "prod": "npm run build", 22 | "test": "jest", 23 | "test:CI": "babel-node tools/testCi.js", 24 | "test:cover": "npm run test -- --coverage", 25 | "test:cover:CI": "npm run test:CI -- --coverage && cat ./coverage/lcov.info", 26 | "test:watch": "jest --watch", 27 | "open:cover": "npm run test:cover && open ./coverage/lcov-report/index.html", 28 | "analyze-bundle": "babel-node ./tools/analyzeBundle.js" 29 | }, 30 | "author": "Qdev Techs", 31 | "license": "MIT", 32 | "dependencies": { 33 | "@testing-library/jest-dom": "5.16.5", 34 | "@testing-library/react": "12.1.5", 35 | "@testing-library/user-event": "13.5.0", 36 | "apisauce": "^3.0.1", 37 | "axios": "^1.7.4", 38 | "bootstrap": "^5.3.3", 39 | "connected-react-router": "6.9.3", 40 | "object-assign": "^4.1.0", 41 | "react": "^17.0.2", 42 | "react-bootstrap": "^2.8.0", 43 | "react-dom": "^17.0.2", 44 | "react-icons-kit": "^1.3.1", 45 | "react-redux": "^7.2.6", 46 | "react-router-dom": "^5.3.0", 47 | "redux": "^4.2.1", 48 | "redux-saga": "^1.2.3", 49 | "reduxsauce": "^0.7.0", 50 | "seamless-immutable": "^7.1.4" 51 | }, 52 | "devDependencies": { 53 | "@babel/cli": "^7.24.8", 54 | "@babel/core": "7.25.2", 55 | "@babel/eslint-parser": "^7.25.1", 56 | "@babel/node": "7.25.0", 57 | "@babel/plugin-transform-class-properties": "^7.24.7", 58 | "@babel/plugin-transform-object-rest-spread": "^7.24.7", 59 | "@babel/plugin-transform-react-constant-elements": "7.25.1", 60 | "@babel/preset-env": "7.25.3", 61 | "@babel/preset-react": "7.24.7", 62 | "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", 63 | "autoprefixer": "^10.4.14", 64 | "babel-jest": "^29.7.0", 65 | "babel-loader": "^8.3.0", 66 | "babel-plugin-transform-react-remove-prop-types": "0.4.24", 67 | "browser-sync": "^3.0.2", 68 | "chalk": "^4.1.2", 69 | "concurrently": "^7.6.0", 70 | "connect-history-api-fallback": "^1.6.0", 71 | "css-loader": "^6.8.1", 72 | "eslint": "^8.57.0", 73 | "eslint-plugin-import": "^2.29.1", 74 | "eslint-plugin-react": "^7.35.0", 75 | "eslint-watch": "^8.0.0", 76 | "history": "^4.10.1", 77 | "html-webpack-plugin": "^5.5.3", 78 | "identity-obj-proxy": "^3.0.0", 79 | "jest": "^29.7.0", 80 | "jest-cli": "^29.7.0", 81 | "jest-environment-jsdom": "29.7.0", 82 | "mini-css-extract-plugin": "^2.7.6", 83 | "mockdate": "^3.0.5", 84 | "node": "^14.17.0", 85 | "open": "^10.1.0", 86 | "postcss-loader": "^7.2.0", 87 | "prompt": "^1.3.0", 88 | "prop-types": "^15.8.1", 89 | "raf": "^3.4.1", 90 | "react-refresh": "^0.14.2", 91 | "react-test-renderer": "^17.0.2", 92 | "redux-immutable-state-invariant": "^2.1.0", 93 | "redux-mock-store": "^1.5.4", 94 | "replace": "git+https://github.com/ALMaclaine/replace.git", 95 | "rimraf": "^6.0.1", 96 | "sass": "^1.77.8", 97 | "sass-loader": "^13.3.0", 98 | "style-loader": "^3.3.1", 99 | "webpack": "^5.88.2", 100 | "webpack-bundle-analyzer": "^4.9.0", 101 | "webpack-dev-middleware": "^5.3.3", 102 | "webpack-hot-middleware": "^2.25.3" 103 | }, 104 | "keywords": [ 105 | "react", 106 | "reactjs", 107 | "react-router", 108 | "hot", 109 | "reload", 110 | "hmr", 111 | "live", 112 | "edit", 113 | "webpack", 114 | "redux", 115 | "flux", 116 | "boilerplate", 117 | "starter" 118 | ], 119 | "repository": { 120 | "type": "git", 121 | "url": "https://github.com/theqdev/react-big-bang" 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types'; 3 | import AppNavigation from "../Navigation/Routes"; 4 | import Footer from "./Footer"; 5 | import MainMenu from "./MainMenu"; 6 | 7 | class App extends Component { 8 | render() { 9 | return ( 10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | } 20 | 21 | App.propTypes = { 22 | children: PropTypes.element, 23 | history : PropTypes.object, 24 | }; 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /src/Components/CategoryDivider.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const CategoryDivider = ({ label }) => { 5 | return ( 6 |
7 |
8 |
9 |
10 |
{label}
11 |
12 |
13 |
14 |
15 | ); 16 | }; 17 | 18 | CategoryDivider.propTypes = { 19 | label: PropTypes.string, 20 | }; 21 | 22 | CategoryDivider.defaultProps = { 23 | label: 'AND', 24 | }; 25 | 26 | export default CategoryDivider; 27 | -------------------------------------------------------------------------------- /src/Components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Row from "react-bootstrap/Row"; 4 | import Col from "react-bootstrap/Col"; 5 | import { FaFacebookSquare, FaInstagram, FaTwitterSquare, FaLinkedin } from 'react-icons/fa'; 6 | import { Container } from "react-bootstrap"; 7 | 8 | const Footer = () => { 9 | return ( 10 |
11 | 12 | 13 | 14 |

About

15 |

React Big Bang - Ready to go React.js starter kit.

16 |

Made with by Qdev Techs © {new Date().getFullYear()}.

17 | 18 | 19 |

Links

20 |
    21 |
  • Home
  • 22 |
  • Documentation
  • 23 |
  • API Example
  • 24 |
25 | 26 | 27 |

Follow / Reach us

28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | ); 37 | }; 38 | 39 | export default Footer; 40 | -------------------------------------------------------------------------------- /src/Components/MainMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import PropTypes from 'prop-types'; 4 | import { Button, Container, Nav, Navbar } from "react-bootstrap"; 5 | import { FaGithub } from 'react-icons/fa'; 6 | 7 | const MainMenu = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | MainMenu.propTypes = { 38 | history: PropTypes.object, 39 | }; 40 | 41 | export default MainMenu; 42 | -------------------------------------------------------------------------------- /src/Components/Root.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { ConnectedRouter } from 'connected-react-router'; // Update this import 4 | import { Provider } from 'react-redux'; 5 | import App from './App'; 6 | 7 | export default class Root extends Component { 8 | render() { 9 | const { store, history } = this.props; 10 | return ( 11 | 12 | {/* Use ConnectedRouter from connected-react-router */} 13 | 14 | 15 | 16 | ); 17 | } 18 | } 19 | 20 | Root.propTypes = { 21 | store: PropTypes.object.isRequired, 22 | history: PropTypes.object.isRequired 23 | }; 24 | -------------------------------------------------------------------------------- /src/Config/AppConfig.js: -------------------------------------------------------------------------------- 1 | export default { 2 | AppName: 'React JS BoilerPlate', 3 | APIServer: 'https://jsonplaceholder.typicode.com', 4 | } 5 | -------------------------------------------------------------------------------- /src/Containers/Docs.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Container from 'react-bootstrap/Container'; 3 | 4 | const Docs = () => { 5 | return ( 6 | 7 |

Documentation

8 |

Overview

9 |

10 | React Big Bang is a solid foundation for React.js-based apps, 11 | backed by the latest techs and tools to get your React projects on 12 | their feet in no time. The boilerplate includes basic API call 13 | examples and UI elements usage examples. 14 |

15 | 16 |

Installation

17 |

To get started, run the following commands:

18 |
    19 |
  • 20 | git clone https://github.com/theqdev/react-big-bang 21 |
  • 22 |
  • 23 | npm run setup or npm install 24 |
  • 25 |
  • 26 | npm start 27 |
  • 28 |
29 | 30 |

Table of contents:

31 | 48 | 49 |
50 |

Routing

51 |

52 | Routes are generally handled in the src/Navigation/Routes.js file. 53 | To add a new route, you will need to add a line like this: 54 |

55 |
 56 |           {``}
 57 |         
58 | 59 |

To add a route with a parameter, you can use something like this:

60 |
 61 |           {``}
 62 |         
63 |

64 | Then, in your component, you will be able to access your parameter by using{' '} 65 | this.props.match.params.id. 66 |

67 |
68 | 69 |
70 |

Components

71 |

72 | Components are split between the Containers and Components folders. 73 | As their names suggest, in the Containers folder, you can store your smart components and 74 | main container components for your pages, which are also usually connected to the Redux store. 75 | For the dumb/presentation components, you can use the Components folder. 76 |

77 |
78 | 79 |
80 |

Redux - Sagas - API

81 |

82 | For our Redux store and data fetching capabilities, we have used a mix of redux-sauce, redux-saga, and apisauce. 83 |

84 |

To create a reducer that can fetch data from an API endpoint, you need to:

85 |
    86 |
  1. 87 | Create a reducer under the Redux folder. With the help of redux-sauce, you will be able to easily 88 | generate your reducers, Types, Action creators, and the store's initial state, all in one file. 89 |
  2. 90 |
  3. 91 | Import and add the reducer to the Redux/index.js combineReducers function. 92 |
  4. 93 |
  5. 94 | Create a saga file under the Sagas directory. Inside this file, you can write your saga effects, 95 | which are executed when a certain Redux type has been dispatched. Usually, this is where we call our API, 96 | then based on its response, we can dispatch success or error actions. 97 |
  6. 98 |
  7. 99 | Import and add the reducer and the saga we just created to the Sagas/index.js. This is where we 100 | map our Redux types to the saga effects. 101 |
  8. 102 |
  9. 103 | Add your API endpoint to the Services/Api.js file. The function for the API call will then be 104 | called from the saga effect function, where you can also pass additional parameters to the API call. 105 |
  10. 106 |
107 |

108 | I would recommend that, at first, you simply copy over the existing examples and adapt them to your needs. 109 |

110 |
111 | 112 |
113 |

UI & CSS

114 |

115 | Regarding styling and CSS usage, we don't have a strict paradigm over it. You can write CSS in any way 116 | you prefer or you might just use bootstrap utilities instead. 117 |

118 |
    119 |
  • No CSS at all - just use utilities
  • 120 |
  • Inline CSS
  • 121 |
  • CSS/SCSS/SASS
  • 122 |
123 |
124 | 125 |
126 |

Scripts and helpers

127 |
    128 |
  • npm run setup - Runs the initial setup script.
  • 129 |
  • npm start - Starts the project in dev mode.
  • 130 |
  • npm run build - Builds the production bundle.
  • 131 |
  • npm run lint:watch - Previews ESLint errors in real-time.
  • 132 |
  • npm run analyze-bundle - Gets an interactive map of the sizes of your dependencies.
  • 133 |
  • npm run test - Runs your test suites.
  • 134 |
135 |
136 | 137 |

138 | The documentation is lightweight, so I hope that the code and examples are self-explanatory. 139 | If you have any suggestions or questions, don't hesitate to send us an email or write us a message. 140 |

141 |
142 | ); 143 | }; 144 | 145 | export default Docs; 146 | -------------------------------------------------------------------------------- /src/Containers/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import Button from 'react-bootstrap/Button'; 4 | import Col from 'react-bootstrap/Col'; 5 | import Row from 'react-bootstrap/Row'; 6 | import Container from 'react-bootstrap/Container'; 7 | import { Image } from 'react-bootstrap'; 8 | import '../Styles/home.scss'; 9 | import Images from '../Styles/Images'; 10 | import CategoryAndDivider from "../Components/CategoryDivider"; 11 | 12 | const Home = () => { 13 | return ( 14 |
15 |
16 |
17 |
18 |

Ready to go react-js boilerplate

19 |

The 💥 you need for your react projects

20 | 21 | 22 | 23 |
24 | 25 |
26 |
27 | 28 | 29 | 30 | 31 |

About the boilerplate

32 |

33 | React Big Bang is a solid foundation for React.js based apps, backed by latest techs and tools to get your React projects on feet in no time. The boilerplate includes basic API call example and UI elements usage example. 34 |

35 |

36 | Check out our website at Qdev and our code marketplace at Alkanyx for more cool stuff. 37 |

38 |

39 | You can also checkout the Github page. 40 |

41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 |
49 | 50 | 51 | 52 |

Ready to go

53 |

Just clone the repo and you are ready to start coding.

54 | 55 | 56 |

Save time

57 |

Skip the time and frustration of configuring a new React app.

58 | 59 |
60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 |

🔮 Webpack - Babel - Router

68 |

Out of the box ES6 configuration for both development and production builds.

69 |
70 |
71 | 72 | 73 | 74 |
75 |
76 |

📅 Redux - Redux Sauce - Redux Sagas

77 |

Quick and on point Redux workflow implementation that allows you to easily create reducers, actions, action creators and middleware to handle the data you need.

78 |
79 |
80 | 81 | 82 | 83 |
84 |
85 |

📃 API Client Ready

86 |

Map your backend API endpoints and you are ready to start fetching data from your API.

87 |
88 |
89 | 90 | 91 | 92 |
93 |
94 |

✔ ESLint - Jest - Browsersync

95 |

Do not let your code get out of control by using linting and tests.

96 |
97 |
98 | 99 | 100 | 101 |
102 |
103 |

🍂 React Bootstrap - SCSS - PostCSS

104 |

Start building your UI quickly with React Boostrap UI kit and modern CSS.

105 |
106 |
107 | 108 |
109 | 110 |
111 | ); 112 | } 113 | 114 | export default Home; 115 | -------------------------------------------------------------------------------- /src/Containers/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Button } from 'react-bootstrap'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const NotFoundPage = () => ( 6 |
7 | 8 |

404

9 |

Page not found

10 |

Sorry, we couldn’t find the page you’re looking for.

11 |
12 | 13 | 16 | 17 |
18 |
19 |
20 | ); 21 | 22 | export default NotFoundPage; 23 | -------------------------------------------------------------------------------- /src/Containers/Posts.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useSelector, useDispatch } from 'react-redux'; 3 | import PostsActions from '../Redux/Posts'; 4 | import PropTypes from 'prop-types'; 5 | import Container from "react-bootstrap/Container"; 6 | import Form from "react-bootstrap/Form"; 7 | 8 | const GetExample = () => { 9 | const dispatch = useDispatch(); 10 | const posts = useSelector(state => state.posts); 11 | 12 | useEffect(() => { 13 | dispatch(PostsActions.postsGet()); 14 | }, [dispatch]); 15 | 16 | return ( 17 |
18 | 19 |

API Get Example

20 |

Here is your data, served as props to your container, after fetched from custom API endpoint.

21 | 22 | {}} // This keeps the textarea non-editable as before 27 | /> 28 | 29 |
30 |
31 | ); 32 | }; 33 | 34 | GetExample.propTypes = { 35 | posts: PropTypes.object, 36 | }; 37 | 38 | export default GetExample; 39 | -------------------------------------------------------------------------------- /src/Images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theqdev/react-big-bang/b09a8d6ae7d80e1d2ec125f4c3eae64b3f297f3d/src/Images/favicon.png -------------------------------------------------------------------------------- /src/Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theqdev/react-big-bang/b09a8d6ae7d80e1d2ec125f4c3eae64b3f297f3d/src/Images/logo.png -------------------------------------------------------------------------------- /src/Images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Navigation/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import NotFoundPage from '../Containers/NotFoundPage'; 3 | import Home from '../Containers/Home'; 4 | import Posts from '../Containers/Posts'; 5 | import Docs from '../Containers/Docs'; 6 | import {Route, Switch} from "react-router-dom"; 7 | 8 | class AppNavigation extends Component { 9 | render(){ 10 | return( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | 22 | export default AppNavigation; 23 | -------------------------------------------------------------------------------- /src/Redux/Posts.js: -------------------------------------------------------------------------------- 1 | import { createReducer, createActions } from 'reduxsauce' 2 | import Immutable from 'seamless-immutable' 3 | 4 | /* ------------- Types and Action Creators ------------- */ 5 | 6 | const { Types, Creators } = createActions({ 7 | postsGet: null, 8 | postsSuccess: ['data'], 9 | postsFailure: null, 10 | }) 11 | 12 | export const PostsActions = Types 13 | export default Creators 14 | 15 | /* ------------- Initial State ------------- */ 16 | 17 | export const INITIAL_STATE = Immutable({ 18 | data: null, 19 | fetching: null, 20 | error: null 21 | }) 22 | 23 | /* ------------- Reducers ------------- */ 24 | 25 | // request the data from an api 26 | export const get = (state, { data }) => 27 | state.merge({ fetching: true, data }) 28 | 29 | // successful api lookup 30 | export const success = (state, { data }) => 31 | state.merge({ fetching: false, error: null, data:data }) 32 | 33 | // Something went wrong somewhere. 34 | export const failure = state => 35 | state.merge({ fetching: false, error: true }) 36 | 37 | /* ------------- Hookup Reducers To Types ------------- */ 38 | 39 | export const Posts = createReducer(INITIAL_STATE, { 40 | [Types.POSTS_GET]: get, 41 | [Types.POSTS_SUCCESS]: success, 42 | [Types.POSTS_FAILURE]: failure, 43 | }) 44 | -------------------------------------------------------------------------------- /src/Redux/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { connectRouter } from 'connected-react-router'; 3 | import { Posts as posts } from './Posts'; 4 | 5 | export const createRootReducer = (history) => 6 | combineReducers({ 7 | router: connectRouter(history), // Integrates the router state into Redux 8 | posts, // Your other reducers 9 | }); 10 | -------------------------------------------------------------------------------- /src/Sagas/PostsSaga.js: -------------------------------------------------------------------------------- 1 | import { put, call } from 'redux-saga/effects'; 2 | import PostsActions from "../Redux/Posts"; 3 | 4 | export function * onExampleFetch(api, action) { 5 | const response = yield call(api.getPosts, action.data) 6 | if (response.ok) { 7 | console.log('API Data fetched'); 8 | yield put(PostsActions.postsSuccess(response.data)) 9 | } else { 10 | console.log('API Data fetched. Backend might be down.'); 11 | yield put(PostsActions.postsFailure()) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Sagas/index.js: -------------------------------------------------------------------------------- 1 | import { takeLatest, all } from 'redux-saga/effects'; 2 | import "regenerator-runtime/runtime"; 3 | import API from '../Services/Api'; 4 | 5 | /* ------------- Types ------------- */ 6 | import { PostsActions } from '../Redux/Posts'; 7 | 8 | /* ------------- Sagas ------------- */ 9 | import { onExampleFetch } from './PostsSaga'; 10 | 11 | /* ------------- API ------------- */ 12 | // The API we use is only used from Sagas, so we create it here and pass along 13 | // to the sagas which need it. 14 | const api = API.create(); 15 | 16 | /* ------------- Connect Types To Sagas ------------- */ 17 | 18 | export default function* rootSaga() { 19 | yield all([ 20 | // some sagas only receive an action 21 | takeLatest(PostsActions.POSTS_GET, onExampleFetch, api), 22 | ]); 23 | } 24 | -------------------------------------------------------------------------------- /src/Services/Api.js: -------------------------------------------------------------------------------- 1 | import apisauce from 'apisauce' 2 | import AppConfig from '../Config/AppConfig' 3 | 4 | // our "constructor" 5 | const create = (baseURL = AppConfig.APIServer ) => { 6 | // ------ 7 | // STEP 1 8 | // ------ 9 | // 10 | // Create and configure an apisauce-based api object. 11 | // 12 | const api = apisauce.create({ 13 | // base URL is read from the "constructor" 14 | baseURL, 15 | // here are some default headers 16 | headers: { 17 | 'Cache-Control': 'no-cache', 18 | "Accept": "application/json", 19 | }, 20 | // 10 second timeout... 21 | timeout: 10000 22 | }) 23 | 24 | // ------ 25 | // STEP 2 26 | // ------ 27 | // 28 | // Define some functions that call the api. The goal is to provide 29 | // a thin wrapper of the api layer providing nicer feeling functions 30 | // rather than "get", "post" and friends. 31 | // 32 | // I generally don't like wrapping the output at this level because 33 | // sometimes specific actions need to be take on `403` or `401`, etc. 34 | // 35 | // Since we can't hide from that, we embrace it by getting out of the 36 | // way at this level. 37 | // 38 | 39 | const getPosts = (token) => api.get('posts', {token:token}) 40 | // const setSettings = (token,data) => api.post(`settings`, {data:data}, {headers: {Authorization: 'Bearer ' + token}}) 41 | 42 | // ------ 43 | // STEP 3 44 | // ------ 45 | // 46 | // Return back a collection of functions that we would consider our 47 | // interface. Most of the time it'll be just the list of all the 48 | // methods in step 2. 49 | // 50 | // Notice we're not returning back the `api` created in step 1? That's 51 | // because it is scoped privately. This is one way to create truly 52 | // private scoped goodies in JavaScript. 53 | // 54 | return { 55 | // a list of the API functions from step 2 56 | getPosts, 57 | } 58 | } 59 | 60 | // let's return back our create method as the default. 61 | export default { 62 | create 63 | } 64 | -------------------------------------------------------------------------------- /src/Store/CreateStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | import { routerMiddleware } from 'connected-react-router'; 4 | import { createBrowserHistory } from 'history'; 5 | import { createRootReducer } from '../Redux'; 6 | import rootSaga from '../Sagas/index'; 7 | 8 | export const history = createBrowserHistory(); 9 | 10 | export default function configureStore(preloadedState) { 11 | const sagaMiddleware = createSagaMiddleware(); 12 | const routingMiddleware = routerMiddleware(history); 13 | 14 | const middleware = [sagaMiddleware, routingMiddleware]; 15 | 16 | const composeEnhancers = 17 | process.env.NODE_ENV !== 'production' && 18 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 19 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ 20 | : compose; 21 | 22 | const store = createStore( 23 | createRootReducer(history), // root reducer with router state 24 | preloadedState, // initial state 25 | composeEnhancers(applyMiddleware(...middleware)) 26 | ); 27 | 28 | sagaMiddleware.run(rootSaga); 29 | 30 | if (module.hot) { 31 | module.hot.accept('../Redux', () => { 32 | store.replaceReducer(createRootReducer(history)); 33 | }); 34 | } 35 | 36 | return store; 37 | } 38 | -------------------------------------------------------------------------------- /src/Styles/Images.js: -------------------------------------------------------------------------------- 1 | import logo from '../Images/logo.png'; 2 | import favicon from '../Images/favicon.png'; 3 | 4 | const images = { 5 | logo, 6 | favicon 7 | } 8 | 9 | export default images; 10 | -------------------------------------------------------------------------------- /src/Styles/app.scss: -------------------------------------------------------------------------------- 1 | @import 'bootstrap.scss'; 2 | 3 | /* Styles */ 4 | .navbar{ 5 | background-color:#1B1C1D !important; 6 | max-height: 500px !important; 7 | height: 93px !important; 8 | font-size: 20px; 9 | } 10 | 11 | .footer{ 12 | height: 200px; 13 | color:#FFF; 14 | background-color:#1B1C1D; 15 | padding:20px; 16 | padding-top:40px; 17 | } 18 | 19 | .footer .social a{ 20 | color:#FFF 21 | } 22 | 23 | .footer-social-icon{ 24 | color: white; 25 | padding-left: 7px; 26 | padding-right: 7px; 27 | } 28 | 29 | .center{ 30 | text-align: center; 31 | } 32 | 33 | pre code { 34 | line-height: 1.6em; 35 | font-size: 13px; 36 | } 37 | pre { 38 | padding: 0.1em 0.5em 0.3em 0.7em; 39 | border-left: 11px solid #ccc; 40 | margin: 1.7em 0 1.7em 0.3em; 41 | overflow: auto; 42 | width: 93%; 43 | } 44 | /* target IE7 and IE6 */ 45 | *:first-child+html pre { 46 | padding-bottom: 2em; 47 | overflow-y: hidden; 48 | overflow: visible; 49 | overflow-x: auto; 50 | } 51 | * html pre { 52 | padding-bottom: 2em; 53 | overflow: visible; 54 | overflow-x: auto; 55 | } 56 | 57 | .grayscale { 58 | filter: gray; /* IE6-9 */ 59 | -webkit-filter: grayscale(97%); /* Chrome 19+ & Safari 6+ */ 60 | } 61 | 62 | .grayscale:hover { 63 | filter: none; 64 | -webkit-filter: grayscale(0%); 65 | } 66 | 67 | .navbar{ 68 | height: 60px!important; 69 | } 70 | -------------------------------------------------------------------------------- /src/Styles/bootstrap.scss: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | $primary: #7695FF !default; 3 | $secondary: #ffa07a !default; 4 | $light: #ffffff !default; 5 | $dark: #333333 !default; 6 | $info: #17a2b8 !default; 7 | $success: #28a745 !default; 8 | $warning: #ffc107 !default; 9 | $danger: #dc3545 !default; 10 | 11 | /* make the customizations */ 12 | //$theme-colors: ( 13 | // 'info': tomato, 14 | // 'danger': teal 15 | //); 16 | 17 | @import '~bootstrap/scss/bootstrap'; 18 | -------------------------------------------------------------------------------- /src/Styles/home.scss: -------------------------------------------------------------------------------- 1 | // Homepage related CSS 2 | 3 | .home-header{ 4 | height: 360px; 5 | color:#FFF; 6 | background-color:#1B1C1D; 7 | text-align: center; 8 | } 9 | -------------------------------------------------------------------------------- /src/Tests/Home.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import { BrowserRouter as Router } from 'react-router-dom'; 4 | import '@testing-library/jest-dom/extend-expect'; // for the custom matchers 5 | import Home from '../Containers/Home'; // Adjust the import path if necessary 6 | 7 | test('renders the Home component', () => { 8 | render( 9 | 10 | 11 | 12 | ); 13 | 14 | // Check if the main header is rendered 15 | expect(screen.getByText(/The 💥 you need for your react projects/i)).toBeInTheDocument(); 16 | 17 | // Check if the "Get started" button is rendered 18 | expect(screen.getByRole('button', { name: /get started/i })).toBeInTheDocument(); 19 | }); 20 | -------------------------------------------------------------------------------- /src/Tests/MathUtilis.spec.js: -------------------------------------------------------------------------------- 1 | import {roundNumber} from '../Utils/math'; 2 | 3 | describe('Math Helper', () => { 4 | describe('roundNumber', () => { 5 | it('returns 0 when passed null', () => { 6 | expect(roundNumber(null)).toEqual(''); 7 | }); 8 | 9 | it('returns 0 when passed 0', () => { 10 | expect(roundNumber(0)).toEqual(0); 11 | }); 12 | 13 | it('rounds up to 1.56 when passed 1.55555 rounded to 2 digits', () => { 14 | expect(roundNumber(1.55555, 2)).toEqual(1.56); 15 | }); 16 | 17 | it('rounds up to -1.56 when passed -1.55555 rounded to 2 digits', () => { 18 | expect(roundNumber(-1.55555, 2)).toEqual(-1.56); 19 | }); 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /src/Utils/math.js: -------------------------------------------------------------------------------- 1 | export function roundNumber(numberToRound, numberOfDecimalPlaces) { 2 | if (numberToRound === 0) { 3 | return 0; 4 | } 5 | 6 | if (!numberToRound) { 7 | return ''; 8 | } 9 | 10 | const scrubbedNumber = numberToRound.toString().replace('$', '').replace(',', ''); 11 | return Math.round(scrubbedNumber * Math.pow(10, numberOfDecimalPlaces)) / Math.pow(10, numberOfDecimalPlaces); 12 | } 13 | -------------------------------------------------------------------------------- /src/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theqdev/react-big-bang/b09a8d6ae7d80e1d2ec125f4c3eae64b3f297f3d/src/favicon.png -------------------------------------------------------------------------------- /src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 13 | <% if (htmlWebpackPlugin.options.trackJSToken) { %> 14 | 15 | 16 | <% } %> 17 | 18 | 19 | 20 | React Big BANG - Ready to go React.js starter kit boilerplate 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { ConnectedRouter } from 'connected-react-router'; 5 | import { history } from './Store/CreateStore'; 6 | import configureStore from './Store/CreateStore'; 7 | import Root from './Components/Root'; 8 | import './Styles/app.scss'; 9 | require('./favicon.png'); 10 | 11 | const store = configureStore(); 12 | 13 | const renderApp = (Component) => { 14 | ReactDOM.render( 15 | 16 | 17 | {/*TOOD: Review this*/} 18 | {/* Pass history and store */} 19 | 20 | , 21 | document.getElementById('app') 22 | ); 23 | }; 24 | 25 | renderApp(Root); 26 | 27 | if (module.hot) { 28 | module.hot.accept('./Components/Root', () => { 29 | const NewRoot = require('./Components/Root').default; 30 | renderApp(NewRoot); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /src/webpack-public-path.js: -------------------------------------------------------------------------------- 1 | // Dynamically set the webpack public path at runtime below 2 | // This magic global is used by webpack to set the public path at runtime. 3 | // The public path is set dynamically to avoid the following issues: 4 | // 1. https://github.com/coryhouse/react-slingshot/issues/205 5 | // 2. https://github.com/coryhouse/react-slingshot/issues/181 6 | // 3. https://github.com/coryhouse/react-slingshot/pull/125 7 | // Documentation: https://webpack.js.org/configuration/output/#output-publicpath 8 | // eslint-disable-next-line no-undef 9 | __webpack_public_path__ = window.location.protocol + "//" + window.location.host + "/"; 10 | -------------------------------------------------------------------------------- /tools/.yarnclean: -------------------------------------------------------------------------------- 1 | !browser-sync-ui/lib/plugins/history # need this for now because of https://github.com/yarnpkg/yarn/issues/1396#issuecomment-255965666 2 | -------------------------------------------------------------------------------- /tools/analyzeBundle.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'; 3 | import config from '../webpack.config.prod'; 4 | 5 | config.plugins.push(new BundleAnalyzerPlugin()); 6 | 7 | process.env.NODE_ENV = 'production'; 8 | 9 | const compiler = webpack(config); 10 | 11 | compiler.run((error, stats) => { 12 | if (error) { 13 | throw new Error(error); 14 | } 15 | 16 | console.log(stats); // eslint-disable-line no-console 17 | }); 18 | -------------------------------------------------------------------------------- /tools/assetsTransformer.js: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------------------- 2 | // This is a fix for jest handling static assets like imported images 3 | // when running tests. It's configured in jest section of package.json 4 | // 5 | // See: 6 | // https://github.com/facebook/jest/issues/2663#issuecomment-317109798 7 | //--------------------------------------------------------------------- 8 | const path = require('path'); 9 | 10 | module.exports = { 11 | process(src, filename /*, config, options */) { 12 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /tools/build.js: -------------------------------------------------------------------------------- 1 | // More info on Webpack's Node API here: https://webpack.js.org/api/node/ 2 | // Allowing console calls below since this is a build file. 3 | /* eslint-disable no-console */ 4 | import webpack from 'webpack'; 5 | import config from '../webpack.config.prod'; 6 | import {chalkError, chalkSuccess, chalkWarning, chalkProcessing} from './chalkConfig'; 7 | 8 | process.env.NODE_ENV = 'production'; // this assures React is built in prod mode and that the Babel dev config doesn't apply. 9 | 10 | console.log(chalkProcessing('Generating minified bundle. This will take a moment...')); 11 | 12 | webpack(config).run((error, stats) => { 13 | if (error) { // so a fatal error occurred. Stop here. 14 | console.log(chalkError(error)); 15 | return 1; 16 | } 17 | 18 | const jsonStats = stats.toJson(); 19 | 20 | if (jsonStats.hasErrors) { 21 | return jsonStats.errors.map(error => console.log(chalkError(error))); 22 | } 23 | 24 | if (jsonStats.hasWarnings) { 25 | console.log(chalkWarning('Webpack generated the following warnings: ')); 26 | jsonStats.warnings.map(warning => console.log(chalkWarning(warning))); 27 | } 28 | 29 | console.log(`Webpack stats: ${stats}`); 30 | 31 | // if we got this far, the build succeeded. 32 | console.log(chalkSuccess('Your app is compiled in production mode in /dist. It\'s ready to roll!')); 33 | 34 | return 0; 35 | }); 36 | -------------------------------------------------------------------------------- /tools/chalkConfig.js: -------------------------------------------------------------------------------- 1 | // Centralized configuration for chalk, which is used to add color to console.log statements. 2 | import chalk from 'chalk'; 3 | export const chalkError = chalk.red; 4 | export const chalkSuccess = chalk.green; 5 | export const chalkWarning = chalk.yellow; 6 | export const chalkProcessing = chalk.blue; 7 | -------------------------------------------------------------------------------- /tools/distServer.js: -------------------------------------------------------------------------------- 1 | // This file configures a web server for testing the production build 2 | // on your local machine. 3 | 4 | import browserSync from 'browser-sync'; 5 | import historyApiFallback from 'connect-history-api-fallback'; 6 | import {chalkProcessing} from './chalkConfig'; 7 | 8 | /* eslint-disable no-console */ 9 | 10 | console.log(chalkProcessing('Opening production build...')); 11 | 12 | // Run Browsersync 13 | browserSync({ 14 | port: 4000, 15 | ui: { 16 | port: 4001 17 | }, 18 | server: { 19 | baseDir: 'dist' 20 | }, 21 | 22 | files: [ 23 | 'src/*.html' 24 | ], 25 | 26 | middleware: [historyApiFallback()] 27 | }); 28 | -------------------------------------------------------------------------------- /tools/fileMock.js: -------------------------------------------------------------------------------- 1 | // Return an empty string or other mock path to emulate the url that 2 | // webpack provides via the file-loader 3 | module.exports = ''; 4 | -------------------------------------------------------------------------------- /tools/setup/setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-var */ 2 | var rimraf = require('rimraf'); 3 | var chalk = require('chalk'); 4 | var replace = require("replace"); 5 | var prompt = require("prompt"); 6 | var prompts = require('./setupPrompts'); 7 | 8 | var chalkSuccess = chalk.green; 9 | var chalkProcessing = chalk.blue; 10 | var chalkWarn = chalk.red; 11 | 12 | /* eslint-disable no-console */ 13 | 14 | console.log(chalkSuccess('Dependencies installed.')); 15 | 16 | prompt.start(); 17 | 18 | console.log(chalkWarn("WARNING: Preparing to delete local git repository...")); 19 | prompt.get([{name: 'deleteGit', description: "Delete the git repository? [Y/n]"}], function(err, result) { 20 | var deleteGit = result.deleteGit.toUpperCase(); 21 | 22 | if (err) { 23 | process.exit(1); 24 | } 25 | 26 | function updatePackage() { 27 | console.log(chalkProcessing('Updating package.json settings:')); 28 | 29 | prompt.get(prompts, function(err, result) { 30 | // parse user responses 31 | // default values provided for fields that will cause npm to complain if left empty 32 | const responses = [ 33 | { 34 | key: 'name', 35 | value: result.projectName || 'new-project' 36 | }, 37 | { 38 | key: 'version', 39 | value: result.version || '0.1.0' 40 | }, 41 | { 42 | key: 'author', 43 | value: result.author 44 | }, 45 | { 46 | key: 'license', 47 | value: result.license || 'MIT' 48 | }, 49 | { 50 | key: 'description', 51 | value: result.description 52 | }, 53 | // simply use an empty URL here to clear the existing repo URL 54 | { 55 | key: 'url', 56 | value: '' 57 | } 58 | ]; 59 | 60 | // update package.json with the user's values 61 | responses.forEach(res => { 62 | replace({ 63 | regex: `("${res.key}"): "(.*?)"`, 64 | replacement: `$1: "${res.value}"`, 65 | paths: ['package.json'], 66 | recursive: false, 67 | silent: true 68 | }); 69 | }); 70 | 71 | // reset package.json 'keywords' field to empty state 72 | replace({ 73 | regex: /"keywords": \[[\s\S]+?\]/, 74 | replacement: `"keywords": []`, 75 | paths: ['package.json'], 76 | recursive: false, 77 | silent: true 78 | }); 79 | 80 | // remove setup script from package.json 81 | replace({ 82 | regex: /\s*"setup":.*,/, 83 | replacement: "", 84 | paths: ['package.json'], 85 | recursive: false, 86 | silent: true 87 | }); 88 | 89 | // remove all setup scripts from the 'tools' folder 90 | console.log(chalkSuccess('\nSetup complete! Cleaning up...\n')); 91 | // rimraf('./tools/setup', error => { 92 | // if (error) throw new Error(error); 93 | // }); 94 | }); 95 | 96 | } 97 | 98 | if (deleteGit.match(/^N.*/)) { 99 | updatePackage(); 100 | } 101 | else { 102 | // remove the original git repository 103 | rimraf('.git', error => { 104 | if (error) throw new Error(error); 105 | console.log(chalkSuccess('Original Git repository removed.\n')); 106 | updatePackage(); 107 | }); 108 | } 109 | }); 110 | -------------------------------------------------------------------------------- /tools/setup/setupMessage.js: -------------------------------------------------------------------------------- 1 | // This script displays an intro message for the setup script 2 | /* eslint-disable no-console */ 3 | console.log('==========================='); 4 | console.log('= React BigBang Setup ='); 5 | console.log('===========================\n'); 6 | console.log('Installing dependencies. Please wait...'); 7 | -------------------------------------------------------------------------------- /tools/setup/setupPrompts.js: -------------------------------------------------------------------------------- 1 | // Define prompts for use with npm 'prompt' module in setup script 2 | module.exports = [ 3 | { 4 | name: 'projectName', 5 | description: 'Project name (default: new-project)', 6 | pattern: /^[^._][a-z0-9-_~]+$/, 7 | message: 'Limited to: lowercase letters, numbers, period, hyphen, ' + 8 | 'underscore, and tilde; cannot begin with period or underscore.' 9 | }, 10 | { 11 | name: 'version', 12 | description: 'Version (default: 0.1.0)' 13 | }, 14 | { 15 | name: 'author', 16 | description: 'Author' 17 | }, 18 | { 19 | name: 'license', 20 | description: 'License (default: MIT)' 21 | }, 22 | { 23 | name: 'description', 24 | description: 'Project description' 25 | } 26 | ]; 27 | -------------------------------------------------------------------------------- /tools/srcServer.js: -------------------------------------------------------------------------------- 1 | // This file configures the development web server 2 | // which supports hot reloading and synchronized testing. 3 | 4 | import browserSync from 'browser-sync'; 5 | import historyApiFallback from 'connect-history-api-fallback'; 6 | import webpack from 'webpack'; 7 | import webpackDevMiddleware from 'webpack-dev-middleware'; 8 | import webpackHotMiddleware from 'webpack-hot-middleware'; 9 | import config from '../webpack.config.dev'; // Ensure this is your webpack config file 10 | 11 | const bundler = webpack(config); 12 | 13 | // Run BrowserSync and use middleware for Hot Module Replacement 14 | browserSync({ 15 | port: 3000, 16 | ui: { 17 | port: 3001, 18 | }, 19 | server: { 20 | baseDir: 'src', 21 | 22 | middleware: [ 23 | historyApiFallback(), // Handles client-side routing 24 | 25 | webpackDevMiddleware(bundler, { 26 | publicPath: config.output.publicPath, // Public path from webpack config 27 | stats: 'minimal', // Use 'minimal' or another preset instead of noInfo/quiet 28 | writeToDisk: false, // Ensure files are not written to disk 29 | }), 30 | 31 | webpackHotMiddleware(bundler, { 32 | log: false, // Set to false to reduce the noise in your console 33 | path: '/__webpack_hmr', // The path which the middleware serves the event stream on 34 | heartbeat: 2000, // Ping interval to keep the connection alive 35 | }), 36 | ], 37 | }, 38 | 39 | files: [ 40 | 'src/*.html', // Watch .html files in the src directory 41 | ], 42 | }); 43 | -------------------------------------------------------------------------------- /tools/startMessage.js: -------------------------------------------------------------------------------- 1 | import {chalkSuccess} from './chalkConfig'; 2 | 3 | /* eslint-disable no-console */ 4 | 5 | console.log(chalkSuccess('Starting app in dev mode...')); 6 | -------------------------------------------------------------------------------- /tools/testCi.js: -------------------------------------------------------------------------------- 1 | import {spawn} from 'child_process'; 2 | 3 | const requiresHarmonyFlag = parseInt(/^v(\d+)\./.exec(process.version)[1], 10) < 7; 4 | const harmonyProxies = requiresHarmonyFlag ? ['--harmony_proxies'] : []; 5 | const args = [ 6 | ...harmonyProxies, 7 | 'node_modules/jest/bin/jest', 8 | // First two args are always node and the script running, i.e. ['node', './tools/testCi.js', ...] 9 | ...process.argv.slice(2) 10 | ]; 11 | 12 | const testCi = spawn('node', args); 13 | const consoleLogger = data => console.log(`${data}`); // eslint-disable-line no-console 14 | 15 | testCi.stdout.on('data', consoleLogger); 16 | testCi.stderr.on('data', consoleLogger); 17 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 3 | import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; 4 | import path from 'path'; 5 | 6 | export default { 7 | mode: 'development', 8 | resolve: { 9 | extensions: ['.js', '.jsx', '.json'], // Automatically resolve these extensions 10 | }, 11 | devtool: 'cheap-module-source-map', // Fastest option for dev while still providing good debugging 12 | entry: [ 13 | './src/webpack-public-path', 14 | 'webpack-hot-middleware/client?reload=true', 15 | path.resolve(__dirname, 'src/index.js'), // Defining path seems necessary for this to work consistently on Windows machines. 16 | ], 17 | target: 'web', 18 | output: { 19 | path: path.resolve(__dirname, 'dist'), 20 | publicPath: '/', 21 | filename: 'bundle.js', 22 | }, 23 | plugins: [ 24 | new webpack.DefinePlugin({ 25 | 'process.env.NODE_ENV': JSON.stringify('development'), 26 | __DEV__: true, 27 | }), 28 | new webpack.HotModuleReplacementPlugin(), 29 | new ReactRefreshWebpackPlugin(), // React Fast Refresh plugin 30 | new HtmlWebpackPlugin({ 31 | template: 'src/index.ejs', 32 | favicon: 'src/Images/favicon.png', 33 | minify: { 34 | removeComments: true, 35 | collapseWhitespace: true, 36 | }, 37 | inject: true, 38 | }), 39 | ], 40 | module: { 41 | rules: [ 42 | { 43 | test: /\.jsx?$/, 44 | exclude: /node_modules/, 45 | use: [ 46 | { 47 | loader: 'babel-loader', 48 | options: { 49 | presets: [ 50 | '@babel/preset-env', // Transpile modern JavaScript to ES5 51 | '@babel/preset-react', // Transpile JSX and other React features 52 | ], 53 | plugins: [ 54 | 'react-refresh/babel', // For React Fast Refresh 55 | '@babel/plugin-transform-react-constant-elements', // Optimization plugin for React 56 | 'babel-plugin-transform-react-remove-prop-types', // Removes propTypes in production builds 57 | '@babel/plugin-transform-class-properties', // Enable class properties syntax 58 | '@babel/plugin-transform-object-rest-spread', // Enable object rest/spread syntax 59 | ], 60 | }, 61 | }, 62 | ], 63 | }, 64 | { 65 | test: /\.eot(\?v=\d+.\d+.\d+)?$/, 66 | type: 'asset/resource', 67 | generator: { 68 | filename: 'fonts/[name].[contenthash][ext]', 69 | }, 70 | }, 71 | { 72 | test: /\.woff(2)?(\?v=\d+\.\d+\.\d+)?$/, 73 | type: 'asset/resource', 74 | generator: { 75 | filename: 'fonts/[name].[contenthash][ext]', 76 | }, 77 | }, 78 | { 79 | test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, 80 | type: 'asset/resource', 81 | generator: { 82 | filename: 'fonts/[name].[contenthash][ext]', 83 | }, 84 | }, 85 | { 86 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 87 | type: 'asset/resource', 88 | generator: { 89 | filename: 'images/[name].[contenthash][ext]', 90 | }, 91 | }, 92 | { 93 | test: /\.(jpe?g|png|gif|ico)$/i, 94 | type: 'asset/resource', 95 | generator: { 96 | filename: 'images/[name].[contenthash][ext]', 97 | }, 98 | }, 99 | { 100 | test: /(\.css|\.scss|\.sass)$/, 101 | use: [ 102 | 'style-loader', 103 | { 104 | loader: 'css-loader', 105 | options: { 106 | sourceMap: true, 107 | }, 108 | }, 109 | { 110 | loader: 'postcss-loader', 111 | options: { 112 | postcssOptions: { 113 | plugins: [ 114 | require('autoprefixer'), 115 | ], 116 | }, 117 | sourceMap: true, 118 | }, 119 | }, 120 | { 121 | loader: 'sass-loader', 122 | options: { 123 | implementation: require('sass'), // Use Dart Sass instead of node-sass 124 | sassOptions: { 125 | includePaths: [path.resolve(__dirname, 'src', 'scss')], 126 | }, 127 | sourceMap: true, 128 | }, 129 | }, 130 | ], 131 | }, 132 | ], 133 | }, 134 | }; 135 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack'; 2 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import path from 'path'; 5 | 6 | const GLOBALS = { 7 | 'process.env.NODE_ENV': JSON.stringify('production'), 8 | __DEV__: false, 9 | }; 10 | 11 | export default { 12 | mode: 'production', 13 | resolve: { 14 | extensions: ['.js', '.jsx', '.json'], // Automatically resolve these extensions 15 | }, 16 | devtool: 'source-map', // Generate source maps for better debugging in production 17 | entry: path.resolve(__dirname, 'src/index'), 18 | target: 'web', 19 | output: { 20 | path: path.resolve(__dirname, 'dist'), 21 | publicPath: '/', 22 | filename: '[name].[contenthash].js', // Use contenthash for better caching 23 | clean: true, // Clean the output directory before emit 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin(GLOBALS), 27 | new MiniCssExtractPlugin({ 28 | filename: '[name].[contenthash].css', // Use contenthash for CSS as well 29 | }), 30 | new HtmlWebpackPlugin({ 31 | template: 'src/index.ejs', 32 | favicon: 'src/Images/favicon.png', 33 | minify: { 34 | removeComments: true, 35 | collapseWhitespace: true, 36 | removeRedundantAttributes: true, 37 | useShortDoctype: true, 38 | removeEmptyAttributes: true, 39 | removeStyleLinkTypeAttributes: true, 40 | keepClosingSlash: true, 41 | minifyJS: true, 42 | minifyCSS: true, 43 | minifyURLs: true, 44 | }, 45 | inject: true, 46 | }), 47 | new webpack.optimize.AggressiveMergingPlugin(), // Optimize chunk merging 48 | ], 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.jsx?$/, 53 | exclude: /node_modules/, 54 | use: ['babel-loader'], // Use babel-loader to transpile JS/JSX files 55 | }, 56 | { 57 | test: /\.eot(\?v=\d+.\d+.\d+)?$/, 58 | type: 'asset/resource', // Handle eot files as resources 59 | generator: { 60 | filename: 'fonts/[name].[contenthash][ext]', 61 | }, 62 | }, 63 | { 64 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 65 | type: 'asset/resource', // Handle woff files as resources 66 | generator: { 67 | filename: 'fonts/[name].[contenthash][ext]', 68 | }, 69 | }, 70 | { 71 | test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, 72 | type: 'asset/resource', // Handle ttf files as resources 73 | generator: { 74 | filename: 'fonts/[name].[contenthash][ext]', 75 | }, 76 | }, 77 | { 78 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 79 | type: 'asset/resource', // Handle svg files as resources 80 | generator: { 81 | filename: 'images/[name].[contenthash][ext]', 82 | }, 83 | }, 84 | { 85 | test: /\.(jpe?g|png|gif|ico)$/i, 86 | type: 'asset/resource', // Handle image files as resources 87 | generator: { 88 | filename: 'images/[name].[contenthash][ext]', 89 | }, 90 | }, 91 | { 92 | test: /(\.css|\.scss|\.sass)$/, 93 | use: [ 94 | MiniCssExtractPlugin.loader, // Extract CSS into separate files 95 | { 96 | loader: 'css-loader', 97 | options: { 98 | sourceMap: true, 99 | importLoaders: 2, 100 | }, 101 | }, 102 | { 103 | loader: 'postcss-loader', 104 | options: { 105 | postcssOptions: { 106 | plugins: [ 107 | require('autoprefixer')(), // Automatically add vendor prefixes 108 | ], 109 | }, 110 | sourceMap: true, 111 | }, 112 | }, 113 | { 114 | loader: 'sass-loader', 115 | options: { 116 | implementation: require('sass'), // Use Dart Sass instead of node-sass 117 | sassOptions: { 118 | includePaths: [path.resolve(__dirname, 'src', 'scss')], 119 | }, 120 | sourceMap: true, 121 | }, 122 | }, 123 | ], 124 | }, 125 | ], 126 | }, 127 | }; 128 | --------------------------------------------------------------------------------