diff --git a/.babelrc b/.babelrc index 2bea1961..5a3acd3e 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,3 @@ { -"presets" : ["react", "es2015"] +"presets" : ["react", "es2016"] } diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..c0cfc0ae --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,211 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +// Inspired by https://github.com/airbnb/javascript but less opinionated. + +// We use eslint-loader so even warnings are very visibile. +// This is why we only use "WARNING" level for potential errors, +// and we don't use "ERROR" level at all. + +// In the future, we might create a separate list of rules for production. +// It would probably be more strict. + +module.exports = { + root: true, + + parser: 'babel-eslint', + + // import plugin is temporarily disabled, scroll below to see why + plugins: [/*'import', */'flowtype', 'jsx-a11y', 'react'], + + env: { + browser: true, + commonjs: true, + es6: true, + jest: true, + node: true + }, + + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + generators: true, + experimentalObjectRestSpread: true + } + }, + + settings: { + 'import/ignore': [ + 'node_modules', + '\\.(json|css|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$', + ], + 'import/extensions': ['.js'], + 'import/resolver': { + node: { + extensions: ['.js', '.json'] + } + } + }, + + rules: { + // http://eslint.org/docs/rules/ + 'array-callback-return': 'warn', + 'default-case': ['warn', { commentPattern: '^no default$' }], + 'dot-location': ['warn', 'property'], + eqeqeq: ['warn', 'allow-null'], + 'guard-for-in': 'warn', + 'new-parens': 'warn', + 'no-array-constructor': 'warn', + 'no-caller': 'warn', + 'no-cond-assign': ['warn', 'always'], + 'no-const-assign': 'warn', + 'no-control-regex': 'warn', + 'no-delete-var': 'warn', + 'no-dupe-args': 'warn', + 'no-dupe-class-members': 'warn', + 'no-dupe-keys': 'warn', + 'no-duplicate-case': 'warn', + 'no-empty-character-class': 'warn', + 'no-empty-pattern': 'warn', + 'no-eval': 'warn', + 'no-ex-assign': 'warn', + 'no-extend-native': 'warn', + 'no-extra-bind': 'warn', + 'no-extra-label': 'warn', + 'no-fallthrough': 'warn', + 'no-func-assign': 'warn', + 'no-implied-eval': 'warn', + 'no-invalid-regexp': 'warn', + 'no-iterator': 'warn', + 'no-label-var': 'warn', + 'no-labels': ['warn', { allowLoop: false, allowSwitch: false }], + 'no-lone-blocks': 'warn', + 'no-loop-func': 'warn', + 'no-mixed-operators': ['warn', { + groups: [ + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ], + allowSamePrecedence: false + }], + 'no-multi-str': 'warn', + 'no-native-reassign': 'warn', + 'no-negated-in-lhs': 'warn', + 'no-new-func': 'warn', + 'no-new-object': 'warn', + 'no-new-symbol': 'warn', + 'no-new-wrappers': 'warn', + 'no-obj-calls': 'warn', + 'no-octal': 'warn', + 'no-octal-escape': 'warn', + 'no-redeclare': 'warn', + 'no-regex-spaces': 'warn', + 'no-restricted-syntax': [ + 'warn', + 'LabeledStatement', + 'WithStatement', + ], + 'no-script-url': 'warn', + 'no-self-assign': 'warn', + 'no-self-compare': 'warn', + 'no-sequences': 'warn', + 'no-shadow-restricted-names': 'warn', + 'no-sparse-arrays': 'warn', + 'no-template-curly-in-string': 'warn', + 'no-this-before-super': 'warn', + 'no-throw-literal': 'warn', + 'no-undef': 'error', + 'no-unexpected-multiline': 'warn', + 'no-unreachable': 'warn', + 'no-unused-expressions': 'warn', + 'no-unused-labels': 'warn', + 'no-unused-vars': ['warn', { + vars: 'local', + varsIgnorePattern: '^_', + args: 'none' + }], + 'no-use-before-define': ['warn', 'nofunc'], + 'no-useless-computed-key': 'warn', + 'no-useless-concat': 'warn', + 'no-useless-constructor': 'warn', + 'no-useless-escape': 'warn', + 'no-useless-rename': ['warn', { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }], + 'no-with': 'warn', + 'no-whitespace-before-property': 'warn', + 'operator-assignment': ['warn', 'always'], + radix: 'warn', + 'require-yield': 'warn', + 'rest-spread-spacing': ['warn', 'never'], + strict: ['warn', 'never'], + 'unicode-bom': ['warn', 'never'], + 'use-isnan': 'warn', + 'valid-typeof': 'warn', + + // https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/ + + // TODO: import rules are temporarily disabled because they don't play well + // with how eslint-loader only checks the file you change. So if module A + // imports module B, and B is missing a default export, the linter will + // record this as an issue in module A. Now if you fix module B, the linter + // will not be aware that it needs to re-lint A as well, so the error + // will stay until the next restart, which is really confusing. + + // This is probably fixable with a patch to eslint-loader. + // When file A is saved, we want to invalidate all files that import it + // *and* that currently have lint errors. This should fix the problem. + + // 'import/default': 'warn', + // 'import/export': 'warn', + // 'import/named': 'warn', + // 'import/namespace': 'warn', + // 'import/no-amd': 'warn', + // 'import/no-duplicates': 'warn', + // 'import/no-extraneous-dependencies': 'warn', + // 'import/no-named-as-default': 'warn', + // 'import/no-named-as-default-member': 'warn', + // 'import/no-unresolved': ['warn', { commonjs: true }], + + // https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules + 'react/jsx-equals-spacing': ['warn', 'never'], + 'react/jsx-no-duplicate-props': ['warn', { ignoreCase: true }], + 'react/jsx-no-undef': 'warn', + 'react/jsx-pascal-case': ['warn', { + allowAllCaps: true, + ignore: [], + }], + 'react/jsx-uses-react': 'warn', + 'react/jsx-uses-vars': 'warn', + 'react/no-danger-with-children': 'warn', + 'react/no-deprecated': 'warn', + 'react/no-direct-mutation-state': 'warn', + 'react/no-is-mounted': 'warn', + 'react/react-in-jsx-scope': 'warn', + 'react/require-render-return': 'warn', + 'react/style-prop-object': 'warn', + + // https://github.com/evcohen/eslint-plugin-jsx-a11y/tree/master/docs/rules + 'jsx-a11y/aria-role': 'warn', + 'jsx-a11y/img-has-alt': 'warn', + 'jsx-a11y/img-redundant-alt': 'warn', + 'jsx-a11y/no-access-key': 'warn', + + // https://github.com/gajus/eslint-plugin-flowtype + 'flowtype/define-flow-type': 'warn', + 'flowtype/require-valid-file-annotation': 'warn', + 'flowtype/use-flow-type': 'warn' + } +}; diff --git a/package.json b/package.json index 7153831a..4b817b01 100644 --- a/package.json +++ b/package.json @@ -14,19 +14,29 @@ "react-dropzone": "~3.5.3" }, "devDependencies": { + "babel-core": "~6.13.2", "webpack": "~1.13.1", + "babel-eslint": "6.1.2", "babel-loader": "~6.2.4", - "babel-core": "~6.13.2", "babel-preset-react": "~6.11.1", - "babel-preset-es2015": "~6.13.2", + "babel-preset-es2016": "~6.11.3", + "react-dev-utils": "^0.2.1", + "eslint": "3.5.0", + "eslint-config-react-app": "^0.2.1", + "eslint-loader": "1.5.0", + "eslint-plugin-flowtype": "2.18.1", + "eslint-plugin-import": "1.12.0", + "eslint-plugin-jsx-a11y": "2.2.2", + "eslint-plugin-react": "6.3.0", + "json-loader": "0.5.4", "strip-loader": "~0.1.2", "style-loader": "~0.13.1", "css-loader": "~0.23.1", "es6-promise": "~3.2.1" }, "scripts": { - "dev": "webpack --config webpack.config.js -d --watch", - "prod": "webpack --config webpack.production.config.js -p" + "dev": "webpack --config webpack.config.dev.js -d --watch", + "build": "webpack --config webpack.config.prod.js -p" }, "repository": { "type": "git", @@ -36,7 +46,7 @@ "internship" ], "author": "Jeremy Booker, et al.", - "license": "GPLv3", + "license": "GPL-3.0", "bugs": { "url": "https://github.com/AppStateESS/InternshipInventory/issues" } diff --git a/webpack.config.dev.js b/webpack.config.dev.js new file mode 100644 index 00000000..018f6d65 --- /dev/null +++ b/webpack.config.dev.js @@ -0,0 +1,40 @@ +var webpack = require('webpack'); +var path = require('path'); +var Promise = require('es6-promise').polyfill(); + +var APP_DIR = path.resolve(__dirname, ''); +var JS_DIR = path.resolve(__dirname, 'javascript'); + +module.exports = { + devtool: 'eval', + entry: { + createInterface: JS_DIR + '/createInterface/CreateInternshipInterface.jsx', + searchInterface: JS_DIR + '/searchInterface/search.jsx', + editAdmin: JS_DIR + '/editAdmin/editAdmin.jsx' + }, + output: { + path: path.join(JS_DIR, "dist"), + filename: "[name].dev.js" + }, + module: { + loaders: [ + { + enforce: "pre", + test: /\.(js|jsx)$/, + loader: "eslint", + include: JS_DIR + }, { + test: /\.(js|jsx)$/, + include: JS_DIR, + loader: 'babel' + }, { + test: /\.css$/, + loader: "style-loader!css-loader" + }] + }, + eslint: { + configFile: path.join(__dirname, '.eslintrc.js'), + useEslintrc: false + }, + devtool: 'source-map' +} diff --git a/webpack.config.js b/webpack.config.js deleted file mode 100644 index b507298b..00000000 --- a/webpack.config.js +++ /dev/null @@ -1,28 +0,0 @@ -var webpack = require('webpack'); -var path = require('path'); -var Promise = require('es6-promise').polyfill(); - -var APP_DIR = path.resolve(__dirname, 'javascript'); - -module.exports = { - entry: { - createInterface: APP_DIR + '/createInterface/CreateInternshipInterface.jsx', - searchInterface: APP_DIR + '/searchInterface/search.jsx', - editAdmin: APP_DIR + '/editAdmin/editAdmin.jsx' - }, - output: { - path: path.join(APP_DIR, "dist"), - filename: "[name].dev.js" - }, - module: { - loaders: [{ - test: /\.jsx?/, - include: APP_DIR, - loader: 'babel' - }, { - test: /\.css$/, - loader: "style-loader!css-loader" - }] - }, - devtool: 'source-map' -} diff --git a/webpack.production.config.js b/webpack.config.prod.js similarity index 66% rename from webpack.production.config.js rename to webpack.config.prod.js index 356d5fe1..1fd24213 100644 --- a/webpack.production.config.js +++ b/webpack.config.prod.js @@ -6,6 +6,9 @@ var Promise = require('es6-promise').polyfill(); var APP_DIR = path.resolve(__dirname, 'javascript'); module.exports = { + // Don't attempt to continue if there are any errors + bail: true, + devtool: 'source-map', entry: { createInterface: APP_DIR + '/create.jsx', searchInterface: APP_DIR = '/serchInterface/search.jsx' @@ -13,10 +16,16 @@ module.exports = { }, output: { path: path.join(APP_DIR, "dist"), - filename: "[name].prod.js" + filename: "[name]-[hash].min.js", + chunkFilename: '[name].[chunkhash:8].chunk.js' }, module: { loaders: [{ + enforce: "pre", + test: /\.(js|jsx)$/, + loader: "eslint", + include: JS_DIR + "/*" + }, { test: /\.jsx?/, include: APP_DIR, loader: 'babel' @@ -37,7 +46,15 @@ module.exports = { }), new webpack.optimize.UglifyJsPlugin({ compress: { + screw_ie8: true, // React doesn't support IE8 anyway warnings: true + }, + mangle: { + screw_ie8: true + }, + output: { + comments: false, + screw_ie8: true } }) ]