diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..61b0ad35 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = crlf +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..bbf58eb6 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +/.vscode +node_modules \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..ad70a432 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + extends: "eslint:recommended", + env: { + "commonjs": true, + "node": true, + "es6": true + }, + parserOptions: { + "ecmaVersion": 2018 + }, + rules: { + "no-console": "off", + "curly": "warn" + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9beacf63 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# See https://help.github.com/ignore-files/ for more about ignoring files. + +# dependencies +node_modules + +# misc +.DS_Store + +npm-debug.log* diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..ef4ebecb --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +save-exact=true +loglevel="error" diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..af23d986 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +/.vscode +/node_modules +package.json +package-lock.json +.eslintrc.js +.eslintignore +.gitignore +.npmrc +.editorconfig +README.md diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..31e93fd3 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,4 @@ +module.exports = { + printWidth: 5000, + tabWidth: 4 +}; diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..322b1e65 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + "recommendations": [ + "formulahendry.auto-rename-tag", + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "eg2.vscode-npm-script", + "christian-kohler.npm-intellisense", + "christian-kohler.path-intellisense", + "esbenp.prettier-vscode", + "amatiasq.sort-imports" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..b73df06c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Node", + "program": "${workspaceFolder}/src/scripts/start.js" + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..78e71f0c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,20 @@ +{ + "editor.minimap.enabled": false, + "editor.wordWrap": "off", + // Turns on auto format on save for all files types. + // This is also required to allow Prettier to auto format files on save. + "editor.formatOnSave": true, + // Turns it off for JavaScript since we use Prettier to auto format on save. + "javascript.format.enable": false, + "javascript.validate.enable": true, + "json.format.enable": false, + // Required to allow auto format on save with Prettier + ESLint + Vetur. + "eslint.autoFixOnSave": true, + "eslint.alwaysShowStatus": true, + "prettier.eslintIntegration": true, + "prettier.stylelintIntegration": true, + "sort-imports.suppress-warnings": true, + "search.exclude": { + "**/node_modules*": true + } +} diff --git a/README.md b/README.md new file mode 100644 index 00000000..62715a6c --- /dev/null +++ b/README.md @@ -0,0 +1,181 @@ +# craco + +Create React App Configuration Override, an easy and comprehensible configuration layer for create-react-app v2. + +Get all the benefits of create-react-app **and** customization without using 'eject' by adding a single craco.config.js file at the root of your application and customize your eslint, babel, postcss configuration and many more.. + +All you have to do is create your app using [create-react-app](https://github.com/facebook/create-react-app/) and customize the configuration with craco.config.js file. + +- [Configuration Overview](#configuration-overview) - Quickly see how you can configure your CRA installation with this plugin. +- [Recipes](#creating-an-app) – Short and easy recipes for common use cases. +- [Documentation](#documentation) - Have a deep understanding on how you can configure your CRA installation with this plugin. +- [Develop a Plugin]() - How to develop a plugin for `craco`. + +**Acknowledgements:** + +We are grateful to [@timarney](https://github.com/timarney) the creator of [react-app-rewired](https://github.com/timarney/react-app-rewired) for is original idea. + +Also, the configuration style of this plugin tend to be influenced by the way [Vue CLI](https://cli.vuejs.org/guide/) do it. + +**Please Note:** + +By doing this you're breaking the ["guarantees"](https://github.com/facebookincubator/create-react-app/issues/99#issuecomment-234657710) that CRA provides. That is to say you now "own" the configs. **No support** will be provided. Proceed with caution. + +## Installation + +Install the plugin from **npm**: + +```bash +$ npm install craco --save-dev +``` + +Create a `craco.config.js` file in the root directory: + +``` +my-app +├── node_modules +├── craco.config.js +└── package.json +``` + +Export your configuration has as an **object literal**: + +```javascript +/* craco.config.js */ + +module.exports = { + ... +} +``` + +or a **function**: + +```javascript +/* craco.config.js */ + +module.exports = function({ env, paths }) { + return { + ... + }; +} +``` + +Update the existing calls to `react-scripts` in the `scripts` section of your `package.json` file to use the `craco` CLI: + +```diff +/* package.json */ + +"scripts": { +- "start": "react-scripts start", ++ "start": "craco start", +- "build": "react-scripts build", ++ "build": "craco build" +} +``` + +Start your app for development: + +```bash +$ npm start +``` + +Or build your app: + +```bash +$ npm run build +``` + +## CLI Options + +When you execute `craco start` or `craco build` a few options are available... + +To change the location of the configuration file: + +```bash +$ npm start craco --config config/my-cra-customized-config.js +``` + +To use a custom version of the `react-scripts` packages with `craco`: + +```bash +$ npm start craco --react-scripts react-scripts-ts +``` + +To activate the **verbose** mode: + +```bash +$ npm start craco --verbose +``` + +## Configuration Overview + +When the property **mode** is available there are 2 possible values: +- `extends`: the provided configuration will extends the CRA settings +- `file`: the CRA settings will be reseted and you will provide an official configuration file for the plugin ([postcss](https://github.com/michael-ciniawsky/postcss-load-config#postcssrc), [eslint](https://eslint.org/docs/user-guide/configuring#configuration-file-formats)) that will supersede any settings. + +```javascript +const { paths, when, whenDev, whenProd, ESLINT_MODES, POSTCSS_MODES } = require("craco"); + +module.exports = { + style: { + modules: { + localIdentName: "" + }, + css: { + loaderOptions: {} || (cssLoaderOptions, { env, paths }) => { return cssLoaderOptions; } + }, + sass: { + loaderOptions: {} || (sassLoaderOptions, { env, paths }) => { return sassLoaderOptions; } + }, + postcss: { + mode: "extends" || "file", + plugins: [], + loaderOptions: {} || (postcssLoaderOptions, { env, paths }) => { return postcssLoaderOptions; } + } + }, + eslint: { + enable: true, + mode: "extends" || "file", + formatter: "", + globals: [], + plugins: [], + extends: [], + rules: {}, + loaderOptions: {} || (eslintOptions, { env, paths }) => { return eslintOptions; } + }, + webpack: { + alias: {}, + plugins: [] + }, + configureWebpack: {} || (webpackConfig, { env, paths }) => { return webpackConfig; }, + devServer: {}, + babel: { + presets: [], + plugins: [], + loaderOptions: {} || (babelLoaderOptions, { env, paths }) => { return babelLoaderOptions; } + }, + plugins: [ + { + plugin: { + overrideCracoConfig: ({ cracoConfig, pluginOptions, context: { env, paths } }) => { return cracoConfig; }, + overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions, context: { env, paths } }) => { return webpackConfig; }, + }, + options: {} + } + ] +}; +``` + +## Documentation + +Coming soon... + +## Develop a plugin + +## Acknowledgements + +[@timarney](https://github.com/timarney) for having created [react-app-rewired](https://github.com/timarney/react-app-rewired). + +## License + +Create React App Config Override is open source software [licensed as MIT](https://github.com/facebook/create-react-app/blob/master/LICENSE). diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ac352111 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1037 @@ +{ + "name": "craco", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", + "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", + "dev": true, + "requires": { + "acorn": "^5.0.3" + } + }, + "ajv": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.4.tgz", + "integrity": "sha512-4Wyjt8+t6YszqaXnLDfMmG/8AlO5Zbcsy3ATHncCzjW/NoPzAId8AK6749Ybjmdt+kUY1gP60fCu46oDxPv/mg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", + "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.7.0.tgz", + "integrity": "sha512-zYCeFQahsxffGl87U2aJ7DPyH8CbWgxBC213Y8+TCanhUTf2gEvfq3EKpHmEcozTLyPmGe9LZdMAwC/CpJBM5A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.5.3", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^2.1.0", + "eslint-scope": "^4.0.0", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^4.0.0", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "imurmurhash": "^0.1.4", + "inquirer": "^6.1.0", + "is-resolvable": "^1.1.0", + "js-yaml": "^3.12.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.5", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "require-uncached": "^1.0.3", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.0.2", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", + "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", + "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "dev": true + }, + "espree": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", + "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", + "dev": true, + "requires": { + "acorn": "^5.6.0", + "acorn-jsx": "^4.1.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "flat-cache": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", + "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", + "integrity": "sha512-io6LkyPVuzCHBSQV9fmOwxZkUk6nIaGmxheLDgmuFv89j0fm2aqDbIXKAGfzCMHqz3HLF2Zf8WSG6VqMh2qFmA==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.0.tgz", + "integrity": "sha512-QIEQG4YyQ2UYZGDC4srMZ7BjHOmNk1lR2JQj5UknBapklm6WHA+VVH7N+sUdX3A7NeCfGF8o4X1S3Ao7nAcIeg==", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.0", + "figures": "^2.0.0", + "lodash": "^4.17.10", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.1.0", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "lodash.mergewith": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", + "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==" + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pluralize": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", + "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", + "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "semver": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", + "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "table": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/table/-/table-5.1.0.tgz", + "integrity": "sha512-e542in22ZLhD/fOIuXs/8yDZ9W61ltF8daM88rkRNtgTIct+vI2fTnAyu/Db2TCfEcI8i7mjZz6meLq0nW7TYg==", + "dev": true, + "requires": { + "ajv": "^6.5.3", + "lodash": "^4.17.10", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "webpack-merge": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz", + "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==", + "requires": { + "lodash": "^4.17.5" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..ae4e92d6 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "craco", + "version": "0.0.1", + "description": "Create React App Configuration Override, an easy and comprehensible configuration layer for create-react-app v2.", + "scripts": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/Sharegate/craco.git" + }, + "keywords": [ + "react", + "create-react-app", + "cra" + ], + "author": "Sharegate", + "license": "Apache 2.0", + "bugs": { + "url": "https://github.com/Sharegate/craco/issues" + }, + "homepage": "https://github.com/Sharegate/craco#readme", + "files": [ + "src/**/*" + ], + "peerDependencies": { + "react-scripts": "^2.0.4" + }, + "devDependencies": { + "eslint": "5.7.0" + }, + "dependencies": { + "lodash.mergewith": "4.6.1", + "webpack-merge": "4.1.4" + } +} diff --git a/src/args.js b/src/args.js new file mode 100644 index 00000000..838ae496 --- /dev/null +++ b/src/args.js @@ -0,0 +1,47 @@ +const { log } = require("./logger"); + +const args = process.argv; + +function findArg(key) { + const index = args.indexOf(key); + + return { + index, + isFound: index !== -1 + }; +} + +function isFlagSet(flag) { + return findArg(flag).isFound; +} + +function getValue(key) { + const result = (isOverrided = false, value) => ({ + isOverrided, + value + }); + + const arg = findArg(key); + + if (arg.isFound) { + const value = findArg(key); + + if (value.isFound) { + return result(true, args[value.index]); + } else { + log(`No value has been provided for cli argument ${key}`); + } + } + + return result(); +} + +const isVerbose = isFlagSet("--verbose"); +const reactScripts = getValue("--react-scripts"); +const config = getValue("--config"); + +module.exports = { + isVerbose, + reactScripts, + config +}; diff --git a/src/config.js b/src/config.js new file mode 100644 index 00000000..6ff6837e --- /dev/null +++ b/src/config.js @@ -0,0 +1,29 @@ +const args = require("./args"); + +const { projectRoot } = require("./paths"); +const { isFunction } = require("./utils"); +const { log } = require("./logger"); +const { applyCracoConfigPlugins } = require("./features/plugins"); + +function loadCracoConfig(context) { + let configFilePath = ""; + + if (args.config.isOverrided) { + configFilePath = require.resolve(`${projectRoot}/${args.config.value}`); + } else { + configFilePath = require.resolve(`${projectRoot}/craco.config.js`); + } + + log("Found craco config file at: ", configFilePath); + + const config = require(configFilePath); + + let resultingConfig = isFunction(config) ? config(context) : config; + resultingConfig = applyCracoConfigPlugins(resultingConfig, context); + + return resultingConfig; +} + +module.exports = { + loadCracoConfig +}; diff --git a/src/cra.js b/src/cra.js new file mode 100644 index 00000000..86ccfd1f --- /dev/null +++ b/src/cra.js @@ -0,0 +1,126 @@ +const path = require("path"); +const args = require("./args"); + +const { log } = require("./logger"); +const { projectRoot, nodeModulesPath } = require("./paths"); + +/************ Common *******************/ + +function discoverScriptsFolderPath() { + let filepath = ""; + + if (args.reactScripts.isOverrided) { + filepath = path.resolve(`${projectRoot}/${args.reactScripts.value}/`); + } else { + filepath = path.resolve(`${nodeModulesPath}/react-scripts/`); + } + + log("Discovered react scripts folder at: ", filepath); + + return filepath; +} + +function resolveConfigFilePath(scriptsFolderPath, fileName) { + return require.resolve(`${scriptsFolderPath}/config/${fileName}`); +} + +const scriptsFolderPath = discoverScriptsFolderPath(); +const craPaths = require(resolveConfigFilePath(scriptsFolderPath, "paths.js")); + +/************ Webpack Dev Config *******************/ + +function getWebpackDevConfigPath() { + return resolveConfigFilePath(scriptsFolderPath, "webpack.config.dev"); +} + +function loadWebpackDevConfig() { + const filepath = getWebpackDevConfigPath(); + + log("Found Webpack dev config at: ", filepath); + + return require(filepath); +} + +function overrideWebpackDevConfig(newConfig) { + const filepath = getWebpackDevConfigPath(); + + require.cache[filepath].exports = newConfig; + + log(`Overrided require cache for module: ${filepath}`); +} + +/************ Webpack Prod Config *******************/ + +function getWebpackProdConfigPath() { + return resolveConfigFilePath(scriptsFolderPath, "webpack.config.prod"); +} + +function loadWebpackProdConfig() { + const filepath = getWebpackProdConfigPath(); + + log("Found Webpack prod config at: ", filepath); + + return require(filepath); +} + +function overrideWebpackProdConfig(newConfig) { + const filepath = getWebpackProdConfigPath(); + + require.cache[filepath].exports = newConfig; + + log(`Overrided require cache for module: ${filepath}`); +} + +/************ Dev Server *******************/ + +function getDevServerConfigPath() { + return resolveConfigFilePath(scriptsFolderPath, "webpackDevServer.config.js"); +} + +function loadDevServerConfigProvider() { + const filepath = getDevServerConfigPath(); + + log("Found dev server config at: ", filepath); + + return require(filepath); +} + +function overrideDevServerConfigProvider(configProvider) { + const filepath = getDevServerConfigPath(); + + require.cache[filepath].exports = configProvider; + + log(`Overrided cache for: ${filepath}`); +} + +/************ Scripts *******************/ + +function start() { + const filepath = require.resolve(`${scriptsFolderPath}/scripts/start`); + + log("Starting CRA at: ", filepath); + + require(filepath); +} + +function build() { + const filepath = require.resolve(`${scriptsFolderPath}/scripts/build`); + + log("Building CRA at: ", filepath); + + require(filepath); +} + +/************ Exports *******************/ + +module.exports = { + loadWebpackDevConfig, + overrideWebpackDevConfig, + loadWebpackProdConfig, + overrideWebpackProdConfig, + loadDevServerConfigProvider, + overrideDevServerConfigProvider, + craPaths, + start, + build +}; diff --git a/src/features/babel.js b/src/features/babel.js new file mode 100644 index 00000000..b402a8d1 --- /dev/null +++ b/src/features/babel.js @@ -0,0 +1,82 @@ +const { getLoaders, loaderByName } = require("../loaders"); +const { log, error } = require("../logger"); +const { isFunction, isArray, deepMergeWithArray } = require("../utils"); + +// TODO: CRA use a cacheIdentifier, should we update it with the other plugins? + +function addPresets(loader, babelPresets) { + if (isArray(babelPresets)) { + if (loader.options) { + loader.options.presets = babelPresets.concat(loader.options.presets || []); + } else { + loader.options = { + presets: babelPresets.concat(loader.options.presets || []) + }; + } + } + + log("Added Babel presets."); +} + +function addPlugins(loader, babelPlugins) { + if (isArray(babelPlugins)) { + if (loader.options) { + loader.options.plugins = babelPlugins.concat(loader.options.plugins || []); + } else { + loader.options = { + plugins: babelPlugins.concat(loader.options.plugins || []) + }; + } + } + + log("Added Babel plugins."); +} + +function applyLoaderOptions(loader, loaderOptions, context) { + if (isFunction(loaderOptions)) { + loader.options = loaderOptions(loader.options || {}, context); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + loader.options = deepMergeWithArray(loader.options || {}, loaderOptions); + } + + log("Applied Babel loader options."); +} + +function overrideLoader(match, cracoConfig, context) { + const { presets, plugins, loaderOptions } = cracoConfig.babel; + + if (presets) { + addPresets(match.loader, presets); + } + + if (plugins) { + addPlugins(match.loader, plugins); + } + + if (loaderOptions) { + applyLoaderOptions(match.loader, loaderOptions, context); + } +} + +function overrideBabel(cracoConfig, webpackConfig, context) { + if (cracoConfig.babel) { + const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName("babel-loader")); + + if (!hasFoundAny) { + error("Cannot find any Babel loaders."); + + return webpackConfig; + } + + matches.forEach(x => { + overrideLoader(x, cracoConfig, context); + }); + } + + return webpackConfig; +} + +module.exports = { + overrideBabel +}; diff --git a/src/features/dev-server.js b/src/features/dev-server.js new file mode 100644 index 00000000..a74dc2b1 --- /dev/null +++ b/src/features/dev-server.js @@ -0,0 +1,76 @@ +const merge = require("webpack-merge"); + +const { isFunction, isString } = require("../utils"); +const { log } = require("../logger"); +const { overrideDevServerConfigProvider, loadDevServerConfigProvider } = require("../cra"); + +function createConfigProviderProxy(cracoConfig, craDevServerConfigProvider, context) { + const proxy = (...rest) => { + const craDevServerConfig = craDevServerConfigProvider(...rest); + let mergedConfig; + + if (isFunction(cracoConfig.devServer)) { + // DOC: The function can either mutate the config and return nothing, OR return a cloned or merged version of the config. + const resultingConfig = cracoConfig.devServer(craDevServerConfig, { + ...context, + ...rest + }); + + mergedConfig = resultingConfig || craDevServerConfig; + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + mergedConfig = merge(craDevServerConfig, cracoConfig.devServer); + } + + log("Merged dev server config"); + + return mergedConfig; + }; + + return proxy; +} + +function setEnvironmentVariable(envProperty, value) { + if (!isString(value)) { + process.env[envProperty] = value.toString(); + } else { + process.env[envProperty] = value; + } +} + +function setMatchingEnvironmentVariables({ browser, https, host, port, publicUrl }) { + if (browser) { + setEnvironmentVariable("BROWSER", browser); + } + + if (https) { + setEnvironmentVariable("HTTPS", https); + } + + if (host) { + setEnvironmentVariable("HOST", host); + } + + if (port) { + setEnvironmentVariable("PORT", port); + } + + if (publicUrl) { + setEnvironmentVariable("PUBLIC_URL", publicUrl); + } +} + +function overrideDevServer(cracoConfig, context) { + if (cracoConfig.devServer) { + setMatchingEnvironmentVariables(cracoConfig.devServer); + + const craDevServerConfigProvider = loadDevServerConfigProvider(); + const proxy = createConfigProviderProxy(cracoConfig, craDevServerConfigProvider, context); + + overrideDevServerConfigProvider(proxy); + } +} + +module.exports = { + overrideDevServer +}; diff --git a/src/features/eslint.js b/src/features/eslint.js new file mode 100644 index 00000000..ec0481c5 --- /dev/null +++ b/src/features/eslint.js @@ -0,0 +1,131 @@ +const { getLoader, removeLoader, loaderByName } = require("../loaders"); +const { log, error } = require("../logger"); +const { isFunction, isArray, deepMergeWithArray } = require("../utils"); + +const ESLINT_MODES = { + extends: "extends", + file: "file" +}; + +function disableEslint(webpackConfig) { + removeLoader(webpackConfig, loaderByName("eslint-loader")); + + log("Disabled ESLint."); +} + +function resetDefaultOptions(loader) { + loader.options.ignore = false; + + log("Reseted ESLint default options."); +} + +function setFormatter(loader, formatter) { + loader.options.formatter = formatter; + + log("Applied ESLint formatter."); +} + +function extendsEslint(loader, eslintConfig) { + let baseConfigExtends = {}; + + if (isArray(eslintConfig.globals)) { + baseConfigExtends.globals = eslintConfig.globals; + + log("Applied ESLint globals."); + } + + if (isArray(eslintConfig.plugins)) { + baseConfigExtends.plugins = eslintConfig.plugins; + + log("Applied ESLint plugins."); + } + + if (isArray(eslintConfig.extends)) { + baseConfigExtends.extends = eslintConfig.extends; + + log("Applied ESLint extends."); + } + + if (eslintConfig.rules) { + baseConfigExtends.rules = eslintConfig.rules; + + log("Applied ESLint rules."); + } + + if (loader.options) { + loader.options.baseConfig = deepMergeWithArray(loader.options.baseConfig || {}, baseConfigExtends); + } else { + loader.options = { + baseConfig: baseConfigExtends + }; + } + + log("Extended ESLint config."); +} + +function useEslintConfigFile(loader) { + if (loader.options) { + loader.options.useEslintrc = true; + delete loader.options.baseConfig; + } else { + loader.options = { + useEslintrc: true + }; + } + + log("Overwrited ESLint config to use a config file."); +} + +function applyLoaderOptions(loader, loaderOptions, context) { + if (isFunction(loaderOptions)) { + loader.options = loaderOptions(loader.options || {}, context); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + loader.options = deepMergeWithArray(loader.options || {}, loaderOptions); + } + + log("Applied ESLint loader options."); +} + +function overrideEsLint(cracoConfig, webpackConfig, context) { + if (cracoConfig.eslint) { + const { isFound, match } = getLoader(webpackConfig, loaderByName("eslint-loader")); + + if (!isFound) { + error("Cannot find ESLint loader (eslint-loader)."); + + return webpackConfig; + } + + const { enable, mode, formatter, loaderOptions } = cracoConfig.eslint; + + if (enable === false) { + disableEslint(webpackConfig); + + return webpackConfig; + } + + resetDefaultOptions(match.loader); + + if (formatter) { + setFormatter(match.loader, formatter); + } + + if (mode === ESLINT_MODES.file) { + useEslintConfigFile(match.loader); + } else { + extendsEslint(match.loader, cracoConfig.eslint, context); + } + + if (loaderOptions) { + applyLoaderOptions(match.loader, loaderOptions); + } + } + + return webpackConfig; +} + +module.exports = { + overrideEsLint, + ESLINT_MODES +}; diff --git a/src/features/plugins.js b/src/features/plugins.js new file mode 100644 index 00000000..33ca0375 --- /dev/null +++ b/src/features/plugins.js @@ -0,0 +1,72 @@ +const { isArray, isFunction } = require("../utils"); +const { log } = require("../logger"); + +/************ Craco Config *******************/ + +function overrideCracoConfig({ plugin, options }, cracoConfig, context) { + if (isFunction(plugin.overrideCracoConfig)) { + const resultingConfig = plugin.overrideCracoConfig({ + cracoConfig: cracoConfig, + pluginOptions: options, + context: context + }); + + if (!resultingConfig) { + throw new Error("craco: Plugin returned an undefined craco config."); + } + } + + log("Overrided Craco config with plugin."); + + return cracoConfig; +} + +function applyCracoConfigPlugins(cracoConfig, context) { + if (isArray(cracoConfig.plugins)) { + cracoConfig.plugins.forEach(x => { + cracoConfig = overrideCracoConfig(x, cracoConfig, context); + }); + } + + log("Applied Craco config plugins."); + + return cracoConfig; +} + +/************ Webpack Config *******************/ + +function overrideWebpack({ plugin, options }, cracoConfig, webpackConfig, context) { + if (isFunction(plugin.overrideWebpackConfig)) { + const resultingConfig = plugin.overrideWebpackConfig({ + cracoConfig: cracoConfig, + webpackConfig: webpackConfig, + pluginOptions: options, + context: context + }); + + if (!resultingConfig) { + throw new Error("craco: Plugin returned an undefined Webpack config."); + } + } + + log("Overrided Webpack config with plugin."); + + return webpackConfig; +} + +function applyWebpackConfigPlugins(cracoConfig, webpackConfig, context) { + if (isArray(cracoConfig.plugins)) { + cracoConfig.plugins.forEach(x => { + webpackConfig = overrideWebpack(x, cracoConfig, webpackConfig, context); + }); + } + + log("Applied Webpack config plugins."); + + return webpackConfig; +} + +module.exports = { + applyCracoConfigPlugins, + applyWebpackConfigPlugins +}; diff --git a/src/features/style/css.js b/src/features/style/css.js new file mode 100644 index 00000000..92a7ff1c --- /dev/null +++ b/src/features/style/css.js @@ -0,0 +1,77 @@ +const { getLoaders, loaderByName } = require("../../loaders"); +const { log, error } = require("../../logger"); +const { isFunction, deepMergeWithArray } = require("../../utils"); + +function setModuleLocalIdentName(match, localIdentName) { + if (match.loader.options) { + delete match.loader.options.getLocalIdent; + match.loader.options.localIdentName = localIdentName; + } else { + match.loader.options = { + localIdentName: localIdentName + }; + } + + log("Overrided CSS modules local ident name."); +} + +function applyLoaderOptions(match, loaderOptions, context) { + if (isFunction(loaderOptions)) { + match.loader.options = loaderOptions(match.loader.options || {}, context); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + match.loader.options = deepMergeWithArray(match.loader.options || {}, loaderOptions); + } + + log("Applied CSS loaders options."); +} + +function overrideCssLoader(match, cssOptions, context) { + if (cssOptions.loaderOptions) { + applyLoaderOptions(match, cssOptions.loaderOptions, context); + } + + log("Overrided CSS loader."); +} + +function overrideModuleLoader(match, modulesOptions) { + if (modulesOptions.localIdentName) { + setModuleLocalIdentName(match, modulesOptions.localIdentName); + } + + log("Overrided CSS module loader."); +} + +function overrideCss(styleConfig, webpackConfig, context) { + if (styleConfig.modules || styleConfig.css) { + const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName("css-loader")); + + // console.log(JSON.stringify(webpackConfig, null, 2)); + + if (!hasFoundAny) { + error("Cannot find any CSS loaders."); + + return webpackConfig; + } + + if (styleConfig.modules) { + const cssModuleLoaders = matches.filter(x => x.loader.options && x.loader.options.modules === true); + + cssModuleLoaders.forEach(x => { + overrideModuleLoader(x, styleConfig.modules); + }); + } + + if (styleConfig.css) { + matches.forEach(x => { + overrideCssLoader(x, styleConfig.css, context); + }); + } + } + + return webpackConfig; +} + +module.exports = { + overrideCss +}; diff --git a/src/features/style/postcss.js b/src/features/style/postcss.js new file mode 100644 index 00000000..aa2f6317 --- /dev/null +++ b/src/features/style/postcss.js @@ -0,0 +1,93 @@ +const { getLoaders, loaderByName } = require("../../loaders"); +const { log, error } = require("../../logger"); +const { isArray, isFunction, deepMergeWithArray } = require("../../utils"); + +const POSTCSS_MODES = { + extends: "extends", + file: "file" +}; + +function usePostcssConfigFile(match) { + if (match.loader.options) { + const ident = match.loader.options.ident; + + match.loader.options = { + ident: ident + }; + } + + log("Overwrited PostCSS config to use a config file."); +} + +function extendsPostcss(match, { plugins }) { + if (plugins) { + addPlugins(match, plugins); + } +} + +function addPlugins(match, postcssPlugins) { + if (isArray(postcssPlugins)) { + if (match.loader.options) { + const craPlugins = match.loader.options.plugins(); + const mergedPlugins = postcssPlugins.concat(craPlugins || []); + + match.loader.options.plugins = () => mergedPlugins; + } else { + match.loader.options = { + plugins: () => postcssPlugins + }; + } + + log("Added PostCSS plugins."); + } +} + +function applyLoaderOptions(match, loaderOptions, context) { + if (isFunction(loaderOptions)) { + match.loader.options = loaderOptions(match.loader.options || {}, context); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + match.loader.options = deepMergeWithArray(match.loader.options || {}, loaderOptions); + } + + log("Applied PostCSS loaders options."); +} + +function overrideLoader(match, postcssConfig) { + const { mode, loaderOptions } = postcssConfig; + + if (mode === POSTCSS_MODES.file) { + usePostcssConfigFile(match); + } else { + extendsPostcss(match, postcssConfig); + } + + if (loaderOptions) { + applyLoaderOptions(match, loaderOptions); + } + + log("Overrided PostCSS loader."); +} + +function overridePostcss(cracoConfig, webpackConfig) { + if (cracoConfig.postcss) { + const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName("postcss-loader")); + + if (!hasFoundAny) { + error("Cannot find any PostCSS loaders."); + + return webpackConfig; + } + + matches.forEach(x => { + overrideLoader(x, cracoConfig.postcss); + }); + } + + return webpackConfig; +} + +module.exports = { + overridePostcss, + POSTCSS_MODES +}; diff --git a/src/features/style/sass.js b/src/features/style/sass.js new file mode 100644 index 00000000..92ddb7c2 --- /dev/null +++ b/src/features/style/sass.js @@ -0,0 +1,61 @@ +const { getLoaders, loaderByName } = require("../../loaders"); +const { log, error } = require("../../logger"); +const { isString, isFunction, deepMergeWithArray } = require("../../utils"); + +function setLoaderProperty(match, key, valueProviders) { + if (isString(match.loader)) { + match.parent[match.index] = { + loader: match.loader, + [key]: valueProviders.whenString() + }; + } else { + match.loader[key] = valueProviders.whenObject(); + } +} + +function applyLoaderOptions(match, loaderOptions, context) { + if (isFunction(loaderOptions)) { + setLoaderProperty(match, "options", { + whenString: () => loaderOptions({}, context), + whenObject: () => loaderOptions(match.loader.options || {}, context) + }); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + setLoaderProperty(match, "options", { + whenString: () => loaderOptions, + whenObject: () => deepMergeWithArray(match.loader.options || {}, loaderOptions) + }); + } + + log("Applied SASS loaders options."); +} + +function overrideLoader(match, sassOptions) { + if (sassOptions.loaderOptions) { + applyLoaderOptions(match, sassOptions.loaderOptions); + } + + log("Overrided SASS loader."); +} + +function overrideSass(styleConfig, webpackConfig) { + if (styleConfig.sass) { + const { hasFoundAny, matches } = getLoaders(webpackConfig, loaderByName("sass-loader")); + + if (!hasFoundAny) { + error("Cannot find any SASS loaders."); + + return webpackConfig; + } + + matches.forEach(x => { + overrideLoader(x, styleConfig.sass.loaderOptions); + }); + } + + return webpackConfig; +} + +module.exports = { + overrideSass +}; diff --git a/src/features/style/style.js b/src/features/style/style.js new file mode 100644 index 00000000..4c912f83 --- /dev/null +++ b/src/features/style/style.js @@ -0,0 +1,17 @@ +const { overrideCss } = require("./css"); +const { overrideSass } = require("./sass"); +const { overridePostcss } = require("./postcss"); + +function overrideStyle(cracoConfig, webpackConfig, context) { + if (cracoConfig.style) { + webpackConfig = overrideCss(cracoConfig.style, webpackConfig, context); + webpackConfig = overrideSass(cracoConfig.style, webpackConfig, context); + webpackConfig = overridePostcss(cracoConfig.style, webpackConfig, context); + } + + return webpackConfig; +} + +module.exports = { + overrideStyle +}; diff --git a/src/features/webpack.js b/src/features/webpack.js new file mode 100644 index 00000000..ec7a00ab --- /dev/null +++ b/src/features/webpack.js @@ -0,0 +1,67 @@ +const merge = require("webpack-merge"); + +const { isFunction, isArray } = require("../utils"); +const { log } = require("../logger"); +const { overrideBabel } = require("./babel"); +const { overrideEsLint } = require("./eslint"); +const { overrideStyle } = require("./style/style"); +const { applyWebpackConfigPlugins } = require("./plugins"); + +function addAlias(webpackConfig, webpackAlias) { + webpackConfig.resolve.alias = Object.assign(webpackConfig.resolve.alias || {}, webpackAlias); + + log("Added Webpack alias."); +} + +function addPlugins(webpackConfig, webpackPlugins) { + if (isArray(webpackPlugins)) { + webpackConfig.plugins = webpackPlugins.concat(webpackConfig.plugins || []); + + log("Added Webpack plugins."); + } +} + +function giveTotalControl(webpackConfig, configureWebpack, context) { + let mergedConfig; + + if (isFunction(configureWebpack)) { + mergedConfig = configureWebpack(webpackConfig, context); + } else { + // TODO: ensure is otherwise a plain object, if not, log an error. + mergedConfig = merge(webpackConfig, configureWebpack); + } + + log("Merged Webpack config with configureWebpack."); + + return mergedConfig; +} + +function overrideWebpack(cracoConfig, webpackConfig, overrideConfig, context) { + if (cracoConfig.webpack) { + const { alias, plugins } = cracoConfig.webpack; + + if (alias) { + addAlias(webpackConfig, alias); + } + + if (plugins) { + addPlugins(webpackConfig, plugins); + } + } + + webpackConfig = overrideBabel(cracoConfig, webpackConfig, context); + webpackConfig = overrideEsLint(cracoConfig, webpackConfig, context); + webpackConfig = overrideStyle(cracoConfig, webpackConfig, context); + + if (cracoConfig.configureWebpack) { + webpackConfig = giveTotalControl(webpackConfig, cracoConfig.configureWebpack, context); + } + + webpackConfig = applyWebpackConfigPlugins(cracoConfig, webpackConfig, context); + + overrideConfig(webpackConfig); +} + +module.exports = { + overrideWebpack +}; diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..68f4b2a2 --- /dev/null +++ b/src/index.js @@ -0,0 +1,32 @@ +const { craPaths } = require("./cra"); +const { getLoader, removeLoader, loaderByName } = require("./loaders"); +const { ESLINT_MODES } = require("./features/eslint"); +const { POSTCSS_MODES } = require("./features/style/postcss"); + +function when(condition, fct) { + if (condition) { + return fct(); + } + + return undefined; +} + +function whenDev(fct) { + return when(process.env.NODE_ENV === "development", fct); +} + +function whenProd(fct) { + return when(process.env.NODE_ENV === "production", fct); +} + +module.exports = { + paths: craPaths, + getLoader, + removeLoader, + loaderByName, + when, + whenDev, + whenProd, + ESLINT_MODES, + POSTCSS_MODES +}; diff --git a/src/loaders.js b/src/loaders.js new file mode 100644 index 00000000..89c74bfa --- /dev/null +++ b/src/loaders.js @@ -0,0 +1,111 @@ +const path = require("path"); + +const { isString, isArray } = require("./utils"); + +function loaderByName(targetLoaderName) { + return rule => { + if (isString(rule.loader)) { + return rule.loader.indexOf(`${path.sep}${targetLoaderName}${path.sep}`) !== -1 || rule.loader.indexOf(`@${targetLoaderName}${path.sep}`) !== -1; + } else if (isString(rule)) { + return rule.indexOf(`${path.sep}${targetLoaderName}${path.sep}`) !== -1 || rule.indexOf(`@${targetLoaderName}${path.sep}`) !== -1; + } + + return false; + }; +} + +function toMatchingLoader(loader, parent, index) { + return { + loader, + parent, + index + }; +} + +function getLoaderRecursively(rules, matcher) { + let loader; + + rules.some((rule, index) => { + if (rule) { + if (matcher(rule)) { + loader = toMatchingLoader(rule, rules, index); + } else if (rule.use) { + loader = getLoaderRecursively(rule.use, matcher); + } else if (rule.oneOf) { + loader = getLoaderRecursively(rule.oneOf, matcher); + } else if (isArray(rule.loader)) { + loader = getLoaderRecursively(rule.loader, matcher); + } + } + + return loader !== undefined; + }); + + return loader; +} + +function getLoader(webpackConfig, matcher) { + const matchingLoader = getLoaderRecursively(webpackConfig.module.rules, matcher); + + return { + isFound: matchingLoader !== undefined, + match: matchingLoader + }; +} + +function getLoadersRecursively(rules, matcher, matchingLoaders) { + rules.forEach((rule, index) => { + if (rule) { + if (matcher(rule)) { + matchingLoaders.push(toMatchingLoader(rule, rules, index)); + } else if (rule.use) { + getLoadersRecursively(rule.use, matcher, matchingLoaders); + } else if (rule.oneOf) { + getLoadersRecursively(rule.oneOf, matcher, matchingLoaders); + } else if (isArray(rule.loader)) { + getLoadersRecursively(rule.loader, matcher, matchingLoaders); + } + } + }); +} + +function getLoaders(webpackConfig, matcher) { + const matchingLoaders = []; + + getLoadersRecursively(webpackConfig.module.rules, matcher, matchingLoaders); + + return { + hasFoundAny: matchingLoaders.length !== 0, + matches: matchingLoaders + }; +} + +function removeLoaderRecursively(rules, matcher) { + for (let i = 0, max = rules.length; i < max; i += 1) { + const rule = rules[i]; + + if (rule) { + if (matcher(rule)) { + rules.splice(i, 1); + break; + } else if (rule.use) { + rule.use = removeLoaderRecursively(rule.use, matcher); + } else if (rule.oneOf) { + rule.oneOf = removeLoaderRecursively(rule.oneOf, matcher); + } + } + } + + return rules; +} + +function removeLoader(webpackConfig, matcher) { + webpackConfig.module.rules = removeLoaderRecursively(webpackConfig.module.rules, matcher); +} + +module.exports = { + getLoader, + getLoaders, + removeLoader, + loaderByName +}; diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 00000000..10d56f31 --- /dev/null +++ b/src/logger.js @@ -0,0 +1,23 @@ +const args = require("./args"); + +function log(...rest) { + if (args.isVerbose) { + console.log("craco: ", ...rest); + } +} + +function lazyLog(fct) { + if (args.isVerbose) { + fct(); + } +} + +function error(...rest) { + console.error("craco: ***", ...rest, "***"); +} + +module.exports = { + log, + lazyLog, + error +}; diff --git a/src/paths.js b/src/paths.js new file mode 100644 index 00000000..5303f6e1 --- /dev/null +++ b/src/paths.js @@ -0,0 +1,10 @@ +const path = require("path"); +const fs = require("fs"); + +const projectRoot = path.resolve(fs.realpathSync(process.cwd())); +const nodeModulesPath = path.resolve(projectRoot, "node_modules"); + +module.exports = { + projectRoot, + nodeModulesPath +}; diff --git a/src/scripts/build.js b/src/scripts/build.js new file mode 100644 index 00000000..ef75ce82 --- /dev/null +++ b/src/scripts/build.js @@ -0,0 +1,21 @@ +process.env.NODE_ENV = "production"; + +const { log } = require("../logger"); +const { craPaths, loadWebpackProdConfig, overrideWebpackProdConfig, build } = require("../cra"); +const { loadCracoConfig } = require("../config"); +const { overrideWebpack } = require("../features/webpack"); + +log("Override started with arguments: ", process.argv); +log("For environment: ", process.env.NODE_ENV); + +const context = { + env: process.env.NODE_ENV, + paths: craPaths +}; + +const cracoConfig = loadCracoConfig(context); +const craWebpackConfig = loadWebpackProdConfig(); + +overrideWebpack(cracoConfig, craWebpackConfig, overrideWebpackProdConfig, context); + +build(); diff --git a/src/scripts/start.js b/src/scripts/start.js new file mode 100644 index 00000000..800cd7a1 --- /dev/null +++ b/src/scripts/start.js @@ -0,0 +1,23 @@ +process.env.NODE_ENV = process.env.NODE_ENV || "development"; + +const { log } = require("../logger"); +const { craPaths, loadWebpackDevConfig, overrideWebpackDevConfig, start } = require("../cra"); +const { loadCracoConfig } = require("../config"); +const { overrideWebpack } = require("../features/webpack"); +const { overrideDevServer } = require("../features/dev-server"); + +log("Override started with arguments: ", process.argv); +log("For environment: ", process.env.NODE_ENV); + +const context = { + env: process.env.NODE_ENV, + paths: craPaths +}; + +const cracoConfig = loadCracoConfig(context); +const craWebpackConfig = loadWebpackDevConfig(); + +overrideWebpack(cracoConfig, craWebpackConfig, overrideWebpackDevConfig, context); +overrideDevServer(cracoConfig, context); + +start(); diff --git a/src/scripts/tests.js b/src/scripts/tests.js new file mode 100644 index 00000000..e69de29b diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..d0a565db --- /dev/null +++ b/src/utils.js @@ -0,0 +1,28 @@ +const mergeWith = require("lodash.mergewith"); + +function isFunction(value) { + return typeof value === "function"; +} + +function isArray(value) { + return Array.isArray(value); +} + +function isString(value) { + return typeof value === "string"; +} + +function deepMergeWithArray(...rest) { + return mergeWith(...rest, (x, y) => { + if (isArray(x)) { + return x.concat(y); + } + }); +} + +module.exports = { + isFunction, + isArray, + isString, + deepMergeWithArray +};