├── .env.production ├── .env ├── src ├── main.scss ├── plugins │ ├── index.js │ └── exception-handler.js ├── app │ ├── users │ │ ├── shared │ │ │ ├── config │ │ │ │ ├── index.js │ │ │ │ └── api-constants.js │ │ │ ├── services │ │ │ │ ├── index.js │ │ │ │ └── users │ │ │ │ │ └── users.js │ │ │ └── state │ │ │ │ ├── index.js │ │ │ │ └── users-data.js │ │ ├── index.js │ │ ├── users-state.js │ │ ├── user-item │ │ │ └── user-item.vue │ │ ├── users.vue │ │ ├── users-routes.js │ │ └── user-list │ │ │ └── user-list.vue │ ├── app.vue │ ├── shared │ │ ├── services │ │ │ ├── index.js │ │ │ ├── key-reflactor │ │ │ │ └── key-reflactor.js │ │ │ ├── app-storage │ │ │ │ └── app-storage.js │ │ │ ├── http-client │ │ │ │ └── http-client.js │ │ │ └── app-logger │ │ │ │ └── app-logger.js │ │ └── components │ │ │ ├── index.js │ │ │ ├── page-not-found │ │ │ └── page-not-found.vue │ │ │ ├── app-header │ │ │ └── app-header.vue │ │ │ └── error-boundary │ │ │ └── error-boundary.vue │ ├── app-state.js │ └── app-routes.js ├── assets │ └── logo.png ├── environment │ └── environment.js └── main.js ├── babel.config.js ├── public ├── favicon.ico ├── mock-data │ └── users.json └── index.html ├── .travis.yml ├── .gitignore ├── LICENSE ├── README.md └── package.json /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=/ -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VUE_APP_API_BASE_URL=http://localhost:8080/ -------------------------------------------------------------------------------- /src/main.scss: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | export * from './exception-handler'; 2 | -------------------------------------------------------------------------------- /src/app/users/shared/config/index.js: -------------------------------------------------------------------------------- 1 | export * from './api-constants'; 2 | -------------------------------------------------------------------------------- /src/app/users/shared/services/index.js: -------------------------------------------------------------------------------- 1 | export * from './users/users'; 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunredhu/vuejs_boilerplate/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arunredhu/vuejs_boilerplate/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /src/app/users/shared/config/api-constants.js: -------------------------------------------------------------------------------- 1 | export const apiConstants = { 2 | users: 'mock-data/users.json' 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/users/shared/state/index.js: -------------------------------------------------------------------------------- 1 | export { default as usersData } from './users-data'; 2 | export * from './users-data'; 3 | -------------------------------------------------------------------------------- /src/app/users/index.js: -------------------------------------------------------------------------------- 1 | export { default as usersRoutes } from './users-routes'; 2 | export { default as usersState } from './users-state'; 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | cache: 5 | directories: 6 | - 'node_modules' 7 | script: 8 | - npm run build 9 | -------------------------------------------------------------------------------- /src/app/users/users-state.js: -------------------------------------------------------------------------------- 1 | import { usersData } from './shared/state'; 2 | 3 | export default { 4 | modules: { 5 | usersData 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/app.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 15 | -------------------------------------------------------------------------------- /src/app/shared/services/index.js: -------------------------------------------------------------------------------- 1 | export * from './http-client/http-client'; 2 | export * from './app-logger/app-logger'; 3 | export * from './app-storage/app-storage'; 4 | export * from './key-reflactor/key-reflactor'; 5 | -------------------------------------------------------------------------------- /src/environment/environment.js: -------------------------------------------------------------------------------- 1 | const { VUE_APP_API_BASE_URL, NODE_ENV = '' } = process.env; 2 | 3 | const environment = NODE_ENV.toLowerCase(); 4 | const apiBaseUrl = VUE_APP_API_BASE_URL; 5 | 6 | export { environment, apiBaseUrl }; 7 | -------------------------------------------------------------------------------- /src/app/users/user-item/user-item.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 13 | -------------------------------------------------------------------------------- /src/app/shared/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as AppHeader } from './app-header/app-header.vue'; 2 | export { 3 | default as AppPageNotFound 4 | } from './page-not-found/page-not-found.vue'; 5 | export { default as ErrorBoundary } from './error-boundary/error-boundary.vue'; 6 | -------------------------------------------------------------------------------- /public/mock-data/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Arun Kumar", 5 | "age": "25", 6 | "profession": "Software developer" 7 | }, 8 | { 9 | "id": 2, 10 | "name": "Deepak Jha", 11 | "age": "32", 12 | "profession": "Application Architect" 13 | } 14 | ] 15 | -------------------------------------------------------------------------------- /src/app/app-state.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import { usersState } from './users'; 5 | 6 | Vue.use(Vuex); 7 | 8 | export default new Vuex.Store({ 9 | state: {}, 10 | mutations: {}, 11 | actions: {}, 12 | modules: { 13 | usersState 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /src/app/users/shared/services/users/users.js: -------------------------------------------------------------------------------- 1 | import { httpClient } from '@/app/shared/services'; 2 | 3 | import { apiConstants } from '../../config'; 4 | 5 | const fetchUsers = () => { 6 | const url = apiConstants.users; 7 | 8 | return httpClient.get(url).then(res => res.data); 9 | }; 10 | 11 | export { fetchUsers }; 12 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import './plugins'; 4 | 5 | import App from './app/app.vue'; 6 | 7 | import './main.scss'; 8 | import router from './app/app-routes'; 9 | import store from './app/app-state'; 10 | 11 | Vue.config.productionTip = false; 12 | 13 | new Vue({ 14 | router, 15 | store, 16 | render: h => h(App) 17 | }).$mount('#app'); 18 | -------------------------------------------------------------------------------- /src/app/users/users.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/plugins/exception-handler.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | import { logger } from '@/app/shared/services/app-logger/app-logger'; 4 | 5 | Vue.config.errorHandler = (err, vm, info) => { 6 | logger.logToServer({ err, vm, info }); 7 | }; 8 | 9 | window.onerror = function(message, source, lineno, colno, error) { 10 | logger.logToServer({ message, source, lineno, colno, error }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/app/shared/components/page-not-found/page-not-found.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 13 | 14 | 22 | 23 | -------------------------------------------------------------------------------- /src/app/users/users-routes.js: -------------------------------------------------------------------------------- 1 | import AppUsers from './users.vue'; 2 | 3 | import AppUserList from './user-list/user-list.vue'; 4 | 5 | const userRoutes = [ 6 | { 7 | path: '/users', 8 | component: AppUsers, 9 | children: [ 10 | { 11 | path: 'user-list', 12 | name: 'user-list', 13 | component: AppUserList 14 | }, 15 | { 16 | path: '', 17 | redirect: { name: 'user-list' } 18 | } 19 | ] 20 | } 21 | ]; 22 | 23 | export default userRoutes; 24 | -------------------------------------------------------------------------------- /src/app/app-routes.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | 4 | import { usersRoutes } from './users'; 5 | 6 | import { AppPageNotFound } from './shared/components'; 7 | 8 | Vue.use(Router); 9 | 10 | const appRoutes = [ 11 | { 12 | path: '/', 13 | redirect: '/users' 14 | }, 15 | { 16 | path: '*', 17 | name: 'page-not-found', 18 | component: AppPageNotFound 19 | } 20 | ]; 21 | 22 | const routes = [...usersRoutes, ...appRoutes]; 23 | 24 | export default new Router({ 25 | mode: 'history', 26 | routes 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/shared/components/app-header/app-header.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/app/shared/services/key-reflactor/key-reflactor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description This method is responsile for creating the object with mirror key and values 3 | * and also add prefix to values if available 4 | * @param {Array} arr Array of strings 5 | * @param {string} prefix prefix 6 | * @returns {Object} object with mirror keys generated from the array of strings 7 | */ 8 | export const reflectKeys = (arr = [], prefix) => 9 | arr.reduce((obj, key) => { 10 | const value = prefix ? prefix + ' ' + key : key; 11 | obj[key] = value; 12 | 13 | return obj; 14 | }, {}); 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vuejs_boilerplate 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/shared/components/error-boundary/error-boundary.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 36 | 37 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/app/users/user-list/user-list.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 arunredhu 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/arunredhu/orders-app.svg?branch=master)](https://travis-ci.org/arunredhu/vuejs_boilerplate) 2 | 3 | # Vue.js boilerplate 4 | 5 | This boilerplate is built on the top of Vue CLI 3.0. This provides an architecture which helps to write a flexible & modular large scale appliction with Vue.js 6 | 7 | # Description 8 | 9 | Below are the series of articles explaining the details of the architecture 10 | 11 | - [Architect a large scale Vue.js Application](http://bit.ly/2X1aaTf) 12 | - [Handle HTTP calls in a large scale Vue.js Application](http://bit.ly/2MjNL2X) 13 | - [Architect state management in a large scale Vue.js application](http://bit.ly/2HN8zu6) 14 | - [Error/Exception handling in Vue.js application](http://bit.ly/2wVK1Km) 15 | 16 | ## Project setup 17 | 18 | ``` 19 | npm install 20 | ``` 21 | 22 | ### Compiles and hot-reloads for development 23 | 24 | ``` 25 | npm run serve 26 | ``` 27 | 28 | ### Compiles and minifies for production 29 | 30 | ``` 31 | npm run build 32 | ``` 33 | 34 | ### Run your tests 35 | 36 | ``` 37 | npm run test 38 | ``` 39 | 40 | ### Lints and fixes files 41 | 42 | ``` 43 | npm run lint 44 | ``` 45 | 46 | ### Customize configuration 47 | 48 | See [Configuration Reference](https://cli.vuejs.org/config/). 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuejs_boilerplate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Arun Redhu", 6 | "homepage": "https://github.com/arunredhu/vuejs_boilerplate", 7 | "scripts": { 8 | "serve": "vue-cli-service serve", 9 | "build": "vue-cli-service build", 10 | "lint": "vue-cli-service lint" 11 | }, 12 | "dependencies": { 13 | "axios": "^0.18.0", 14 | "core-js": "^2.6.5", 15 | "vue": "^2.6.10", 16 | "vue-router": "^3.0.3", 17 | "vuex": "^3.0.1" 18 | }, 19 | "devDependencies": { 20 | "@vue/cli-plugin-babel": "^3.7.0", 21 | "@vue/cli-plugin-eslint": "^3.7.0", 22 | "@vue/cli-service": "^3.7.0", 23 | "babel-eslint": "^10.0.1", 24 | "eslint": "^5.16.0", 25 | "eslint-plugin-vue": "^5.0.0", 26 | "node-sass": "^4.12.0", 27 | "sass-loader": "^7.1.0", 28 | "vue-template-compiler": "^2.5.21" 29 | }, 30 | "eslintConfig": { 31 | "root": true, 32 | "env": { 33 | "node": true 34 | }, 35 | "extends": [ 36 | "plugin:vue/essential", 37 | "eslint:recommended" 38 | ], 39 | "rules": {}, 40 | "parserOptions": { 41 | "parser": "babel-eslint" 42 | } 43 | }, 44 | "postcss": { 45 | "plugins": { 46 | "autoprefixer": {} 47 | } 48 | }, 49 | "browserslist": [ 50 | "> 1%", 51 | "last 2 versions" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /src/app/shared/services/app-storage/app-storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App Storage class 3 | * @description This will be responsible for storing data into the application. 4 | * Commonly, people use LocalStorage or SessionStorage. This is just a wrapper over them 5 | * because to restrict the usage of Global window storage throughtout the application 6 | * Default, this is just using the LocalStorage 7 | */ 8 | export class AppStorage { 9 | constructor(storage) { 10 | this.storage = storage || window.localStorage; 11 | 12 | /** Is storage is supported or not */ 13 | if (!this.isSupported()) { 14 | throw new Error('Storage is not supported by browser!'); 15 | } 16 | } 17 | 18 | setItem(key, value) { 19 | this.storage.setItem(key, JSON.stringify(value)); 20 | } 21 | 22 | getItem(key) { 23 | return JSON.parse(this.storage.getItem(key)); 24 | } 25 | 26 | removeItem(key) { 27 | this.storage.removeItem(key); 28 | } 29 | 30 | clear() { 31 | this.storage.clear(); 32 | } 33 | 34 | /** 35 | * @description Check for storage support 36 | * @returns {boolean} supported 37 | * */ 38 | isSupported() { 39 | let supported = true; 40 | 41 | if (!this.storage) { 42 | supported = false; 43 | } 44 | 45 | return supported; 46 | } 47 | } 48 | 49 | /** 50 | * Creating the instance of storage. Default will be localStorage 51 | * but if you want to create instance for session storage then pass window.sessionStorage as parameter 52 | */ 53 | const appLocalStorage = new AppStorage(); 54 | const appSessionStorage = new AppStorage(window.sessionStorage); 55 | 56 | export { appLocalStorage, appSessionStorage }; 57 | -------------------------------------------------------------------------------- /src/app/shared/services/http-client/http-client.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { apiBaseUrl } from '@/environment/environment'; 4 | 5 | /** 6 | * Axios basic configuration 7 | * Some general configuration can be added like timeout, headers, params etc. More details can be found on https://github.com/axios/axios 8 | * */ 9 | const config = { 10 | baseURL: apiBaseUrl 11 | }; 12 | 13 | /** 14 | * Creating the instance of Axios 15 | * It is because, in large scale application we may need to consume APIs from more than single server, 16 | * So, may need to create multiple http client with different config 17 | * Only this client will be used rather than axios in the application 18 | **/ 19 | const httpClient = axios.create(config); 20 | 21 | /** 22 | * Auth interceptors 23 | * @description Configuration related to AUTH token can be done in interceptors. 24 | * Currenlty it is just doing nothing but idea to to show the capability of axios and its interceptors 25 | * In future, interceptors can be created into separate files and consumed into multiple http clients 26 | * @param {*} config 27 | */ 28 | const authInterceptor = config => { 29 | /** add auth token */ 30 | return config; 31 | }; 32 | 33 | const loggerInterceptor = config => { 34 | /** Add logging here */ 35 | return config; 36 | }; 37 | 38 | /** Adding the request interceptors */ 39 | httpClient.interceptors.request.use(authInterceptor); 40 | httpClient.interceptors.request.use(loggerInterceptor); 41 | 42 | /** Adding the response interceptors */ 43 | httpClient.interceptors.response.use( 44 | response => { 45 | return response; 46 | }, 47 | error => { 48 | /** Do something with response error */ 49 | return Promise.reject(error); 50 | } 51 | ); 52 | 53 | export { httpClient }; 54 | -------------------------------------------------------------------------------- /src/app/shared/services/app-logger/app-logger.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console: ["off"] */ 2 | import { environment } from '@/environment/environment'; 3 | 4 | /** 5 | * @description Logger class 6 | * This is responsible for logging of all kind of stuff in the application 7 | * Default, we are using the console api for logging and this provides the basic level of logging such as 8 | * you can use the available method of console in developement but in production these will be replaced with empty methods 9 | * This can be extended with the help of adding Log level functionality 10 | */ 11 | class AppLogger { 12 | /** 13 | * @constructor AppLogger 14 | */ 15 | constructor() { 16 | /** Initializing the configuration of logger */ 17 | this.initLogger(); 18 | } 19 | 20 | /** 21 | * @description Initializing the configuration such as if environment is production then all log method will be replaced with empty methods 22 | * except logToServer, which will be responsible for logging the important stuff on server 23 | */ 24 | initLogger() { 25 | /** Checking the environment */ 26 | if (environment !== 'production') { 27 | this.log = console.log.bind(console); 28 | 29 | this.debug = console.debug.bind(console); 30 | 31 | this.info = console.info.bind(console); 32 | 33 | this.warn = console.warn.bind(console); 34 | 35 | this.error = console.error.bind(console); 36 | 37 | this.logToServer = this.error; 38 | } else { 39 | /** In case of production replace the functions definition */ 40 | this.log = this.debug = this.info = this.warn = this.error = () => {}; 41 | 42 | this.logToServer = err => { 43 | /** temp added to print in the console during production */ 44 | console.error(err); // 45 | /** TODO: API integration for logging to server or any custom logic in case of Production environment */ 46 | }; 47 | } 48 | } 49 | } 50 | 51 | /** Creating the instance of logger */ 52 | const logger = new AppLogger(); 53 | 54 | export { logger }; 55 | -------------------------------------------------------------------------------- /src/app/users/shared/state/users-data.js: -------------------------------------------------------------------------------- 1 | import { reflectKeys } from '@/app/shared/services'; 2 | 3 | import { fetchUsers } from '../services'; 4 | 5 | /** Initial state */ 6 | const initialState = { 7 | loading: false, 8 | data: null, 9 | error: null 10 | }; 11 | 12 | /** Prefix for mutation types and actiontypes */ 13 | const namespacedPrefix = '[USERS]'; 14 | 15 | /** 16 | * Mutation types 17 | */ 18 | const mutationTypes = reflectKeys( 19 | [ 20 | 'USERS_DATA_SUCCESS', 21 | 'USERS_DATA_REQUEST', 22 | 'USERS_DATA_ERROR', 23 | 'USERS_DATA_RESET' 24 | ], 25 | namespacedPrefix 26 | ); 27 | 28 | const { 29 | USERS_DATA_ERROR, 30 | USERS_DATA_REQUEST, 31 | USERS_DATA_RESET, 32 | USERS_DATA_SUCCESS 33 | } = mutationTypes; 34 | 35 | /** 36 | * Users data mutations 37 | */ 38 | const mutations = { 39 | /** user data request */ 40 | [USERS_DATA_REQUEST](state) { 41 | Object.assign(state, { loading: true, error: null }); 42 | }, 43 | 44 | /** user data success */ 45 | [USERS_DATA_SUCCESS](state, payload) { 46 | Object.assign(state, { loading: false, data: payload }); 47 | }, 48 | 49 | /** user data error */ 50 | [USERS_DATA_ERROR](state, payload) { 51 | Object.assign(state, { 52 | loading: false, 53 | data: null, 54 | error: payload || true 55 | }); 56 | }, 57 | 58 | /** reset user data */ 59 | [USERS_DATA_RESET](state) { 60 | Object.assign(state, ...initialState); 61 | } 62 | }; 63 | 64 | /** Actions types constants */ 65 | export const actionsTypes = reflectKeys(['FETCH_USER_DATA'], namespacedPrefix); 66 | 67 | /** 68 | * Users data actions 69 | */ 70 | const actions = { 71 | /** fetch user data */ 72 | async [actionsTypes.FETCH_USER_DATA](context, authCred) { 73 | context.commit(USERS_DATA_REQUEST); 74 | 75 | const result = await fetchUsers(authCred).catch(e => { 76 | context.commit(USERS_DATA_ERROR, e); 77 | }); 78 | 79 | if (result) { 80 | context.commit(USERS_DATA_SUCCESS, result); 81 | } 82 | 83 | return result; 84 | } 85 | }; 86 | 87 | export default { 88 | mutations, 89 | actions, 90 | state: initialState 91 | }; 92 | --------------------------------------------------------------------------------