├── .babelrc ├── .gitignore ├── README.md ├── package.json ├── src ├── App.tsx ├── actions │ ├── MainActions │ │ ├── MainTypes.ts │ │ └── index.tsx │ ├── commonActionTypes.ts │ └── createRequestTypes.ts ├── index.html ├── index.tsx ├── interfaces │ └── IReducers.ts ├── pages │ ├── FrontPage │ │ ├── FrontPage.tsx │ │ └── index.ts │ └── index.ts ├── reducers │ ├── MainReducer │ │ └── index.tsx │ └── index.tsx ├── routes │ └── index.tsx ├── sagas │ └── index.ts ├── store │ └── index.ts └── styles │ ├── index.scss │ └── partials │ └── base.scss ├── tsconfig.json ├── tslint.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | # Dependencies 3 | # 4 | # When releasing a production app, you may 5 | # consider including your node_modules and 6 | # bower_components directory in your git repo, 7 | # but during development, its best to exclude it, 8 | # since different developers may be working on 9 | # different kernels, where dependencies would 10 | # need to be recompiled anyway. 11 | # 12 | # More on that here about node_modules dir: 13 | # http://www.futurealoof.com/posts/nodemodules-in-git.html 14 | # (credit Mikeal Rogers, @mikeal) 15 | # 16 | # About bower_components dir, you can see this: 17 | # http://addyosmani.com/blog/checking-in-front-end-dependencies/ 18 | # (credit Addy Osmani, @addyosmani) 19 | # 20 | ################################################ 21 | 22 | node_modules 23 | bower_components 24 | dist 25 | 26 | 27 | 28 | ################################################ 29 | # Sails.js / Waterline / Grunt 30 | # 31 | # Files generated by Sails and Grunt, or related 32 | # tasks and adapters. 33 | ################################################ 34 | .tmp 35 | dump.rdb 36 | 37 | 38 | 39 | 40 | 41 | ################################################ 42 | # Node.js / NPM 43 | # 44 | # Common files generated by Node, NPM, and the 45 | # related ecosystem. 46 | ################################################ 47 | lib-cov 48 | *.seed 49 | *.log 50 | *.out 51 | *.pid 52 | npm-debug.log 53 | 54 | 55 | 56 | 57 | 58 | ################################################ 59 | # Miscellaneous 60 | # 61 | # Common files generated by text editors, 62 | # operating systems, file systems, etc. 63 | ################################################ 64 | 65 | *~ 66 | *# 67 | .DS_STORE 68 | .netbeans 69 | nbproject 70 | .idea 71 | .node_history 72 | package-lock.json 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Quick Start 2 | 3 | ``` bash 4 | # Install dependencies 5 | npm install 6 | 7 | # Serve on localhost:3000 8 | npm start 9 | 10 | # Build for production 11 | npm run build 12 | ``` 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_webpack_starter", 3 | "version": "1.0.0", 4 | "description": "Boilerplate for React apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server --mode development --open --hot", 8 | "build": "./node_modules/.bin/webpack --mode production" 9 | }, 10 | "author": "Brad Traversy & Vladimir Paziuk", 11 | "license": "MIT", 12 | "dependencies": { 13 | "history": "^4.7.2", 14 | "react": "^16.4.1", 15 | "react-dom": "^16.4.1", 16 | "react-redux": "^5.0.7", 17 | "react-router-dom": "^4.3.1", 18 | "react-router-redux": "^5.0.0-alpha.9", 19 | "redux": "^4.0.0", 20 | "redux-saga": "^0.16.0" 21 | }, 22 | "devDependencies": { 23 | "@types/history": "^4.7.0", 24 | "@types/react": "^16.4.7", 25 | "@types/react-dom": "^16.0.6", 26 | "@types/react-redux": "^6.0.6", 27 | "@types/react-router-dom": "^4.3.0", 28 | "@types/react-router-redux": "^5.0.15", 29 | "autoprefixer": "^9.0.2", 30 | "babel-core": "^6.26.0", 31 | "babel-loader": "^7.1.4", 32 | "babel-preset-env": "^1.6.1", 33 | "babel-preset-react": "^6.24.1", 34 | "clean-webpack-plugin": "^0.1.19", 35 | "css-loader": "^0.28.11", 36 | "file-loader": "^1.1.11", 37 | "html-webpack-plugin": "^3.1.0", 38 | "node-sass": "^4.9.2", 39 | "postcss-loader": "^2.1.6", 40 | "redux-devtools-extension": "^2.13.5", 41 | "sass-loader": "^7.0.3", 42 | "style-loader": "^0.20.3", 43 | "ts-loader": "^4.4.2", 44 | "tslint": "^5.11.0", 45 | "tslint-loader": "^3.6.0", 46 | "tslint-react": "^3.6.0", 47 | "typescript": "^2.9.2", 48 | "webpack": "^4.4.0", 49 | "webpack-cli": "^2.0.13", 50 | "webpack-dev-server": "^3.1.1" 51 | }, 52 | "browserslist": { 53 | "production": [ 54 | "last 3 version" 55 | ] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { compose } from "redux"; 3 | import { withRouter } from "react-router-dom"; 4 | 5 | import routes from "./routes"; 6 | 7 | class App extends React.Component { 8 | render(): JSX.Element { 9 | console.log(this.props); 10 | 11 | return ( 12 |
13 | {routes} 14 |
15 | ); 16 | } 17 | } 18 | 19 | export default compose( 20 | withRouter, 21 | )(App); 22 | -------------------------------------------------------------------------------- /src/actions/MainActions/MainTypes.ts: -------------------------------------------------------------------------------- 1 | export const FETCH_DATA: string = "main => FETCH_DATA"; 2 | -------------------------------------------------------------------------------- /src/actions/MainActions/index.tsx: -------------------------------------------------------------------------------- 1 | import { FETCH_DATA } from "./MainTypes"; 2 | import { START, SUCCESS, ERROR } from "../commonActionTypes"; 3 | import { IActionCreator } from "../../interfaces/IReducers"; 4 | 5 | export const startFetchingData: IActionCreator = () => ({ 6 | type: FETCH_DATA + START, 7 | }); 8 | 9 | export const fetchDataSuccess: IActionCreator = (payload) => ({ 10 | type: FETCH_DATA + SUCCESS, 11 | payload, 12 | }); 13 | 14 | export const fetchDataError: IActionCreator = (payload) => ({ 15 | type: FETCH_DATA + ERROR, 16 | payload, 17 | }); 18 | 19 | export const fetchData: IActionCreator = () => ({ 20 | type: FETCH_DATA, 21 | }); 22 | -------------------------------------------------------------------------------- /src/actions/commonActionTypes.ts: -------------------------------------------------------------------------------- 1 | export const START: string = "_START"; 2 | export const STOP: string = "_STOP"; 3 | export const RESET: string = "_RESET"; 4 | export const SUCCESS: string = "_SUCCESS"; 5 | export const ERROR: string = "_ERROR"; 6 | -------------------------------------------------------------------------------- /src/actions/createRequestTypes.ts: -------------------------------------------------------------------------------- 1 | const REQUEST: string = "REQUEST"; 2 | const SUCCESS: string = "SUCCESS"; 3 | const FAILURE: string = "FAILURE"; 4 | 5 | export interface IRequestTypes { 6 | [key: string]: string; 7 | } 8 | 9 | function createRequestTypes(base: string): IRequestTypes { 10 | return [REQUEST, SUCCESS, FAILURE].reduce((acc: IRequestTypes, type) => { 11 | acc[type] = `${base}_${type}`; 12 | return acc; 13 | }, {}); 14 | } 15 | 16 | export default createRequestTypes; 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | My React App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { ConnectedRouter } from "react-router-redux"; 5 | 6 | import App from "./App"; 7 | import store, { history } from "./store"; 8 | 9 | import "./styles/index.scss"; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | , 17 | document.getElementById("app"), 18 | ); 19 | -------------------------------------------------------------------------------- /src/interfaces/IReducers.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch, AnyAction, Action } from "redux"; 2 | 3 | export type IActionHandler = (state: State, payload?: any) => State; 4 | 5 | export interface IActionHandlers { 6 | [key: string]: IActionHandler; 7 | } 8 | 9 | export interface IActionObject

{ 10 | type: string; 11 | payload?: P; 12 | } 13 | 14 | export type IActionCreator

= (payload?: P) => IActionObject

; 15 | 16 | export type ICommonAction = 17 | (data1?: A1, data2?: A2, data3?: A3, data4?: A4) => (dispatch: Dispatch, getState?: any) => void; 18 | -------------------------------------------------------------------------------- /src/pages/FrontPage/FrontPage.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { IFrontPage } from "./"; 3 | 4 | class FrontPage extends React.Component { 5 | componentDidMount(): void { 6 | this.props.fetchData(); 7 | } 8 | 9 | render(): JSX.Element { 10 | return ( 11 |

12 |

Front page

13 |
14 | ); 15 | } 16 | } 17 | 18 | export default FrontPage; 19 | -------------------------------------------------------------------------------- /src/pages/FrontPage/index.ts: -------------------------------------------------------------------------------- 1 | import { connect, MapDispatchToProps, MapStateToProps } from "react-redux"; 2 | import { compose } from "redux"; 3 | 4 | import { IGlobalStore } from "../../reducers/index"; 5 | import { IActionCreator } from "../../interfaces/IReducers"; 6 | 7 | import { fetchData } from "../../actions/MainActions/index"; 8 | 9 | import FrontPage from "./FrontPage"; 10 | 11 | // interface IOwnProps {} 12 | 13 | interface IStateProps { 14 | data: string[]; 15 | } 16 | interface IDispatchProps { 17 | fetchData: IActionCreator; 18 | } 19 | 20 | export type IFrontPage = IStateProps & IDispatchProps; 21 | // export type IFrontPage = IStateProps & IDispatchProps & IOwnProps; 22 | 23 | const mapStateToProps: MapStateToProps = ({main}) => ({ 24 | data: main.data, 25 | }); 26 | 27 | const mapDispatchToProps: MapDispatchToProps = { 28 | fetchData, 29 | }; 30 | 31 | export default compose( 32 | connect(mapStateToProps, mapDispatchToProps), 33 | )(FrontPage); 34 | -------------------------------------------------------------------------------- /src/pages/index.ts: -------------------------------------------------------------------------------- 1 | import FrontPage from "./FrontPage"; 2 | 3 | export default { 4 | FrontPage, 5 | }; 6 | -------------------------------------------------------------------------------- /src/reducers/MainReducer/index.tsx: -------------------------------------------------------------------------------- 1 | import { IActionHandler, IActionHandlers, IActionObject } from "../../interfaces/IReducers"; 2 | import { START, SUCCESS, ERROR } from "../../actions/commonActionTypes"; 3 | import { FETCH_DATA } from "../../actions/MainActions/MainTypes"; 4 | 5 | export interface IMainReducer { 6 | isLoading: boolean; 7 | data: string[]; 8 | error: string; 9 | } 10 | 11 | const initialState: IMainReducer = { 12 | isLoading: false, 13 | data: [], 14 | error: "", 15 | }; 16 | 17 | const startFetching: IActionHandler = (state) => ({ 18 | ...state, 19 | isLoading: true, 20 | }); 21 | 22 | const fetchSuccess: IActionHandler = (state, payload) => ({ 23 | ...state, 24 | isLoading: false, 25 | data: payload, 26 | }); 27 | 28 | const fetchError: IActionHandler = (state, payload) => ({ 29 | ...state, 30 | isLoading: false, 31 | data: payload, 32 | }); 33 | 34 | const reducerHandler: IActionHandlers = { 35 | [FETCH_DATA + START]: startFetching, 36 | [FETCH_DATA + SUCCESS]: fetchSuccess, 37 | [FETCH_DATA + ERROR]: fetchError, 38 | }; 39 | 40 | export default (state = initialState, action: IActionObject): IMainReducer => { 41 | const reducer: IActionHandler = reducerHandler[action.type]; 42 | 43 | return reducer ? reducer(state, action.payload) : state; 44 | }; 45 | -------------------------------------------------------------------------------- /src/reducers/index.tsx: -------------------------------------------------------------------------------- 1 | import { Reducer, combineReducers } from "redux"; 2 | import { routerReducer, RouterState } from "react-router-redux"; 3 | import { IActionObject } from "../interfaces/IReducers"; 4 | 5 | // Reducers 6 | import main, { IMainReducer } from "./MainReducer"; 7 | 8 | // Reducers Interfaces 9 | export interface IGlobalStore { 10 | router: RouterState; 11 | main: IMainReducer; 12 | } 13 | 14 | const reducer: Reducer = combineReducers({ 15 | router: routerReducer, 16 | main, 17 | }); 18 | 19 | export default reducer; 20 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Route, Switch } from "react-router-dom"; 3 | 4 | import pages from "../pages"; 5 | 6 | const routes: JSX.Element = ( 7 | 8 | 9 | 10 | ); 11 | 12 | export default routes; 13 | -------------------------------------------------------------------------------- /src/sagas/index.ts: -------------------------------------------------------------------------------- 1 | import { SagaIterator } from "redux-saga"; 2 | import { put, takeEvery, all, call, take } from "redux-saga/effects"; 3 | 4 | import { startFetchingData, fetchDataSuccess, fetchDataError } from "../actions/MainActions"; 5 | import { FETCH_DATA } from "../actions/MainActions/MainTypes"; 6 | 7 | function* watchFetchData(): SagaIterator { 8 | while (true) { 9 | yield take(FETCH_DATA); 10 | yield put(startFetchingData()); 11 | yield put(fetchDataSuccess([])); 12 | } 13 | } 14 | 15 | // TODO: Define type 16 | function* rootSaga(): any { 17 | yield all([ 18 | watchFetchData(), 19 | ]); 20 | } 21 | 22 | export default rootSaga; 23 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, Store, Middleware } from "redux"; 2 | import createSagaMiddleware, { SagaMiddleware } from "redux-saga"; 3 | import { routerMiddleware } from "react-router-redux"; 4 | 5 | import { composeWithDevTools } from "redux-devtools-extension/developmentOnly"; 6 | import { History, createBrowserHistory } from "history"; 7 | 8 | import reducer from "../reducers"; 9 | import saga from "../sagas"; 10 | 11 | export const history: History = createBrowserHistory(); 12 | 13 | const sagaMiddleware: SagaMiddleware = createSagaMiddleware(); 14 | const middleware: Middleware[] = [ routerMiddleware(history), sagaMiddleware ]; 15 | 16 | const store: Store = createStore(reducer, composeWithDevTools(applyMiddleware(...middleware))); 17 | 18 | sagaMiddleware.run(saga); 19 | 20 | export default store; 21 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "./partials/base"; 2 | 3 | -------------------------------------------------------------------------------- /src/styles/partials/base.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: #ccc; 3 | display: flex; 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist/", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true, /* Enable all strict type-checking options. */ 25 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react"], 3 | "rules": { 4 | "member-access": [true, "no-public"], 5 | "ordered-imports": false, 6 | "object-literal-sort-keys": false, 7 | "jsx-no-lambda": false, 8 | "typedef": [ 9 | true, 10 | "call-signature", 11 | "parameter", 12 | "property-declaration", 13 | "variable-declaration", 14 | "member-variable-declaration" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 3 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 4 | 5 | const settings = { 6 | distPath: path.join(__dirname, "dist"), 7 | srcPath: path.join(__dirname, "src") 8 | }; 9 | 10 | function srcPathExtend(subpath) { 11 | return path.join(settings.srcPath, subpath) 12 | } 13 | 14 | module.exports = (env, options) => { 15 | const isDevMode = options.mode === "development"; 16 | 17 | return { 18 | devtool: isDevMode ? "source-map" : false, 19 | resolve: { 20 | extensions: [".ts", ".tsx", ".js"], 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.tsx?$/, 26 | use: ["babel-loader", "ts-loader", "tslint-loader"] 27 | }, 28 | { 29 | test: /\.scss$/, 30 | use: [ 31 | "style-loader", 32 | { 33 | loader: "css-loader", 34 | options: { 35 | sourceMap: isDevMode 36 | } 37 | }, 38 | { 39 | loader: "postcss-loader", 40 | options: { 41 | plugins: [ 42 | require("autoprefixer")() 43 | ], 44 | sourceMap: isDevMode 45 | } 46 | }, 47 | { 48 | loader: "sass-loader", 49 | options: { 50 | sourceMap: isDevMode 51 | } 52 | } 53 | ] 54 | }, 55 | { 56 | test: /\.(ttf|eot|woff|woff2)$/, 57 | use: { 58 | loader: "file-loader", 59 | options: { 60 | name: "fonts/[name].[ext]", 61 | }, 62 | }, 63 | }, 64 | { 65 | test: /\.(jpe?g|png|gif|svg|ico)$/i, 66 | use: [ 67 | { 68 | loader: "file-loader", 69 | options: { 70 | outputPath: "assets/" 71 | } 72 | } 73 | ] 74 | } 75 | ] 76 | }, 77 | plugins: [ 78 | new CleanWebpackPlugin([settings.distPath], { 79 | verbose: true 80 | }), 81 | new HtmlWebpackPlugin({ 82 | template: srcPathExtend("index.html") 83 | }) 84 | ] 85 | }; 86 | }; 87 | --------------------------------------------------------------------------------