Repository: dilanx/craco Branch: main Commit: 56840ceaedaa Files: 165 Total size: 216.8 KB Directory structure: gitextract_a92n3n5z/ ├── .eslintrc.json ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── run-tests.yml │ ├── website-deploy.yml │ └── website-test-deploy.yml ├── .gitignore ├── .prettierignore ├── .vscode/ │ └── extensions.json ├── LICENSE ├── README.md ├── lerna.json ├── package.json ├── packages/ │ ├── craco/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── bin/ │ │ │ │ ├── craco.ts │ │ │ │ └── jest.ts │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── args.ts │ │ │ │ ├── asset-modules.ts │ │ │ │ ├── config.ts │ │ │ │ ├── cra.ts │ │ │ │ ├── features/ │ │ │ │ │ ├── dev-server/ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ ├── create-config-provider-proxy.ts │ │ │ │ │ │ ├── override-utils.ts │ │ │ │ │ │ ├── override.ts │ │ │ │ │ │ └── set-environment-variables.ts │ │ │ │ │ ├── jest/ │ │ │ │ │ │ ├── api.ts │ │ │ │ │ │ ├── create-jest-babel-transform.ts │ │ │ │ │ │ ├── jest-babel-transform.ts │ │ │ │ │ │ ├── merge-jest-config.ts │ │ │ │ │ │ └── override.ts │ │ │ │ │ ├── paths/ │ │ │ │ │ │ └── override.ts │ │ │ │ │ ├── plugins.ts │ │ │ │ │ └── webpack/ │ │ │ │ │ ├── api.ts │ │ │ │ │ ├── babel.ts │ │ │ │ │ ├── eslint.ts │ │ │ │ │ ├── merge-webpack-config.ts │ │ │ │ │ ├── override.ts │ │ │ │ │ ├── style/ │ │ │ │ │ │ ├── css.ts │ │ │ │ │ │ ├── postcss.ts │ │ │ │ │ │ ├── sass.ts │ │ │ │ │ │ └── style.ts │ │ │ │ │ └── typescript.ts │ │ │ │ ├── loaders.ts │ │ │ │ ├── logger.ts │ │ │ │ ├── paths.ts │ │ │ │ ├── plugin-utils.ts │ │ │ │ ├── user-config-utils.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── validate-cra-version.ts │ │ │ │ └── webpack-plugins.ts │ │ │ └── scripts/ │ │ │ ├── build.ts │ │ │ ├── start.ts │ │ │ └── test.ts │ │ └── tsconfig.json │ └── craco-types/ │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── asset-modules.ts │ │ ├── config.ts │ │ ├── context.ts │ │ ├── index.ts │ │ ├── loaders.ts │ │ ├── plugins.ts │ │ └── providers.ts │ └── tsconfig.json ├── recipes/ │ └── README.md ├── test/ │ ├── README.md │ ├── integration/ │ │ ├── fixtures/ │ │ │ ├── autoprefixer-test/ │ │ │ │ ├── index.test.js │ │ │ │ └── test-package-files/ │ │ │ │ ├── craco.config.js │ │ │ │ ├── package.json │ │ │ │ ├── public/ │ │ │ │ │ └── index.html │ │ │ │ └── src/ │ │ │ │ ├── index.css │ │ │ │ └── index.js │ │ │ └── basic-integration-test/ │ │ │ ├── index.test.js │ │ │ └── test-package-files/ │ │ │ ├── craco.config.js │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ └── index.html │ │ │ └── src/ │ │ │ └── index.js │ │ ├── jest.config.js │ │ ├── setup.js │ │ └── teardown.js │ └── unit/ │ ├── jest.config.js │ └── merging-tests/ │ ├── autoprefixer-options/ │ │ ├── autoprefixer.test.js │ │ └── craco.config.js │ ├── configuration-merging/ │ │ ├── cra.mock.config.js │ │ ├── craco.config.js │ │ └── merging.test.js │ ├── custom-babel-config/ │ │ ├── babel.config.mock.js │ │ ├── babel.test.js │ │ └── craco.config.js │ ├── custom-craco-plugin/ │ │ ├── craco-plugin-mock/ │ │ │ └── index.js │ │ ├── craco.config.js │ │ └── plugin.test.js │ ├── custom-env-variables/ │ │ ├── craco.config.js │ │ └── env.test.js │ ├── custom-eslint-config/ │ │ ├── craco.config.js │ │ ├── eslint.config.mock.js │ │ └── eslint.test.js │ ├── custom-jest-config/ │ │ ├── craco.config.js │ │ ├── jest.config.mock.js │ │ └── jest.test.js │ ├── custom-postcss-config/ │ │ ├── craco.config.js │ │ ├── postcss.config.mock.js │ │ └── postcss.test.js │ └── html-webpack-plugin/ │ ├── craco.config.js │ └── html-webpack-plugin.test.js ├── tsconfig.json └── website/ ├── .gitignore ├── README.md ├── babel.config.js ├── docs/ │ ├── configuration/ │ │ ├── babel.md │ │ ├── devserver.md │ │ ├── eslint.md │ │ ├── getting-started.md │ │ ├── jest.md │ │ ├── plugins.md │ │ ├── style.md │ │ ├── typescript.md │ │ └── webpack.md │ ├── configuration-api.md │ ├── getting-started.md │ ├── plugin-api/ │ │ ├── getting-started.md │ │ ├── hooks.md │ │ └── utility-functions/ │ │ ├── miscellaneous.md │ │ ├── webpack-asset-modules.md │ │ ├── webpack-loaders.md │ │ └── webpack-plugins.md │ └── welcome.md ├── docusaurus.config.js ├── package.json ├── plugins/ │ └── plugins.md ├── recipes/ │ ├── add-autoprefixer-options.md │ ├── add-postcss-features.md │ ├── add-stylelint.md │ ├── add-webpack-alias-to-jest.md │ ├── extends-postcss-plugins.md │ ├── set-css-loader-locals-convention.md │ ├── use-a-jest-config-file.md │ ├── use-a-postcss-config-file.md │ ├── use-an-eslint-config-file.md │ ├── use-an-https-dev-server.md │ ├── use-ant-design.md │ ├── use-babel-plugin-react-css-modules.md │ ├── use-dart-sass.md │ ├── use-html-loader.md │ ├── use-less-loader.md │ ├── use-linaria.md │ ├── use-markdown-loader.md │ ├── use-mobx.md │ ├── use-preact.md │ ├── use-purescript.md │ └── use-ts-loader.md ├── sidebars.js ├── sidebarsRecipes.js ├── src/ │ ├── components/ │ │ └── HomepageFeatures/ │ │ ├── index.js │ │ └── styles.module.css │ ├── css/ │ │ └── custom.scss │ └── pages/ │ └── index.js └── static/ ├── .nojekyll └── CNAME ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "es2021": true, "node": true }, "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "overrides": [], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "plugins": ["@typescript-eslint"], "rules": { "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-var-requires": "off", "@typescript-eslint/no-non-null-assertion": "off" } } ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf ================================================ FILE: .github/CODEOWNERS ================================================ * @dilanx ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [dilanx] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Something with CRACO isn't working correctly title: '' labels: bug assignees: '' --- **What's happening** (clearly describe what's going wrong) **What should happen** (clearly describe what should happen instead) **To reproduce** (list the steps to reproduce this behavior) **CRACO version** (ex. 7.0.0) **CRACO config** ```js // paste your config here ``` **package.json** ```jsonc // paste your package.json (or at least your project dependencies) here ``` **Additional information** (anything else that could be useful for us to help you solve your problem) ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: CRACO should have this functionality title: '' labels: feature request assignees: '' --- (clearly describe the functionality you think CRACO should have) ================================================ FILE: .github/workflows/run-tests.yml ================================================ name: tests on: [push, pull_request] jobs: build: runs-on: ubuntu-latest timeout-minutes: 10 strategy: matrix: node-version: [16.x, 18.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm ci - run: npm run build - run: npm run test:unit - run: npx playwright install-deps - run: NODE_ENV=production npm run test:integration ================================================ FILE: .github/workflows/website-deploy.yml ================================================ name: Deploy website to GitHub Pages on: push: branches: - main paths: - '.github/workflows/website-deploy.yml' - 'website/**' jobs: deploy: name: Deploy website to GitHub Pages runs-on: ubuntu-latest defaults: run: working-directory: website steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 with: node-version: 18 cache: npm - name: Install dependencies run: npm ci - name: Build website run: npm run build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./website/build user_name: github-actions[bot] user_email: 41898282+github-actions[bot]@users.noreply.github.com ================================================ FILE: .github/workflows/website-test-deploy.yml ================================================ name: Test website deployment defaults: run: working-directory: website on: pull_request: branches: - main paths: - 'website/**' jobs: test-deploy: name: Test website deployment runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v3 with: node-version: 18 cache: npm - name: Install dependencies run: npm ci - name: Test build website run: npm run build ================================================ FILE: .gitignore ================================================ # See https://help.github.com/ignore-files/ for more about ignoring files. # compiled dist # dependencies node_modules packages/debug # misc .DS_Store /.idea npm-debug.log* lerna-debug.log* *.tgz ================================================ FILE: .prettierignore ================================================ /.vscode /node_modules /recipes package.json package-lock.json tsconfig.json ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "esbenp.prettier-vscode" ] } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================

CRACO

**C**reate **R**eact **A**pp **C**onfiguration **O**verride, an easy and comprehensible configuration layer for create-react-app. **Find config docs, API docs, plugins, and example configs at [craco.js.org](https://craco.js.org)!**


[![npm status](https://img.shields.io/npm/v/@craco/craco.svg)](https://www.npmjs.com/package/@craco/craco) [![npm downloads](https://img.shields.io/npm/dm/@craco/craco.svg)](https://www.npmjs.com/package/@craco/craco) [![npm license](https://img.shields.io/npm/l/@craco/craco?color=orange)](https://github.com/dilanx/craco/blob/main/packages/craco/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/dilanx/craco?color=red)](https://github.com/dilanx/craco) [![GitHub contributors](https://img.shields.io/github/contributors/dilanx/craco?color=blueviolet)](https://github.com/dilanx/craco/graphs/contributors) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blueviolet.svg)](https://github.com/dilanx/craco/pulls) ![Node.js CI](https://github.com/dilanx/craco/actions/workflows/run-tests.yml/badge.svg)
Get all the benefits of [Create React App](https://create-react-app.dev) **and** customization without using 'eject' by adding a single configuration (e.g. `craco.config.js`) file at the root of your application and customize your ESLint, Babel, PostCSS configurations and many more. 1. Install the latest version of the package from npm as a dev dependency: ``` npm i -D @craco/craco ``` 2. Create a CRACO configuration file in your project's root directory and [configure](https://craco.js.org/docs/): ```diff my-app ├── node_modules + ├── craco.config.js └── package.json ``` 3. Update the existing calls to `react-scripts` in the `scripts` section of your `package.json` to use the `craco` CLI: ```diff title="package.json" "scripts": { - "start": "react-scripts start" + "start": "craco start" - "build": "react-scripts build" + "build": "craco build" - "test": "react-scripts test" + "test": "craco test" } ``` Visit [craco.js.org](https://craco.js.org) to learn more. ================================================ FILE: lerna.json ================================================ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, "version": "7.1.0" } ================================================ FILE: package.json ================================================ { "name": "craco", "private": true, "license": "Apache-2.0", "keywords": [ "react", "create-react-app", "cra" ], "workspaces": [ "packages/*" ], "scripts": { "test:unit": "jest --config test/unit/jest.config.js", "test:integration": "NODE_ENV=production jest --config test/integration/jest.config.js --runInBand", "lint": "npm run lint:ts && npm run lint:es", "lint:ts": "tsc --noEmit", "lint:es": "eslint --ext .ts", "tslint": "tsc --noEmit", "bootstrap": "lerna bootstrap", "build": "lerna exec npm run build", "build:types": "npm run build -w @craco/types", "build:craco": "npm run build -w @craco/craco", "publish": "lerna publish --no-private", "publish:alpha": "lerna publish --dist-tag alpha --no-private", "pack": "lerna exec npm pack", "refresh": "npm run build && npm run bootstrap", "refresh:types": "npm run build:types && npm run bootstrap" }, "prettier": "@dilanx/config/prettier", "devDependencies": { "@dilanx/config": "^1.1.0", "@typescript-eslint/eslint-plugin": "^5.38.1", "@typescript-eslint/parser": "^5.38.1", "eslint": "^8.24.0", "jest": "^29.5.0", "jest-playwright-preset": "^3.0.1", "lerna": "^6.5.1", "playwright": "^1.33.0", "prettier": "2.7.1" } } ================================================ FILE: packages/craco/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: packages/craco/README.md ================================================

CRACO

**C**reate **R**eact **A**pp **C**onfiguration **O**verride, an easy and comprehensible configuration layer for create-react-app. **Find config docs, API docs, plugins, and example configs at [craco.js.org](https://craco.js.org)!**


[![npm status](https://img.shields.io/npm/v/@craco/craco.svg)](https://www.npmjs.com/package/@craco/craco) [![npm downloads](https://img.shields.io/npm/dm/@craco/craco.svg)](https://www.npmjs.com/package/@craco/craco) [![npm license](https://img.shields.io/npm/l/@craco/craco?color=orange)](https://github.com/dilanx/craco/blob/main/packages/craco/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/dilanx/craco?color=red)](https://github.com/dilanx/craco) [![GitHub contributors](https://img.shields.io/github/contributors/dilanx/craco?color=blueviolet)](https://github.com/dilanx/craco/graphs/contributors) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blueviolet.svg)](https://github.com/dilanx/craco/pulls)
Get all the benefits of [Create React App](https://create-react-app.dev) **and** customization without using 'eject' by adding a single configuration (e.g. `craco.config.js`) file at the root of your application and customize your ESLint, Babel, PostCSS configurations and many more. 1. Install the latest version of the package from npm as a dev dependency: ``` npm i -D @craco/craco ``` 2. Create a CRACO configuration file in your project's root directory and [configure](https://craco.js.org/docs/): ```diff my-app ├── node_modules + ├── craco.config.js └── package.json ``` 3. Update the existing calls to `react-scripts` in the `scripts` section of your `package.json` to use the `craco` CLI: ```diff title="package.json" "scripts": { - "start": "react-scripts start" + "start": "craco start" - "build": "react-scripts build" + "build": "craco build" - "test": "react-scripts test" + "test": "craco test" } ``` Visit [craco.js.org](https://craco.js.org) to learn more. ================================================ FILE: packages/craco/package.json ================================================ { "name": "@craco/craco", "description": "Create React App Configuration Override, an easy and comprehensible configuration layer for create-react-app.", "version": "7.1.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "scripts": { "build": "tsc", "prepack": "npm run build" }, "publishConfig": { "access": "public" }, "repository": { "type": "git", "url": "https://github.com/dilanx/craco.git", "directory": "packages/craco" }, "keywords": [ "react", "create-react-app", "cra" ], "author": "Dilan Nair (https://www.dilanxd.com)", "contributors": [ "Groupe Sharegate inc." ], "license": "Apache-2.0", "bugs": { "url": "https://github.com/dilanx/craco/issues" }, "homepage": "https://craco.js.org", "engines": { "node": ">=6" }, "bin": { "craco": "./dist/bin/craco.js" }, "peerDependencies": { "react-scripts": "^5.0.0" }, "devDependencies": { "@babel/types": "^7.19.3", "@craco/types": "^7.1.0", "@jest/types": "^27.5.1", "@types/cross-spawn": "^6.0.2", "@types/eslint": "^8.4.6", "@types/jest": "^27.5.2", "@types/lodash": "^4.14.186", "@types/semver": "^7.3.12", "babel-jest": "^29.7.0", "eslint-webpack-plugin": "^3.2.0", "jest": "^27.5.1", "react-scripts": "5.*", "ts-jest": "^27.1.5", "typescript": "^4.8.4", "webpack": "^5.74.0" }, "dependencies": { "autoprefixer": "^10.4.12", "cosmiconfig": "^7.0.1", "cosmiconfig-typescript-loader": "^1.0.0", "cross-spawn": "^7.0.3", "lodash": "^4.17.21", "semver": "^7.3.7", "webpack-merge": "^5.8.0" } } ================================================ FILE: packages/craco/src/bin/craco.ts ================================================ #!/usr/bin/env node import spawn from 'cross-spawn'; const args = process.argv.slice(2); const scriptIndex = args.findIndex( (x) => x === 'build' || x === 'start' || x === 'test' ); const script = scriptIndex === -1 ? args[0] : args[scriptIndex]; switch (script) { case 'build': case 'start': case 'test': { const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []; const scriptPath = require.resolve(`../scripts/${script}`); const scriptArgs = args.slice(scriptIndex + 1); const processArgs = nodeArgs.concat(scriptPath).concat(scriptArgs); const child = spawn.sync('node', processArgs, { stdio: 'inherit' }); if (child.signal) { if (child.signal === 'SIGKILL') { console.log(` The build failed because the process exited too early. This probably means the system ran out of memory or someone called \`kill -9\` on the process. `); } else if (child.signal === 'SIGTERM') { console.log(` The build failed because the process exited too early. Someone might have called \`kill\` or \`killall\`, or the system could be shutting down. `); } process.exit(1); } process.exit(child.status ?? undefined); break; } default: console.log(`Unknown script "${script}".`); console.log('Perhaps you need to update craco?'); break; } ================================================ FILE: packages/craco/src/bin/jest.ts ================================================ #!/usr/bin/env node /* * Copied (and converted to TS) from https://github.com/timarney/react-app-rewired/blob/master/packages/react-app-rewired/bin/jest.js * This file is necessary to allow usage of craco as a drop-in replacement * for react-scripts with WebStorms's test runner UI. * * For more information, see https://github.com/dilanx/craco/pull/41 */ import spawn from 'cross-spawn'; const args = process.argv.slice(2); const setupScriptFileIndex = args.findIndex((x) => x === '--setupTestFrameworkScriptFile') + 1; const isIntelliJ = setupScriptFileIndex !== -1 ? false : args[setupScriptFileIndex].indexOf('jest-intellij') !== -1; const result = spawn.sync( process.argv[0], ([] as any[]).concat(require.resolve('../scripts/test'), args), { stdio: 'inherit', env: Object.assign({}, process.env, isIntelliJ ? { CI: 1 } : null), } ); process.exit(result.signal ? 1 : result.status ?? undefined); ================================================ FILE: packages/craco/src/index.ts ================================================ import { addAfterAssetModule, addAfterAssetModules, addBeforeAssetModule, addBeforeAssetModules, assetModuleByName, getAssetModule, getAssetModules, removeAssetModules, } from './lib/asset-modules'; import { createDevServerConfigProviderProxy } from './lib/features/dev-server/api'; import { createJestConfig } from './lib/features/jest/api'; import { createWebpackDevConfig, createWebpackProdConfig, } from './lib/features/webpack/api'; import { addAfterLoader, addAfterLoaders, addBeforeLoader, addBeforeLoaders, getLoader, getLoaders, loaderByName, removeLoaders, } from './lib/loaders'; import { gitHubIssueUrl, throwUnexpectedConfigError } from './lib/plugin-utils'; import { when, whenDev, whenProd, whenTest } from './lib/user-config-utils'; import { addPlugins, getPlugin, pluginByName, removePlugins, } from './lib/webpack-plugins'; export { getLoader, getLoaders, removeLoaders, addBeforeLoader, addBeforeLoaders, addAfterLoader, addAfterLoaders, loaderByName, getAssetModule, getAssetModules, removeAssetModules, addBeforeAssetModule, addBeforeAssetModules, addAfterAssetModule, addAfterAssetModules, assetModuleByName, getPlugin, pluginByName, addPlugins, removePlugins, when, whenDev, whenProd, whenTest, throwUnexpectedConfigError, gitHubIssueUrl, createJestConfig, createWebpackDevConfig, createWebpackProdConfig, createDevServerConfigProviderProxy, }; ================================================ FILE: packages/craco/src/lib/args.ts ================================================ export interface CliArgs { [key: string]: string | boolean; } export interface CliArgSpec { [key: string]: { arg: string; value: boolean }; } const args: CliArgSpec = { verbose: { arg: '--verbose', value: false, }, config: { arg: '--config', value: true, }, }; let processedArgs: CliArgs = {}; export function getArgs() { return processedArgs; } export function setArgs(values?: CliArgs) { processedArgs = { ...processedArgs, ...values, }; } export function findArgsFromCli() { const processed: CliArgs = {}; let i = 0; while (i < process.argv.length) { const arg = process.argv[i]; for (const a in args) { if (arg === args[a].arg) { processed[a] = args[a].value ? process.argv[i + 1] : true; i++; } } i++; } setArgs(processed); } ================================================ FILE: packages/craco/src/lib/asset-modules.ts ================================================ import type { AssetModule, AssetModuleMatcher, AssetModuleType, } from '@craco/types'; import type { Configuration as WebpackConfig, RuleSetRule } from 'webpack'; export function assetModuleByName(assetModuleName: AssetModuleType) { return (rule: RuleSetRule) => rule.type === assetModuleName; } const toMatchingAssetModule = ( rule: RuleSetRule, index: number ): AssetModule => ({ rule, index, }); export function getAssetModule( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher ) { let matchingAssetModule: AssetModule | undefined; (webpackConfig.module?.rules as RuleSetRule[])?.some((rule, index) => { if (matcher(rule)) { matchingAssetModule = toMatchingAssetModule(rule, index); } return matchingAssetModule !== undefined; }); return { isFound: matchingAssetModule !== undefined, match: matchingAssetModule, }; } export function getAssetModules( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher ) { const matchingAssetModules: AssetModule[] = []; (webpackConfig.module?.rules as RuleSetRule[])?.forEach((rule, index) => { if (matcher(rule)) { matchingAssetModules.push(toMatchingAssetModule(rule, index)); } }); return { hasFoundAny: matchingAssetModules.length !== 0, matches: matchingAssetModules, }; } export function removeAssetModules( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher ) { const toRemove: number[] = []; (webpackConfig.module?.rules as RuleSetRule[])?.forEach((rule, index) => { if (matcher(rule)) { toRemove.push(index); } }); toRemove.forEach((index) => { webpackConfig.module?.rules?.splice(index, 1); }); return { rules: webpackConfig.module?.rules, removedCount: toRemove.length, }; } function addAssetModule( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule, positionAdapter: (index: number) => number ) { const { match } = getAssetModule(webpackConfig, matcher); if (match !== undefined) { webpackConfig.module?.rules?.splice( positionAdapter(match.index), 0, newAssetModule ); return { isAdded: true }; } return { isAdded: false }; } export const addBeforeAssetModule = ( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule ) => addAssetModule(webpackConfig, matcher, newAssetModule, (x) => x); export const addAfterAssetModule = ( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule ) => addAssetModule(webpackConfig, matcher, newAssetModule, (x) => x + 1); function addAssetModules( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule, positionAdapter: (index: number) => number ) { const { matches } = getAssetModules(webpackConfig, matcher); if (matches.length !== 0) { matches.forEach((match) => { webpackConfig.module?.rules?.splice( positionAdapter(match.index), 0, newAssetModule ); }); return { isAdded: true, addedCount: matches.length }; } return { isAdded: false, addedCount: 0 }; } export const addBeforeAssetModules = ( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule ) => addAssetModules(webpackConfig, matcher, newAssetModule, (x) => x); export const addAfterAssetModules = ( webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: RuleSetRule ) => addAssetModules(webpackConfig, matcher, newAssetModule, (x) => x + 1); ================================================ FILE: packages/craco/src/lib/config.ts ================================================ import type { BaseContext, CracoConfig } from '@craco/types'; import { cosmiconfigSync } from 'cosmiconfig'; import tsLoader from 'cosmiconfig-typescript-loader'; import path from 'path'; import { getArgs } from './args'; import { applyCracoConfigPlugins } from './features/plugins'; import { log } from './logger'; import { projectRoot } from './paths'; import { deepMergeWithArray, isArray, isFunction, isString } from './utils'; const DEFAULT_CONFIG: CracoConfig = { reactScriptsVersion: 'react-scripts', style: { postcss: { mode: 'extends', }, }, eslint: { mode: 'extends', }, jest: { babel: { addPresets: true, addPlugins: true, }, }, }; const moduleName = 'craco'; const explorer = cosmiconfigSync(moduleName, { searchPlaces: [ 'package.json', `${moduleName}.config.ts`, `${moduleName}.config.js`, `${moduleName}.config.cjs`, `.${moduleName}rc.ts`, `.${moduleName}rc.js`, `.${moduleName}rc`, ], loaders: { '.ts': tsLoader(), }, }); function ensureConfigSanity(cracoConfig: CracoConfig) { if (cracoConfig.plugins && isArray(cracoConfig.plugins)) { cracoConfig.plugins.forEach((x, index) => { if (!x.plugin) { throw new Error( `craco: Malformed plugin at index: ${index} of 'plugins'.` ); } }); } } export function processCracoConfig( cracoConfig: CracoConfig, context: BaseContext ) { const resultingCracoConfig = deepMergeWithArray( {}, DEFAULT_CONFIG, cracoConfig ); ensureConfigSanity(resultingCracoConfig); return applyCracoConfigPlugins(resultingCracoConfig, context); } function getConfigPath() { const args = getArgs(); if (args.config && isString(args.config)) { return path.resolve(projectRoot, args.config); } else { const packageJsonPath = path.join(projectRoot, 'package.json'); const packageJson = require(packageJsonPath); if (packageJson.cracoConfig && isString(packageJson.cracoConfig)) { // take it as the path to the config file if it's path-like, otherwise assume it contains the config content below return path.resolve(projectRoot, packageJson.cracoConfig); } else { const result = explorer.search(projectRoot); if (result === null) { throw new Error( 'craco: Config file not found. check if file exists at root (craco.config.ts, craco.config.js, .cracorc.js, .cracorc.json, .cracorc.yaml, .cracorc)' ); } return result.filepath; } } } function getConfigAsObject(context: BaseContext) { const configFilePath = getConfigPath(); log('Config file path resolved to: ', configFilePath); const result = explorer.load(configFilePath); const configAsObject = isFunction(result?.config) ? result?.config(context) : result?.config; if (!configAsObject) { throw new Error("craco: Config function didn't return a config object."); } return configAsObject; } export function loadCracoConfig(context: BaseContext) { const configAsObject = getConfigAsObject(context); if (configAsObject instanceof Promise) { throw new Error( 'craco: Config function returned a promise. Use `loadCracoConfigAsync` instead of `loadCracoConfig`.' ); } return processCracoConfig(configAsObject, context); } // The "build", "start", and "test" scripts use this to wait for any promises to resolve before they run. export async function loadCracoConfigAsync(context: BaseContext) { const configAsObject = await getConfigAsObject(context); if (!configAsObject) { throw new Error("craco: Async config didn't return a config object."); } return processCracoConfig(configAsObject, context); } ================================================ FILE: packages/craco/src/lib/cra.ts ================================================ import type { CracoConfig, CraPaths, DevServerConfigProvider, JestConfigProvider, } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import path from 'path'; import semver from 'semver'; import { log } from './logger'; import { projectRoot } from './paths'; let envLoaded = false; const CRA_LATEST_SUPPORTED_MAJOR_VERSION = '5.0.0'; /************ Common ************/ function resolveConfigFilePath(cracoConfig: CracoConfig, fileName: string) { if (!envLoaded) { // Environment variables must be loaded before the CRA paths, otherwise they will not be applied. require(resolveConfigFilePathInner(cracoConfig, 'env.js')); envLoaded = true; } return resolveConfigFilePathInner(cracoConfig, fileName); } function resolveConfigFilePathInner( cracoConfig: CracoConfig, fileName: string ) { return require.resolve( path.join( cracoConfig.reactScriptsVersion ?? 'react-scripts', 'config', fileName ), { paths: [projectRoot] } ); } function resolveScriptsFilePath(cracoConfig: CracoConfig, fileName: string) { return require.resolve( path.join( cracoConfig.reactScriptsVersion ?? 'react-scripts', 'scripts', fileName ), { paths: [projectRoot] } ); } function resolveReactDevUtilsPath(fileName: string) { return require.resolve(path.join('react-dev-utils', fileName), { paths: [projectRoot], }); } function overrideModule(modulePath: string, newModule: any) { if (!require.cache[modulePath]) { throw new Error(`Module not found: ${modulePath}`); } require.cache[modulePath]!.exports = newModule; log(`Overrode require cache for module: ${modulePath}`); } function resolvePackageJson(cracoConfig: CracoConfig) { return require.resolve( path.join( cracoConfig.reactScriptsVersion ?? 'react-scripts', 'package.json' ), { paths: [projectRoot] } ); } export function getReactScriptVersion(cracoConfig: CracoConfig) { const reactScriptPackageJsonPath = resolvePackageJson(cracoConfig); const { version } = require(reactScriptPackageJsonPath); return { version, isSupported: semver.gte(version, CRA_LATEST_SUPPORTED_MAJOR_VERSION), }; } /************ Paths ************/ let _resolvedCraPaths: any = null; export function getCraPathsPath(cracoConfig: CracoConfig) { return resolveConfigFilePath(cracoConfig, 'paths.js'); } export function getCraPaths(cracoConfig: CracoConfig) { if (!_resolvedCraPaths) { _resolvedCraPaths = require(getCraPathsPath(cracoConfig)); } return _resolvedCraPaths; } export function overrideCraPaths( cracoConfig: CracoConfig, newConfig?: CraPaths ) { overrideModule(getCraPathsPath(cracoConfig), newConfig); log('Overrode CRA paths config.'); } /************ Webpack Dev Config ************/ function getWebpackDevConfigPath(cracoConfig: CracoConfig) { try { return { filepath: resolveConfigFilePath(cracoConfig, 'webpack.config.js'), isLegacy: false, }; } catch (e) { return { filepath: resolveConfigFilePath(cracoConfig, 'webpack.config.dev.js'), isLegacy: true, }; } } export function loadWebpackDevConfig(cracoConfig: CracoConfig): WebpackConfig { const result = getWebpackDevConfigPath(cracoConfig); log('Found Webpack dev config at: ', result.filepath); if (result.isLegacy) { return require(result.filepath); } return require(result.filepath)('development'); } export function overrideWebpackDevConfig( cracoConfig: CracoConfig, newConfig: WebpackConfig ) { const result = getWebpackDevConfigPath(cracoConfig); if (result.isLegacy) { overrideModule(result.filepath, newConfig); } else { overrideModule(result.filepath, () => newConfig); } log('Overrode Webpack dev config.'); } /************ Webpack Prod Config ************/ function getWebpackProdConfigPath(cracoConfig: CracoConfig) { try { return { filepath: resolveConfigFilePath(cracoConfig, 'webpack.config.js'), isLegacy: false, }; } catch (e) { return { filepath: resolveConfigFilePath(cracoConfig, 'webpack.config.prod.js'), isLegacy: true, }; } } export function loadWebpackProdConfig(cracoConfig: CracoConfig): WebpackConfig { const result = getWebpackProdConfigPath(cracoConfig); log('Found Webpack prod config at: ', result.filepath); if (result.isLegacy) { return require(result.filepath); } return require(result.filepath)('production'); } export function overrideWebpackProdConfig( cracoConfig: CracoConfig, newConfig: WebpackConfig ) { const result = getWebpackProdConfigPath(cracoConfig); if (result.isLegacy) { overrideModule(result.filepath, newConfig); } else { overrideModule(result.filepath, () => newConfig); } log('Overrode Webpack prod config.'); } /************ Dev Server Config ************/ function getDevServerConfigPath(cracoConfig: CracoConfig) { return resolveConfigFilePath(cracoConfig, 'webpackDevServer.config.js'); } function getDevServerUtilsPath() { return resolveReactDevUtilsPath('WebpackDevServerUtils.js'); } export function loadDevServerConfigProvider( cracoConfig: CracoConfig ): DevServerConfigProvider { const filepath = getDevServerConfigPath(cracoConfig); log('Found dev server config at: ', filepath); return require(filepath); } export function overrideDevServerConfigProvider( cracoConfig: CracoConfig, configProvider: any ) { const filepath = getDevServerConfigPath(cracoConfig); overrideModule(filepath, configProvider); log('Overrode dev server config provider.'); } export function loadDevServerUtils() { const filepath = getDevServerUtilsPath(); log('Found dev server utils at: ', filepath); return require(filepath); } export function overrideDevServerUtils(newUtils: any) { const filepath = getDevServerUtilsPath(); overrideModule(filepath, newUtils); log('Overrode dev server utils.'); } /************ Jest Config ************/ function getCreateJestConfigPath(cracoConfig: CracoConfig) { return resolveScriptsFilePath(cracoConfig, 'utils/createJestConfig.js'); } // https://github.com/facebook/create-react-app/blob/main/packages/react-scripts/scripts/utils/createJestConfig.js export function loadJestConfigProvider( cracoConfig: CracoConfig ): JestConfigProvider { const filepath = getCreateJestConfigPath(cracoConfig); log('Found jest config at: ', filepath); return require(filepath); } export function overrideJestConfigProvider( cracoConfig: CracoConfig, configProvider: any ) { const filepath = getCreateJestConfigPath(cracoConfig); overrideModule(filepath, configProvider); log('Overrode Jest config provider.'); } /************ Scripts *******************/ export function start(cracoConfig: CracoConfig) { const filepath = resolveScriptsFilePath(cracoConfig, 'start.js'); log('Starting CRA at: ', filepath); require(filepath); } export function build(cracoConfig: CracoConfig) { const filepath = resolveScriptsFilePath(cracoConfig, 'build.js'); log('Building CRA at: ', filepath); require(filepath); } export function test(cracoConfig: CracoConfig) { const filepath = resolveScriptsFilePath(cracoConfig, 'test.js'); log('Testing CRA at: ', filepath); require(filepath); } ================================================ FILE: packages/craco/src/lib/features/dev-server/api.ts ================================================ import type { CracoConfig, DevServerContext } from '@craco/types'; import type { CliArgs } from '../../args'; import { setArgs } from '../../args'; import { processCracoConfig } from '../../config'; import { getCraPaths } from '../../cra'; import { isFunction } from '../../utils'; import { createConfigProviderProxy } from './create-config-provider-proxy'; export function createDevServerConfigProviderProxy( callerCracoConfig: CracoConfig, callerContext: DevServerContext, options: CliArgs ) { if (!callerCracoConfig) { throw new Error("craco: 'cracoConfig' is required."); } if (isFunction(callerCracoConfig)) { throw new Error("craco: 'cracoConfig' should be an object."); } if (!process.env.NODE_ENV) { process.env.NODE_ENV = 'development'; } setArgs(options); const context: DevServerContext = { env: process.env.NODE_ENV, ...callerContext, }; const cracoConfig = processCracoConfig(callerCracoConfig, context); context.paths = getCraPaths(cracoConfig); const proxy = createConfigProviderProxy(cracoConfig, context); return proxy; } ================================================ FILE: packages/craco/src/lib/features/dev-server/create-config-provider-proxy.ts ================================================ import type { CracoConfig, DevServerContext, DevServerConfigProvider, } from '@craco/types'; import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import merge from 'webpack-merge'; import { loadDevServerConfigProvider } from '../../cra'; import { log } from '../../logger'; import { isFunction } from '../../utils'; import { applyDevServerConfigPlugins } from '../plugins'; import { setEnvironmentVariables } from './set-environment-variables'; function createProxy( cracoConfig: CracoConfig, craDevServerConfigProvider: DevServerConfigProvider, context: DevServerContext ) { const proxy = (proxy: any, allowedHost: string) => { let devServerConfig = craDevServerConfigProvider(proxy, allowedHost); if (isFunction(cracoConfig.devServer)) { devServerConfig = cracoConfig.devServer(devServerConfig, { ...context, proxy, allowedHost, }); if (!devServerConfig) { throw new Error( "craco: 'devServer' function didn't return a config object." ); } } else { // TODO: ensure is otherwise a plain object, if not, log an error. devServerConfig = merge( devServerConfig, cracoConfig.devServer || {} ); } devServerConfig = applyDevServerConfigPlugins( cracoConfig, devServerConfig, { ...context, proxy, allowedHost, } ); log('Merged DevServer config.'); return devServerConfig; }; return proxy; } export function createConfigProviderProxy( cracoConfig: CracoConfig, context: DevServerContext ) { const craDevServerConfigProvider = loadDevServerConfigProvider(cracoConfig); const proxy = createProxy(cracoConfig, craDevServerConfigProvider, context); return proxy; } ================================================ FILE: packages/craco/src/lib/features/dev-server/override-utils.ts ================================================ import type { CracoConfig } from '@craco/types'; import { loadDevServerUtils, overrideDevServerUtils } from '../../cra'; import { log } from '../../logger'; function overrideWebpackCompilerToDisableTypeScriptTypeChecking( craDevServersUtils: any ) { if (craDevServersUtils.createCompiler) { const craCreateCompiler = craDevServersUtils.createCompiler; craDevServersUtils.createCompiler = (args: any) => { const newArgs = { ...args, useTypeScript: false, }; return craCreateCompiler(newArgs); }; log('Overrided Webpack compiler to disable TypeScript type checking.'); } return craDevServersUtils; } function overrideUtils(cracoConfig: CracoConfig) { if (cracoConfig.typescript) { const { enableTypeChecking } = cracoConfig.typescript; if (enableTypeChecking === false) { const craDevServersUtils = loadDevServerUtils(); const resultingDevServersUtils = overrideWebpackCompilerToDisableTypeScriptTypeChecking( craDevServersUtils ); overrideDevServerUtils(resultingDevServersUtils); } } } export { overrideUtils as overrideDevServerUtils }; ================================================ FILE: packages/craco/src/lib/features/dev-server/override.ts ================================================ import type { CracoConfig, DevServerContext } from '@craco/types'; import { overrideDevServerConfigProvider } from '../../cra'; import { isFunction } from '../../utils'; import { createConfigProviderProxy } from './create-config-provider-proxy'; import { overrideDevServerUtils } from './override-utils'; import { setEnvironmentVariables } from './set-environment-variables'; export function overrideDevServer( cracoConfig: CracoConfig, context: DevServerContext ) { overrideDevServerUtils(cracoConfig); if (cracoConfig.devServer && !isFunction(cracoConfig.devServer)) { setEnvironmentVariables(cracoConfig.devServer); } const proxy = createConfigProviderProxy(cracoConfig, context); overrideDevServerConfigProvider(cracoConfig, proxy); } ================================================ FILE: packages/craco/src/lib/features/dev-server/set-environment-variables.ts ================================================ import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import { isString } from '../../utils'; function setEnvironmentVariable(envProperty: string, value: any) { if (!isString(value)) { process.env[envProperty] = value.toString(); } else { process.env[envProperty] = value; } } export function setEnvironmentVariables(devServerConfig: DevServerConfig) { const { open, https, host, port } = devServerConfig; if (open === false) { setEnvironmentVariable('BROWSER', 'none'); } if (https) { setEnvironmentVariable('HTTPS', 'true'); } if (host) { setEnvironmentVariable('HOST', host); } if (port) { setEnvironmentVariable('PORT', port); } } ================================================ FILE: packages/craco/src/lib/features/jest/api.ts ================================================ import type { CracoConfig, JestContext } from '@craco/types'; import type { Config as JestConfig } from '@jest/types'; import type { CliArgs } from '../../args'; import { setArgs } from '../../args'; import { processCracoConfig } from '../../config'; import { getCraPaths, loadJestConfigProvider } from '../../cra'; import { isFunction } from '../../utils'; import { mergeJestConfig } from './merge-jest-config'; export function createJestConfig( callerCracoConfig: CracoConfig, callerContext: JestContext = {}, options: CliArgs = {} ): JestConfig.InitialOptions { if (!callerCracoConfig) { throw new Error("craco: 'cracoConfig' is required."); } if (isFunction(callerCracoConfig)) { throw new Error("craco: 'cracoConfig' should be an object."); } if (!process.env.NODE_ENV) { process.env.NODE_ENV = 'development'; } setArgs(options); const context: JestContext = { env: process.env.NODE_ENV, ...callerContext, }; const cracoConfig = processCracoConfig(callerCracoConfig, context); context.paths = getCraPaths(cracoConfig); const craJestConfigProvider = loadJestConfigProvider(cracoConfig); return mergeJestConfig(cracoConfig, craJestConfigProvider, context); } ================================================ FILE: packages/craco/src/lib/features/jest/create-jest-babel-transform.ts ================================================ import type { CracoConfig } from '@craco/types'; import babelJest from 'babel-jest'; import { loadCracoConfig } from '../../config'; import { isArray } from '../../utils'; /** * To check if support jsx-runtime * Copy from https://github.com/facebook/create-react-app/blob/2b1161b34641bb4d2f269661cd636bbcd4888406/packages/react-scripts/config/jest/babelTransform.js#L12 */ const hasJsxRuntime = (() => { if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { return false; } try { require.resolve('react/jsx-runtime'); return true; } catch (e) { return false; } })(); export function createJestBabelTransform(cracoConfig?: CracoConfig): any { if (!cracoConfig) { const context = { env: process.env.NODE_ENV }; cracoConfig = loadCracoConfig(context); } const craBabelTransformer: any = { presets: [ [ 'babel-preset-react-app', { runtime: hasJsxRuntime ? 'automatic' : 'classic', }, ], ], babelrc: false, configFile: false, }; if (cracoConfig) { const { addPresets, addPlugins } = cracoConfig.jest?.babel ?? {}; if (cracoConfig.babel) { if (addPresets) { const { presets } = cracoConfig.babel; if (isArray(presets)) { craBabelTransformer.presets = craBabelTransformer.presets?.concat(presets); } } if (addPlugins) { const { plugins } = cracoConfig.babel; if (isArray(plugins)) { craBabelTransformer.plugins = plugins; } } } } return babelJest.createTransformer ? babelJest.createTransformer(craBabelTransformer) : undefined; } ================================================ FILE: packages/craco/src/lib/features/jest/jest-babel-transform.ts ================================================ import type { TransformOptions as BTransformOptions } from '@babel/core'; import type { SyncTransformer, TransformOptions as JTransformOptions, } from '@jest/transform'; import { loadCracoConfigAsync } from '../../config'; import { createJestBabelTransform } from './create-jest-babel-transform'; let jestBabelTransform: SyncTransformer | undefined; // cracoConfig is only available inside the transform, but the transform needs to include whatever options cracoConfig // specifies. So, the first time this transform is run, it generates a new transform -- using cracoConfig -- and // uses that to process files. module.exports = { ...createJestBabelTransform(), async processAsync( src: string, filename: string, transformOptions: JTransformOptions ) { if (!jestBabelTransform) { const context = { env: process.env.NODE_ENV }; const cracoConfig = await loadCracoConfigAsync(context); jestBabelTransform = createJestBabelTransform(cracoConfig); } return jestBabelTransform?.process(src, filename, transformOptions); }, }; ================================================ FILE: packages/craco/src/lib/features/jest/merge-jest-config.ts ================================================ import type { Configure, CracoConfig, JestConfigProvider, JestContext, } from '@craco/types'; import type { Config as JestConfig } from '@jest/types'; import path from 'path'; import { log } from '../../logger'; import { projectRoot } from '../../paths'; import { deepMergeWithArray, isArray, isFunction } from '../../utils'; import { applyJestConfigPlugins } from '../plugins'; const BABEL_TRANSFORM_ENTRY_KEY = '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$'; function overrideBabelTransform( jestConfig: JestConfig.InitialOptions, cracoConfig: CracoConfig, transformKey: string ) { // The cracoConfig needs to be available within the jest-babel-transform in order to honor its settings. // This approach is based on https://github.com/facebook/jest/issues/1468#issuecomment-384825178 jestConfig.globals = jestConfig.globals || {}; jestConfig.globals._cracoConfig = cracoConfig; if (!jestConfig.transform) { jestConfig.transform = {}; } jestConfig.transform[transformKey] = require.resolve( './jest-babel-transform' ); log('Overrided Jest Babel transformer.'); } function configureBabel( jestConfig: JestConfig.InitialOptions, cracoConfig: CracoConfig ) { const { addPresets, addPlugins } = cracoConfig.jest?.babel ?? {}; if (addPresets || addPlugins) { if (cracoConfig.babel) { const { presets, plugins } = cracoConfig.babel; if (isArray(presets) || isArray(plugins)) { if (!jestConfig.transform) { jestConfig.transform = {}; } if (jestConfig.transform[BABEL_TRANSFORM_ENTRY_KEY]) { overrideBabelTransform( jestConfig, cracoConfig, BABEL_TRANSFORM_ENTRY_KEY ); } else { throw new Error( `craco: Cannot find Jest transform entry for Babel ${BABEL_TRANSFORM_ENTRY_KEY}.` ); } } } } } function giveTotalControl( jestConfig: JestConfig.InitialOptions, configureJest: Configure, context: JestContext ) { if (isFunction(configureJest)) { jestConfig = configureJest(jestConfig, context); if (!jestConfig) { throw new Error( "craco: 'jest.configure' function didn't returned a Jest config object." ); } } else { // TODO: ensure is otherwise a plain object, if not, log an error. jestConfig = deepMergeWithArray({}, jestConfig, configureJest); } log("Merged Jest config with 'jest.configure'."); return jestConfig; } export function mergeJestConfig( cracoConfig: CracoConfig, craJestConfigProvider: JestConfigProvider, context: JestContext ): JestConfig.InitialOptions { const customResolve = (relativePath: string) => require.resolve( path.join( cracoConfig.reactScriptsVersion ?? 'react-scripts', relativePath ), { paths: [projectRoot] } ); let jestConfig = craJestConfigProvider(customResolve, projectRoot, false); if (cracoConfig.jest) { configureBabel(jestConfig, cracoConfig); const jestContext = { ...context, resolve: customResolve, rootDir: projectRoot, }; if (cracoConfig.jest.configure) { jestConfig = giveTotalControl( jestConfig, cracoConfig.jest.configure, jestContext ); } jestConfig = applyJestConfigPlugins(cracoConfig, jestConfig, jestContext); log('Merged Jest config.'); } return jestConfig; } ================================================ FILE: packages/craco/src/lib/features/jest/override.ts ================================================ import type { CracoConfig, JestContext } from '@craco/types'; import { loadJestConfigProvider, overrideJestConfigProvider } from '../../cra'; import { log } from '../../logger'; import { mergeJestConfig } from './merge-jest-config'; export function overrideJest(cracoConfig: CracoConfig, context: JestContext) { if (cracoConfig.jest) { const craJestConfigProvider = loadJestConfigProvider(cracoConfig); const proxy = () => { return mergeJestConfig(cracoConfig, craJestConfigProvider, context); }; overrideJestConfigProvider(cracoConfig, proxy); log('Overrided Jest config.'); } } ================================================ FILE: packages/craco/src/lib/features/paths/override.ts ================================================ import type { BaseContext, CracoConfig, CraPaths } from '@craco/types'; import { overrideCraPaths } from '../../cra'; import { isFunction } from '../../utils'; export function overridePaths(cracoConfig: CracoConfig, context: BaseContext) { let newConfig: CraPaths | undefined = context.paths; if (cracoConfig.paths) { if (isFunction(cracoConfig.paths)) { newConfig = cracoConfig.paths(newConfig, context); } else { newConfig = { ...newConfig, ...cracoConfig.paths, }; } overrideCraPaths(cracoConfig, newConfig); } return newConfig; } ================================================ FILE: packages/craco/src/lib/features/plugins.ts ================================================ import type { BaseContext, CracoConfig, CracoPluginDefinition, DevServerContext, JestContext, WebpackContext, } from '@craco/types'; import type { Config as JestConfig } from '@jest/types'; import type { Configuration as WebpackConfig } from 'webpack'; import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import { log } from '../logger'; /************ Craco Config ************/ function overrideCraco( { plugin, options }: CracoPluginDefinition, cracoConfig: CracoConfig, context: BaseContext ) { if (plugin.overrideCracoConfig) { const resultingConfig = plugin.overrideCracoConfig({ cracoConfig: cracoConfig, pluginOptions: options, context: context, }); if (!resultingConfig) { throw new Error('craco: Plugin returned an undefined craco config.'); } return resultingConfig; } log('Overrided craco config with plugin.'); return cracoConfig; } export function applyCracoConfigPlugins( cracoConfig: CracoConfig, context: BaseContext ) { if (cracoConfig.plugins) { cracoConfig.plugins.forEach((plugin) => { cracoConfig = overrideCraco(plugin, cracoConfig, context); }); } log('Applied craco config plugins.'); return cracoConfig; } /************ Webpack Config ************/ function overrideWebpack( { plugin, options }: CracoPluginDefinition, cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: WebpackContext ) { if (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.'); } return resultingConfig; } log('Overrided webpack config with plugin.'); return webpackConfig; } export function applyWebpackConfigPlugins( cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: WebpackContext ) { if (cracoConfig.plugins) { cracoConfig.plugins.forEach((plugin) => { webpackConfig = overrideWebpack( plugin, cracoConfig, webpackConfig, context ); }); } log('Applied webpack config plugins.'); return webpackConfig; } /************ DevServer Config ************/ function overrideDevServer( { plugin, options }: CracoPluginDefinition, cracoConfig: CracoConfig, devServerConfig: DevServerConfig, context: DevServerContext ) { if (plugin.overrideDevServerConfig) { const resultingConfig = plugin.overrideDevServerConfig({ cracoConfig: cracoConfig, devServerConfig: devServerConfig, pluginOptions: options, context: context, }); if (!resultingConfig) { throw new Error('craco: Plugin returned an undefined devServer config.'); } return resultingConfig; } log('Overrided devServer config with plugin.'); return devServerConfig; } export function applyDevServerConfigPlugins( cracoConfig: CracoConfig, devServerConfig: DevServerConfig, context: DevServerContext ) { if (cracoConfig.plugins) { cracoConfig.plugins.forEach((plugin) => { devServerConfig = overrideDevServer( plugin, cracoConfig, devServerConfig, context ); }); } log('Applied devServer config plugins.'); return devServerConfig; } /************ Jest Config *******************/ function overrideJest( { plugin, options }: CracoPluginDefinition, cracoConfig: CracoConfig, jestConfig: JestConfig.InitialOptions, context: JestContext ) { if (plugin.overrideJestConfig) { const resultingConfig = plugin.overrideJestConfig({ cracoConfig: cracoConfig, jestConfig: jestConfig, pluginOptions: options, context: context, }); if (!resultingConfig) { throw new Error('craco: Plugin returned an undefined Jest config.'); } return resultingConfig; } log('Overrided Jest config with plugin.'); return jestConfig; } export function applyJestConfigPlugins( cracoConfig: CracoConfig, jestConfig: JestConfig.InitialOptions, context: JestContext ) { if (cracoConfig.plugins) { cracoConfig.plugins.forEach((plugin) => { jestConfig = overrideJest(plugin, cracoConfig, jestConfig, context); }); } log('Applied Jest config plugins.'); return jestConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/api.ts ================================================ import type { CracoConfig, WebpackContext } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import type { CliArgs } from '../../args'; import { setArgs } from '../../args'; import { processCracoConfig } from '../../config'; import { getCraPaths, loadWebpackDevConfig, loadWebpackProdConfig, } from '../../cra'; import { isFunction } from '../../utils'; import { mergeWebpackConfig } from './merge-webpack-config'; export function createWebpackDevConfig( callerCracoConfig: CracoConfig, callerContext?: WebpackContext, options?: CliArgs ) { return createWebpackConfig( callerCracoConfig, callerContext, loadWebpackDevConfig, 'development', options ); } export function createWebpackProdConfig( callerCracoConfig: CracoConfig, callerContext?: WebpackContext, options?: CliArgs ) { return createWebpackConfig( callerCracoConfig, callerContext, loadWebpackProdConfig, 'production', options ); } function createWebpackConfig( callerCracoConfig: CracoConfig, callerContext: WebpackContext = {}, loadWebpackConfig: (cracoConfig: CracoConfig) => WebpackConfig, env: string, options: CliArgs = {} ) { if (!callerCracoConfig) { throw new Error("craco: 'cracoConfig' is required."); } if (isFunction(callerCracoConfig)) { throw new Error("craco: 'cracoConfig' should be an object."); } if (!process.env.NODE_ENV) { process.env.NODE_ENV = env; } setArgs(options); const context: WebpackContext = { env: process.env.NODE_ENV, ...callerContext, }; const cracoConfig = processCracoConfig(callerCracoConfig, context); context.paths = getCraPaths(cracoConfig); const craWebpackConfig = loadWebpackConfig(cracoConfig); const resultingWebpackConfig = mergeWebpackConfig( cracoConfig, craWebpackConfig, context ); return resultingWebpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/babel.ts ================================================ import type { BaseContext, CompleteLoader, Configure, CracoConfig, } from '@craco/types'; import type { Configuration as WebpackConfig, RuleSetRule } from 'webpack'; import { TransformOptions } from '@babel/core'; import { getLoaders, loaderByName } from '../../loaders'; import { log, logError } from '../../logger'; import { deepMergeWithArray, isArray, isFunction, isString } from '../../utils'; // TODO: CRA use a cacheIdentifier, should we update it with the new plugins? function addPresets(loader: RuleSetRule, babelPresets: any[]) { if (isArray(babelPresets)) { if (loader.options && !isString(loader.options)) { if (loader.options.presets) { loader.options.presets = loader.options.presets.concat(babelPresets); } else { loader.options.presets = babelPresets; } } else { loader.options = { presets: babelPresets, }; } } log('Added Babel presets.'); } function addPlugins(loader: RuleSetRule, babelPlugins: any[]) { if (isArray(babelPlugins)) { if (loader.options && !isString(loader.options)) { if (loader.options.plugins) { loader.options.plugins = loader.options.plugins.concat(babelPlugins); } else { loader.options.plugins = babelPlugins; } } else { loader.options = { plugins: babelPlugins, }; } } log('Added Babel plugins.'); } function addAssumptions( loader: RuleSetRule, babelAssumptions: { [assumption: string]: boolean } ) { if (loader.options && !isString(loader.options)) { if (loader.options.assumptions) { loader.options.assumptions = { ...loader.options.assumptions, ...babelAssumptions, }; } else { loader.options.assumptions = babelAssumptions; } } else { loader.options = { assumptions: babelAssumptions, }; } log('Added Babel assumptions.'); } function applyLoaderOptions( loader: RuleSetRule, loaderOptions: Configure, context: BaseContext ) { if (isFunction(loaderOptions)) { loader.options = loaderOptions( (loader.options as TransformOptions) || {}, context ); if (!loader.options) { throw new Error( "craco: 'babel.loaderOptions' function didn't return a loader config object." ); } } 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: CompleteLoader, cracoConfig: CracoConfig, context: BaseContext ) { const { presets, plugins, assumptions, loaderOptions } = cracoConfig.babel ?? {}; if (presets) { addPresets(match.loader, presets); } if (plugins) { addPlugins(match.loader, plugins); } if (assumptions) { addAssumptions(match.loader, assumptions); } if (loaderOptions) { applyLoaderOptions(match.loader, loaderOptions, context); } } export function overrideBabel( cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (cracoConfig.babel) { const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('babel-loader') ); if (!hasFoundAny) { logError('Cannot find any Babel loaders.'); return webpackConfig; } matches.forEach((x) => { overrideLoader(x as CompleteLoader, cracoConfig, context); }); } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/eslint.ts ================================================ import type { BaseContext, Configure, CracoConfig, CracoEsLintConfig, } from '@craco/types'; import type { PluginOptions } from 'eslint-webpack-plugin/types/options'; import type { Configuration as WebpackConfig } from 'webpack'; import { log, logError } from '../../logger'; import { deepMergeWithArray, isFunction } from '../../utils'; import { getPlugin, pluginByName, removePlugins } from '../../webpack-plugins'; function disableEslint(webpackConfig: WebpackConfig) { const { hasRemovedAny } = removePlugins( webpackConfig, pluginByName('ESLintWebpackPlugin') ); if (hasRemovedAny) { log('Disabled ESLint.'); } else { logError("Couldn't disabled ESLint."); } } function extendsEslintConfig( plugin: any, eslintConfig: CracoEsLintConfig, context: BaseContext ) { const { configure } = eslintConfig; if (configure) { if (isFunction(configure)) { if (plugin.options) { plugin.options.baseConfig = configure( plugin.options.baseConfig || {}, context ); } else { plugin.options = { baseConfig: configure({}, context), }; } if (!plugin.options.baseConfig) { throw new Error( "craco: 'eslint.configure' function didn't return a config object." ); } } else { // TODO: ensure is otherwise a plain object, if not, log an error. if (plugin.options) { plugin.options.baseConfig = deepMergeWithArray( {}, plugin.options.baseConfig || {}, configure ); } else { plugin.options = { baseConfig: configure, }; } } log("Merged ESLint config with 'eslint.configure'."); } } function useEslintConfigFile(plugin: any) { if (plugin.options) { plugin.options.useEslintrc = true; delete plugin.options.baseConfig; } else { plugin.options = { useEslintrc: true, }; } log('Overrided ESLint config to use a config file.'); } function enableEslintIgnoreFile(plugin: any) { if (plugin.options) { plugin.options.ignore = true; } else { plugin.options = { ignore: true, }; } log('Overrided ESLint config to enable an ignore file.'); } function applyPluginOptions( plugin: any, pluginOptions: Configure, context: BaseContext ) { if (isFunction(pluginOptions)) { plugin.options = pluginOptions(plugin.options || {}, context); if (!plugin.options) { throw new Error( "craco: 'eslint.pluginOptions' function didn't return a config object." ); } } else { // TODO: ensure is otherwise a plain object, if not, log an error. plugin.options = deepMergeWithArray(plugin.options || {}, pluginOptions); } log('Applied ESLint plugin options.'); } export function overrideEsLint( cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (cracoConfig.eslint) { const { isFound, match } = getPlugin( webpackConfig, pluginByName('ESLintWebpackPlugin') ); if (!isFound) { logError('Cannot find ESLint plugin (ESLintWebpackPlugin).'); return webpackConfig; } const { enable, mode, pluginOptions } = cracoConfig.eslint; if (enable === false) { disableEslint(webpackConfig); return webpackConfig; } enableEslintIgnoreFile(match); if (mode === 'file') { useEslintConfigFile(match); } else { extendsEslintConfig(match, cracoConfig.eslint, context); } if (pluginOptions) { applyPluginOptions(match, pluginOptions, context); } } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/merge-webpack-config.ts ================================================ import type { AddWebpackPlugins, Configure, CracoConfig, WebpackAlias, WebpackContext, } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import merge from 'webpack-merge'; import { log } from '../../logger'; import { isArray, isFunction } from '../../utils'; import { addPlugins as addWebpackPlugins, pluginByName, removePlugins as removeWebpackPlugins, } from '../../webpack-plugins'; import { applyWebpackConfigPlugins } from '../plugins'; import { overrideBabel } from './babel'; import { overrideEsLint } from './eslint'; import { overrideStyle } from './style/style'; import { overrideTypeScript } from './typescript'; function addAlias(webpackConfig: WebpackConfig, webpackAlias: WebpackAlias) { if (webpackConfig.resolve) { // TODO: ensure is a plain object, if not, log an error. webpackConfig.resolve.alias = Object.assign( webpackConfig.resolve.alias || {}, webpackAlias ); } log('Added webpack alias.'); } function addPlugins( webpackConfig: WebpackConfig, webpackPlugins: AddWebpackPlugins ) { if (isArray(webpackPlugins)) { addWebpackPlugins(webpackConfig, webpackPlugins); log('Added webpack plugins.'); } else { throw new Error( `craco: 'webpack.plugins.add' needs to be a an array of plugins` ); } } function removePluginsFromWebpackConfig( webpackConfig: WebpackConfig, remove: string[] | undefined ) { if (!remove) { return; } if (isArray(remove)) { for (const pluginName of remove) { const { hasRemovedAny } = removeWebpackPlugins( webpackConfig, pluginByName(pluginName) ); if (hasRemovedAny) { log(`Removed webpack plugin ${pluginName}.`); } else { log(`Did not remove webpack plugin ${pluginName}.`); } } log('Removed webpack plugins.'); } else { throw new Error( `craco: 'webpack.plugins.remove' needs to be a an array of plugin names` ); } } function giveTotalControl( webpackConfig: WebpackConfig, configureWebpack: Configure, context: WebpackContext ) { if (isFunction(configureWebpack)) { webpackConfig = configureWebpack(webpackConfig, context); if (!webpackConfig) { throw new Error( "craco: 'webpack.configure' function didn't returned a webpack config object." ); } } else { // TODO: ensure is otherwise a plain object, if not, log an error. webpackConfig = merge(webpackConfig, configureWebpack); } log("Merged webpack config with 'webpack.configure'."); return webpackConfig; } export function mergeWebpackConfig( cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: WebpackContext ) { let resultingWebpackConfig = webpackConfig; resultingWebpackConfig = overrideBabel( cracoConfig, resultingWebpackConfig, context ); resultingWebpackConfig = overrideEsLint( cracoConfig, resultingWebpackConfig, context ); resultingWebpackConfig = overrideStyle( cracoConfig, resultingWebpackConfig, context ); resultingWebpackConfig = overrideTypeScript( cracoConfig, resultingWebpackConfig ); if (cracoConfig.webpack) { const { alias, plugins, configure } = cracoConfig.webpack; if (alias) { addAlias(resultingWebpackConfig, alias); } if (plugins) { // we still support the old format of plugin: [] where the array is a list of the plugins to add if (isArray(plugins)) { addPlugins(resultingWebpackConfig, plugins); } else { const { add, remove } = plugins; if (remove) { removePluginsFromWebpackConfig(resultingWebpackConfig, remove); } // Add after removing to preserve any plugins explicitly added via the Craco config if (add) { addPlugins(resultingWebpackConfig, add); } } } if (configure) { resultingWebpackConfig = giveTotalControl( resultingWebpackConfig, configure, context ); } } resultingWebpackConfig = applyWebpackConfigPlugins( cracoConfig, resultingWebpackConfig, context ); return resultingWebpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/override.ts ================================================ import type { CracoConfig, WebpackContext } from '@craco/types'; import { loadWebpackDevConfig, loadWebpackProdConfig, overrideWebpackDevConfig, overrideWebpackProdConfig, } from '../../cra'; import { mergeWebpackConfig } from './merge-webpack-config'; export function overrideWebpackDev( cracoConfig: CracoConfig, context: WebpackContext ) { const craWebpackConfig = loadWebpackDevConfig(cracoConfig); const resultingWebpackConfig = mergeWebpackConfig( cracoConfig, craWebpackConfig, context ); overrideWebpackDevConfig(cracoConfig, resultingWebpackConfig); } export function overrideWebpackProd( cracoConfig: CracoConfig, context: WebpackContext ) { const craWebpackConfig = loadWebpackProdConfig(cracoConfig); const resultingWebpackConfig = mergeWebpackConfig( cracoConfig, craWebpackConfig, context ); overrideWebpackProdConfig(cracoConfig, resultingWebpackConfig); } ================================================ FILE: packages/craco/src/lib/features/webpack/style/css.ts ================================================ import type { BaseContext, CompleteLoader, Configure, CracoStyleConfig, } from '@craco/types'; import type { Configuration as WebpackConfig, RuleSetRule } from 'webpack'; import { getLoaders, loaderByName } from '../../../loaders'; import { log, logError } from '../../../logger'; import { deepMergeWithArray, isBoolean, isFunction, isString, } from '../../../utils'; interface CompleteLoaderModule { loader: { options: { [key: string]: any; }; }; } function setModuleLocalIdentName( match: CompleteLoaderModule, localIdentName: string ) { // The css-loader version of create-react-app has changed from 2.1.1 to 3.2.0 // https://github.com/facebook/create-react-app/commit/f79f30 if (isBoolean(match.loader.options.modules)) { delete match.loader?.options?.getLocalIdent; match.loader.options.localIdentName = localIdentName; } else { // This setting applies to create-react-app@3.3.0 delete match.loader.options.modules.getLocalIdent; match.loader.options.modules.localIdentName = localIdentName; } log('Overrided CSS modules local ident name.'); } function applyLoaderOptions( match: CompleteLoader, loaderOptions: Configure, context: BaseContext ) { if (isFunction(loaderOptions)) { match.loader.options = loaderOptions( (match.loader as RuleSetRule).options || {}, context ); if (!match.loader.options) { throw new Error( "craco: 'style.css.loaderOptions' function didn't return a loader config object." ); } } 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: CompleteLoader, { css: cssOptions }: CracoStyleConfig, context: BaseContext ) { if (cssOptions?.loaderOptions) { applyLoaderOptions(match, cssOptions.loaderOptions, context); log('Overrided CSS loader.'); } } function overrideModuleLoader( match: CompleteLoader, modulesOptions: { [key: string]: any } | undefined ) { if (modulesOptions?.localIdentName) { setModuleLocalIdentName( match as CompleteLoaderModule, modulesOptions.localIdentName ); log('Overrided CSS module loader.'); } } export function overrideCss( styleConfig: CracoStyleConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (styleConfig.modules || styleConfig.css) { const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('css-loader') ); if (!hasFoundAny) { logError('Cannot find any CSS loaders.'); return webpackConfig; } if (styleConfig.modules) { const cssModuleLoaders = matches.filter( (x) => !isString(x.loader) && x.loader?.options && !isString(x.loader?.options) && x.loader.options.modules ); cssModuleLoaders.forEach((x) => { overrideModuleLoader(x as CompleteLoader, styleConfig.modules); }); } if (styleConfig.css) { matches.forEach((x) => { overrideCssLoader(x as CompleteLoader, styleConfig, context); }); } } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/style/postcss.ts ================================================ import type { BaseContext, CompleteLoader, Configure, CracoStyleConfig, Loader, } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import { isString } from 'lodash'; import { getLoaders, loaderByName } from '../../../loaders'; import { log, logError } from '../../../logger'; import { projectRoot } from '../../../paths'; import { deepMergeWithArray, isArray, isFunction } from '../../../utils'; const CRA_PLUGINS = (presetEnv: any) => { return [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')(presetEnv), require(require.resolve('postcss-normalize', { paths: [projectRoot] })), ]; }; const CRA_PRESET_ENV = { autoprefixer: { flexbox: 'no-2009', }, stage: 3, }; function usePostcssConfigFile(match: Loader) { if ( !isString(match.loader?.options) && match.loader?.options?.postcssOptions ) { const ident = match.loader.options.postcssOptions.ident; const sourceMap = match.loader.options.postcssOptions.sourceMap; match.loader.options.postcssOptions = { ident: ident, sourceMap: sourceMap, }; log('Overwrited PostCSS config to use a config file.'); } } function extendsPostcss( match: CompleteLoader, { postcss: postcssOptions }: CracoStyleConfig ) { const { plugins, env } = postcssOptions ?? {}; if (isArray(plugins) || env) { let postcssPlugins: any[]; if (env) { const mergedPreset = deepMergeWithArray({}, CRA_PRESET_ENV, env); postcssPlugins = CRA_PLUGINS(mergedPreset); log('Merged PostCSS env preset.'); } else { let craPlugins: any[] = []; if (!isString(match.loader.options)) { const options = match.loader.options?.postcssOptions; if (isFunction(options)) { craPlugins = options().plugins; } else { craPlugins = options?.plugins; } } postcssPlugins = craPlugins || []; } if (plugins) { postcssPlugins = isFunction(plugins) ? plugins(postcssPlugins) : postcssPlugins.concat(plugins); log('Added PostCSS plugins.'); } if (match.loader.options && !isString(match.loader.options)) { if (match.loader.options.postcssOptions) { match.loader.options.postcssOptions.plugins = postcssPlugins; } else { match.loader.options.postcssOptions = { plugins: postcssPlugins, }; } } } } function applyLoaderOptions( match: CompleteLoader, loaderOptions: Configure, context: BaseContext ) { if (isFunction(loaderOptions)) { match.loader.options = loaderOptions(match.loader.options || {}, context); if (!match.loader.options) { throw new Error( "craco: 'style.postcss.loaderOptions' function didn't return a loader config object." ); } } 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: CompleteLoader, styleConfig: CracoStyleConfig, context: BaseContext ) { const { mode, loaderOptions } = styleConfig.postcss ?? {}; if (mode === 'file') { usePostcssConfigFile(match); } else { extendsPostcss(match, styleConfig); } if (loaderOptions) { applyLoaderOptions(match, loaderOptions, context); } log('Overrided PostCSS loader.'); } export function overridePostcss( styleConfig: CracoStyleConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (styleConfig.postcss) { const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('postcss-loader') ); if (!hasFoundAny) { logError('Cannot find any PostCSS loaders.'); return webpackConfig; } matches.forEach((x) => { overrideLoader(x as CompleteLoader, styleConfig, context); }); } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/style/sass.ts ================================================ import type { BaseContext, CompleteLoader, Configure, CracoStyleConfig, } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import { getLoaders, loaderByName } from '../../../loaders'; import { log, logError } from '../../../logger'; import { deepMergeWithArray, isFunction, isString } from '../../../utils'; function setLoaderProperty( match: CompleteLoader, key: string, valueProviders: { whenString: () => any; whenObject: () => any; } ) { if (isString(match.loader)) { (match.parent as any)[match.index] = { loader: match.loader, [key]: valueProviders.whenString(), }; } else { (match.loader as any)[key] = valueProviders.whenObject(); } } function applyLoaderOptions( match: CompleteLoader, loaderOptions: Configure, context: BaseContext ) { if (isFunction(loaderOptions)) { setLoaderProperty(match, 'options', { whenString: () => loaderOptions({}, context), whenObject: () => loaderOptions(match.loader.options || {}, context), }); if (!match.loader.options) { throw new Error( "craco: 'style.sass.loaderOptions' function didn't return a loader config object." ); } } 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: CompleteLoader, { sass: sassOptions }: CracoStyleConfig, context: BaseContext ) { const { loaderOptions } = sassOptions ?? {}; if (loaderOptions) { applyLoaderOptions(match, loaderOptions, context); log('Overrided Sass loader.'); } } export function overrideSass( styleConfig: CracoStyleConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (styleConfig.sass) { const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('sass-loader') ); if (!hasFoundAny) { logError('Cannot find any Sass loaders.'); return webpackConfig; } matches.forEach((x) => { overrideLoader(x as CompleteLoader, styleConfig, context); }); } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/style/style.ts ================================================ import type { BaseContext, CracoConfig } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import { overrideCss } from './css'; import { overridePostcss } from './postcss'; import { overrideSass } from './sass'; export function overrideStyle( cracoConfig: CracoConfig, webpackConfig: WebpackConfig, context: BaseContext ) { if (cracoConfig.style) { webpackConfig = overrideCss(cracoConfig.style, webpackConfig, context); webpackConfig = overrideSass(cracoConfig.style, webpackConfig, context); webpackConfig = overridePostcss(cracoConfig.style, webpackConfig, context); } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/features/webpack/typescript.ts ================================================ import type { CracoConfig } from '@craco/types'; import type { Configuration as WebpackConfig } from 'webpack'; import { log } from '../../logger'; function disableTypeChecking(webpackConfig: WebpackConfig) { webpackConfig.plugins = webpackConfig.plugins?.filter( (plugin) => plugin.constructor.name !== 'ForkTsCheckerWebpackPlugin' ); log('Disabled TypeScript type checking.'); return webpackConfig; } export function overrideTypeScript( cracoConfig: CracoConfig, webpackConfig: WebpackConfig ) { if (cracoConfig.typescript) { const { enableTypeChecking } = cracoConfig.typescript; if (enableTypeChecking === false) { disableTypeChecking(webpackConfig); } } return webpackConfig; } ================================================ FILE: packages/craco/src/lib/loaders.ts ================================================ import type { Loader, LoaderMatcher } from '@craco/types'; import type { Configuration as WebpackConfig, RuleSetRule, RuleSetUseItem, } from 'webpack'; import path from 'path'; import { isArray, isString } from './utils'; type Ul = T[] | undefined; export function loaderByName(targetLoaderName: string) { return (rule: RuleSetRule | RuleSetUseItem) => { if (!isString(rule) && 'loader' in rule && 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; }; } const toMatchingLoader = ( loader: RuleSetRule, parent: Ul, index: number ): Loader => ({ loader, parent, index }); function getLoaderRecursively(rules: Ul, matcher: LoaderMatcher) { let loader: Loader | undefined; rules?.some((rule, index) => { if (rule) { if (matcher(rule)) { loader = toMatchingLoader(rule, rules, index); } else if (!isString(rule)) { if (rule.use) { if (isString(rule.use) && matcher(rule.use)) { loader = toMatchingLoader({ loader: rule.use }, rules, index); } else { loader = getLoaderRecursively(rule.use as RuleSetRule[], 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; } export function getLoader( webpackConfig: WebpackConfig, matcher: LoaderMatcher ) { const matchingLoader = getLoaderRecursively( webpackConfig.module?.rules as RuleSetRule[], matcher ); return { isFound: matchingLoader !== undefined, match: matchingLoader }; } function getLoadersRecursively( rules: Ul, matcher: LoaderMatcher, matchingLoaders: Loader[] ) { rules?.forEach((rule, index) => { if (rule) { if (matcher(rule)) { matchingLoaders.push(toMatchingLoader(rule, rules, index)); } else if (!isString(rule)) { if (rule.use) { if (isString(rule.use) && matcher(rule.use)) { matchingLoaders.push( toMatchingLoader({ loader: rule.use }, rules, index) ); } else { getLoadersRecursively( rule.use as RuleSetRule[], matcher, matchingLoaders ); } } else if (rule.oneOf) { getLoadersRecursively(rule.oneOf, matcher, matchingLoaders); } else if (isArray(rule.loader)) { getLoadersRecursively(rule.loader, matcher, matchingLoaders); } } } }); } export function getLoaders( webpackConfig: WebpackConfig, matcher: LoaderMatcher ) { const matchingLoaders: Loader[] = []; getLoadersRecursively( webpackConfig.module?.rules as Ul, matcher, matchingLoaders ); return { hasFoundAny: matchingLoaders.length !== 0, matches: matchingLoaders, }; } function removeLoadersRecursively( rules: Ul, matcher: LoaderMatcher ): { rules: Ul; removedCount: number; } { const toRemove = []; let removedCount = 0; if (!rules) { return { rules, removedCount: 0 }; } for (let i = 0, max = rules.length; i < max; i += 1) { const rule = rules[i]; if (rule) { if (matcher(rule)) { toRemove.push(i); } else if (!isString(rule)) { if (rule.use) { let result; if (isString(rule.use) && matcher(rule.use)) { toRemove.push(i); removedCount++; rule.use = undefined; } else { result = removeLoadersRecursively( rule.use as RuleSetRule[], matcher ); removedCount += result.removedCount; (rule.use as Ul) = result.rules; } } else if (rule.oneOf) { const result = removeLoadersRecursively(rule.oneOf, matcher); removedCount += result.removedCount; (rule.oneOf as Ul) = result.rules; } } } } toRemove.forEach((ruleIndex, i) => { rules.splice(ruleIndex - i, 1); }); return { rules, removedCount: removedCount + toRemove.length }; } export function removeLoaders( webpackConfig: WebpackConfig, matcher: LoaderMatcher ) { const result = removeLoadersRecursively( webpackConfig.module?.rules as Ul, matcher ); return { hasRemovedAny: result.removedCount > 0, removedCount: result.removedCount, }; } function addLoader( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule, positionAdapter: (index: number) => number ) { const { isFound, match } = getLoader(webpackConfig, matcher); if (isFound) { match!.parent?.splice(positionAdapter(match!.index), 0, newLoader); return { isAdded: true }; } return { isAdded: false }; } export const addBeforeLoader = ( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule ) => addLoader(webpackConfig, matcher, newLoader, (x) => x); export const addAfterLoader = ( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule ) => addLoader(webpackConfig, matcher, newLoader, (x) => x + 1); function addLoaders( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule, positionAdapter: (index: number) => number ) { const { hasFoundAny, matches } = getLoaders(webpackConfig, matcher); if (hasFoundAny) { matches.forEach((match) => { match!.parent?.splice(positionAdapter(match.index), 0, newLoader); }); return { isAdded: true, addedCount: matches.length }; } return { isAdded: false, addedCount: 0 }; } export const addBeforeLoaders = ( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule ) => addLoaders(webpackConfig, matcher, newLoader, (x) => x); export const addAfterLoaders = ( webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: RuleSetRule ) => addLoaders(webpackConfig, matcher, newLoader, (x) => x + 1); ================================================ FILE: packages/craco/src/lib/logger.ts ================================================ import { getArgs } from './args'; export function log(...rest: any[]) { if (getArgs().verbose) { console.log(...rest); } } export function logError(...rest: any[]) { console.error(...rest); } ================================================ FILE: packages/craco/src/lib/paths.ts ================================================ import fs from 'fs'; import { log } from './logger'; export const projectRoot = fs.realpathSync(process.cwd()); log('Project root path resolved to: ', projectRoot); ================================================ FILE: packages/craco/src/lib/plugin-utils.ts ================================================ interface ConfigError { message: string; packageName?: string; githubRepo?: string; githubIssueQuery?: string; } export function gitHubIssueUrl(repo: string, query?: string) { return `https://github.com/${repo}/issues?q=is%3Aissue${ query ? `+${query}` : '' }`; } function showNpmPackageUrl(packageName: string) { return `\n * https://www.npmjs.com/package/${packageName}\n\n`; } function showGitHubIssueUrl(repo: string, query?: string) { return ( `Please check to see if there's already an issue in the ${repo} repo:\n\n` + ` * ${gitHubIssueUrl(repo, query)}\n\n` + "If not, please open an issue and we'll take a look. (Or you can send a PR!)\n\n" ); } function showPackageUpdateInstructions( packageName: string, repo?: string, query?: string ) { return ( `Please try updating ${packageName} to the latest version:\n\n` + ` $ yarn upgrade ${packageName}\n\n` + 'Or:\n\n' + ` $ npm update ${packageName}\n\n` + `If that doesn't work, ${packageName} needs to be fixed to support the latest version.\n` + (repo ? showGitHubIssueUrl(repo, query) : showNpmPackageUrl(packageName)) ); } export function throwUnexpectedConfigError({ message, packageName, githubRepo: repo, githubIssueQuery: query, }: ConfigError) { throw new Error( `${message}\n\n` + 'This error probably occurred because you updated react-scripts or craco. ' + (packageName ? showPackageUpdateInstructions(packageName, repo, query) : 'You will need to update this plugin to work with the latest version.\n\n') + 'You might also want to look for related issues in the ' + 'craco and create-react-app repos:\n\n' + ` * ${gitHubIssueUrl('dilanx/craco', query)}\n` + ` * ${gitHubIssueUrl('facebook/create-react-app', query)}\n` ); } ================================================ FILE: packages/craco/src/lib/user-config-utils.ts ================================================ export function when( condition: boolean, fn: () => T, unmetValue?: T ): T | undefined { if (condition) { return fn(); } return unmetValue; } export function whenDev(fn: () => T, unmetValue?: T): T | undefined { return when(process.env.NODE_ENV === 'development', fn, unmetValue); } export function whenProd(fn: () => T, unmetValue?: T): T | undefined { return when(process.env.NODE_ENV === 'production', fn, unmetValue); } export function whenTest(fn: () => T, unmetValue?: T): T | undefined { return when(process.env.NODE_ENV === 'test', fn, unmetValue); } ================================================ FILE: packages/craco/src/lib/utils.ts ================================================ import { mergeWith } from 'lodash'; export function isFunction(value: any): value is (...args: any[]) => any { return typeof value === 'function'; } export function isArray(value: any): value is Array { return Array.isArray(value); } export function isString(value: any): value is string { return typeof value === 'string'; } export function isBoolean(value: any): value is boolean { return typeof value === 'boolean'; } export function deepMergeWithArray(dest: any, ...src: any) { return mergeWith(dest, ...src, (x: any, y: any) => { if (isArray(x)) { return x.concat(y); } }); } ================================================ FILE: packages/craco/src/lib/validate-cra-version.ts ================================================ import type { CracoConfig } from '@craco/types'; import { getReactScriptVersion } from '../lib/cra'; export function validateCraVersion(cracoConfig: CracoConfig) { const { isSupported, version } = getReactScriptVersion(cracoConfig); if (!isSupported) { throw new Error( `Your current version of react-scripts(${version}) is not supported by this version of CRACO. Please try updating react-scripts to the latest version:\n\n` + ` $ yarn upgrade react-scripts\n\n` + 'Or:\n\n' + ` $ npm update react-scripts\n\n` + `If that doesn't work or if you can't, refer to the following table to choose the right version of CRACO.\n` + 'https://github.com/gsoft-inc/craco/blob/master/packages/craco/README.md#backward-compatibility' ); } } ================================================ FILE: packages/craco/src/lib/webpack-plugins.ts ================================================ import type { Configuration as WebpackConfig } from 'webpack'; export function pluginByName(targetPluginName: string) { return (plugin: any) => { return plugin.constructor.name === targetPluginName; }; } export function getPlugin( webpackConfig: WebpackConfig, matcher: (value: any, index?: number, obj?: any[]) => boolean ) { const matchingPlugin = webpackConfig.plugins?.find(matcher); return { isFound: matchingPlugin !== undefined, match: matchingPlugin, }; } export function addPlugins( webpackConfig: WebpackConfig, webpackPlugins: any[] ) { const prependPlugins = []; const appendPlugins = []; for (const webpackPlugin of webpackPlugins) { if (Array.isArray(webpackPlugin)) { const [plugin, order] = webpackPlugin; if (order === 'append') { appendPlugins.push(plugin); } else { // Existing behaviour is to prepend prependPlugins.push(plugin); } continue; } prependPlugins.push(webpackPlugin); } webpackConfig.plugins = [ ...prependPlugins, ...(webpackConfig.plugins as any[]), ...appendPlugins, ]; } export function removePlugins( webpackConfig: WebpackConfig, matcher: (value: any, index?: number, array?: any[]) => boolean ) { const prevCount = webpackConfig.plugins?.length ?? 0; webpackConfig.plugins = webpackConfig.plugins?.filter( (x, i, a) => !matcher(x, i, a) ); const removedPluginsCount = prevCount - (webpackConfig.plugins?.length ?? 0); return { hasRemovedAny: removedPluginsCount > 0, removedCount: removedPluginsCount, }; } ================================================ FILE: packages/craco/src/scripts/build.ts ================================================ import type { BaseContext } from '@craco/types'; process.env.NODE_ENV = process.env.NODE_ENV || 'production'; import { findArgsFromCli } from '../lib/args'; // Make sure this is called before "paths" is imported. findArgsFromCli(); import { loadCracoConfigAsync } from '../lib/config'; import { build, getCraPaths } from '../lib/cra'; import { overridePaths } from '../lib/features/paths/override'; import { overrideWebpackProd, overrideWebpackDev, } from '../lib/features/webpack/override'; import { log } from '../lib/logger'; import { validateCraVersion } from '../lib/validate-cra-version'; log('Override started with arguments: ', process.argv); log('For environment: ', process.env.NODE_ENV); const context: BaseContext = { env: process.env.NODE_ENV, }; loadCracoConfigAsync(context).then((cracoConfig) => { validateCraVersion(cracoConfig); context.paths = getCraPaths(cracoConfig); context.paths = overridePaths(cracoConfig, context); process.env.NODE_ENV === 'production' ? overrideWebpackProd(cracoConfig, context) : overrideWebpackDev(cracoConfig, context); build(cracoConfig); }); ================================================ FILE: packages/craco/src/scripts/start.ts ================================================ import type { BaseContext, CracoConfig } from '@craco/types'; process.env.NODE_ENV = process.env.NODE_ENV || 'development'; import { findArgsFromCli } from '../lib/args'; // Make sure this is called before "paths" is imported. findArgsFromCli(); import { loadCracoConfigAsync } from '../lib/config'; import { getCraPaths, start } from '../lib/cra'; import { overrideDevServer } from '../lib/features/dev-server/override'; import { overrideWebpackDev } from '../lib/features/webpack/override'; import { overridePaths } from '../lib/features/paths/override'; import { log } from '../lib/logger'; import { validateCraVersion } from '../lib/validate-cra-version'; log('Override started with arguments: ', process.argv); log('For environment: ', process.env.NODE_ENV); const context: BaseContext = { env: process.env.NODE_ENV, }; loadCracoConfigAsync(context).then((cracoConfig: CracoConfig) => { validateCraVersion(cracoConfig); context.paths = getCraPaths(cracoConfig); context.paths = overridePaths(cracoConfig, context); overrideWebpackDev(cracoConfig, context); overrideDevServer(cracoConfig, context); start(cracoConfig); }); ================================================ FILE: packages/craco/src/scripts/test.ts ================================================ import type { BaseContext, CracoConfig } from '@craco/types'; process.env.NODE_ENV = process.env.NODE_ENV || 'test'; import { findArgsFromCli } from '../lib/args'; // Make sure this is called before "paths" is imported. findArgsFromCli(); import { loadCracoConfigAsync } from '../lib/config'; import { getCraPaths, test } from '../lib/cra'; import { overrideJest } from '../lib/features/jest/override'; import { overridePaths } from '../lib/features/paths/override'; import { log } from '../lib/logger'; import { validateCraVersion } from '../lib/validate-cra-version'; log('Override started with arguments: ', process.argv); log('For environment: ', process.env.NODE_ENV); const context: BaseContext = { env: process.env.NODE_ENV, }; loadCracoConfigAsync(context).then((cracoConfig: CracoConfig) => { validateCraVersion(cracoConfig); context.paths = getCraPaths(cracoConfig); context.paths = overridePaths(cracoConfig, context); overrideJest(cracoConfig, context); test(cracoConfig); }); ================================================ FILE: packages/craco/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "noEmit": false, "outDir": "dist" }, "include": ["src"] } ================================================ FILE: packages/craco-types/README.md ================================================ # @craco/types Official type definitions for [CRACO](https://www.npmjs.com/package/@craco/craco) ================================================ FILE: packages/craco-types/package.json ================================================ { "name": "@craco/types", "description": "Official type declarations for @craco/craco", "version": "7.1.0", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ "dist" ], "scripts": { "build": "tsc", "prepack": "npm run build" }, "repository": { "type": "git", "url": "git+https://github.com/dilanx/craco.git", "directory": "packages/craco-types" }, "author": "Dilan Nair (https://www.dilanxd.com)", "license": "Apache-2.0", "bugs": { "url": "https://github.com/dilanx/craco/issues" }, "homepage": "https://craco.js.org", "dependencies": { "@babel/types": "^7.19.3", "@jest/types": "^27.5.1", "@types/eslint": "^8.4.6", "autoprefixer": "^10.4.12", "eslint-webpack-plugin": "^3.2.0", "webpack": "^5.74.0" }, "devDependencies": { "typescript": "^4.8.4" }, "publishConfig": { "access": "public" } } ================================================ FILE: packages/craco-types/src/asset-modules.ts ================================================ import type { RuleSetRule } from 'webpack'; export type AssetModuleType = | 'javascript/auto' | 'javascript/dynamic' | 'javascript/esm' | 'json' | 'webassembly/sync' | 'webassembly/async' | 'asset' | 'asset/source' | 'asset/resource' | 'asset/inline'; export type AssetModuleMatcher = (rule: RuleSetRule) => boolean; export interface AssetModule { rule: RuleSetRule; index: number; } ================================================ FILE: packages/craco-types/src/config.ts ================================================ import type { TransformOptions } from '@babel/core'; import type { Options as AutoprefixerOptions } from 'autoprefixer'; import type { Linter } from 'eslint'; import type { PluginOptions } from 'eslint-webpack-plugin/types/options'; import type { Configuration as WebpackConfig, WebpackPluginInstance, } from 'webpack'; import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import type { BaseContext, CraPaths, DevServerContext, JestContext, WebpackContext, } from './context'; import type { CracoPlugin } from './plugins'; import type { Config as JestConfig } from '@jest/types'; export type Configure = | Config | ((config: Config, context: Context) => Config); export interface CracoStyleConfig { modules?: { localIdentName?: string; }; css?: { loaderOptions?: Configure; }; sass?: { loaderOptions?: Configure; }; postcss?: { mode?: 'extends' | 'file'; plugins?: any[] | ((plugins: any[]) => any[]); env?: { autoprefixer?: AutoprefixerOptions; stage?: 0 | 1 | 2 | 3 | 4 | false; features?: { [featureId: string]: any }; }; loaderOptions?: Configure; }; } export interface CracoBabelConfig { presets?: any[]; plugins?: any[]; assumptions?: { [assumption: string]: boolean }; loaderOptions?: Configure; } export interface CracoEsLintConfig { enable?: boolean; mode?: 'extends' | 'file'; configure?: Configure; pluginOptions?: Configure; } export type WebpackAlias = { [alias: string]: string }; export type AddWebpackPlugins = ( | WebpackPluginInstance | [WebpackPluginInstance, 'append' | 'prepend'] )[]; export interface CracoWebpackConfig { alias?: WebpackAlias; plugins?: { add?: AddWebpackPlugins; remove?: string[]; }; configure?: Configure; } export type CracoDevServerConfig = Configure; export interface CracoJestConfig { babel?: { addPresets?: boolean; addPlugins?: boolean; }; configure?: Configure; } export interface CracoTypeScriptConfig { enableTypeChecking?: boolean; } export interface CracoPluginDefinition { plugin: CracoPlugin; options?: Options; } export interface CracoConfig { reactScriptsVersion?: string; style?: CracoStyleConfig; eslint?: CracoEsLintConfig; babel?: CracoBabelConfig; jest?: CracoJestConfig; typescript?: CracoTypeScriptConfig; webpack?: CracoWebpackConfig; devServer?: CracoDevServerConfig; plugins?: CracoPluginDefinition[]; paths?: Configure; } ================================================ FILE: packages/craco-types/src/context.ts ================================================ import type { ProxyConfigArray } from 'webpack-dev-server'; export interface CraPaths { dotenv: string; appPath: string; appBuild: string; appPublic: string; appHtml: string; appIndexJs: string; appPackageJson: string; appSrc: string; appTsConfig: string; appJsConfig: string; yarnLockFile: string; testsSetup: string; proxySetup: string; appNodeModules: string; swSrc: string; publicUrlOrPath: string; ownPath: string; ownNodeModules: string; appTypeDeclarations: string; ownTypeDeclarations: string; moduleFileExtensions: string[]; } export interface BaseContext { env?: string; paths?: CraPaths; } export type WebpackContext = BaseContext; export interface DevServerContext extends BaseContext { proxy?: ProxyConfigArray; allowedHost?: string; } export interface JestContext extends BaseContext { resolve?: (id: string) => string; rootDir?: string; } ================================================ FILE: packages/craco-types/src/index.ts ================================================ export { AssetModuleType, AssetModuleMatcher, AssetModule, } from './asset-modules'; export { Configure, CracoStyleConfig, CracoBabelConfig, CracoEsLintConfig, WebpackAlias, AddWebpackPlugins, CracoWebpackConfig, CracoDevServerConfig, CracoJestConfig, CracoTypeScriptConfig, CracoPluginDefinition, CracoConfig, } from './config'; export { CraPaths, BaseContext, WebpackContext, DevServerContext, JestContext, } from './context'; export { LoaderMatcher, Loader, CompleteLoader } from './loaders'; export { PluginOptions, CracoConfigOverride, WebpackConfigOverride, DevServerConfigOverride, JestConfigOverride, CracoPlugin, } from './plugins'; export { DevServerConfigProvider, JestConfigProvider } from './providers'; ================================================ FILE: packages/craco-types/src/loaders.ts ================================================ import type { RuleSetRule, RuleSetUseItem } from 'webpack'; // TODO these typings need to be updated I'm pretty sure export type LoaderMatcher = (rule: RuleSetRule | RuleSetUseItem) => boolean; export interface Loader { loader?: RuleSetRule; parent?: RuleSetRule[]; index: number; } export type CompleteLoader = Loader & { loader: RuleSetRule; }; ================================================ FILE: packages/craco-types/src/plugins.ts ================================================ import type { CracoConfig } from './config'; import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import type { Config as JestConfig } from '@jest/types'; import type { Configuration as WebpackConfig } from 'webpack'; import type { BaseContext, DevServerContext, JestContext, WebpackContext, } from './context'; export type PluginOptions = any; export interface CracoConfigOverride { cracoConfig: CracoConfig; pluginOptions: PluginOptions; context: BaseContext; } export interface WebpackConfigOverride { webpackConfig: WebpackConfig; cracoConfig: CracoConfig; pluginOptions: PluginOptions; context: WebpackContext; } export interface DevServerConfigOverride { devServerConfig: DevServerConfig; cracoConfig: CracoConfig; pluginOptions: PluginOptions; context: DevServerContext; } export interface JestConfigOverride { jestConfig: JestConfig.InitialOptions; cracoConfig: CracoConfig; pluginOptions: PluginOptions; context: JestContext; } export interface CracoPlugin { overrideCracoConfig?: ( cracoConfigOverride: CracoConfigOverride ) => CracoConfig; overrideWebpackConfig?: ( webpackConfigOverride: WebpackConfigOverride ) => WebpackConfig; overrideDevServerConfig?: ( devServerConfigOverride: DevServerConfigOverride ) => DevServerConfig; overrideJestConfig?: ( jestConfigOverride: JestConfigOverride ) => JestConfig.InitialOptions; } ================================================ FILE: packages/craco-types/src/providers.ts ================================================ import type { Configuration as DevServerConfig } from 'webpack-dev-server'; import type { Config as JestConfig } from '@jest/types'; export type DevServerConfigProvider = ( proxy: any, allowedHost: string ) => DevServerConfig; export type JestConfigProvider = ( customResolve: (relativePath: string) => string, projectRoot: string, isEjecting: boolean ) => JestConfig.InitialOptions; ================================================ FILE: packages/craco-types/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "noEmit": false, "outDir": "dist" }, "include": ["src"] } ================================================ FILE: recipes/README.md ================================================ # CRACO Recipes Recipes have been moved to https://craco.js.org/recipes/. ================================================ FILE: test/README.md ================================================ # CRACO End-to-End Tests ## Usage These tests ensure various functionality contracts are upheld across dependency upgrades. To get started locally, run `npm run test:unit` or `npm run test:integration`. ## How do these work? ### `unit/` These tests are non-integration and do not involve the building of any individual packages. ### `integration/fixtures/` Each `fixture/` gets spun up in a temporary directory and has its dependencies installed with npm.
### `integration/setup.js` This script runs before any integration tests are executed. It creates a temporary directory for each fixture, installs the local version of CRACO, any other required packages, and builds the package. You can then use an individual .test.js within the fixture to start a server or check for properties of the build files. ### `integration/teardown.js` This removes all temporary directories generated during integration tests. ## How can I make my own tests? ### Unit tests To create your own unit test, you can simply setup a directory within `unit/`, make a .test.js, import necessary craco files, then test specific features. This test can be run with `npm run test:unit`. ### Integration tests To create your own integration test, create a new directory under `integration/fixtures/`. You can then create a directory within called `test-package-files`, which should contain all necessary files for your package (public/index.html, craco.config.js, src/index.js, etc). These will be copied over to the temporary directory on test execution. You can then create a index.test.js in `integration/fixtures/` to interface with the built package. It's recommended run a local server, then use playwright to test individual features of the live website. View `integration/fixtures/basic-integration-test` for an example. This integration test will be run with `npm run test:integration`. ================================================ FILE: test/integration/fixtures/autoprefixer-test/index.test.js ================================================ 'use strict'; const path = require('path'); const fs = require('fs'); test('Should have the expected styles', async () => { const expectedStyles = "::-webkit-input-placeholder{color:gray}::placeholder{color:gray}.image{background-image:url(https://google.com)}@media (-webkit-min-device-pixel-ratio:2),(min-resolution:2dppx){.image{background-image:url(http://nxetflix.com)}}"; //we check if the compiled css is what we expect for autoprefixer const prom = new Promise((resolve, reject) => { fs.readdir('./test/integration/fixtures/autoprefixer-test/test-project/build/static/css', (err, files) => { if (err) reject(); //we don't know what the bundle name will be beforehand const cssFile = files.find(file => path.extname(file) === '.css'); expect(cssFile).not.toBe(0); fs.readFile(`./test/integration/fixtures/autoprefixer-test/test-project/build/static/css/${cssFile}`, 'utf8', (err, data) => { if (err) reject(); data = data.substring(0, data.indexOf("/*")-1); expect(data).toBe(expectedStyles); resolve(); }); }); }); await prom; }); ================================================ FILE: test/integration/fixtures/autoprefixer-test/test-package-files/craco.config.js ================================================ const webpack = require('webpack'); const isDevelopment = false; module.exports = { style: { postcss: { plugins: [ require('autoprefixer')() ] } } }; ================================================ FILE: test/integration/fixtures/autoprefixer-test/test-package-files/package.json ================================================ { "name": "craco-postcss-test", "version": "0.1.0", "private": true, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "web-vitals": "^3.3.1", "autoprefixer": "^10.4.14" }, "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] } ================================================ FILE: test/integration/fixtures/autoprefixer-test/test-package-files/public/index.html ================================================ React App
================================================ FILE: test/integration/fixtures/autoprefixer-test/test-package-files/src/index.css ================================================ ::placeholder { color: gray; } .image { background-image: url("https://google.com"); } @media (min-resolution: 2dppx) { .image { background-image: url("http://nxetflix.com"); } } ================================================ FILE: test/integration/fixtures/autoprefixer-test/test-package-files/src/index.js ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; ReactDOM.render(

Hi!

, document.getElementById('root') ); ================================================ FILE: test/integration/fixtures/basic-integration-test/index.test.js ================================================ 'use strict'; const { join } = require('path'); const { execSync, spawn } = require('child_process'); beforeAll(async () => { // Start a local server to serve the test project // We cached serve by installing it locally const server = spawn('npx', ['serve@14.2.0', '-s', 'build', '-l', global.PORT], { cwd: join(__dirname, 'test-project'), }); // Log any server errors to the console server.stderr.on('data', (data) => { console.error(`stderr: ${data}`); }); // Leave time for the server to initialize await new Promise((resolve) => { setTimeout(resolve, 5000); }); }); test('Should have the expected custom craco variable name', async () => { await page.goto(global.URL, { waitUntil: 'domcontentloaded' }); //todo: make the url changeble const cracoTestText = await page.$eval( '#craco-test', (element) => element.textContent ); expect(cracoTestText).toBe('CRACO is working!'); }); afterAll(() => { // Stop the local server execSync(`kill $(lsof -t -i:${global.PORT} -a -c node)`, { cwd: __dirname, stdio: 'ignore', }); }); ================================================ FILE: test/integration/fixtures/basic-integration-test/test-package-files/craco.config.js ================================================ const webpack = require('webpack'); const isDevelopment = false; module.exports = { webpack: { configure: (webpackConfig) => { webpackConfig.plugins.push( new webpack.DefinePlugin({ __CUSTOM_GLOBAL_CONSTANT__: JSON.stringify('CRACO is working!'), }) ); return webpackConfig; }, }, }; ================================================ FILE: test/integration/fixtures/basic-integration-test/test-package-files/package.json ================================================ { "name": "craco-basic-test", "version": "0.1.0", "private": true, "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "serve": "14.2.0", "web-vitals": "^3.3.1" }, "scripts": { "start": "craco start", "build": "craco build", "test": "craco test", "eject": "react-scripts eject" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" ] } ================================================ FILE: test/integration/fixtures/basic-integration-test/test-package-files/public/index.html ================================================ React App
================================================ FILE: test/integration/fixtures/basic-integration-test/test-package-files/src/index.js ================================================ /* global __CUSTOM_GLOBAL_CONSTANT__ */ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; ReactDOM.render(

Testing CRACO

{__CUSTOM_GLOBAL_CONSTANT__}

, document.getElementById('root') ); ================================================ FILE: test/integration/jest.config.js ================================================ 'use strict'; module.exports = { // testEnvironment: 'node', testMatch: ['/**/*.test.js'], testPathIgnorePatterns: ['/src/', 'node_modules'], transform: { '^.+\\.jsx?$': 'babel-jest', }, moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, globalSetup: './setup.js', globalTeardown: './teardown.js', testEnvironmentOptions: { 'jest-playwright': { browsers: ['firefox'], launchOptions: { headless: true, timeout: 500000, }, }, }, globals: { PORT: 3009, URL: 'http://localhost:3009', }, preset: 'jest-playwright-preset', }; ================================================ FILE: test/integration/setup.js ================================================ const { join } = require('path'); const { execSync } = require('child_process'); const fs = require('fs'); const rootPath = 'test/integration/fixtures'; const cwd = process.cwd(); // Set up the environment for integration tests module.exports = async function (globalConfig, projectConfig) { fs.readdir(rootPath, { withFileTypes: true }, (err, entries) => { if (err) { console.error(`Error reading directory: ${err.message}`); return; } const directoryNames = entries .filter((entry) => entry.isDirectory()) .map((entry) => entry.name); directoryNames.forEach((directoryName) => { //copy files in directory/test-package-files to directory/test-project const testPackageFilesPath = join( rootPath, directoryName, 'test-package-files' ); const testProjectPath = join(rootPath, directoryName, 'test-project'); execSync( `cd ${join( rootPath, directoryName )} && npx create-react-app test-project`, { cwd: cwd, stdio: 'inherit' } ); execSync(`cp -r ${testPackageFilesPath}/* ${testProjectPath}`, { cwd: cwd }); //install craco execSync(`npm install ../../../../../packages/craco`, { cwd: testProjectPath, stdio: 'inherit' }); //install other necessary files execSync(`npm install`, { cwd: testProjectPath, stdio: 'inherit' }); //build project execSync('npm run build', { cwd: testProjectPath, stdio: 'inherit' }); }); }); }; ================================================ FILE: test/integration/teardown.js ================================================ // Clean up the environment after integration tests const { execSync } = require('child_process'); const { join } = require('path'); const fs = require('fs'); const rootPath = 'test/integration/fixtures'; const cwd = process.cwd(); module.exports = async () => { fs.readdir(rootPath, { withFileTypes: true }, (err, entries) => { if (err) { console.error(`Error reading directory: ${err.message}`); return; } const directoryNames = entries .filter((entry) => entry.isDirectory()) .map((entry) => entry.name); directoryNames.forEach((directoryName) => { //clean up test-project execSync(`rm -rf ${join(rootPath, directoryName, 'test-project')}`, { cwd: cwd, }); }); }); }; ================================================ FILE: test/unit/jest.config.js ================================================ 'use strict'; module.exports = { testEnvironment: 'node', testMatch: ['/**/*.test.js'], testPathIgnorePatterns: ['/src/', 'node_modules'], transform: { '^.+\\.jsx?$': 'babel-jest', }, moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, }; ================================================ FILE: test/unit/merging-tests/autoprefixer-options/autoprefixer.test.js ================================================ const cracoConfig = require('./craco.config'); const autoprefixer = require('autoprefixer'); describe('CRACO autoprefixer configuration', () => { it('correctly applies custom autoprefixer options', () => { const postcssPlugins = cracoConfig.style.postcss.plugins; const autoprefixerPluginEntry = postcssPlugins.find( (pluginEntry) => pluginEntry.plugin === autoprefixer ); expect(autoprefixerPluginEntry).toBeDefined(); expect(autoprefixerPluginEntry.options.grid).toEqual('autoplace'); expect(autoprefixerPluginEntry.options.overrideBrowserslist).toEqual([ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 11', ]); }); it('does not remove other PostCSS plugins', () => { const postcssPlugins = cracoConfig.style.postcss.plugins; const autoprefixerPluginEntry = postcssPlugins.find( (pluginEntry) => pluginEntry.plugin === autoprefixer ); const pluginCountWithoutAutoprefixer = postcssPlugins.length - (autoprefixerPluginEntry ? 1 : 0); expect(pluginCountWithoutAutoprefixer).toBeGreaterThanOrEqual(0); }); }); ================================================ FILE: test/unit/merging-tests/autoprefixer-options/craco.config.js ================================================ module.exports = { style: { postcss: { plugins: [ { plugin: require('autoprefixer'), options: { grid: 'autoplace', overrideBrowserslist: [ '>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 11', ], }, }, ], }, }, }; ================================================ FILE: test/unit/merging-tests/configuration-merging/cra.mock.config.js ================================================ const craConfigMock = { entry: './src/index.js', output: { path: '/dist', filename: 'bundle.js', }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: 'babel-loader', }, ], }, }; module.exports = craConfigMock; ================================================ FILE: test/unit/merging-tests/configuration-merging/craco.config.js ================================================ module.exports = { webpack: { configure: (webpackConfig, { env, paths }) => { // Add a custom loader for SVG files webpackConfig.module.rules.push({ test: /\.svg$/, use: ['@svgr/webpack'], }); // Ensure webpackConfig.resolve exists if (!webpackConfig.resolve) { webpackConfig.resolve = {}; } // Add an alias webpackConfig.resolve.alias = { ...webpackConfig.resolve.alias, '@components': './src/components', }; // Update output folder webpackConfig.output.path = '/dist/custom'; return webpackConfig; }, }, babel: { plugins: [ // Add an additional Babel plugin 'babel-plugin-transform-class-properties', ], }, }; ================================================ FILE: test/unit/merging-tests/configuration-merging/merging.test.js ================================================ 'use strict'; const cracoConfig = require('./craco.config'); const craConfigMock = require('./cra.mock.config'); //Create a simple mock of the CRA configuration and a custom CRACO configuration //Then ensure that the final configuration produced by CRACO correctly merges both configurations describe('CRACO configuration merging', () => { it('correctly merges CRA and CRACO configurations', () => { const webpackConfig = cracoConfig.webpack.configure(craConfigMock, {}); // Test if the original CRA rules are present const jsRule = webpackConfig.module.rules.find( (rule) => rule.test.toString() === /\.(js|jsx)$/.toString() ); expect(jsRule).toBeDefined(); expect(jsRule.exclude).toEqual(/node_modules/); expect(jsRule.use).toEqual('babel-loader'); // Test if the custom SVG loader is added const svgRule = webpackConfig.module.rules.find( (rule) => rule.test.toString() === /\.svg$/.toString() ); expect(svgRule).toBeDefined(); expect(svgRule.use).toEqual(['@svgr/webpack']); // Test if the alias is added expect(webpackConfig.resolve.alias['@components']).toEqual( './src/components' ); // Test if the output path is updated expect(webpackConfig.output.path).toEqual('/dist/custom'); }); it('correctly adds the additional Babel plugin', () => { const babelConfig = cracoConfig.babel; // Test if the Babel plugin is added expect(babelConfig.plugins).toContain( 'babel-plugin-transform-class-properties' ); }); }); ================================================ FILE: test/unit/merging-tests/custom-babel-config/babel.config.mock.js ================================================ const babelConfigMock = { presets: ['@babel/preset-env', '@babel/preset-react'], }; module.exports = babelConfigMock; ================================================ FILE: test/unit/merging-tests/custom-babel-config/babel.test.js ================================================ const cracoConfig = require('./craco.config'); const babelConfigMock = require('./babel.config.mock'); describe('CRACO Babel configuration', () => { it('correctly applies custom Babel presets', () => { const babelConfig = cracoConfig.babel.loaderOptions; // Test if the original Babel presets are present expect(babelConfig.presets).toContain('@babel/preset-env'); expect(babelConfig.presets).toContain('@babel/preset-react'); // Test if the custom Babel preset is added expect(babelConfig.presets).toContain('@babel/preset-typescript'); }); it('does not remove existing Babel presets', () => { const babelConfig = cracoConfig.babel.loaderOptions; // Test if the number of presets in the custom configuration is greater than or equal to the original configuration expect(babelConfig.presets.length).toBeGreaterThanOrEqual( babelConfigMock.presets.length ); }); }); ================================================ FILE: test/unit/merging-tests/custom-babel-config/craco.config.js ================================================ module.exports = { babel: { loaderOptions: { presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript', ], }, }, }; ================================================ FILE: test/unit/merging-tests/custom-craco-plugin/craco-plugin-mock/index.js ================================================ const fs = require('fs'); const path = require('path'); function onPostBuild({ paths }) { const pluginLogPath = path.join(__dirname, '..', 'plugin.log'); fs.writeFileSync(pluginLogPath, 'Plugin executed successfully'); } module.exports = { onPostBuild, }; ================================================ FILE: test/unit/merging-tests/custom-craco-plugin/craco.config.js ================================================ const CracoPluginMock = require('./craco-plugin-mock'); module.exports = { plugins: [ { plugin: CracoPluginMock, }, ], }; ================================================ FILE: test/unit/merging-tests/custom-craco-plugin/plugin.test.js ================================================ const fs = require('fs'); const path = require('path'); const cracoConfig = require('./craco.config'); const CracoPluginMock = require('./craco-plugin-mock'); describe('CRACO custom plugin', () => { const pluginLogPath = path.join(__dirname, 'plugin.log'); afterEach(() => { if (fs.existsSync(pluginLogPath)) { fs.unlinkSync(pluginLogPath); } }); it('correctly includes the custom plugin', () => { const pluginEntry = cracoConfig.plugins.find( (pluginEntry) => pluginEntry.plugin === CracoPluginMock ); expect(pluginEntry).toBeDefined(); }); it('correctly executes the onPostBuild function of the custom plugin', () => { const pluginEntry = cracoConfig.plugins.find( (pluginEntry) => pluginEntry.plugin === CracoPluginMock ); pluginEntry.plugin.onPostBuild({}); const logContent = fs.readFileSync(pluginLogPath, 'utf-8'); expect(logContent).toEqual('Plugin executed successfully'); }); }); ================================================ FILE: test/unit/merging-tests/custom-env-variables/craco.config.js ================================================ module.exports = { env: { MY_CUSTOM_ENV_VAR: 'custom-env-value', }, }; ================================================ FILE: test/unit/merging-tests/custom-env-variables/env.test.js ================================================ const cracoConfig = require('./craco.config'); describe('CRACO environment variables', () => { it('correctly sets custom environment variables', () => { const originalProcessEnv = { ...process.env }; // Apply the custom environment variables from the CRACO configuration process.env = { ...process.env, ...cracoConfig.env, }; // Test if the custom environment variable is set expect(process.env.MY_CUSTOM_ENV_VAR).toEqual('custom-env-value'); // Restore the original process.env to avoid side effects process.env = originalProcessEnv; }); it('does not remove existing environment variables', () => { const originalProcessEnv = { ...process.env }; // Apply the custom environment variables from the CRACO configuration process.env = { ...process.env, ...cracoConfig.env, }; // Test if the existing environment variables are not removed const originalEnvVarCount = Object.keys(originalProcessEnv).length; const newEnvVarCount = Object.keys(process.env).length; expect(newEnvVarCount).toBeGreaterThanOrEqual(originalEnvVarCount); // Restore the original process.env to avoid side effects process.env = originalProcessEnv; }); }); ================================================ FILE: test/unit/merging-tests/custom-eslint-config/craco.config.js ================================================ module.exports = { eslint: { configure: { extends: ['react-app', 'plugin:prettier/recommended'], rules: { 'no-console': 'error', 'no-debugger': 'error', }, }, }, }; ================================================ FILE: test/unit/merging-tests/custom-eslint-config/eslint.config.mock.js ================================================ const eslintConfigMock = { extends: ['react-app'], rules: { 'no-console': 'warn', }, }; module.exports = eslintConfigMock; ================================================ FILE: test/unit/merging-tests/custom-eslint-config/eslint.test.js ================================================ const cracoConfig = require('./craco.config'); const eslintConfigMock = require('./eslint.config.mock'); describe('CRACO ESLint configuration', () => { it('correctly applies custom ESLint configuration', () => { const eslintConfig = cracoConfig.eslint.configure; // Test if the original ESLint extends are present expect(eslintConfig.extends).toContain('react-app'); // Test if the custom ESLint extends are added expect(eslintConfig.extends).toContain('plugin:prettier/recommended'); // Test if the custom ESLint rules are applied expect(eslintConfig.rules['no-console']).toEqual('error'); expect(eslintConfig.rules['no-debugger']).toEqual('error'); }); it('does not remove existing ESLint rules', () => { const eslintConfig = cracoConfig.eslint.configure; // Test if the number of rules in the custom configuration is greater than or equal to the original configuration expect(Object.keys(eslintConfig.rules).length).toBeGreaterThanOrEqual( Object.keys(eslintConfigMock.rules).length ); }); }); ================================================ FILE: test/unit/merging-tests/custom-jest-config/craco.config.js ================================================ module.exports = { jest: { configure: { transform: { '^.+\\.[t|j]sx?$': 'babel-jest', }, moduleNameMapper: { '^@components/(.*)$': '/src/components/$1', }, }, }, }; ================================================ FILE: test/unit/merging-tests/custom-jest-config/jest.config.mock.js ================================================ const jestConfigMock = { transform: { '^.+\\.[t|j]sx?$': 'babel-jest', }, }; module.exports = jestConfigMock; ================================================ FILE: test/unit/merging-tests/custom-jest-config/jest.test.js ================================================ const cracoConfig = require('./craco.config'); const jestConfigMock = require('./jest.config.mock'); describe('CRACO Jest configuration', () => { it('correctly applies custom Jest configuration', () => { const jestConfig = cracoConfig.jest.configure; // Test if the original Jest transform configuration is present expect(jestConfig.transform['^.+\\.[t|j]sx?$']).toEqual('babel-jest'); // Test if the custom Jest moduleNameMapper configuration is added expect(jestConfig.moduleNameMapper['^@components/(.*)$']).toEqual( '/src/components/$1' ); }); it('does not remove existing Jest configurations', () => { const jestConfig = cracoConfig.jest.configure; // Test if the number of configurations in the custom configuration is greater than or equal to the original configuration expect(Object.keys(jestConfig).length).toBeGreaterThanOrEqual( Object.keys(jestConfigMock).length ); }); }); ================================================ FILE: test/unit/merging-tests/custom-postcss-config/craco.config.js ================================================ module.exports = { style: { postcss: { plugins: [require('autoprefixer'), require('postcss-nested')], }, }, }; ================================================ FILE: test/unit/merging-tests/custom-postcss-config/postcss.config.mock.js ================================================ const postcssConfigMock = { plugins: [require('autoprefixer')], }; module.exports = postcssConfigMock; ================================================ FILE: test/unit/merging-tests/custom-postcss-config/postcss.test.js ================================================ const cracoConfig = require('./craco.config'); const postcssConfigMock = require('./postcss.config.mock'); //Checks if a custom PostCSS configuration is correctly applied describe('CRACO PostCSS configuration', () => { it('correctly applies custom PostCSS plugins', () => { const postcssConfig = cracoConfig.style.postcss; // Test if the original PostCSS plugins are present expect(postcssConfig.plugins).toContainEqual(require('autoprefixer')); // Test if the custom PostCSS plugin is added expect(postcssConfig.plugins).toContainEqual(require('postcss-nested')); }); it('does not remove existing PostCSS plugins', () => { const postcssConfig = cracoConfig.style.postcss; // Test if the number of plugins in the custom configuration is greater than or equal to the original configuration expect(postcssConfig.plugins.length).toBeGreaterThanOrEqual( postcssConfigMock.plugins.length ); }); }); ================================================ FILE: test/unit/merging-tests/html-webpack-plugin/craco.config.js ================================================ module.exports = { webpack: { configure: (webpackConfig, { env, paths }) => { const HtmlWebpackPlugin = require('html-webpack-plugin'); // Find the HtmlWebpackPlugin in the plugins array const htmlWebpackPluginIndex = webpackConfig.plugins.findIndex( (plugin) => plugin instanceof HtmlWebpackPlugin ); if (htmlWebpackPluginIndex >= 0) { // Create a new HtmlWebpackPlugin instance with the custom title const updatedHtmlWebpackPlugin = new HtmlWebpackPlugin({ ...webpackConfig.plugins[htmlWebpackPluginIndex].userOptions, title: 'My Custom Title', }); // Replace the original HtmlWebpackPlugin instance with the updated one webpackConfig.plugins.splice( htmlWebpackPluginIndex, 1, updatedHtmlWebpackPlugin ); } return webpackConfig; }, }, }; ================================================ FILE: test/unit/merging-tests/html-webpack-plugin/html-webpack-plugin.test.js ================================================ const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const cracoConfig = require('./craco.config'); describe('CRACO HtmlWebpackPlugin configuration', () => { it('correctly applies custom HtmlWebpackPlugin configuration', async () => { const webpackConfig = { mode: 'development', plugins: [new HtmlWebpackPlugin()], }; // Apply custom configuration to the webpack config const newWebpackConfig = cracoConfig.webpack.configure(webpackConfig, {}); // Find the HtmlWebpackPlugin in the newWebpackConfig const htmlWebpackPlugin = newWebpackConfig.plugins.find( (plugin) => plugin instanceof HtmlWebpackPlugin ); // Test if the custom title is set expect(htmlWebpackPlugin.userOptions.title).toEqual('My Custom Title'); }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "es5", "module": "commonjs", "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "moduleResolution": "node", "downlevelIteration": true, "declaration": true, "noEmit": true, }, "include": ["packages/**/*"] } ================================================ FILE: website/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: website/README.md ================================================ # Website This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. ### Installation ``` $ yarn ``` ### Local Development ``` $ yarn start ``` This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. ### Build ``` $ yarn build ``` This command generates static content into the `build` directory and can be served using any static contents hosting service. ### Deployment Using SSH: ``` $ USE_SSH=true yarn deploy ``` Not using SSH: ``` $ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. ================================================ FILE: website/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: website/docs/configuration/babel.md ================================================ --- description: Customize Babel --- # Babel ```js title="craco.config.js" module.exports = { // ... babel: { presets: [ /* ... */ ], plugins: [ /* ... */ ], loaderOptions: { /* ... */ }, loaderOptions: (babelLoaderOptions, { env, paths }) => { /* ... */ return babelLoaderOptions; }, }, }; ``` :::tip Properties listed twice in the outline above (for example, `loaderOptions`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## babel.presets `[string | [string, object]]` Any Babel presets: https://babeljs.io/docs/en/presets/ ## babel.plugins `[string | [string, object]]` Any Babel plugins: https://babeljs.io/docs/en/plugins ## babel.loaderOptions `BabelLoaderOptions` or `(options: BabelLoaderOptions, { env, paths }) => BabelLoaderOptions` Any babel-loader options: https://github.com/babel/babel-loader#options ================================================ FILE: website/docs/configuration/devserver.md ================================================ --- description: Configure DevServer --- # DevServer ```js title="craco.config.js" module.exports = { // ... devServer: { /* ... */ }, devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => { /* ... */ return devServerConfig; }, }; ``` :::tip Properties listed twice in the outline above (for example, `devServer`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## devServer `DevServerConfig` or `(config: DevServerConfig, { env, paths, proxy, allowedHost }) => DevServerConfig` Any DevServer configuration options: https://webpack.js.org/configuration/dev-server/#devserver The function version of `configure` provides two extra properties within the [context object](./getting-started.md#context-object--env-paths-): - `proxy` - DevServer proxy config array - `allowedHost` - the allowed host ================================================ FILE: website/docs/configuration/eslint.md ================================================ --- description: Customize ESLint --- # ESLint ```js title="craco.config.js" module.exports = { // ... eslint: { enable: true /* (default value) */, mode: 'extends' /* (default value) */ || 'file', configure: { /* ... */ }, configure: (eslintConfig, { env, paths }) => { /* ... */ return eslintConfig; }, pluginOptions: { /* ... */ }, pluginOptions: (eslintPluginOptions, { env, paths }) => { /* ... */ return eslintPluginOptions; }, }, }; ``` :::tip Properties listed twice in the outline above (for example, `configure`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## eslint.enable `boolean = true` Whether or not ESLint is enabled. ## eslint.mode `'extends' | 'file' = 'extends'` See [override modes](./getting-started.md#override-modes). ## eslint.configure `ESLintConfig` or `(config: ESLintConfig, { env, paths }) => ESLintConfig` Any ESLint configuration options: https://eslint.org/docs/latest/user-guide/configuring/ ## eslint.pluginOptions `ESLintPluginOptions` or `(options: ESLintPluginOptions, { env, paths }) => ESLintPluginOptions` Any ESLint plugin configuration options: https://github.com/webpack-contrib/eslint-webpack-plugin#options ================================================ FILE: website/docs/configuration/getting-started.md ================================================ --- description: Setting up the configuration file --- # Getting Started ## Creating the file CRACO can be configured in a file with any of the following names: 1. `craco.config.ts` 2. `craco.config.js` 3. `craco.config.cjs` 4. `.cracorc.ts` 5. `.cracorc.js` 6. `.cracorc` If multiple configuration files are found, CRACO will use the one highest on the list above. You can also [specify a file path in your `package.json` file], which will take priority over all files listed above. ### Setting a custom location #### Option 1: package.json (recommended) You can change the location of your config file by specifying a value for `cracoConfig` in your `package.json` file. ```json title="package.json" { "cracoConfig": "config/craco-config-with-custom-name.js" } ``` #### Option 2: CLI (for backward compatibility) You can change the location of your config file by specifying a file path with the `--config` CLI option. ```json title="package.json" { "scripts": { "start": "craco start --config config/craco-config-with-custom-name.js" } } ``` :::caution The CLI option doesn't support Babel with Jest. ::: ## Configuration tips ### Object literals and functions Many config override properties in the CRACO config can be assigned either one of two things: - an **object literal**, which will be merged with the original config ```js title="craco.config.js (example)" module.exports = { webpack: { configure: { entry: './path/to/my/entry/file.js', }, }, }; ``` - a **function** that takes in the original config as the first argument (and optionally a [context object](#context-object--env-paths-)) and returns the new config ```js title="craco.config.js (example)" module.exports = { webpack: { configure: (webpackConfig, { env, paths }) => { webpackConfig.entry = './path/to/my/entry/file.js'; return webpackConfig; }, }, }; ``` Configuration outlines within this documentation will show both of these options as two properties with the same name (for example, two properties named `configure` where one is an object literal and the other is a function). ### Context object (`{ env, paths }`) The function version of config override properties accepts an optional second argument, which is a single object containing the following properties: - `env` - the current NODE_ENV (development, production, etc.) - `paths` - an object that contains all the paths used by CRA Some configuration sections, like [`jest.configure`](./jest.md#jestconfigure) and [`devServer`](./devserver.md#devserver-1), include extra properties in their context object. ### Override modes Some sections have a `mode` property, which can be assigned one of two values: - `extends` - the provided configuration will extend the CRA settings (**default**) - `file` - the CRA settings will be reset and you'll need to provide an official configuration file for the plugin that will supersede any settings ## Configuration helpers ### when ```js title="craco.config.js (example)" module.exports = { eslint: { mode: 'file', configure: { formatter: when( process.env.NODE_ENV === 'CI', require('eslint-formatter-vso') ), }, }, webpack: { plugins: [ new ConfigWebpackPlugin(), ...whenDev(() => [new CircularDependencyPlugin()], []), ], }, }; ``` #### `when(condition, fn, [unmetValue])` `when(condition: boolean, fn: () => T, unmetValue?: T): T | undefined` If `condition` evaluates to true, `fn` is called and the helper will return what that function returns. If false, `unmetValue` will be returned (or `undefined` if not provided). #### `whenDev(fn, [unmetValue])` `whenDev(fn: () => T, unmetValue?: T): T | undefined` Equivalent to `when(process.env.NODE_ENV === 'development', fn, unmetValue)`. #### `whenProd(fn, [unmetValue])` `whenProd(fn: () => T, unmetValue?: T): T | undefined` Equivalent to `when(process.env.NODE_ENV === 'production', fn, unmetValue)`. #### `whenTest(fn, [unmetValue])` `whenTest(fn: () => T, unmetValue?: T): T | undefined` Equivalent to `when(process.env.NODE_ENV === 'test', fn, unmetValue)`. ## Exporting your configuration You can export your configuration in a variety of ways. :::tip The function options will be called with an object containing the current environment variables (for example, `NODE_ENV`). ::: ### Object literal ```js title="craco.config.js" module.exports = { ... }; ``` ### Function ```js title="craco.config.js" module.exports = function ({ env }) { return { ... }; }; ``` ### Promise or Async Function ```js title="craco.config.js" module.exports = async function ({ env }) { await ...; return { ... }; }; ``` ## Using a custom `react-scripts` package If you're using a fork of Create React App's `react-scripts` package, you can specify the name in your configuration so CRACO knows where the scripts are. If this property is omitted, CRACO defaults to `react-scripts`. ```js title="craco.config.js" module.exports = { // ... reactScriptsVersion: 'custom-react-scripts-package', }; ``` ================================================ FILE: website/docs/configuration/jest.md ================================================ --- description: Customize Jest --- # Jest ```js title="craco.config.js" module.exports = { // ... jest: { babel: { addPresets: true /* (default value) */, addPlugins: true /* (default value) */, }, configure: { /* ... */ }, configure: (jestConfig, { env, paths, resolve, rootDir }) => { /* ... */ return jestConfig; }, }, }; ``` :::tip Properties listed twice in the outline above (for example, `configure`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## jest.babel Configuration options for the `babel-jest` transformer: https://jestjs.io/docs/code-transformation ### jest.babel.addPresets `boolean = true` Whether or not Babel presets should be added. ### jest.babel.addPlugins `boolean = true` Whether or not Babel plugins should be added. ## jest.configure `JestConfig` or `(config: JestConfig, { env, paths, resolve, rootDir }) => JestConfig` Any Jest configuration options: https://jestjs.io/docs/configuration The function version of `configure` provides two extra properties within the [context object](./getting-started.md#context-object--env-paths-): - `resolve` - provided by CRA - `rootDir` - provided by CRA ================================================ FILE: website/docs/configuration/plugins.md ================================================ --- description: Include third-party CRACO plugins --- # CRACO Plugins View a list of [community maintained plugins](/plugins) or [develop your own](../plugin-api/getting-started.md). ```js title="craco.config.js" module.exports = { // ... plugins: [ { plugin: require('some-craco-plugin'), options: { /* ... */ }, }, // ... ], }; ``` ================================================ FILE: website/docs/configuration/style.md ================================================ --- description: Customize CSS preprocessors --- # Style ```js title="craco.config.js" module.exports = { // ... style: { modules: { localIdentName: '', }, css: { loaderOptions: { /* ... */ }, loaderOptions: (cssLoaderOptions, { env, paths }) => { /* ... */ return cssLoaderOptions; }, }, sass: { loaderOptions: { /* ... */ }, loaderOptions: (sassLoaderOptions, { env, paths }) => { /* ... */ return sassLoaderOptions; }, }, postcss: { mode: 'extends' /* (default value) */ || 'file', plugins: [require('plugin-to-append')], plugins: (plugins) => [require('plugin-to-prepend')].concat(plugins), env: { autoprefixer: { /* ... */ }, stage: 3, features: { /* ... */ }, }, loaderOptions: { /* ... */ }, loaderOptions: (postcssLoaderOptions, { env, paths }) => { /* ... */ return postcssLoaderOptions; }, }, }, }; ``` :::tip Properties listed twice in the outline above (for example, `loaderOptions`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## style.modules ### style.modules.localIdentName `string` https://github.com/webpack-contrib/css-loader#localidentname ## style.css ### style.css.loaderOptions `CSSLoaderOptions` or `(options: CSSLoaderOptions, { env, paths }) => CSSLoaderOptions` Any css-loader configuration options: https://github.com/webpack-contrib/css-loader#options ## style.sass ### style.sass.loaderOptions `SASSLoaderOptions` or `(options: SASSLoaderOptions, { env, paths }) => SASSLoaderOptions` Any sass-loader configuration options: https://github.com/webpack-contrib/sass-loader#options ## style.postcss ### style.postcss.mode `'extends' | 'file' = 'extends'` See [override modes](./getting-started.md#override-modes). ### style.postcss.plugins `[PostCSSPlugin]` or `(plugins: [PostCSSPlugin]) => [PostCSSPlugin]` Any PostCSS plugins: https://github.com/postcss/postcss#plugins ### style.postcss.env #### style.postcss.env.autoprefixer `AutoprefixerOptions` Any autoprefixer options: https://github.com/postcss/autoprefixer#options #### style.postcss.env.stage `0 | 1 | 2 | 3 | 4` Any valid CSS stage: https://cssdb.org/#the-staging-process #### style.postcss.env.features Any CSS features: https://preset-env.cssdb.org/features/ ### style.postcss.loaderOptions `PostCSSLoaderOptions` or `(options: PostCSSLoaderOptions, { env, paths }) => PostCSSLoaderOptions` Any postcss-loader options: https://github.com/webpack-contrib/postcss-loader#options ================================================ FILE: website/docs/configuration/typescript.md ================================================ --- description: Customize TypeScript --- # TypeScript ```js title="craco.config.js" module.exports = { // ... typescript: { enableTypeChecking: true /* (default value) */, }, }; ``` ## typescript.enableTypeChecking `boolean = true` Whether or not TypeScript type checking is enabled. ================================================ FILE: website/docs/configuration/webpack.md ================================================ --- description: Customize Webpack --- # Webpack ```js title="craco.config.js" module.exports = { // ... webpack: { alias: { /* ... */ }, plugins: { add: [ /* ... */ ], remove: [ /* ... */ ], }, configure: { /* ... */}, configure: (webpackConfig, { env, paths }) => { /* ... */ return webpackConfig; }, }, }; ``` :::tip Properties listed twice in the outline above (for example, `configure`) can be assigned an **object literal** or a **function**. See [configuration tips](./getting-started.md#object-literals-and-functions) for details. ::: ## webpack.alias `object` See https://webpack.js.org/configuration/resolve/#resolvealias. ## webpack.plugins ### webpack.plugins.add `[WebpackPlugin | [WebpackPlugin, 'append' | 'prepend']]` An array of Webpack plugins to add: https://webpack.js.org/plugins/ You can specify whether or not each plugin is appended or prepended to the existing list of Webpack plugins. If not specified, the default is to prepend. Check out the following example: ```js title="craco.config.js" const CopyPlugin = require('copy-webpack-plugin'); const ESLintPlugin = require('eslint-webpack-plugin'); const HtmlPlugin = require('html-webpack-plugin'); module.exports = { webpack: { plugins: { add: [ new CopyPlugin() /* this plugin will be prepended */, [new ESLintPlugin(), 'prepend'] /* this one, too */, [new HtmlPlugin(), 'append'] /* not this one though */, ], }, }, }; ``` ### webpack.plugins.remove `[string]` An array of plugin constructor names to remove. ## webpack.configure `WebpackConfig` or `(config: WebpackConfig, { env, paths }) => WebpackConfig` Any Webpack configuration options: https://webpack.js.org/configuration/ ================================================ FILE: website/docs/configuration-api.md ================================================ # Configuration API To integrate with other tools, it's useful to have access to the configuration generated by CRACO. The CRACO Configuration API supports Jest and Webpack. ## Jest `createJestConfig(cracoConfig, context = {}, options = { verbose: false, config: null })` A [CRACO config](./configuration/getting-started.md), a [JEST context object](./configuration/jest.md#jestconfigure), and an options object (`{ verbose?: boolean, config?: string }`) are taken as arguments and the generated Jest config object is returned. :::note `createJestConfig` does **not** accept `cracoConfig` as a function. If your `craco.config.js` exposes a config function, you have to call it yourself before using it here. ::: ```js title="jest.config.js (example)" const { createJestConfig } = require('@craco/craco'); const cracoConfig = require('./craco.config.js'); const jestConfig = createJestConfig(cracoConfig); module.exports = jestConfig; ``` ## Webpack `createWebpackDevConfig(cracoConfig, context = {}, options = { verbose: false, config: null })` `createWebpackProdConfig(cracoConfig, context = {}, options = { verbose: false, config: null })` :::note `createWebpackDevConfig` and `createWebpackProdConfig` do **not** accept `cracoConfig` as a function. If your `craco.config.js` exposes a config function, you have to call it yourself before using it here. ::: ```js title="webpack.config.js (example)" const { createWebpackDevConfig } = require('@craco/craco'); const cracoConfig = require('./craco.config.js'); const webpackConfig = createWebpackDevConfig(cracoConfig); module.exports = webpackConfig; ``` ================================================ FILE: website/docs/getting-started.md ================================================ # Getting Started :::info The current **CRACO** version requires **Create React App 5** (`react-scripts 5.x.x`). If using an older version of CRA, [use the appropriate version of CRACO](#backward-compatibility). ::: ## Set up CRACO 1. Install the latest version of the package from npm as a dev dependency: ``` npm i -D @craco/craco ``` 2. Create a CRACO configuration file in your project's root directory and [configure](./configuration/getting-started.md): ```diff my-app ├── node_modules + ├── craco.config.js └── package.json ``` 3. Update the existing calls to `react-scripts` in the `scripts` section of your `package.json` to use the `craco` CLI: ```diff title="package.json" "scripts": { - "start": "react-scripts start" + "start": "craco start" - "build": "react-scripts build" + "build": "craco build" - "test": "react-scripts test" + "test": "craco test" } ``` You can now start or build your app like normal: ``` npm start ``` ``` npm run build ``` ## Start configuring Check out the [configuration documentation](./configuration/getting-started.md). ## TypeScript support CRACO provides official typings that you can use if you'd like type checking and IDE autocompletion in your configuration file: ``` npm i -D @craco/types ``` ## Backward compatibility CRACO is not meant to be backward compatible with older versions of Create React App and will only support the latest version. If your project uses an old version (which can be determined by the version of the `react-scripts` dependency in your project), refer to the following table to select the appropriate CRACO version. | react-scripts version | CRACO version | | --------------------- | ------------- | | 5.x.x (latest) | 7.0.0 | | 4.x.x | 6.4.5 | | < 4.0.0 | 5.8.0 | ## Debugging ### Verbose logging To activate verbose logging, specify the CLI option `--verbose` ```json title="package.json" { "scripts": { "start": "craco start --verbose" } } ``` ================================================ FILE: website/docs/plugin-api/getting-started.md ================================================ --- description: Get started with CRACO plugin development --- # Getting Started CRACO has a nice plugin API. You can view a list of [community maintained plugins](/plugins) or develop your own. ## Develop a plugin CRACO provides a bunch of [hooks](./hooks.md) and [utility functions](../../category/utility-functions) to make plugin development easy. A plugin is structured using the four hooks (all are optional): ```js title="craco-example-plugin.js" module.exports = { overrideCracoConfig: ({ cracoConfig, pluginOptions, context }) => { /* ... */ return cracoConfig; }, overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions, context, }) => { /* ... */ return webpackConfig; }, overrideDevServerConfig: ({ devServerConfig, cracoConfig, pluginOptions, context, }) => { /* ... */ return devServerConfig; }, overrideJestConfig: ({ jestConfig, cracoConfig, pluginOptions, context }) => { /* ... */ return jestConfig; }, }; ``` :::note Notice how all hooks only accept a single object as an argument. The outline above destructures each object. ::: :::caution important All functions must return the updated configuration object. ::: Get started by checking out the documentation for [hooks](./hooks.md). ================================================ FILE: website/docs/plugin-api/hooks.md ================================================ --- description: Hooks available to CRACO plugins --- # Hooks There are four hooks available to a plugin: - [`overrideCracoConfig`](#overridecracoconfig) - customize the CRACO config object **before** it is processed by CRACO - [`overrideWebpackConfig`](#overridewebpackconfig) - customize the Webpack config **after** it is processed by CRACO - [`overrideDevServerConfig`](#overridedevserverconfig) - customize the DevServer config **after** it is processed by CRACO - [`overrideJestConfig`](#overridejestconfig) - customize the Jest config **after** it is processed by CRACO :::caution important Every function **must** return the updated configuration object. ::: ## overrideCracoConfig `overrideCracoConfig(data): CracoConfig` `data` is an object with the following structure: | Property | Description | | --------------- | --------------------------------------------------------------------------------------------------------------- | | `cracoConfig` | The config object read from the [CRACO config](../configuration/getting-started.md) provided by the consumer | | `pluginOptions` | The plugin options provided by the consumer | | `context` | A [context object](../configuration/getting-started.md#context-object--env-paths-) containing `env` and `paths` | This function must return a valid [CRACO config](../configuration/getting-started.md).
Example ```js title="craco-log-plugin.js" module.exports = { overrideCracoConfig: ({ cracoConfig, pluginOptions, context: { env, paths }, }) => { if (pluginOptions.preText) { console.log(pluginOptions.preText); } console.log(JSON.stringify(cracoConfig, null, 4)); return cracoConfig; }, }; ``` ```js title="craco.config.js" const logPlugin = require('./craco-log-plugin'); module.exports = { // ... plugins: [ { plugin: logPlugin, options: { preText: 'CRACO CONFIG' }, }, ], }; ```
## overrideWebpackConfig `overrideWebpackConfig(data): WebpackConfig` `data` is an object with the following structure: | Property | Description | | --------------- | --------------------------------------------------------------------------------------------------------------- | | `webpackConfig` | The [Webpack config](https://webpack.js.org/configuration/) object already customized by CRACO | | `cracoConfig` | The config object read from the [CRACO config](../configuration/getting-started.md) provided by the consumer | | `pluginOptions` | The plugin options provided by the consumer | | `context` | A [context object](../configuration/getting-started.md#context-object--env-paths-) containing `env` and `paths` | This function must return a valid [Webpack config](https://webpack.js.org/configuration/).
Example ```js title="craco-log-plugin.js" module.exports = { overrideWebpackConfig: ({ webpackConfig, cracoConfig, pluginOptions, context: { env, paths }, }) => { if (pluginOptions.preText) { console.log(pluginOptions.preText); } console.log(JSON.stringify(webpackConfig, null, 4)); return webpackConfig; }, }; ``` ```js title="craco.config.js" const logPlugin = require('./craco-log-plugin'); module.exports = { // ... plugins: [ { plugin: logPlugin, options: { preText: 'WEBPACK CONFIG' }, }, ], }; ```
## overrideDevServerConfig `overrideDevServerConfig(data): DevServerConfig` `data` is an object with the following structure: | Property | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `devServerConfig` | The [DevServer config](https://webpack.js.org/configuration/dev-server/#devserver) object already customized by CRACO | | `cracoConfig` | The config object read from the [CRACO config](../configuration/getting-started.md) provided by the consumer | | `pluginOptions` | The plugin options provided by the consumer | | `context` | A [context object](../configuration/getting-started.md#context-object--env-paths-) ([DevServer-specific](../configuration/devserver.md#devserver-1)) containing `env`, `paths`, and `allowedHost` | This function must return a valid [DevServer config](https://webpack.js.org/configuration/dev-server/#devserver).
Example ```js title="craco-log-plugin.js" module.exports = { overrideDevServerConfig: ({ devServerConfig, cracoConfig, pluginOptions, context: { env, paths, allowedHost }, }) => { if (pluginOptions.preText) { console.log(pluginOptions.preText); } console.log(JSON.stringify(devServerConfig, null, 4)); return devServerConfig; }, }; ``` ```js title="craco.config.js" const logPlugin = require('./craco-log-plugin'); module.exports = { // ... plugins: [ { plugin: logPlugin, options: { preText: 'DEVSERVER CONFIG' }, }, ], }; ```
## overrideJestConfig `overrideJestConfig(data): JestConfig` `data` is an object with the following structure: | Property | Description | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `jestConfig` | The [Jest config](https://jestjs.io/docs/configuration) object already customized by CRACO | | `cracoConfig` | The config object read from the [CRACO config](../configuration/getting-started.md) provided by the consumer | | `pluginOptions` | The plugin options provided by the consumer | | `context` | A [context object](../configuration/getting-started.md#context-object--env-paths-) ([Jest-specific](../configuration/jest.md#jestconfigure)) containing `env`, `paths`, `resolve`, and `rootDir` | This function must return a valid [Jest config](https://jestjs.io/docs/configuration).
Example ```js title="craco-log-plugin.js" module.exports = { overrideJestConfig: ({ jestConfig, cracoConfig, pluginOptions, context: { env, paths, resolve, rootDir }, }) => { if (pluginOptions.preText) { console.log(pluginOptions.preText); } console.log(JSON.stringify(jestConfig, null, 4)); return jestConfig; }, }; ``` ```js title="craco.config.js" const logPlugin = require('./craco-log-plugin'); module.exports = { // ... plugins: [ { plugin: logPlugin, options: { preText: 'JEST CONFIG' }, }, ], }; ```
================================================ FILE: website/docs/plugin-api/utility-functions/miscellaneous.md ================================================ # Miscellaneous Other useful utility functions ```js const { throwUnexpectedConfigError } = require('@craco/craco'); ``` ## Functions ### throwUnexpectedConfigError `throwUnexpectedConfigError(options)` Raises an error and crashes Node.js. `options` should be an object with the following structure: | Property | Description | Type / Format | Required | | ------------------ | ------------------------------------------- | --------------------- | -------- | | `message` | An error message explaining what went wrong | string | Yes | | `packageName` | npm package name | string | No | | `githubRepo` | GitHub repo where people can open an issue | string: username/repo | No | | `githubIssueQuery` | Search string to find related issues | string | No | :::tip Throw an error if the configuration changes and does not match your expectations. (For example, `getLoader` cannot find a loader and `isFound` is false.) Create React App might update the structure of their webpack config, so it is very important to show a helpful error message when something breaks. ::: #### Example ``` $ yarn start yarn run v1.12.3 $ craco start /path/to/your/app/craco.config.js:23 throw new Error( ^ Error: Can't find eslint-loader in the webpack config! This error probably occurred because you updated react-scripts or craco. Please try updating craco-less to the latest version: $ yarn upgrade craco-less Or: $ npm update craco-less If that doesn't work, craco-less needs to be fixed to support the latest version. Please check to see if there's already an issue in the ndbroadbent/craco-less repo: * https://github.com/DocSpring/craco-less/issues?q=is%3Aissue+webpack+eslint-loader If not, please open an issue and we'll take a look. (Or you can send a PR!) You might also want to look for related issues in the craco and create-react-app repos: * https://github.com/dilanx/craco/issues?q=is%3Aissue+webpack+eslint-loader * https://github.com/facebook/create-react-app/issues?q=is%3Aissue+webpack+eslint-loader at throwUnexpectedConfigError (/path/to/your/app/craco.config.js:23:19) ... ``` #### Usage ```js const { getLoader, loaderByName, throwUnexpectedConfigError, } = require('@craco/craco'); // Create a helper function if you need to call this multiple times const throwError = (message, githubIssueQuery) => throwUnexpectedConfigError({ packageName: 'craco-less', githubRepo: 'ndbroadbent/craco-less', message, githubIssueQuery, }); const { isFound, match } = getLoader( webpackConfig, loaderByName('eslint-loader') ); if (!isFound) { throwError( "Can't find eslint-loader in the webpack config!", 'webpack+eslint-loader' ); } ``` ================================================ FILE: website/docs/plugin-api/utility-functions/webpack-asset-modules.md ================================================ # Webpack Asset Modules Utility functions for [Webpack asset modules](https://webpack.js.org/guides/asset-modules/) ```js const { assetModuleByName, getAssetModule, getAssetModules, addBeforeAssetModule, addBeforeAssetModules, addAfterAssetModule, addAfterAssetModules, removeAssetModules, } = require('@craco/craco'); ``` ## Functions ### assetModuleByName `assetModuleByName(targetAssetModuleName: string): AssetModuleMatcher` Returns an [asset module matcher](#assetmodulematcher) function to be used with other asset module utility functions to match a name to an existing asset module. ### getAssetModule `getAssetModule(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher)` Retrieve the **first** asset module for which the matcher returns true from the Webpack config. #### Return Type ``` { isFound: boolean; match: { rule: Rule; index: number; } } ``` #### Usage ```js const { getAssetModule, assetModuleByName } = require('@craco/craco'); const { isFound, match } = getAssetModule( webpackConfig, assetModuleByName('asset/source') ); if (isFound) { // do stuff... } ``` ### getAssetModules `getAssetModules(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher)` Retrieve **all** of the asset modules for which the matcher returns true from the Webpack config. #### Return Type ``` { hasFoundAny: boolean; matches: [ { rule: Rule; index: number; } ] } ``` #### Usage ```js const { getAssetModules, assetModuleByName } = require('@craco/craco'); const { hasFoundAny, matches } = getAssetModules( webpackConfig, assetModuleByName('asset/inline') ); if (hasFoundAny) { matches.forEach((x) => { // do stuff... }); } ``` ### addBeforeAssetModule `addBeforeAssetModule(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: Rule)` Add a new asset module **before** the asset module for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; } ``` #### Usage ```js const { addBeforeAssetModule, assetModuleByName } = require('@craco/craco'); const myNewWebpackAssetModule = { test: /\.png/, type: 'asset/resource', }; addBeforeAssetModule( webpackConfig, assetModuleByName('asset/source'), myNewWebpackAssetModule ); ``` ### addBeforeAssetModules `addBeforeAssetModules(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: Rule)` Add a new asset module **before all** of the asset modules for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; addedCount: number; } ``` #### Usage ```js const { addBeforeAssetModules, assetModuleByName } = require('@craco/craco'); const myNewWebpackAssetModule = { test: /\.png/, type: 'asset/resource', }; addBeforeAssetModules( webpackConfig, assetModuleByName('asset/source'), myNewWebpackAssetModule ); ``` ### addAfterAssetModule `addAfterAssetModule(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: Rule)` Add a new asset module **after** the asset module for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; } ``` #### Usage ```js const { addAfterAssetModule, assetModuleByName } = require('@craco/craco'); const myNewWebpackAssetModule = { test: /\.png/, type: 'asset/resource', }; addAfterAssetModule( webpackConfig, assetModuleByName('asset/source'), myNewWebpackAssetModule ); ``` ### addAfterAssetModules `addAfterAssetModules(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher, newAssetModule: Rule)` Add a new asset module **after all** of the asset modules for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; addedCount: number; } ``` #### Usage ```js const { addAfterAssetModules, assetModuleByName } = require('@craco/craco'); const myNewWebpackAssetModule = { test: /\.png/, type: 'asset/resource', }; addAfterAssetModules( webpackConfig, assetModuleByName('asset/source'), myNewWebpackAssetModule ); ``` ### removeAssetModules `removeAssetModules(webpackConfig: WebpackConfig, matcher: AssetModuleMatcher)` Remove **all** of the asset modules for which the matcher returns true from the Webpack config. #### Return Type ``` { hasRemovedAny: boolean; removedCount: number; } ``` #### Usage ```js const { removeAssetModules, assetModuleByName } = require('@craco/craco'); removeAssetModules(webpackConfig, assetModuleByName('asset/source')); ``` ## Reference ### WebpackConfig See https://webpack.js.org/configuration/. ### AssetModuleMatcher An asset module matcher should return true if the provided asset module (within a rule) matches the specified criteria. The function is of the following type: ``` (rule: Rule) => boolean; ``` ### Rule See https://webpack.js.org/configuration/module/#rule. ### ================================================ FILE: website/docs/plugin-api/utility-functions/webpack-loaders.md ================================================ # Webpack Loaders Utility functions for [Webpack loaders](https://webpack.js.org/loaders/) ```js const { loaderByName, getLoader, getLoaders, addBeforeLoader, addBeforeLoaders, addAfterLoader, addAfterLoaders, removeLoaders, } = require('@craco/craco'); ``` ## Functions ### loaderByName `loaderByName(targetLoaderName: string): LoaderMatcher` Returns a [loader matcher](#loadermatcher) function to be used with other loader utility functions to match a name to an existing loader. ### getLoader `getLoader(webpackConfig: WebpackConfig, matcher: LoaderMatcher)` Retrieve the **first** loader for which the matcher returns true from the Webpack config. #### Return Type ``` { isFound: boolean; match: { loader: Rule; parent: Rule[]; index: number; } } ``` #### Usage ```js const { getLoader, loaderByName } = require('@craco/craco'); const { isFound, match } = getLoader( webpackConfig, loaderByName('eslint-loader') ); if (isFound) { // do stuff... } ``` ### getLoaders `getLoaders(webpackConfig: WebpackConfig, matcher: LoaderMatcher)` Retrieve **all** of the loaders for which the matcher returns true from the Webpack config. #### Return Type ``` { hasFoundAny: boolean; matches: [ { loader: Rule; parent: Rule[]; index: number; } ] } ``` #### Usage ```js const { getLoaders, loaderByName } = require('@craco/craco'); const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('babel-loader') ); if (hasFoundAny) { matches.forEach((x) => { // do stuff... }); } ``` ### addBeforeLoader `addBeforeLoader(webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: Rule)` Add a new loader **before** the loader for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; } ``` #### Usage ```js const { addBeforeLoader, loaderByName } = require('@craco/craco'); const myNewWebpackLoader = { loader: require.resolve('tslint-loader'), }; addBeforeLoader( webpackConfig, loaderByName('eslint-loader'), myNewWebpackLoader ); ``` ### addBeforeLoaders `addBeforeLoaders(webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: Rule)` Add a new loader **before all** of the loaders for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; addedCount: number; } ``` #### Usage ```js const { addBeforeLoaders, loaderByName } = require('@craco/craco'); const myNewWebpackLoader = { loader: require.resolve('tslint-loader'), }; addBeforeLoaders( webpackConfig, loaderByName('eslint-loader'), myNewWebpackLoader ); ``` ### addAfterLoader `addAfterLoader(webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: Rule)` Add a new loader **after** the loader for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; } ``` #### Usage ```js const { addAfterLoader, loaderByName } = require('@craco/craco'); const myNewWebpackLoader = { loader: require.resolve('tslint-loader'), }; addAfterLoader( webpackConfig, loaderByName('eslint-loader'), myNewWebpackLoader ); ``` ### addAfterLoaders `addAfterLoaders(webpackConfig: WebpackConfig, matcher: LoaderMatcher, newLoader: Rule)` Add a new loader **after all** of the loaders for which the matcher returns true in the Webpack config. #### Return Type ``` { isAdded: boolean; addedCount: number; } ``` #### Usage ```js const { addAfterLoaders, loaderByName } = require('@craco/craco'); const myNewWebpackLoader = { loader: require.resolve('tslint-loader'), }; addAfterLoaders( webpackConfig, loaderByName('eslint-loader'), myNewWebpackLoader ); ``` ### removeLoaders `removeLoaders(webpackConfig: WebpackConfig, matcher: LoaderMatcher)` Remove **all** of the loaders for which the matcher returns true from the Webpack config. #### Return Type ``` { hasRemovedAny: boolean; removedCount: number; } ``` #### Usage ```js const { removeLoaders, loaderByName } = require('@craco/craco'); removeLoaders(webpackConfig, loaderByName('eslint-loader')); ``` ## Reference ### WebpackConfig See https://webpack.js.org/configuration/. ### LoaderMatcher A loader matcher should return true if the provided loader (within a rule) matches the specified criteria. The function is of the following type: ``` (rule: Rule) => boolean; ``` ### Rule See https://webpack.js.org/configuration/module/#rule. ### ================================================ FILE: website/docs/plugin-api/utility-functions/webpack-plugins.md ================================================ # Webpack Plugins Utility functions for [Webpack plugins](https://webpack.js.org/plugins/) ```js const { pluginByName, getPlugin, addPlugins, removePlugins, } = require('@craco/craco'); ``` ## Functions ### pluginByName `pluginByName(targetPluginName: string): PluginMatcher` Returns a [plugin matcher](#pluginmatcher) function to be used with other plugin utility functions to match a name to an existing plugin. This name is the plugin's constructor (`plugin.constructor.name`). ### getPlugin `getPlugin(webpackConfig: WebpackConfig, matcher: PluginMatcher)` Retrieve the **first** plugin for which the matcher returns true from the Webpack config. #### Return Type ``` { isFound: boolean; match: WebpackPlugin; } ``` #### Usage ```js const { getPlugin, pluginByName } = require('@craco/craco'); const { isFound, match } = getPlugin( webpackConfig, pluginByName('ESLintWebpackPlugin') ); if (isFound) { // do stuff... } ``` ### addPlugins `addPlugins(webpackConfig: WebpackConfig, plugins: [WebpackPlugin | [WebpackPlugin, 'append' | 'prepend']]): void` Add new plugins to the Webpack config. Plugins are added with the [same syntax used within the CRACO config](../../configuration/webpack.md#webpackpluginsadd). #### Usage ```js const { addPlugins } = require('@craco/craco'); const myNewWebpackPlugin = require.resolve('ESLintWebpackPlugin'); addPlugins(webpackConfig, [myNewWebpackPlugin]); addPlugins(webpackConfig, [[myNewWebpackPlugin, 'append']]); addPlugins(webpackConfig, [[myNewWebpackPlugin, 'prepend']]); ``` ### removePlugins `removePlugins(webpackConfig: WebpackConfig, matcher: PluginMatcher)` Remove **all** of the plugins for which the matcher returns true from the Webpack config. #### Return Type ``` { hasRemovedAny: boolean; removedCount: number; } ``` #### Usage ```js const { removePlugins, pluginByName } = require('@craco/craco'); removePlugins(webpackConfig, pluginByName('ESLintWebpackPlugin')); ``` ## Reference ### WebpackConfig See https://webpack.js.org/configuration/. ### PluginMatcher A loader matcher should return true if the provided plugin matches the specified criteria. The function is of the following type: ``` (plugin: WebpackPlugin) => boolean; ``` ### WebpackPlugin See https://webpack.js.org/plugins/. ================================================ FILE: website/docs/welcome.md ================================================ --- title: Welcome slug: / --- # CRACO ## What is CRACO? To customize most things when using [Create React App](https://create-react-app.dev), you can eject. However, you'll then need to maintain every configuration and script yourself, which can be a bit annoying. **CRACO**, which stands for **C**reate **R**eact **A**pp **C**onfiguration **O**verride, allows you to get all of the benefits of [Create React App](https://create-react-app.dev) without ejecting. Customize your configurations ESLint, Babel, PostCSS, and many more with just a single configuration file at the root of your project. :::caution 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. ::: ## About the documentation On this site, you can find the complete documentation for CRACO. If you notice any mistakes or have something you think should be added, create an [issue](https://github.com/dilanx/craco/issues) or submit a pull request. ## CRA toolchain ### Introduction Create React App is intended to allow people to get started with writing React apps quickly. It does this by packaging several key components with a solid default configuration. After some initial experimentation, many people find the default CRA is not quite the right fit. Yet, selecting and configuring a toolchain featuring all of the components CRA already offers is overwhelming. CRACO allows you to enjoy the recognizable project structure of CRA while changing detailed configuration settings of each component. ### Notes on CRA configurations and problem solving Keep in mind that there are _some_ configuration settings available to CRA without CRACO. Getting exactly what you want may involve a combination of making changes through your CRACO configuration file and by using some of the more limited but still important settings available in CRA. Before jumping into customizing your CRACO configuration, step back and think about each part of the problem you're trying to solve. Be sure to review these resources on the CRA configuration, as it may save you time: - [Important environment variables that configure CRA](https://create-react-app.dev/docs/advanced-configuration/) - [Learn about using `postbuild` commands in `package.json`](https://stackoverflow.com/questions/41495658/use-custom-build-output-folder-when-using-create-react-app/51818028#51818028) - [Proxying API or other requests](https://create-react-app.dev/docs/proxying-api-requests-in-development/) or ["how to integrate CRA's dev server with a second backend"](https://github.com/facebook/create-react-app/issues/147) - [Search CRACO issues for gotchas, hints, and examples](https://github.com/dilanx/craco/issues?q=is%3Aissue+sort%3Aupdated-desc) ### Ejecting CRA to learn Avoiding ejecting is a major goal for many CRACO users. However, if you're still learning toolchains and modern frontend workflows, it may be helpful to create a sample ejected CRA project to see how the default CRA app configures each of the components. While CRACO's default configuration inherits directly from CRA's default settings, seeing the default CRA config files throughout the ejected CRA file structure may give you useful perspective. You may even want to try testing a change in the ejected app to better understand how it would be done with your CRACO-based project. ## Acknowledgements - Thanks to [@gsoft-inc](https://github.com/gsoft-inc), the original creator of CRACO, who then passed the project off to me. Specifically, a big shout out to [@patricklafrance](https://github.com/patricklafrance). - Thanks to [@timarney](https://github.com/timarney), the creator of [react-app-rewired](https://github.com/timarney/react-app-rewired), for his original idea. ## License Copyright © 2022 Dilan Nair. This code is licensed under the Apache License 2.0, which can be viewed [here](https://github.com/dilanx/craco/blob/main/packages/craco/LICENSE). ================================================ FILE: website/docusaurus.config.js ================================================ // @ts-check // Note: type annotations allow type checking and IDEs autocompletion const darkCodeThemeStyles = require('prism-react-renderer/themes/vsDark').styles; /** @type {import('@docusaurus/types').Config} */ const config = { title: 'CRACO', tagline: 'Create React App Configuration Override', url: 'https://craco.js.org', baseUrl: '/', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.ico', trailingSlash: true, // GitHub pages deployment config. // If you aren't using GitHub pages, you don't need these. organizationName: 'dilanx', // Usually your GitHub org/user name. projectName: 'craco', // Usually your repo name. // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want // to replace "en" with "zh-Hans". i18n: { defaultLocale: 'en', locales: ['en'], }, presets: [ [ 'classic', /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. // Remove this to remove the "edit this page" links. editUrl: 'https://github.com/dilanx/craco/tree/main/website', }, blog: false, theme: { customCss: require.resolve('./src/css/custom.scss'), }, gtag: { trackingID: "G-WNH8FQZPF4", }, }), ], ], plugins: [ [ '@docusaurus/plugin-content-docs', { id: 'plugins', path: 'plugins', routeBasePath: 'plugins', sidebarPath: false, editUrl: 'https://github.com/dilanx/craco/tree/main/website', }, ], [ '@docusaurus/plugin-content-docs', { id: 'recipes', path: 'recipes', routeBasePath: 'recipes', sidebarPath: require.resolve('./sidebarsRecipes.js'), editUrl: 'https://github.com/dilanx/craco/tree/main/website', }, ], 'docusaurus-plugin-sass', ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { title: 'CRACO', logo: { alt: 'CRACO logo', src: 'img/craco.png', }, items: [ { type: 'doc', docId: 'welcome', position: 'left', label: 'Documentation', }, { to: 'plugins', label: 'Plugins', position: 'left', }, { to: 'recipes', label: 'Recipes', position: 'left', }, { href: 'https://github.com/dilanx/craco', label: 'GitHub', position: 'right', }, { href: 'https://www.npmjs.com/package/@craco/craco', label: 'npm', position: 'right', }, ], }, footer: { style: 'dark', logo: { alt: 'Dilan Nair Open Source Logo', src: '/img/open-source.svg', href: 'https://www.dilanxd.com', width: 280, target: '_blank', }, links: [ { title: 'Documentation', items: [ { label: 'About CRACO', to: 'docs', }, { label: 'Getting Started', to: 'docs/getting-started', }, { label: 'Configuration', to: 'docs/configuration/getting-started', }, { label: 'Configuration API', to: 'docs/configuration-api', }, { label: 'Plugin API', to: 'docs/plugin-api/getting-started', }, ], }, { title: 'Community', items: [ { label: 'Community Maintained Plugins', to: 'plugins', }, { label: 'Recipes', to: 'recipes', }, { label: 'Issues', href: 'https://github.com/dilanx/craco/issues', }, { label: 'Discussions', href: 'https://github.com/dilanx/craco/discussions', }, { label: 'Stack Overflow', href: 'https://stackoverflow.com/questions/tagged/craco', }, ], }, { title: 'More', items: [ { label: 'GitHub', href: 'https://github.com/dilanx/craco', }, { label: 'npm', href: 'https://www.npmjs.com/package/@craco/craco', }, ], }, ], copyright: `Copyright © ${new Date().getFullYear()} Dilan Nair`, }, prism: { darkTheme: { plain: { color: '#D4D4D4', backgroundColor: '#212121', }, styles: [ ...darkCodeThemeStyles, { types: ['title'], style: { color: '#569CD6', fontWeight: 'bold', }, }, { types: ['property', 'parameter'], style: { color: '#9CDCFE', }, }, { types: ['script'], style: { color: '#D4D4D4', }, }, { types: ['boolean', 'arrow', 'atrule', 'tag'], style: { color: '#569CD6', }, }, { types: ['number', 'color', 'unit'], style: { color: '#B5CEA8', }, }, { types: ['font-matter'], style: { color: '#CE9178', }, }, { types: ['keyword', 'rule'], style: { color: '#C586C0', }, }, { types: ['regex'], style: { color: '#D16969', }, }, { types: ['maybe-class-name'], style: { color: '#4EC9B0', }, }, { types: ['constant'], style: { color: '#4FC1FF', }, }, ], }, }, colorMode: { defaultMode: 'dark', disableSwitch: true, }, }), }; module.exports = config; ================================================ FILE: website/package.json ================================================ { "name": "craco-website", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "GIT_USER=dilanx docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@docusaurus/core": "^2.2.0", "@docusaurus/preset-classic": "^2.2.0", "@heroicons/react": "^2.0.12", "@mdx-js/react": "^1.6.22", "clsx": "^1.2.1", "docusaurus-plugin-sass": "^0.2.2", "prism-react-renderer": "^1.3.5", "react": "^17.0.2", "react-dom": "^17.0.2", "sass": "^1.55.0" }, "devDependencies": { "@docusaurus/module-type-aliases": "^2.2.0" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "engines": { "node": ">=16.14" } } ================================================ FILE: website/plugins/plugins.md ================================================ --- title: Plugins slug: / --- # Community Maintained Plugins Here is a list of some CRACO plugins maintained by the community. - [craco-antd](https://github.com/DocSpring/craco-antd) by [@DocSpring](https://github.com/DocSpring) - [craco-babel-loader](https://github.com/rjerue/craco-babel-loader) by [@rjerue](https://github.com/rjerue/) - [craco-base64-inline-loader](https://github.com/melMass/craco-base64-inline-loader) by [@melMass](https://github.com/melMass) - [craco-cesium](https://www.npmjs.com/package/craco-cesium) by [rot1024](https://github.com/rot1024) - [craco-css-modules](https://www.npmjs.com/package/craco-css-modules) by [@crazyurus](https://github.com/crazyurus) - [craco-esbuild](https://github.com/pradel/create-react-app-esbuild) by [@pradel](https://github.com/pradel) - [craco-favicons](https://github.com/rickysullivan/craco-favicons) by [@rickysullivan](https://github.com/rickysullivan) - [craco-image-optimizer-plugin](https://github.com/kkulbae/craco-image-optimizer-plugin) by [@kkulbae](https://github.com/kkulbae) - [craco-interpolate-html-plugin](https://github.com/kkulbae/craco-interpolate-html-plugin) by [@kkulbae](https://github.com/kkulbae) - [craco-less](https://github.com/DocSpring/craco-less) by [@DocSpring](https://github.com/DocSpring) - [craco-linaria](https://github.com/jednano/craco-linaria) by [jednano](https://github.com/jednano) - [craco-parameter-decorator](https://github.com/org-redtea/craco-parameter-decorator) by [@Hokid](https://github.com/Hokid) - [craco-plugin-env](https://github.com/ponjs/craco-plugin-env) by [@ponjs](https://github.com/ponjs) - [craco-plugin-react-hot-reload](https://github.com/HasanAyan/craco-plugin-react-hot-reload) by [@hasanayan](https://github.com/hasanayan) - [craco-plugin-scoped-css](https://github.com/gaoxiaoliangz/react-scoped-css/tree/master/packages/craco-plugin-scoped-css) by [gaoxiaoliangz](https://github.com/gaoxiaoliangz) - [craco-plugin-single-spa-application](https://github.com/hasanayan/craco-plugin-single-spa-application) by [@hasanayan](https://github.com/hasanayan) - [craco-purescript-loader](https://github.com/andys8/craco-purescript-loader) by [@andys8](https://github.com/andys8) - [craco-styled-jsx](https://github.com/cr4zyc4t/craco-styled-jsx) by [@cr4zyc4t](https://github.com/cr4zyc4t) - [craco-preact](https://github.com/DocSpring/craco-preact) by [@DocSpring](https://github.com/DocSpring) - [craco-raw-loader](https://github.com/melMass/craco-raw-loader) by [@melMass](https://github.com/melMass) - [craco-sass-resources-loader](https://github.com/tilap/craco-sass-resources-loader) by [tilap](https://github.com/tilap) - [craco-use-babelrc](https://github.com/jackwilsdon/craco-use-babelrc) by [@jackwilsdon](https://github.com/jackwilsdon) - [craco-workbox](https://github.com/kevinsperrine/craco-workbox) by [@kevinsperrine](https://github.com/kevinsperrine) - [react-app-alias](https://github.com/oklas/react-app-alias) by [@oklas](https://github.com/oklas) - [storybook-preset-craco](https://github.com/artisanofcode/storybook-preset-craco) by [@danielknell](https://github.com/danielknell) Developed one yourself? [Add it to the list!](https://github.com/dilanx/craco/tree/main/website/plugins/plugins.md) ================================================ FILE: website/recipes/add-autoprefixer-options.md ================================================ # Add Autoprefixer options ```js title="craco.config.js" module.exports = { style: { postcss: { env: { autoprefixer: { cascade: true, }, }, }, }, }; ``` ================================================ FILE: website/recipes/add-postcss-features.md ================================================ # Add PostCSS features ```js title="craco.config.js" module.exports = { style: { postcss: { env: { stage: 3, features: { 'nesting-rules': true, }, }, }, }, }; ``` ================================================ FILE: website/recipes/add-stylelint.md ================================================ # Add Stylelint ```js title="craco.config.js" const path = require('path'); const StyleLintPlugin = require('stylelint-webpack-plugin'); module.exports = { webpack: { plugins: { add: [ new StyleLintPlugin({ configBasedir: __dirname, context: path.resolve(__dirname, 'src'), files: ['**/*.css'], }), ], }, }, }; ``` ================================================ FILE: website/recipes/add-webpack-alias-to-jest.md ================================================ # Add Webpack alias to Jest ```js title="craco.config.js" // You can also use craco-alias plugin: https://github.com/risenforces/craco-alias const path = require('path'); module.exports = { webpack: { alias: { '@components': path.resolve(__dirname, 'src/components/'), }, }, jest: { configure: { moduleNameMapper: { '^@components(.*)$': '/src/components$1', }, }, }, }; ``` ================================================ FILE: website/recipes/extends-postcss-plugins.md ================================================ # Extends PostCSS plugins ```js title="craco.config.js" module.exports = { style: { postcss: { plugins: [ require('cssnano')({ preset: 'default', }), ], }, }, }; ``` ================================================ FILE: website/recipes/set-css-loader-locals-convention.md ================================================ # Set css-loader locals convention ```js title="craco.config.js" /** * This example shows how to add the localsConvention option to the css-loader. * Useful if you like to write CSS/SASS classes using BEM notation in css modules. * https://github.com/webpack-contrib/css-loader#localsconvention */ module.exports = { style: { css: { loaderOptions: { localsConvention: 'camelCase', }, }, }, }; ``` ================================================ FILE: website/recipes/use-a-jest-config-file.md ================================================ # Use a Jest config file ```js title="craco.config.js" module.exports = { jest: { configure: { globals: { CONFIG: true, }, }, }, }; ``` ```js title="jest.config.js" const { createJestConfig } = require('@craco/craco'); const cracoConfig = require('./craco.config.js'); const jestConfig = createJestConfig(cracoConfig); module.exports = jestConfig; ``` ================================================ FILE: website/recipes/use-a-postcss-config-file.md ================================================ # Use a PostCSS config file ```js title="craco.config.js" const { POSTCSS_MODES } = require('@craco/craco'); module.exports = { style: { postcss: { mode: POSTCSS_MODES.file, }, }, }; ``` ```js title="postcss.config.js" module.exports = { plugins: [ require('postcss-flexbugs-fixes'), require('postcss-preset-env')({ autoprefixer: { flexbox: 'no-2009', }, stage: 3, features: { 'nesting-rules': true, }, }), ], }; ``` ================================================ FILE: website/recipes/use-an-eslint-config-file.md ================================================ # Use an ESLint config file ```js title="craco.config.js" module.exports = { eslint: { mode: 'file', }, }; ``` ```js title=".eslintrc.js" module.exports = { extends: ['eslint-config-react-app'], }; ``` ================================================ FILE: website/recipes/use-an-https-dev-server.md ================================================ # Use an HTTPS dev server ```js title="craco.config.js" const path = require('path'); const fs = require('fs'); const { whenDev } = require('@craco/craco'); module.exports = { devServer: whenDev(() => ({ https: true, pfx: fs.readFileSync(path.resolve('./localhost.pfx')), pfxPassphrase: 'temp123!', })), }; ``` ================================================ FILE: website/recipes/use-ant-design.md ================================================ # Use Ant Design ```js title="craco.config.js" // Official documentation available at: https://github.com/FormAPI/craco-antd module.exports = { plugins: [ { plugin: require('craco-antd'), options: { customizeTheme: { '@primary-color': '#1DA57A', }, lessLoaderOptions: { noIeCompat: true, }, }, }, ], }; ``` ================================================ FILE: website/recipes/use-babel-plugin-react-css-modules.md ================================================ # Use babel-plugin-react-css-modules ```js title="craco.config.js" const CSS_MODULE_LOCAL_IDENT_NAME = '[local]___[hash:base64:5]'; module.exports = { style: { modules: { localIdentName: CSS_MODULE_LOCAL_IDENT_NAME, }, }, babel: { plugins: [ [ 'babel-plugin-react-css-modules', { generateScopedName: CSS_MODULE_LOCAL_IDENT_NAME, attributeNames: { activeStyleName: 'activeClassName' }, }, ], ], }, }; ``` ================================================ FILE: website/recipes/use-dart-sass.md ================================================ # Use Dart Sass ```js title="craco.config.js" /** * This example shows how to configure the sass-loader for Dart Sass. * Note: Only Dart Sass ('sass') currently supports @use. */ module.exports = { style: { sass: { loaderOptions: { // Prefer 'sass' (dart-sass) over 'node-sass' if both packages are installed. implementation: require('sass'), // Workaround for this bug: https://github.com/webpack-contrib/sass-loader/issues/804 webpackImporter: false, }, }, }, }; ``` ================================================ FILE: website/recipes/use-html-loader.md ================================================ # Use html-loader ```js title="craco.config.js" /** * To use this, ensure you have added `html-loader` as a dev dependency in your `package.json` first * Learn more: https://github.com/webpack-contrib/html-loader */ const { loaderByName, addBeforeLoader } = require('@craco/craco'); module.exports = { webpack: { configure: (webpackConfig) => { webpackConfig.resolve.extensions.push('.html'); const htmlLoader = { loader: require.resolve('html-loader'), test: /\.html$/, exclude: /node_modules/, }; addBeforeLoader(webpackConfig, loaderByName('file-loader'), htmlLoader); return webpackConfig; }, }, }; ``` ```ts title="typings.d.ts" /** * To resolve "Cannot find module error on importing html file in webpack" if you use Typescript * Usage: import foo from './foo.html'; */ declare module '*.html' { const value: string; export default value; } ``` ================================================ FILE: website/recipes/use-less-loader.md ================================================ # Use less-loader ```js title="craco.config.js" // Official documentation available at: https://github.com/FormAPI/craco-less module.exports = { plugins: [ { plugin: require('craco-less'), options: { noIeCompat: true, }, }, ], }; ``` ================================================ FILE: website/recipes/use-linaria.md ================================================ # Use Linaria ```js title="craco.config.js" // Official documentation available at: https://github.com/jednano/craco-linaria module.exports = { plugins: [{ plugin: require('craco-linaria') }], }; ``` ================================================ FILE: website/recipes/use-markdown-loader.md ================================================ # Use markdown-loader ```js title="craco.config.js" // Import Markdown files as HTML into your React Application // const { addBeforeLoader, loaderByName } = require('@craco/craco'); // Additional configuration for Typescript users: add `declare module '*.md'` to your `index.d.ts` file. module.exports = { webpack: { configure: (webpackConfig) => { webpackConfig.resolve.extensions.push('.md'); const markdownLoader = { test: /\.md$/, exclude: /node_modules/, use: [ { loader: require.resolve('html-loader'), }, { loader: require.resolve('markdown-loader'), options: { // see }, }, ], }; addBeforeLoader( webpackConfig, loaderByName('file-loader'), markdownLoader ); return webpackConfig; }, }, }; ``` ================================================ FILE: website/recipes/use-mobx.md ================================================ # Use MobX ```js title="craco.config.js" module.exports = { babel: { plugins: [['@babel/plugin-proposal-decorators', { legacy: true }]], }, }; ``` ================================================ FILE: website/recipes/use-preact.md ================================================ # Use Preact ```js title="craco.config.js" // Official documentation available at: https://github.com/FormAPI/craco-preact module.exports = { plugins: [{ plugin: require('craco-preact') }], }; ``` ================================================ FILE: website/recipes/use-purescript.md ================================================ # Use PureScript ```js title="craco.config.js" // Use PureScript in React Application // const { addBeforeLoader, loaderByName } = require('@craco/craco'); const path = require('path'); // Detect watch // const isWatch = process.argv.some((a) => a === '--watch'); const isWebpackDevServer = process.argv.some( (a) => path.basename(a) === 'webpack-dev-server' ); module.exports = { webpack: { configure: (webpackConfig) => { const { resolve } = webpackConfig; // Resolve purescript extension resolve.extensions.push('.purs'); // Allow imports outside of `src` folder for purescript dependencies webpackConfig.resolve.plugins = resolve.plugins.filter( ({ constructor: c }) => !c || c.name !== 'ModuleScopePlugin' ); // PureScript loader const pursLoader = { loader: 'purs-loader', test: /\.purs$/, exclude: /node_modules/, query: { src: ['src/**/*.purs'], spago: true, pscIde: true, watch: isWebpackDevServer || isWatch, }, }; // Append purs-loader before file-loader addBeforeLoader(webpackConfig, loaderByName('file-loader'), pursLoader); return webpackConfig; }, }, }; ``` ================================================ FILE: website/recipes/use-ts-loader.md ================================================ # Use ts-loader ```js title="craco.config.js" // This recipe replaces usage of babel-loader for compilation of TypeScript / JavaScript application code // with ts-loader running in transpileOnly mode (as type checking is still performed by the // fork-ts-checker-webpack-plugin). Should further customisation of ts-loader be required, the options // below can be adjusted. babel-loader is still used to require non-application TypeScript / JavaScript // To use this, ensure you have added ts-loader as a dependency in your `package.json` first const { addAfterLoader, removeLoaders, loaderByName, getLoaders, throwUnexpectedConfigError, } = require('@craco/craco'); const throwError = (message) => throwUnexpectedConfigError({ packageName: 'craco', githubRepo: 'gsoft-inc/craco', message, githubIssueQuery: 'webpack', }); module.exports = { webpack: { configure: (webpackConfig, { paths }) => { const { hasFoundAny, matches } = getLoaders( webpackConfig, loaderByName('babel-loader') ); if (!hasFoundAny) throwError('failed to find babel-loader'); console.log('removing babel-loader'); const { hasRemovedAny, removedCount } = removeLoaders( webpackConfig, loaderByName('babel-loader') ); if (!hasRemovedAny) throwError('no babel-loader to remove'); if (removedCount !== 2) throwError('had expected to remove 2 babel loader instances'); console.log('adding ts-loader'); const tsLoader = { test: /\.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, loader: require.resolve('ts-loader'), options: { transpileOnly: true }, }; const { isAdded: tsLoaderIsAdded } = addAfterLoader( webpackConfig, loaderByName('url-loader'), tsLoader ); if (!tsLoaderIsAdded) throwError('failed to add ts-loader'); console.log('added ts-loader'); console.log('adding non-application JS babel-loader back'); const { isAdded: babelLoaderIsAdded } = addAfterLoader( webpackConfig, loaderByName('ts-loader'), matches[1].loader // babel-loader ); if (!babelLoaderIsAdded) throwError('failed to add back babel-loader for non-application JS'); console.log('added non-application JS babel-loader back'); return webpackConfig; }, }, }; ``` ================================================ FILE: website/sidebars.js ================================================ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { docs: [ 'welcome', 'getting-started', { type: 'category', label: 'Configuration', link: { type: 'generated-index', }, items: [ 'configuration/getting-started', 'configuration/style', 'configuration/eslint', 'configuration/babel', 'configuration/typescript', 'configuration/webpack', 'configuration/jest', 'configuration/devserver', 'configuration/plugins', ], }, 'configuration-api', { type: 'category', label: 'Plugin API', link: { type: 'generated-index', }, items: [ 'plugin-api/getting-started', 'plugin-api/hooks', { type: 'category', label: 'Utility Functions', link: { type: 'generated-index', description: 'CRACO provides a suite of utility functions to help you develop your plugins.', }, items: [ 'plugin-api/utility-functions/webpack-loaders', 'plugin-api/utility-functions/webpack-asset-modules', 'plugin-api/utility-functions/webpack-plugins', 'plugin-api/utility-functions/miscellaneous', ], }, ], }, ], }; module.exports = sidebars; ================================================ FILE: website/sidebarsRecipes.js ================================================ // @ts-check /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebarsRecipes = { docs: [ { type: 'category', label: 'Recipes', link: { type: 'generated-index', slug: '/', description: 'A collection of configs for common use cases.', }, items: [ { type: 'autogenerated', dirName: '.', }, ], }, ], }; module.exports = sidebarsRecipes; ================================================ FILE: website/src/components/HomepageFeatures/index.js ================================================ import React from 'react'; import clsx from 'clsx'; import styles from './styles.module.css'; const FeatureList = [ { title: 'Easy to Use', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> Docusaurus was designed from the ground up to be easily installed and used to get your website up and running quickly. ), }, { title: 'Focus on What Matters', Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default, description: ( <> Docusaurus lets you focus on your docs, and we'll do the chores. Go ahead and move your docs into the docs directory. ), }, { title: 'Powered by React', Svg: require('@site/static/img/undraw_docusaurus_react.svg').default, description: ( <> Extend or customize your website layout by reusing React. Docusaurus can be extended while reusing the same header and footer. ), }, ]; function Feature({Svg, title, description}) { return (

{title}

{description}

); } export default function HomepageFeatures() { return (
{FeatureList.map((props, idx) => ( ))}
); } ================================================ FILE: website/src/components/HomepageFeatures/styles.module.css ================================================ .features { display: flex; align-items: center; padding: 2rem 0; width: 100%; } .featureSvg { height: 200px; width: 200px; } ================================================ FILE: website/src/css/custom.scss ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #2e8555; --ifm-color-primary-dark: #29784c; --ifm-color-primary-darker: #277148; --ifm-color-primary-darkest: #205d3b; --ifm-color-primary-light: #33925d; --ifm-color-primary-lighter: #359962; --ifm-color-primary-lightest: #3cad6e; --ifm-code-font-size: 95%; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); } /* For readability concerns, you should choose a lighter palette in dark mode. */ [data-theme='dark'] { --ifm-color-primary: #28dfb7; --ifm-color-primary-dark: #1fcea8; --ifm-color-primary-darker: #1dc39e; --ifm-color-primary-darkest: #18a082; --ifm-color-primary-light: #3fe2bf; --ifm-color-primary-lighter: #4ae4c2; --ifm-color-primary-lightest: #6de9ce; } .craco-main { display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 64px; .header { display: flex; justify-content: center; align-items: center; gap: 16px; img { width: 256px; height: 256px; } .text { font-size: 48px; font-weight: bold; p { margin: 0; } span { color: var(--ifm-color-primary); } margin-bottom: 32px; } @media screen and (max-width: 768px) { flex-direction: column; img { width: 128px; height: 128px; } .text { font-size: 32px; } } } .buttons { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; align-items: center; a { padding: 12px 48px; font-size: 18px; font-weight: bold; color: var(--color); border: 2px solid var(--color); border-radius: 16px; display: flex; justify-content: center; align-items: center; gap: 8px; &:hover { text-decoration: none; background-color: var(--color); color: black; } p { margin: 0; } svg { width: 24px; height: 24px; } } } } ================================================ FILE: website/src/pages/index.js ================================================ import React, { useEffect, useState } from 'react'; import Layout from '@theme/Layout'; import { PlayIcon, StarIcon } from '@heroicons/react/24/solid'; import CracoLogo from '@site/static/img/craco.png'; export default function Home() { const [stars, setStars] = useState('-'); useEffect(() => { fetch('https://api.github.com/repos/dilanx/craco') .then((response) => response.json()) .then((data) => setStars( data.stargazers_count.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') ) ); }, []); return (
CRACO logo

CREATE

REACT

APP

CONFIGURATION

OVERRIDE

); } ================================================ FILE: website/static/.nojekyll ================================================ ================================================ FILE: website/static/CNAME ================================================ craco.js.org