├── .gitignore ├── src ├── assets │ └── stylesheets │ │ └── base.scss ├── components │ └── App │ │ ├── __tests__ │ │ └── index.js │ │ └── index.js └── index.js ├── __tests__ ├── cssStub.js └── config.js ├── .babelrc ├── dist └── index.html ├── webpack.prod.config.js ├── server.js ├── webpack.config.js ├── package.json ├── README.md └── .eslintrc /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *npm-debug.log 3 | *.DS_Store 4 | dist/bundle.js 5 | coverage 6 | -------------------------------------------------------------------------------- /src/assets/stylesheets/base.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: helvetica; 3 | font-weight: 400; 4 | } 5 | -------------------------------------------------------------------------------- /__tests__/cssStub.js: -------------------------------------------------------------------------------- 1 | // This is a stub file to keep Jest from yelling 2 | // about the CSS module imports in our components. 3 | export default {}; 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | targets: { node: "8.11.4" }, 5 | }], 6 | "stage-0", 7 | "react" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Node + React Starter 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /__tests__/config.js: -------------------------------------------------------------------------------- 1 | // Enzyme needs this Adapter or it throws an error in the tests. 2 | // You can read more here: http://airbnb.io/enzyme/docs/installation/index.html 3 | 4 | import { configure } from 'enzyme'; 5 | import Adapter from 'enzyme-adapter-react-16'; 6 | 7 | configure({ adapter: new Adapter() }); 8 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const config = require('./webpack.config.js'); 2 | const webpack = require('webpack'); 3 | 4 | config.plugins.push( 5 | new webpack.DefinePlugin({ 6 | "process.env": { 7 | "NODE_ENV": JSON.stringify("production") 8 | } 9 | }) 10 | ); 11 | 12 | config.plugins.push( 13 | new webpack.optimize.UglifyJsPlugin({ 14 | compress: { 15 | warnings: false 16 | } 17 | }) 18 | ); 19 | 20 | module.exports = config; 21 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const app = express(); 4 | const PORT = process.env.PORT || 8080; 5 | 6 | app.use(express.static(path.join(__dirname, 'dist'))); 7 | 8 | app.get('/', function(request, response) { 9 | response.sendFile(__dirname + '/dist/index.html'); 10 | }); 11 | 12 | app.listen(PORT, error => ( 13 | error 14 | ? console.error(error) 15 | : console.info(`Listening on port ${PORT}. Visit http://localhost:${PORT}/ in your browser.`) 16 | )); 17 | -------------------------------------------------------------------------------- /src/components/App/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | 4 | import '__tests__/config'; 5 | 6 | import App from '../index'; 7 | 8 | test('it should render the App component', () => { 9 | const wrapper = shallow( 10 | 11 | ); 12 | 13 | expect(wrapper.find('h1').text()).toEqual('Hello, World!'); 14 | }); 15 | 16 | it('should run a solid smoke test', () => { 17 | // this test is intentionally failing 18 | expect(true).toEqual(false); 19 | }); 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { ApolloClient } from 'apollo-client'; 4 | import { HttpLink } from 'apollo-link-http'; 5 | import { InMemoryCache } from 'apollo-cache-inmemory'; 6 | import { ApolloProvider } from 'react-apollo'; 7 | 8 | import App from './components/App'; 9 | 10 | const client = new ApolloClient({ 11 | link: new HttpLink({ 12 | uri: 'https://api.github.com/graphql', 13 | headers: { 14 | authorization: 'bearer YOUR-TOKEN-HERE' 15 | } 16 | }), 17 | cache: new InMemoryCache() 18 | }); 19 | 20 | render( 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const path = require('path'); 3 | // React v.16 uses some newer JS functionality, so to ensure everything 4 | // works across all browsers, we're adding babel-polyfill here. 5 | require('babel-polyfill'); 6 | 7 | module.exports = { 8 | entry: [ 9 | './src/index' 10 | ], 11 | module: { 12 | loaders: [ 13 | { test: /\.js?$/, loader: 'babel-loader', exclude: /node_modules/ }, 14 | { test: /\.s?css$/, loader: 'style-loader!css-loader!sass-loader' }, 15 | ] 16 | }, 17 | resolve: { 18 | modules: [ 19 | path.resolve('./'), 20 | path.resolve('./node_modules'), 21 | ], 22 | extensions: ['.js','.scss'], 23 | }, 24 | output: { 25 | path: path.join(__dirname, '/dist'), 26 | publicPath: '/', 27 | filename: 'bundle.js' 28 | }, 29 | devtool: 'cheap-eval-source-map', 30 | devServer: { 31 | contentBase: './dist', 32 | hot: true 33 | }, 34 | plugins: [ 35 | new webpack.optimize.OccurrenceOrderPlugin(), 36 | new webpack.HotModuleReplacementPlugin(), 37 | new webpack.NoEmitOnErrorsPlugin() 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/App/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withApollo } from 'react-apollo'; 3 | import gql from 'graphql-tag'; 4 | 5 | import 'src/assets/stylesheets/base.scss'; 6 | 7 | function packageQuery(name, owner) { 8 | return gql` 9 | query { 10 | repository(name: "${name}", owner: "${owner}") { 11 | object(expression: "master:package.json") { 12 | ... on Blob { 13 | text 14 | } 15 | } 16 | } 17 | } 18 | `; 19 | } 20 | 21 | class App extends Component { 22 | state = { 23 | owner: 'sendgrid', 24 | repo: '', 25 | dependencies: {} 26 | }; 27 | 28 | handleChange = (e) => { 29 | const { id, value } = e.target; 30 | return this.setState({ [id]: value }); 31 | }; 32 | 33 | handleSubmit = async (e) => { 34 | e.preventDefault(); 35 | const response = await this.props.client.query({ 36 | query: packageQuery(this.state.repo, this.state.owner) 37 | }); 38 | const { repository } = response.data; 39 | const parsed = JSON.parse(repository.object.text); 40 | return this.setState({ dependencies: parsed.dependencies }); 41 | }; 42 | 43 | render() { 44 | return ( 45 |
46 |

dep-check

47 |
48 |
49 | 50 | 51 |
52 |
53 | 54 | 55 | 56 |
57 |
58 |
    59 | {Object.keys(this.state.dependencies).map((dep, i) => ( 60 |
  • 61 | {dep}: {this.state.dependencies[dep]} 62 |
  • 63 | ))} 64 |
65 |
66 | ); 67 | } 68 | } 69 | 70 | export default withApollo(App); 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-node-starter", 3 | "version": "1.1.0", 4 | "description": "an example for deploying a React + NodeJS app to Heroku", 5 | "main": "server.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.prod.config.js", 8 | "dev": "webpack-dev-server --hot --inline", 9 | "lint": "eslint src/**", 10 | "lint:watch": "esw -w src/**", 11 | "review": "npm run lint && npm test", 12 | "start": "npm run build && NODE_ENV=production node server.js", 13 | "test": "jest src/**", 14 | "test:watch": "jest src/** --watch", 15 | "test:watchAll": "jest src/** --watchAll", 16 | "test:coverage:report": "open coverage/lcov-report/index.html" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "dependencies": { 21 | "apollo-cache-inmemory": "^1.2.9", 22 | "apollo-client": "^2.4.1", 23 | "apollo-link-http": "^1.5.4", 24 | "axios": "^0.18.0", 25 | "babel-core": "^6.26.0", 26 | "babel-loader": "^7.1.2", 27 | "babel-plugin-transform-regenerator": "^6.26.0", 28 | "babel-polyfill": "^6.26.0", 29 | "babel-preset-env": "^1.6.0", 30 | "babel-preset-react": "^6.24.1", 31 | "babel-preset-stage-0": "^6.24.1", 32 | "css-loader": "^0.28.7", 33 | "enzyme-adapter-react-16": "^1.0.0", 34 | "express": "^4.15.5", 35 | "graphql": "^14.0.2", 36 | "graphql-tag": "^2.9.2", 37 | "node-sass": "^4.5.3", 38 | "prop-types": "^15.6.0", 39 | "raf": "^3.3.2", 40 | "react": "^16.5.0", 41 | "react-apollo": "^2.1.11", 42 | "react-dom": "^16.5.0", 43 | "sass-loader": "^6.0.6", 44 | "style-loader": "^0.18.2", 45 | "webpack": "^3.6.0" 46 | }, 47 | "devDependencies": { 48 | "babel-eslint": "^8.0.1", 49 | "enzyme": "^3.0.0", 50 | "eslint": "^4.7.2", 51 | "eslint-loader": "^1.9.0", 52 | "eslint-plugin-jsx-a11y": "^6.0.2", 53 | "eslint-plugin-react": "^7.4.0", 54 | "eslint-watch": "^3.1.2", 55 | "jest": "^21.2.0", 56 | "react-addons-test-utils": "^15.6.2", 57 | "react-test-renderer": "^16.0.0", 58 | "webpack-dev-middleware": "^1.12.0", 59 | "webpack-dev-server": "^2.8.2", 60 | "webpack-hot-middleware": "^2.19.1" 61 | }, 62 | "jest": { 63 | "collectCoverage": true, 64 | "collectCoverageFrom": [ 65 | "src/**", 66 | "!src/index.js" 67 | ], 68 | "coverageThreshold": { 69 | "global": { 70 | "branches": 90, 71 | "functions": 90, 72 | "lines": 90, 73 | "statements": 90 74 | } 75 | }, 76 | "moduleDirectories": [ 77 | "node_modules", 78 | "./" 79 | ], 80 | "moduleNameMapper": { 81 | "^.+.(css|scss|sass)$": "/__tests__/cssStub.js" 82 | }, 83 | "setupFiles": [ 84 | "raf/polyfill" 85 | ] 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Node Starter 2 | _for [Heroku](https://www.heroku.com/) deployment_ 3 | 4 | ## OVERVIEW 5 | This is a simple starter to get you up and running for React projects. This is intended to provide: 6 | 7 | * a lightweight Webpack config (for development and production) 8 | * some helpful tooling for development workflow 9 | * a similar setup to what you'll see in the wild 10 | * Heroku-ready deployment setup 11 | 12 | ## UP & RUNNING 13 | Install dependencies: 14 | ``` 15 | $ npm install 16 | ``` 17 | _or_ 18 | ``` 19 | $ yarn 20 | ``` 21 | 22 | Fire up a development server: 23 | ``` 24 | $ yarn dev 25 | ``` 26 | 27 | Once the server is running, you can visit `http://localhost:8080/` 28 | 29 | ## Linting 30 | _This assumes you have eslint and eslint-watch installed. If you don't, run the following:_ 31 | ``` 32 | $ npm i -g eslint eslint-watch 33 | ``` 34 | or if you need permissions: 35 | ``` 36 | $ sudo npm i -g eslint eslint-watch 37 | ``` 38 | 39 | To run the linter once: 40 | ``` 41 | $ yarn lint 42 | ``` 43 | 44 | To run the watch task: 45 | ``` 46 | $ yarn lint:watch 47 | ``` 48 | 49 | ## Testing 50 | An initial test suite has been setup with two tests (one passing and one intentionally failing). 51 | We're using Jest and Enzyme for our test setup. The basic test setup lives in `./__tests__`. 52 | The main configuration for Jest lives at the bottom of `package.json`. I've also added a few 53 | handy scripts, which I've listed below. Jest also gives us a test coverage tool for free, so I've added that too. The setup is at the bottom of `package.json`. Everything is set to 90% coverage, but your welcome to update that to whatever you'd like. 54 | 55 | To run the tests once: 56 | ``` 57 | $ yarn test 58 | ``` 59 | 60 | To run the watch script (for only relevant test files) 61 | ``` 62 | $ yarn test:watch 63 | ``` 64 | 65 | To run the watch script (for all test files) 66 | ``` 67 | $ yarn test:watchAll 68 | ``` 69 | 70 | To view the coverage report: 71 | ``` 72 | $ yarn test:coverage:report 73 | ``` 74 | 75 | ## Review 76 | If you'd like to run the linter and tests at once (this is a nice check before pushing to Github or deploys), you can run: 77 | 78 | ``` 79 | $ yarn review 80 | ``` 81 | 82 | ## Production Build 83 | 84 | To build your production assets and run the server: 85 | ``` 86 | $ yarn start 87 | ``` 88 | 89 | ## CHANGELOG 90 | **v2.0.0** 91 | This app has been updated to use React v.16.0.0! 🎉 92 | 93 | Major Changes: 94 | * Updates React and ReactDOM to v16 95 | * Replaces Mocha with Jest 96 | * Adds `babel-polyfill` and updates Babel config 97 | * Colocates tests with components 98 | 99 | Minor Changes: 100 | * Updates all other dependencies to latest 101 | * Allows absolute import paths 102 | * Adds new test scripts 103 | * Adds test coverage 104 | 105 | **v1.1.0** 106 | This app has been updated to use React v15.6 and Webpack 3.5! 🎉 107 | 108 | Major Changes: 109 | * Updates React and ReactDOM to v15.6 110 | * Updates Webpack to v3.5 111 | 112 | Minor Changes: 113 | * Updates all other dependencies to latest 114 | * Updates App.js syntax 115 | * Updates eslint rules 116 | * Updates server.js 117 | * Updates README 118 | 119 | **v1.0.0** 120 | This app has been updated to use React v15.5 and Webpack 2.3! 🎉 121 | 122 | Major Changes: 123 | * Updates React and ReactDOM to v15.5 124 | * Updates Webpack to v2.3 125 | * Enables hot-reloading for local development 126 | * Adds initial test suite with Enzyme, Expect, and Mocha 127 | 128 | Minor Changes: 129 | * Updates all other dependencies to latest 130 | * Updates eslint rules 131 | * Updates npm scripts 132 | * Refactors server.js 133 | * Updates README 134 | 135 | **v.0.2.0** 136 | This app has been updated to use React v15 and Babel v6! I have also updated the file structure to reflect naming conventions you'll most likely see in other applications. If you'd like to go back to v.0.0.1 (which should've been named 0.1.0), you can find go back to [this commit](https://github.com/alanbsmith/react-node-example/commit/dd6d745c4b7066fd12104d5005b805afaf469d91). 137 | 138 | ## DEPLOYING TO HEROKU 139 | This app is set up for deployment to Heroku! 140 | 141 | _This assumes you have already have a Heroku account and have the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) installed_ 142 | ``` 143 | $ heroku login 144 | $ heroku create -a name-of-your-app 145 | $ git push heroku master 146 | $ heroku open 147 | ``` 148 | 149 | Heroku will follow the `build` command in your `package.json` and compile assets with `webpack.prod.config.js`. It runs the Express web server in `server.js`. 150 | 151 | If you're unfamiliar with Heroku deployment (or just need a refresher), they have a really great walkthrough [here](https://devcenter.heroku.com/articles/getting-started-with-nodejs#introduction). 152 | 153 | ## REDUX STARTER 154 | If you're looking for a similar, minimalistic Redux starter, I would recommend Marc Garreau's [here](https://github.com/marcgarreau/redux-starter) 155 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | root: true, 3 | parser: 'babel-eslint', 4 | plugins: [/*'import', */'jsx-a11y', 'react'], 5 | 6 | env: { 7 | browser: true, 8 | commonjs: true, 9 | es6: true, 10 | jest: true, 11 | node: true 12 | }, 13 | 14 | parserOptions: { 15 | ecmaVersion: 6, 16 | sourceType: 'module', 17 | ecmaFeatures: { 18 | jsx: true, 19 | generators: true, 20 | experimentalObjectRestSpread: true 21 | } 22 | }, 23 | 24 | settings: { 25 | 'import/ignore': [ 26 | 'node_modules', 27 | '\\.(json|css|jpg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm)$', 28 | ], 29 | 'import/extensions': ['.js'], 30 | 'import/resolver': { 31 | node: { 32 | extensions: ['.js', '.json'] 33 | } 34 | } 35 | }, 36 | 37 | rules: { 38 | // http://eslint.org/docs/rules/ 39 | 'array-callback-return': 'warn', 40 | 'camelcase': 'warn', 41 | 'curly': 'warn', 42 | 'default-case': ['warn', { commentPattern: '^no default$' }], 43 | 'dot-location': ['warn', 'property'], 44 | 'eol-last': 'warn', 45 | 'eqeqeq': ['warn', 'always'], 46 | 'indent': ['warn', 2, { "SwitchCase": 1 }], 47 | 'guard-for-in': 'warn', 48 | 'keyword-spacing': 'warn', 49 | 'new-parens': 'warn', 50 | 'no-array-constructor': 'warn', 51 | 'no-caller': 'warn', 52 | 'no-cond-assign': ['warn', 'always'], 53 | 'no-const-assign': 'warn', 54 | 'no-control-regex': 'warn', 55 | 'no-delete-var': 'warn', 56 | 'no-dupe-args': 'warn', 57 | 'no-dupe-class-members': 'warn', 58 | 'no-dupe-keys': 'warn', 59 | 'no-duplicate-case': 'warn', 60 | 'no-empty-character-class': 'warn', 61 | 'no-empty-pattern': 'warn', 62 | 'no-eval': 'warn', 63 | 'no-ex-assign': 'warn', 64 | 'no-extend-native': 'warn', 65 | 'no-extra-bind': 'warn', 66 | 'no-extra-label': 'warn', 67 | 'no-fallthrough': 'warn', 68 | 'no-func-assign': 'warn', 69 | 'no-global-assign': 'warn', 70 | 'no-implied-eval': 'warn', 71 | 'no-invalid-regexp': 'warn', 72 | 'no-iterator': 'warn', 73 | 'no-label-var': 'warn', 74 | 'no-labels': ['warn', { allowLoop: false, allowSwitch: false }], 75 | 'no-lone-blocks': 'warn', 76 | 'no-loop-func': 'warn', 77 | 'no-mixed-operators': ['warn', { 78 | groups: [ 79 | ['&', '|', '^', '~', '<<', '>>', '>>>'], 80 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='], 81 | ['&&', '||'], 82 | ['in', 'instanceof'] 83 | ], 84 | allowSamePrecedence: false 85 | }], 86 | 'no-multi-str': 'warn', 87 | 'no-new-func': 'warn', 88 | 'no-new-object': 'warn', 89 | 'no-new-symbol': 'warn', 90 | 'no-new-wrappers': 'warn', 91 | 'no-obj-calls': 'warn', 92 | 'no-octal': 'warn', 93 | 'no-octal-escape': 'warn', 94 | 'no-redeclare': 'warn', 95 | 'no-regex-spaces': 'warn', 96 | 'no-restricted-syntax': [ 97 | 'warn', 98 | 'LabeledStatement', 99 | 'WithStatement', 100 | ], 101 | 'no-script-url': 'warn', 102 | 'no-self-assign': 'warn', 103 | 'no-self-compare': 'warn', 104 | 'no-sequences': 'warn', 105 | 'no-shadow-restricted-names': 'warn', 106 | 'no-sparse-arrays': 'warn', 107 | 'no-template-curly-in-string': 'warn', 108 | 'no-this-before-super': 'warn', 109 | 'no-throw-literal': 'warn', 110 | 'no-undef': 'warn', 111 | 'no-unexpected-multiline': 'warn', 112 | 'no-unreachable': 'warn', 113 | 'no-unsafe-negation': 'warn', 114 | 'no-unused-expressions': 'warn', 115 | 'no-unused-labels': 'warn', 116 | 'no-unused-vars': ['warn', { vars: 'local', args: 'none' }], 117 | 'no-use-before-define': ['warn', 'nofunc'], 118 | 'no-useless-computed-key': 'warn', 119 | 'no-useless-concat': 'warn', 120 | 'no-useless-constructor': 'warn', 121 | 'no-useless-escape': 'warn', 122 | 'no-useless-rename': ['warn', { 123 | ignoreDestructuring: false, 124 | ignoreImport: false, 125 | ignoreExport: false, 126 | }], 127 | 'no-with': 'warn', 128 | 'no-whitespace-before-property': 'warn', 129 | 'object-curly-spacing': ['warn', 'always'], 130 | 'operator-assignment': ['warn', 'always'], 131 | radix: 'warn', 132 | 'require-yield': 'warn', 133 | 'rest-spread-spacing': ['warn', 'never'], 134 | 'semi': 'warn', 135 | strict: ['warn', 'never'], 136 | 'unicode-bom': ['warn', 'never'], 137 | 'use-isnan': 'warn', 138 | 'valid-typeof': 'warn', 139 | 140 | 'react/jsx-boolean-value': 'warn', 141 | 'react/jsx-closing-bracket-location': 'warn', 142 | 'react/jsx-curly-spacing': 'warn', 143 | 'react/jsx-equals-spacing': ['warn', 'never'], 144 | 'react/jsx-first-prop-new-line': ['warn', 'multiline'], 145 | 'react/jsx-handler-names': 'warn', 146 | 'react/jsx-indent': ['warn', 2], 147 | 'react/jsx-indent-props': ['warn', 2], 148 | 'react/jsx-key': 'warn', 149 | 'react/jsx-max-props-per-line': 'warn', 150 | 'react/jsx-no-bind': ['warn', {'allowArrowFunctions': true}], 151 | 'react/jsx-no-comment-textnodes': 'warn', 152 | 'react/jsx-no-duplicate-props': ['warn', { ignoreCase: true }], 153 | 'react/jsx-no-undef': 'warn', 154 | 'react/jsx-pascal-case': ['warn', { 155 | allowAllCaps: true, 156 | ignore: [], 157 | }], 158 | 'react/jsx-sort-props': 'warn', 159 | 'react/jsx-tag-spacing': 'warn', 160 | 'react/jsx-uses-react': 'warn', 161 | 'react/jsx-uses-vars': 'warn', 162 | 'react/jsx-wrap-multilines': 'warn', 163 | 'react/no-deprecated': 'warn', 164 | 'react/no-did-mount-set-state': 'warn', 165 | 'react/no-did-update-set-state': 'warn', 166 | 'react/no-direct-mutation-state': 'warn', 167 | 'react/no-is-mounted': 'warn', 168 | 'react/no-unused-prop-types': 'warn', 169 | 'react/prefer-es6-class': 'warn', 170 | 'react/prefer-stateless-function': 'warn', 171 | 'react/prop-types': 'warn', 172 | 'react/react-in-jsx-scope': 'warn', 173 | 'react/require-render-return': 'warn', 174 | 'react/self-closing-comp': 'warn', 175 | 'react/sort-comp': 'warn', 176 | 'react/sort-prop-types': 'warn', 177 | 'react/style-prop-object': 'warn', 178 | 'react/void-dom-elements-no-children': 'warn', 179 | 180 | // https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules 181 | 'jsx-a11y/aria-role': 'warn', 182 | 'jsx-a11y/alt-text': 'warn', 183 | 'jsx-a11y/img-redundant-alt': 'warn', 184 | 'jsx-a11y/no-access-key': 'warn' 185 | } 186 | } 187 | --------------------------------------------------------------------------------