Showing preview only (254K chars total). Download the full file or copy to clipboard to get everything.
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
================================================
<div align="center">
<a href="https://craco.js.org">
<img src="https://craco.js.org/img/craco.png" width="200" height="200">
</a>
<h1>CRACO</h1>
<p>
**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)!**
</p>
<br>
[](https://www.npmjs.com/package/@craco/craco) [](https://www.npmjs.com/package/@craco/craco) [](https://github.com/dilanx/craco/blob/main/packages/craco/LICENSE) [](https://github.com/dilanx/craco) [](https://github.com/dilanx/craco/graphs/contributors) [](https://github.com/dilanx/craco/pulls) 
</div>
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
================================================
<div align="center">
<a href="https://craco.js.org">
<img src="https://craco.js.org/img/craco.png" width="200" height="200">
</a>
<h1>CRACO</h1>
<p>
**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)!**
</p>
<br>
[](https://www.npmjs.com/package/@craco/craco) [](https://www.npmjs.com/package/@craco/craco) [](https://github.com/dilanx/craco/blob/main/packages/craco/LICENSE) [](https://github.com/dilanx/craco) [](https://github.com/dilanx/craco/graphs/contributors) [](https://github.com/dilanx/craco/pulls)
</div>
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>(
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<BTransformOptions> | 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<BTransformOptions>
) {
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<JestConfig.InitialOptions, JestContext>,
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<any>,
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<any>,
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<any>,
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<any>,
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<TransformOptions, BaseContext>,
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<PluginOptions, BaseContext>,
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<WebpackConfig, WebpackContext>,
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<any, BaseContext>,
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<any, BaseContext>,
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<any, BaseContext>,
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> = 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<RuleSetRule>,
index: number
): Loader => ({ loader, parent, index });
function getLoaderRecursively(rules: Ul<RuleSetRule>, 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<RuleSetRule>,
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<RuleSetRule>,
matcher,
matchingLoaders
);
return {
hasFoundAny: matchingLoaders.length !== 0,
matches: matchingLoaders,
};
}
function removeLoadersRecursively(
rules: Ul<RuleSetRule>,
matcher: LoaderMatcher
): {
rules: Ul<RuleSetRule>;
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<RuleSetRule>) = result.rules;
}
} else if (rule.oneOf) {
const result = removeLoadersRecursively(rule.oneOf, matcher);
removedCount += result.removedCount;
(rule.oneOf as Ul<RuleSetRule>) = 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<RuleSetRule>,
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<T>(
condition: boolean,
fn: () => T,
unmetValue?: T
): T | undefined {
if (condition) {
return fn();
}
return unmetValue;
}
export function whenDev<T>(fn: () => T, unmetValue?: T): T | undefined {
return when<T>(process.env.NODE_ENV === 'development', fn, unmetValue);
}
export function whenProd<T>(fn: () => T, unmetValue?: T): T | undefined {
return when<T>(process.env.NODE_ENV === 'production', fn, unmetValue);
}
export function whenTest<T>(fn: () => T, unmetValue?: T): T | undefined {
return when<T>(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<any> {
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, Context> =
| Config
| ((config: Config, context: Context) => Config);
export interface CracoStyleConfig {
modules?: {
localIdentName?: string;
};
css?: {
loaderOptions?: Configure<any, BaseContext>;
};
sass?: {
loaderOptions?: Configure<any, BaseContext>;
};
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<any, BaseContext>;
};
}
export interface CracoBabelConfig {
presets?: any[];
plugins?: any[];
assumptions?: { [assumption: string]: boolean };
loaderOptions?: Configure<TransformOptions, BaseContext>;
}
export interface CracoEsLintConfig {
enable?: boolean;
mode?: 'extends' | 'file';
configure?: Configure<Linter.Config, BaseContext>;
pluginOptions?: Configure<PluginOptions, BaseContext>;
}
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<WebpackConfig, WebpackContext>;
}
export type CracoDevServerConfig = Configure<DevServerConfig, DevServerContext>;
export interface CracoJestConfig {
babel?: {
addPresets?: boolean;
addPlugins?: boolean;
};
configure?: Configure<JestConfig.InitialOptions, JestContext>;
}
export interface CracoTypeScriptConfig {
enableTypeChecking?: boolean;
}
export interface CracoPluginDefinition<Options> {
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<any>[];
paths?: Configure<CraPaths | undefined, BaseContext>;
}
================================================
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.<br>
### `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>.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>.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/<testname>` 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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React App</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
</body>
</html>
================================================
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(
<React.StrictMode>
<div className="container">
<h1>Hi!</h1>
</div>
</React.StrictMode>,
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
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React App</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div id="root"></div>
<script src="index.js"></script>
</body>
</html>
================================================
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(
<React.StrictMode>
<div className="container">
<h1>Testing CRACO</h1>
<p id="craco-test">{__CUSTOM_GLOBAL_CONSTANT__}</p>
</div>
</React.StrictMode>,
document.getElementById('root')
);
================================================
FILE: test/integration/jest.config.js
================================================
'use strict';
module.exports = {
// testEnvironment: 'node',
testMatch: ['<rootDir>/**/*.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: ['<rootDir>/**/*.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/(.*)$': '<rootDir>/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(
'<rootDir>/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=<Your GitHub username> 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
<!-- prettier-ignore -->
```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
<!-- prettier-ignore -->
```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
<!-- prettier-ignore -->
```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<T>(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<T>(fn: () => T, unmetValue?: T): T | undefined`
Equivalent to `when(process.env.NODE_ENV === 'development', fn, unmetValue)`.
#### `whenProd(fn, [unmetValue])`
`whenProd<T>(fn: () => T, unmetValue?: T): T | undefined`
Equivalent to `when(process.env.NODE_ENV === 'production', fn, unmetValue)`.
#### `whenTest(fn, [unmetValue])`
`whenTest<T>(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
<!-- prettier-ignore -->
```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).
<!-- prettier-ignore -->
```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
<!-- prettier-ignore -->
```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
<!-- prettier-ignore -->
```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
<!-- prettier-ignore -->
```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).
<details>
<summary>Example</summary>
```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' },
},
],
};
```
</details>
## 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/).
<details>
<summary>Example</summary>
```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' },
},
],
};
```
</details>
## 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).
<details>
<summary>Example</summary>
```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' },
},
],
};
```
</details>
## 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).
<details>
<summary>Example</summary>
```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' },
},
],
};
```
</details>
================================================
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, assetModuleB
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
SYMBOL INDEX (181 symbols across 42 files)
FILE: packages/craco-types/src/asset-modules.ts
type AssetModuleType (line 3) | type AssetModuleType =
type AssetModuleMatcher (line 15) | type AssetModuleMatcher = (rule: RuleSetRule) => boolean;
type AssetModule (line 17) | interface AssetModule {
FILE: packages/craco-types/src/config.ts
type Configure (line 20) | type Configure<Config, Context> =
type CracoStyleConfig (line 24) | interface CracoStyleConfig {
type CracoBabelConfig (line 46) | interface CracoBabelConfig {
type CracoEsLintConfig (line 53) | interface CracoEsLintConfig {
type WebpackAlias (line 60) | type WebpackAlias = { [alias: string]: string };
type AddWebpackPlugins (line 61) | type AddWebpackPlugins = (
type CracoWebpackConfig (line 66) | interface CracoWebpackConfig {
type CracoDevServerConfig (line 75) | type CracoDevServerConfig = Configure<DevServerConfig, DevServerContext>;
type CracoJestConfig (line 77) | interface CracoJestConfig {
type CracoTypeScriptConfig (line 86) | interface CracoTypeScriptConfig {
type CracoPluginDefinition (line 90) | interface CracoPluginDefinition<Options> {
type CracoConfig (line 95) | interface CracoConfig {
FILE: packages/craco-types/src/context.ts
type CraPaths (line 3) | interface CraPaths {
type BaseContext (line 27) | interface BaseContext {
type WebpackContext (line 32) | type WebpackContext = BaseContext;
type DevServerContext (line 34) | interface DevServerContext extends BaseContext {
type JestContext (line 39) | interface JestContext extends BaseContext {
FILE: packages/craco-types/src/loaders.ts
type LoaderMatcher (line 5) | type LoaderMatcher = (rule: RuleSetRule | RuleSetUseItem) => boolean;
type Loader (line 7) | interface Loader {
type CompleteLoader (line 13) | type CompleteLoader = Loader & {
FILE: packages/craco-types/src/plugins.ts
type PluginOptions (line 12) | type PluginOptions = any;
type CracoConfigOverride (line 14) | interface CracoConfigOverride {
type WebpackConfigOverride (line 20) | interface WebpackConfigOverride {
type DevServerConfigOverride (line 27) | interface DevServerConfigOverride {
type JestConfigOverride (line 34) | interface JestConfigOverride {
type CracoPlugin (line 41) | interface CracoPlugin {
FILE: packages/craco-types/src/providers.ts
type DevServerConfigProvider (line 4) | type DevServerConfigProvider = (
type JestConfigProvider (line 9) | type JestConfigProvider = (
FILE: packages/craco/src/lib/args.ts
type CliArgs (line 1) | interface CliArgs {
type CliArgSpec (line 5) | interface CliArgSpec {
function getArgs (line 22) | function getArgs() {
function setArgs (line 26) | function setArgs(values?: CliArgs) {
function findArgsFromCli (line 33) | function findArgsFromCli() {
FILE: packages/craco/src/lib/asset-modules.ts
function assetModuleByName (line 8) | function assetModuleByName(assetModuleName: AssetModuleType) {
function getAssetModule (line 20) | function getAssetModule(
function getAssetModules (line 37) | function getAssetModules(
function removeAssetModules (line 54) | function removeAssetModules(
function addAssetModule (line 75) | function addAssetModule(
function addAssetModules (line 108) | function addAssetModules(
FILE: packages/craco/src/lib/config.ts
constant DEFAULT_CONFIG (line 12) | const DEFAULT_CONFIG: CracoConfig = {
function ensureConfigSanity (line 46) | function ensureConfigSanity(cracoConfig: CracoConfig) {
function processCracoConfig (line 58) | function processCracoConfig(
function getConfigPath (line 72) | function getConfigPath() {
function getConfigAsObject (line 99) | function getConfigAsObject(context: BaseContext) {
function loadCracoConfig (line 114) | function loadCracoConfig(context: BaseContext) {
function loadCracoConfigAsync (line 127) | async function loadCracoConfigAsync(context: BaseContext) {
FILE: packages/craco/src/lib/cra.ts
constant CRA_LATEST_SUPPORTED_MAJOR_VERSION (line 15) | const CRA_LATEST_SUPPORTED_MAJOR_VERSION = '5.0.0';
function resolveConfigFilePath (line 19) | function resolveConfigFilePath(cracoConfig: CracoConfig, fileName: strin...
function resolveConfigFilePathInner (line 30) | function resolveConfigFilePathInner(
function resolveScriptsFilePath (line 44) | function resolveScriptsFilePath(cracoConfig: CracoConfig, fileName: stri...
function resolveReactDevUtilsPath (line 55) | function resolveReactDevUtilsPath(fileName: string) {
function overrideModule (line 61) | function overrideModule(modulePath: string, newModule: any) {
function resolvePackageJson (line 69) | function resolvePackageJson(cracoConfig: CracoConfig) {
function getReactScriptVersion (line 79) | function getReactScriptVersion(cracoConfig: CracoConfig) {
function getCraPathsPath (line 93) | function getCraPathsPath(cracoConfig: CracoConfig) {
function getCraPaths (line 97) | function getCraPaths(cracoConfig: CracoConfig) {
function overrideCraPaths (line 105) | function overrideCraPaths(
function getWebpackDevConfigPath (line 116) | function getWebpackDevConfigPath(cracoConfig: CracoConfig) {
function loadWebpackDevConfig (line 130) | function loadWebpackDevConfig(cracoConfig: CracoConfig): WebpackConfig {
function overrideWebpackDevConfig (line 142) | function overrideWebpackDevConfig(
function getWebpackProdConfigPath (line 159) | function getWebpackProdConfigPath(cracoConfig: CracoConfig) {
function loadWebpackProdConfig (line 173) | function loadWebpackProdConfig(cracoConfig: CracoConfig): WebpackConfig {
function overrideWebpackProdConfig (line 185) | function overrideWebpackProdConfig(
function getDevServerConfigPath (line 202) | function getDevServerConfigPath(cracoConfig: CracoConfig) {
function getDevServerUtilsPath (line 206) | function getDevServerUtilsPath() {
function loadDevServerConfigProvider (line 210) | function loadDevServerConfigProvider(
function overrideDevServerConfigProvider (line 220) | function overrideDevServerConfigProvider(
function loadDevServerUtils (line 231) | function loadDevServerUtils() {
function overrideDevServerUtils (line 239) | function overrideDevServerUtils(newUtils: any) {
function getCreateJestConfigPath (line 249) | function getCreateJestConfigPath(cracoConfig: CracoConfig) {
function loadJestConfigProvider (line 255) | function loadJestConfigProvider(
function overrideJestConfigProvider (line 265) | function overrideJestConfigProvider(
function start (line 278) | function start(cracoConfig: CracoConfig) {
function build (line 286) | function build(cracoConfig: CracoConfig) {
function test (line 294) | function test(cracoConfig: CracoConfig) {
FILE: packages/craco/src/lib/features/dev-server/api.ts
function createDevServerConfigProviderProxy (line 10) | function createDevServerConfigProviderProxy(
FILE: packages/craco/src/lib/features/dev-server/create-config-provider-proxy.ts
function createProxy (line 15) | function createProxy(
function createConfigProviderProxy (line 61) | function createConfigProviderProxy(
FILE: packages/craco/src/lib/features/dev-server/override-utils.ts
function overrideWebpackCompilerToDisableTypeScriptTypeChecking (line 6) | function overrideWebpackCompilerToDisableTypeScriptTypeChecking(
function overrideUtils (line 27) | function overrideUtils(cracoConfig: CracoConfig) {
FILE: packages/craco/src/lib/features/dev-server/override.ts
function overrideDevServer (line 9) | function overrideDevServer(
FILE: packages/craco/src/lib/features/dev-server/set-environment-variables.ts
function setEnvironmentVariable (line 5) | function setEnvironmentVariable(envProperty: string, value: any) {
function setEnvironmentVariables (line 13) | function setEnvironmentVariables(devServerConfig: DevServerConfig) {
FILE: packages/craco/src/lib/features/jest/api.ts
function createJestConfig (line 11) | function createJestConfig(
FILE: packages/craco/src/lib/features/jest/create-jest-babel-transform.ts
function createJestBabelTransform (line 24) | function createJestBabelTransform(cracoConfig?: CracoConfig): any {
FILE: packages/craco/src/lib/features/jest/jest-babel-transform.ts
method processAsync (line 17) | async processAsync(
FILE: packages/craco/src/lib/features/jest/merge-jest-config.ts
constant BABEL_TRANSFORM_ENTRY_KEY (line 15) | const BABEL_TRANSFORM_ENTRY_KEY = '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$';
function overrideBabelTransform (line 17) | function overrideBabelTransform(
function configureBabel (line 37) | function configureBabel(
function giveTotalControl (line 67) | function giveTotalControl(
function mergeJestConfig (line 90) | function mergeJestConfig(
FILE: packages/craco/src/lib/features/jest/override.ts
function overrideJest (line 7) | function overrideJest(cracoConfig: CracoConfig, context: JestContext) {
FILE: packages/craco/src/lib/features/paths/override.ts
function overridePaths (line 6) | function overridePaths(cracoConfig: CracoConfig, context: BaseContext) {
FILE: packages/craco/src/lib/features/plugins.ts
function overrideCraco (line 17) | function overrideCraco(
function applyCracoConfigPlugins (line 41) | function applyCracoConfigPlugins(
function overrideWebpack (line 58) | function overrideWebpack(
function applyWebpackConfigPlugins (line 84) | function applyWebpackConfigPlugins(
function overrideDevServer (line 107) | function overrideDevServer(
function applyDevServerConfigPlugins (line 133) | function applyDevServerConfigPlugins(
function overrideJest (line 156) | function overrideJest(
function applyJestConfigPlugins (line 182) | function applyJestConfigPlugins(
FILE: packages/craco/src/lib/features/webpack/api.ts
function createWebpackDevConfig (line 15) | function createWebpackDevConfig(
function createWebpackProdConfig (line 29) | function createWebpackProdConfig(
function createWebpackConfig (line 43) | function createWebpackConfig(
FILE: packages/craco/src/lib/features/webpack/babel.ts
function addPresets (line 16) | function addPresets(loader: RuleSetRule, babelPresets: any[]) {
function addPlugins (line 34) | function addPlugins(loader: RuleSetRule, babelPlugins: any[]) {
function addAssumptions (line 52) | function addAssumptions(
function applyLoaderOptions (line 74) | function applyLoaderOptions(
function overrideLoader (line 102) | function overrideLoader(
function overrideBabel (line 127) | function overrideBabel(
FILE: packages/craco/src/lib/features/webpack/eslint.ts
function disableEslint (line 14) | function disableEslint(webpackConfig: WebpackConfig) {
function extendsEslintConfig (line 27) | function extendsEslintConfig(
function useEslintConfigFile (line 71) | function useEslintConfigFile(plugin: any) {
function enableEslintIgnoreFile (line 84) | function enableEslintIgnoreFile(plugin: any) {
function applyPluginOptions (line 96) | function applyPluginOptions(
function overrideEsLint (line 117) | function overrideEsLint(
FILE: packages/craco/src/lib/features/webpack/merge-webpack-config.ts
function addAlias (line 24) | function addAlias(webpackConfig: WebpackConfig, webpackAlias: WebpackAli...
function addPlugins (line 36) | function addPlugins(
function removePluginsFromWebpackConfig (line 51) | function removePluginsFromWebpackConfig(
function giveTotalControl (line 81) | function giveTotalControl(
function mergeWebpackConfig (line 104) | function mergeWebpackConfig(
FILE: packages/craco/src/lib/features/webpack/override.ts
function overrideWebpackDev (line 11) | function overrideWebpackDev(
function overrideWebpackProd (line 25) | function overrideWebpackProd(
FILE: packages/craco/src/lib/features/webpack/style/css.ts
type CompleteLoaderModule (line 18) | interface CompleteLoaderModule {
function setModuleLocalIdentName (line 26) | function setModuleLocalIdentName(
function applyLoaderOptions (line 44) | function applyLoaderOptions(
function overrideCssLoader (line 72) | function overrideCssLoader(
function overrideModuleLoader (line 84) | function overrideModuleLoader(
function overrideCss (line 98) | function overrideCss(
FILE: packages/craco/src/lib/features/webpack/style/postcss.ts
constant CRA_PRESET_ENV (line 24) | const CRA_PRESET_ENV = {
function usePostcssConfigFile (line 31) | function usePostcssConfigFile(match: Loader) {
function extendsPostcss (line 48) | function extendsPostcss(
function applyLoaderOptions (line 96) | function applyLoaderOptions(
function overrideLoader (line 121) | function overrideLoader(
function overridePostcss (line 141) | function overridePostcss(
FILE: packages/craco/src/lib/features/webpack/style/sass.ts
function setLoaderProperty (line 13) | function setLoaderProperty(
function applyLoaderOptions (line 31) | function applyLoaderOptions(
function overrideLoader (line 59) | function overrideLoader(
function overrideSass (line 73) | function overrideSass(
FILE: packages/craco/src/lib/features/webpack/style/style.ts
function overrideStyle (line 8) | function overrideStyle(
FILE: packages/craco/src/lib/features/webpack/typescript.ts
function disableTypeChecking (line 6) | function disableTypeChecking(webpackConfig: WebpackConfig) {
function overrideTypeScript (line 16) | function overrideTypeScript(
FILE: packages/craco/src/lib/loaders.ts
type Ul (line 11) | type Ul<T> = T[] | undefined;
function loaderByName (line 13) | function loaderByName(targetLoaderName: string) {
function getLoaderRecursively (line 37) | function getLoaderRecursively(rules: Ul<RuleSetRule>, matcher: LoaderMat...
function getLoader (line 65) | function getLoader(
function getLoadersRecursively (line 77) | function getLoadersRecursively(
function getLoaders (line 109) | function getLoaders(
function removeLoadersRecursively (line 127) | function removeLoadersRecursively(
function removeLoaders (line 179) | function removeLoaders(
function addLoader (line 194) | function addLoader(
function addLoaders (line 223) | function addLoaders(
FILE: packages/craco/src/lib/logger.ts
function log (line 3) | function log(...rest: any[]) {
function logError (line 9) | function logError(...rest: any[]) {
FILE: packages/craco/src/lib/plugin-utils.ts
type ConfigError (line 1) | interface ConfigError {
function gitHubIssueUrl (line 8) | function gitHubIssueUrl(repo: string, query?: string) {
function showNpmPackageUrl (line 14) | function showNpmPackageUrl(packageName: string) {
function showGitHubIssueUrl (line 18) | function showGitHubIssueUrl(repo: string, query?: string) {
function showPackageUpdateInstructions (line 26) | function showPackageUpdateInstructions(
function throwUnexpectedConfigError (line 41) | function throwUnexpectedConfigError({
FILE: packages/craco/src/lib/user-config-utils.ts
function when (line 1) | function when<T>(
function whenDev (line 13) | function whenDev<T>(fn: () => T, unmetValue?: T): T | undefined {
function whenProd (line 17) | function whenProd<T>(fn: () => T, unmetValue?: T): T | undefined {
function whenTest (line 21) | function whenTest<T>(fn: () => T, unmetValue?: T): T | undefined {
FILE: packages/craco/src/lib/utils.ts
function isFunction (line 3) | function isFunction(value: any): value is (...args: any[]) => any {
function isArray (line 7) | function isArray(value: any): value is Array<any> {
function isString (line 11) | function isString(value: any): value is string {
function isBoolean (line 15) | function isBoolean(value: any): value is boolean {
function deepMergeWithArray (line 19) | function deepMergeWithArray(dest: any, ...src: any) {
FILE: packages/craco/src/lib/validate-cra-version.ts
function validateCraVersion (line 5) | function validateCraVersion(cracoConfig: CracoConfig) {
FILE: packages/craco/src/lib/webpack-plugins.ts
function pluginByName (line 3) | function pluginByName(targetPluginName: string) {
function getPlugin (line 9) | function getPlugin(
function addPlugins (line 21) | function addPlugins(
function removePlugins (line 49) | function removePlugins(
FILE: test/unit/merging-tests/custom-craco-plugin/craco-plugin-mock/index.js
function onPostBuild (line 4) | function onPostBuild({ paths }) {
FILE: website/src/components/HomepageFeatures/index.js
function Feature (line 38) | function Feature({Svg, title, description}) {
function HomepageFeatures (line 52) | function HomepageFeatures() {
FILE: website/src/pages/index.js
function Home (line 6) | function Home() {
Condensed preview — 165 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (243K chars).
[
{
"path": ".eslintrc.json",
"chars": 483,
"preview": "{\n \"env\": {\n \"es2021\": true,\n \"node\": true\n },\n \"extends\": [\"eslint:recommended\", \"plugin:@typescript-eslint/re"
},
{
"path": ".gitattributes",
"chars": 19,
"preview": "* text=auto eol=lf\n"
},
{
"path": ".github/CODEOWNERS",
"chars": 10,
"preview": "* @dilanx\n"
},
{
"path": ".github/FUNDING.yml",
"chars": 64,
"preview": "# These are supported funding model platforms\n\ngithub: [dilanx]\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 597,
"preview": "---\nname: Bug report\nabout: Something with CRACO isn't working correctly\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Wha"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 189,
"preview": "---\nname: Feature request\nabout: CRACO should have this functionality\ntitle: ''\nlabels: feature request\nassignees: ''\n\n-"
},
{
"path": ".github/workflows/run-tests.yml",
"chars": 546,
"preview": "name: tests\n\non: [push, pull_request]\n\njobs:\n build:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n\n strategy:\n"
},
{
"path": ".github/workflows/website-deploy.yml",
"chars": 859,
"preview": "name: Deploy website to GitHub Pages\n\non:\n push:\n branches:\n - main\n paths:\n - '.github/workflows/websi"
},
{
"path": ".github/workflows/website-test-deploy.yml",
"chars": 501,
"preview": "name: Test website deployment\n\ndefaults:\n run:\n working-directory: website\n\non:\n pull_request:\n branches:\n "
},
{
"path": ".gitignore",
"chars": 199,
"preview": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# compiled\ndist\n\n# dependencies\nnode_modules"
},
{
"path": ".prettierignore",
"chars": 76,
"preview": "/.vscode\n/node_modules\n/recipes\npackage.json\npackage-lock.json\ntsconfig.json"
},
{
"path": ".vscode/extensions.json",
"chars": 68,
"preview": "{\n \"recommendations\": [\n \"esbenp.prettier-vscode\"\n ]\n}\n"
},
{
"path": "LICENSE",
"chars": 10173,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 2296,
"preview": "<div align=\"center\">\n <a href=\"https://craco.js.org\">\n <img src=\"https://craco.js.org/img/craco.png\" width=\"200\" hei"
},
{
"path": "lerna.json",
"chars": 111,
"preview": "{\n \"$schema\": \"node_modules/lerna/schemas/lerna-schema.json\",\n \"useWorkspaces\": true,\n \"version\": \"7.1.0\"\n}\n"
},
{
"path": "package.json",
"chars": 1312,
"preview": "{\n \"name\": \"craco\",\n \"private\": true,\n \"license\": \"Apache-2.0\",\n \"keywords\": [\n \"react\",\n \"create-react-app\",\n"
},
{
"path": "packages/craco/LICENSE",
"chars": 10173,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "packages/craco/README.md",
"chars": 2207,
"preview": "<div align=\"center\">\n <a href=\"https://craco.js.org\">\n <img src=\"https://craco.js.org/img/craco.png\" width=\"200\" hei"
},
{
"path": "packages/craco/package.json",
"chars": 1690,
"preview": "{\n \"name\": \"@craco/craco\",\n \"description\": \"Create React App Configuration Override, an easy and comprehensible config"
},
{
"path": "packages/craco/src/bin/craco.ts",
"chars": 1589,
"preview": "#!/usr/bin/env node\n\nimport spawn from 'cross-spawn';\n\nconst args = process.argv.slice(2);\nconst scriptIndex = args.find"
},
{
"path": "packages/craco/src/bin/jest.ts",
"chars": 941,
"preview": "#!/usr/bin/env node\n\n/*\n * Copied (and converted to TS) from https://github.com/timarney/react-app-rewired/blob/master/p"
},
{
"path": "packages/craco/src/index.ts",
"chars": 1473,
"preview": "import {\n addAfterAssetModule,\n addAfterAssetModules,\n addBeforeAssetModule,\n addBeforeAssetModules,\n assetModuleBy"
},
{
"path": "packages/craco/src/lib/args.ts",
"chars": 833,
"preview": "export interface CliArgs {\n [key: string]: string | boolean;\n}\n\nexport interface CliArgSpec {\n [key: string]: { arg: s"
},
{
"path": "packages/craco/src/lib/asset-modules.ts",
"chars": 3589,
"preview": "import type {\n AssetModule,\n AssetModuleMatcher,\n AssetModuleType,\n} from '@craco/types';\nimport type { Configuration"
},
{
"path": "packages/craco/src/lib/config.ts",
"chars": 3719,
"preview": "import type { BaseContext, CracoConfig } from '@craco/types';\n\nimport { cosmiconfigSync } from 'cosmiconfig';\nimport tsL"
},
{
"path": "packages/craco/src/lib/cra.ts",
"chars": 7354,
"preview": "import type {\n CracoConfig,\n CraPaths,\n DevServerConfigProvider,\n JestConfigProvider,\n} from '@craco/types';\nimport "
},
{
"path": "packages/craco/src/lib/features/dev-server/api.ts",
"chars": 1099,
"preview": "import type { CracoConfig, DevServerContext } from '@craco/types';\nimport type { CliArgs } from '../../args';\n\nimport { "
},
{
"path": "packages/craco/src/lib/features/dev-server/create-config-provider-proxy.ts",
"chars": 1820,
"preview": "import type {\n CracoConfig,\n DevServerContext,\n DevServerConfigProvider,\n} from '@craco/types';\nimport type { Configu"
},
{
"path": "packages/craco/src/lib/features/dev-server/override-utils.ts",
"chars": 1166,
"preview": "import type { CracoConfig } from '@craco/types';\n\nimport { loadDevServerUtils, overrideDevServerUtils } from '../../cra'"
},
{
"path": "packages/craco/src/lib/features/dev-server/override.ts",
"chars": 761,
"preview": "import type { CracoConfig, DevServerContext } from '@craco/types';\n\nimport { overrideDevServerConfigProvider } from '../"
},
{
"path": "packages/craco/src/lib/features/dev-server/set-environment-variables.ts",
"chars": 712,
"preview": "import type { Configuration as DevServerConfig } from 'webpack-dev-server';\n\nimport { isString } from '../../utils';\n\nfu"
},
{
"path": "packages/craco/src/lib/features/jest/api.ts",
"chars": 1222,
"preview": "import type { CracoConfig, JestContext } from '@craco/types';\nimport type { Config as JestConfig } from '@jest/types';\ni"
},
{
"path": "packages/craco/src/lib/features/jest/create-jest-babel-transform.ts",
"chars": 1674,
"preview": "import type { CracoConfig } from '@craco/types';\n\nimport babelJest from 'babel-jest';\nimport { loadCracoConfig } from '."
},
{
"path": "packages/craco/src/lib/features/jest/jest-babel-transform.ts",
"chars": 1118,
"preview": "import type { TransformOptions as BTransformOptions } from '@babel/core';\nimport type {\n SyncTransformer,\n TransformOp"
},
{
"path": "packages/craco/src/lib/features/jest/merge-jest-config.ts",
"chars": 3463,
"preview": "import type {\n Configure,\n CracoConfig,\n JestConfigProvider,\n JestContext,\n} from '@craco/types';\nimport type { Conf"
},
{
"path": "packages/craco/src/lib/features/jest/override.ts",
"chars": 615,
"preview": "import type { CracoConfig, JestContext } from '@craco/types';\n\nimport { loadJestConfigProvider, overrideJestConfigProvid"
},
{
"path": "packages/craco/src/lib/features/paths/override.ts",
"chars": 596,
"preview": "import type { BaseContext, CracoConfig, CraPaths } from '@craco/types';\n\nimport { overrideCraPaths } from '../../cra';\ni"
},
{
"path": "packages/craco/src/lib/features/plugins.ts",
"chars": 4478,
"preview": "import type {\n BaseContext,\n CracoConfig,\n CracoPluginDefinition,\n DevServerContext,\n JestContext,\n WebpackContext"
},
{
"path": "packages/craco/src/lib/features/webpack/api.ts",
"chars": 1911,
"preview": "import type { CracoConfig, WebpackContext } from '@craco/types';\nimport type { Configuration as WebpackConfig } from 'we"
},
{
"path": "packages/craco/src/lib/features/webpack/babel.ts",
"chars": 3556,
"preview": "import type {\n BaseContext,\n CompleteLoader,\n Configure,\n CracoConfig,\n} from '@craco/types';\nimport type { Configur"
},
{
"path": "packages/craco/src/lib/features/webpack/eslint.ts",
"chars": 3682,
"preview": "import type {\n BaseContext,\n Configure,\n CracoConfig,\n CracoEsLintConfig,\n} from '@craco/types';\nimport type { Plugi"
},
{
"path": "packages/craco/src/lib/features/webpack/merge-webpack-config.ts",
"chars": 4244,
"preview": "import type {\n AddWebpackPlugins,\n Configure,\n CracoConfig,\n WebpackAlias,\n WebpackContext,\n} from '@craco/types';\n"
},
{
"path": "packages/craco/src/lib/features/webpack/override.ts",
"chars": 933,
"preview": "import type { CracoConfig, WebpackContext } from '@craco/types';\n\nimport {\n loadWebpackDevConfig,\n loadWebpackProdConf"
},
{
"path": "packages/craco/src/lib/features/webpack/style/css.ts",
"chars": 3320,
"preview": "import type {\n BaseContext,\n CompleteLoader,\n Configure,\n CracoStyleConfig,\n} from '@craco/types';\nimport type { Con"
},
{
"path": "packages/craco/src/lib/features/webpack/style/postcss.ts",
"chars": 4026,
"preview": "import type {\n BaseContext,\n CompleteLoader,\n Configure,\n CracoStyleConfig,\n Loader,\n} from '@craco/types';\nimport "
},
{
"path": "packages/craco/src/lib/features/webpack/style/sass.ts",
"chars": 2347,
"preview": "import type {\n BaseContext,\n CompleteLoader,\n Configure,\n CracoStyleConfig,\n} from '@craco/types';\nimport type { Con"
},
{
"path": "packages/craco/src/lib/features/webpack/style/style.ts",
"chars": 657,
"preview": "import type { BaseContext, CracoConfig } from '@craco/types';\nimport type { Configuration as WebpackConfig } from 'webpa"
},
{
"path": "packages/craco/src/lib/features/webpack/typescript.ts",
"chars": 730,
"preview": "import type { CracoConfig } from '@craco/types';\nimport type { Configuration as WebpackConfig } from 'webpack';\n\nimport "
},
{
"path": "packages/craco/src/lib/loaders.ts",
"chars": 6532,
"preview": "import type { Loader, LoaderMatcher } from '@craco/types';\nimport type {\n Configuration as WebpackConfig,\n RuleSetRule"
},
{
"path": "packages/craco/src/lib/logger.ts",
"chars": 214,
"preview": "import { getArgs } from './args';\n\nexport function log(...rest: any[]) {\n if (getArgs().verbose) {\n console.lo"
},
{
"path": "packages/craco/src/lib/paths.ts",
"chars": 167,
"preview": "import fs from 'fs';\nimport { log } from './logger';\n\nexport const projectRoot = fs.realpathSync(process.cwd());\n\nlog('P"
},
{
"path": "packages/craco/src/lib/plugin-utils.ts",
"chars": 1847,
"preview": "interface ConfigError {\n message: string;\n packageName?: string;\n githubRepo?: string;\n githubIssueQuery?: string;\n}"
},
{
"path": "packages/craco/src/lib/user-config-utils.ts",
"chars": 606,
"preview": "export function when<T>(\n condition: boolean,\n fn: () => T,\n unmetValue?: T\n): T | undefined {\n if (condition) {\n "
},
{
"path": "packages/craco/src/lib/utils.ts",
"chars": 617,
"preview": "import { mergeWith } from 'lodash';\n\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return"
},
{
"path": "packages/craco/src/lib/validate-cra-version.ts",
"chars": 795,
"preview": "import type { CracoConfig } from '@craco/types';\n\nimport { getReactScriptVersion } from '../lib/cra';\n\nexport function v"
},
{
"path": "packages/craco/src/lib/webpack-plugins.ts",
"chars": 1758,
"preview": "import type { Configuration as WebpackConfig } from 'webpack';\n\nexport function pluginByName(targetPluginName: string) {"
},
{
"path": "packages/craco/src/scripts/build.ts",
"chars": 1125,
"preview": "import type { BaseContext } from '@craco/types';\n\nprocess.env.NODE_ENV = process.env.NODE_ENV || 'production';\n\nimport {"
},
{
"path": "packages/craco/src/scripts/start.ts",
"chars": 1151,
"preview": "import type { BaseContext, CracoConfig } from '@craco/types';\n\nprocess.env.NODE_ENV = process.env.NODE_ENV || 'developme"
},
{
"path": "packages/craco/src/scripts/test.ts",
"chars": 1010,
"preview": "import type { BaseContext, CracoConfig } from '@craco/types';\n\nprocess.env.NODE_ENV = process.env.NODE_ENV || 'test';\n\ni"
},
{
"path": "packages/craco/tsconfig.json",
"chars": 131,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"compilerOptions\": {\n \"noEmit\": false,\n \"outDir\": \"dist\"\n },\n \"include\":"
},
{
"path": "packages/craco-types/README.md",
"chars": 98,
"preview": "# @craco/types\n\nOfficial type definitions for [CRACO](https://www.npmjs.com/package/@craco/craco)\n"
},
{
"path": "packages/craco-types/package.json",
"chars": 911,
"preview": "{\n \"name\": \"@craco/types\",\n \"description\": \"Official type declarations for @craco/craco\",\n \"version\": \"7.1.0\",\n \"mai"
},
{
"path": "packages/craco-types/src/asset-modules.ts",
"chars": 411,
"preview": "import type { RuleSetRule } from 'webpack';\n\nexport type AssetModuleType =\n | 'javascript/auto'\n | 'javascript/dynamic"
},
{
"path": "packages/craco-types/src/config.ts",
"chars": 2798,
"preview": "import type { TransformOptions } from '@babel/core';\nimport type { Options as AutoprefixerOptions } from 'autoprefixer';"
},
{
"path": "packages/craco-types/src/context.ts",
"chars": 913,
"preview": "import type { ProxyConfigArray } from 'webpack-dev-server';\n\nexport interface CraPaths {\n dotenv: string;\n appPath: st"
},
{
"path": "packages/craco-types/src/index.ts",
"chars": 774,
"preview": "export {\n AssetModuleType,\n AssetModuleMatcher,\n AssetModule,\n} from './asset-modules';\n\nexport {\n Configure,\n Crac"
},
{
"path": "packages/craco-types/src/loaders.ts",
"chars": 359,
"preview": "import type { RuleSetRule, RuleSetUseItem } from 'webpack';\n\n// TODO these typings need to be updated I'm pretty sure\n\ne"
},
{
"path": "packages/craco-types/src/plugins.ts",
"chars": 1436,
"preview": "import type { CracoConfig } from './config';\nimport type { Configuration as DevServerConfig } from 'webpack-dev-server';"
},
{
"path": "packages/craco-types/src/providers.ts",
"chars": 396,
"preview": "import type { Configuration as DevServerConfig } from 'webpack-dev-server';\nimport type { Config as JestConfig } from '@"
},
{
"path": "packages/craco-types/tsconfig.json",
"chars": 131,
"preview": "{\n \"extends\": \"../../tsconfig.json\",\n \"compilerOptions\": {\n \"noEmit\": false,\n \"outDir\": \"dist\"\n },\n \"include\":"
},
{
"path": "recipes/README.md",
"chars": 75,
"preview": "# CRACO Recipes\n\nRecipes have been moved to https://craco.js.org/recipes/.\n"
},
{
"path": "test/README.md",
"chars": 1912,
"preview": "# CRACO End-to-End Tests\n\n## Usage\n\nThese tests ensure various functionality contracts are upheld across dependency upgr"
},
{
"path": "test/integration/fixtures/autoprefixer-test/index.test.js",
"chars": 1130,
"preview": "'use strict';\nconst path = require('path');\nconst fs = require('fs');\n\ntest('Should have the expected styles', async () "
},
{
"path": "test/integration/fixtures/autoprefixer-test/test-package-files/craco.config.js",
"chars": 182,
"preview": "const webpack = require('webpack');\nconst isDevelopment = false;\nmodule.exports = {\n style: {\n postcss: {\n plug"
},
{
"path": "test/integration/fixtures/autoprefixer-test/test-package-files/package.json",
"chars": 445,
"preview": "{\n \"name\": \"craco-postcss-test\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"react\": \"^18.2.0\",\n "
},
{
"path": "test/integration/fixtures/autoprefixer-test/test-package-files/public/index.html",
"chars": 247,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>React App</title>\n <link rel=\"style"
},
{
"path": "test/integration/fixtures/autoprefixer-test/test-package-files/src/index.css",
"chars": 192,
"preview": "::placeholder {\n color: gray;\n}\n\n.image {\n background-image: url(\"https://google.com\");\n}\n@media (min-resolution: 2dpp"
},
{
"path": "test/integration/fixtures/autoprefixer-test/test-package-files/src/index.js",
"chars": 244,
"preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\n\nReactDOM.render(\n <React.StrictMode"
},
{
"path": "test/integration/fixtures/basic-integration-test/index.test.js",
"chars": 1093,
"preview": "'use strict';\nconst { join } = require('path');\nconst { execSync, spawn } = require('child_process');\n\nbeforeAll(async ("
},
{
"path": "test/integration/fixtures/basic-integration-test/test-package-files/craco.config.js",
"chars": 341,
"preview": "const webpack = require('webpack');\nconst isDevelopment = false;\nmodule.exports = {\n webpack: {\n configure: (webpack"
},
{
"path": "test/integration/fixtures/basic-integration-test/test-package-files/package.json",
"chars": 434,
"preview": "{\n \"name\": \"craco-basic-test\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"dependencies\": {\n \"react\": \"^18.2.0\",\n "
},
{
"path": "test/integration/fixtures/basic-integration-test/test-package-files/public/index.html",
"chars": 247,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>React App</title>\n <link rel=\"style"
},
{
"path": "test/integration/fixtures/basic-integration-test/test-package-files/src/index.js",
"chars": 352,
"preview": "/* global __CUSTOM_GLOBAL_CONSTANT__ */\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css"
},
{
"path": "test/integration/jest.config.js",
"chars": 638,
"preview": "'use strict';\n\nmodule.exports = {\n // testEnvironment: 'node',\n testMatch: ['<rootDir>/**/*.test.js'],\n testPathIgnor"
},
{
"path": "test/integration/setup.js",
"chars": 1547,
"preview": "const { join } = require('path');\nconst { execSync } = require('child_process');\nconst fs = require('fs');\nconst rootPat"
},
{
"path": "test/integration/teardown.js",
"chars": 750,
"preview": "// Clean up the environment after integration tests\nconst { execSync } = require('child_process');\nconst { join } = requ"
},
{
"path": "test/unit/jest.config.js",
"chars": 292,
"preview": "'use strict';\n\nmodule.exports = {\n testEnvironment: 'node',\n testMatch: ['<rootDir>/**/*.test.js'],\n testPathIgnorePa"
},
{
"path": "test/unit/merging-tests/autoprefixer-options/autoprefixer.test.js",
"chars": 1115,
"preview": "const cracoConfig = require('./craco.config');\nconst autoprefixer = require('autoprefixer');\n\ndescribe('CRACO autoprefix"
},
{
"path": "test/unit/merging-tests/autoprefixer-options/craco.config.js",
"chars": 378,
"preview": "module.exports = {\n style: {\n postcss: {\n plugins: [\n {\n plugin: require('autoprefixer'),\n "
},
{
"path": "test/unit/merging-tests/configuration-merging/cra.mock.config.js",
"chars": 295,
"preview": "const craConfigMock = {\n entry: './src/index.js',\n output: {\n path: '/dist',\n filename: 'bundle.js',\n },\n modu"
},
{
"path": "test/unit/merging-tests/configuration-merging/craco.config.js",
"chars": 764,
"preview": "module.exports = {\n webpack: {\n configure: (webpackConfig, { env, paths }) => {\n // Add a custom loader for SVG"
},
{
"path": "test/unit/merging-tests/configuration-merging/merging.test.js",
"chars": 1540,
"preview": "'use strict';\nconst cracoConfig = require('./craco.config');\nconst craConfigMock = require('./cra.mock.config');\n\n//Crea"
},
{
"path": "test/unit/merging-tests/custom-babel-config/babel.config.mock.js",
"chars": 121,
"preview": "const babelConfigMock = {\n presets: ['@babel/preset-env', '@babel/preset-react'],\n};\n\nmodule.exports = babelConfigMock;"
},
{
"path": "test/unit/merging-tests/custom-babel-config/babel.test.js",
"chars": 924,
"preview": "const cracoConfig = require('./craco.config');\nconst babelConfigMock = require('./babel.config.mock');\n\ndescribe('CRACO "
},
{
"path": "test/unit/merging-tests/custom-babel-config/craco.config.js",
"chars": 188,
"preview": "module.exports = {\n babel: {\n loaderOptions: {\n presets: [\n '@babel/preset-env',\n '@babel/preset-"
},
{
"path": "test/unit/merging-tests/custom-craco-plugin/craco-plugin-mock/index.js",
"chars": 264,
"preview": "const fs = require('fs');\nconst path = require('path');\n\nfunction onPostBuild({ paths }) {\n const pluginLogPath = path."
},
{
"path": "test/unit/merging-tests/custom-craco-plugin/craco.config.js",
"chars": 141,
"preview": "const CracoPluginMock = require('./craco-plugin-mock');\n\nmodule.exports = {\n plugins: [\n {\n plugin: CracoPlugin"
},
{
"path": "test/unit/merging-tests/custom-craco-plugin/plugin.test.js",
"chars": 968,
"preview": "const fs = require('fs');\nconst path = require('path');\nconst cracoConfig = require('./craco.config');\nconst CracoPlugin"
},
{
"path": "test/unit/merging-tests/custom-env-variables/craco.config.js",
"chars": 79,
"preview": "module.exports = {\n env: {\n MY_CUSTOM_ENV_VAR: 'custom-env-value',\n },\n};\n"
},
{
"path": "test/unit/merging-tests/custom-env-variables/env.test.js",
"chars": 1240,
"preview": "const cracoConfig = require('./craco.config');\n\ndescribe('CRACO environment variables', () => {\n it('correctly sets cus"
},
{
"path": "test/unit/merging-tests/custom-eslint-config/craco.config.js",
"chars": 211,
"preview": "module.exports = {\n eslint: {\n configure: {\n extends: ['react-app', 'plugin:prettier/recommended'],\n rules"
},
{
"path": "test/unit/merging-tests/custom-eslint-config/eslint.config.mock.js",
"chars": 134,
"preview": "const eslintConfigMock = {\n extends: ['react-app'],\n rules: {\n 'no-console': 'warn',\n },\n};\n\nmodule.exports = esli"
},
{
"path": "test/unit/merging-tests/custom-eslint-config/eslint.test.js",
"chars": 1065,
"preview": "const cracoConfig = require('./craco.config');\nconst eslintConfigMock = require('./eslint.config.mock');\n\ndescribe('CRAC"
},
{
"path": "test/unit/merging-tests/custom-jest-config/craco.config.js",
"chars": 226,
"preview": "module.exports = {\n jest: {\n configure: {\n transform: {\n '^.+\\\\.[t|j]sx?$': 'babel-jest',\n },\n "
},
{
"path": "test/unit/merging-tests/custom-jest-config/jest.config.mock.js",
"chars": 119,
"preview": "const jestConfigMock = {\n transform: {\n '^.+\\\\.[t|j]sx?$': 'babel-jest',\n },\n};\n\nmodule.exports = jestConfigMock;\n"
},
{
"path": "test/unit/merging-tests/custom-jest-config/jest.test.js",
"chars": 960,
"preview": "const cracoConfig = require('./craco.config');\nconst jestConfigMock = require('./jest.config.mock');\n\ndescribe('CRACO Je"
},
{
"path": "test/unit/merging-tests/custom-postcss-config/craco.config.js",
"chars": 129,
"preview": "module.exports = {\n style: {\n postcss: {\n plugins: [require('autoprefixer'), require('postcss-nested')],\n },"
},
{
"path": "test/unit/merging-tests/custom-postcss-config/postcss.config.mock.js",
"chars": 106,
"preview": "const postcssConfigMock = {\n plugins: [require('autoprefixer')],\n};\n\nmodule.exports = postcssConfigMock;\n"
},
{
"path": "test/unit/merging-tests/custom-postcss-config/postcss.test.js",
"chars": 949,
"preview": "const cracoConfig = require('./craco.config');\nconst postcssConfigMock = require('./postcss.config.mock');\n\n//Checks if "
},
{
"path": "test/unit/merging-tests/html-webpack-plugin/craco.config.js",
"chars": 905,
"preview": "module.exports = {\n webpack: {\n configure: (webpackConfig, { env, paths }) => {\n const HtmlWebpackPlugin = requ"
},
{
"path": "test/unit/merging-tests/html-webpack-plugin/html-webpack-plugin.test.js",
"chars": 826,
"preview": "const webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst cracoConfig = requir"
},
{
"path": "tsconfig.json",
"chars": 501,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"module\": \"commonjs\",\n \"allowJs\": true,\n \"skip"
},
{
"path": "website/.gitignore",
"chars": 233,
"preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
},
{
"path": "website/README.md",
"chars": 770,
"preview": "# Website\n\nThis website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.\n\n### I"
},
{
"path": "website/babel.config.js",
"chars": 89,
"preview": "module.exports = {\n presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
},
{
"path": "website/docs/configuration/babel.md",
"chars": 998,
"preview": "---\ndescription: Customize Babel\n---\n\n# Babel\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.exports = {"
},
{
"path": "website/docs/configuration/devserver.md",
"chars": 963,
"preview": "---\ndescription: Configure DevServer\n---\n\n# DevServer\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.exp"
},
{
"path": "website/docs/configuration/eslint.md",
"chars": 1364,
"preview": "---\ndescription: Customize ESLint\n---\n\n# ESLint\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.exports ="
},
{
"path": "website/docs/configuration/getting-started.md",
"chars": 5168,
"preview": "---\ndescription: Setting up the configuration file\n---\n\n# Getting Started\n\n## Creating the file\n\nCRACO can be configured"
},
{
"path": "website/docs/configuration/jest.md",
"chars": 1322,
"preview": "---\ndescription: Customize Jest\n---\n\n# Jest\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.exports = {\n "
},
{
"path": "website/docs/configuration/plugins.md",
"chars": 393,
"preview": "---\ndescription: Include third-party CRACO plugins\n---\n\n# CRACO Plugins\n\nView a list of [community maintained plugins](/"
},
{
"path": "website/docs/configuration/style.md",
"chars": 2738,
"preview": "---\ndescription: Customize CSS preprocessors\n---\n\n# Style\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule"
},
{
"path": "website/docs/configuration/typescript.md",
"chars": 325,
"preview": "---\ndescription: Customize TypeScript\n---\n\n# TypeScript\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.e"
},
{
"path": "website/docs/configuration/webpack.md",
"chars": 1809,
"preview": "---\ndescription: Customize Webpack\n---\n\n# Webpack\n\n<!-- prettier-ignore -->\n```js title=\"craco.config.js\"\nmodule.exports"
},
{
"path": "website/docs/configuration-api.md",
"chars": 1627,
"preview": "# Configuration API\n\nTo integrate with other tools, it's useful to have access to the configuration generated by CRACO.\n"
},
{
"path": "website/docs/getting-started.md",
"chars": 2089,
"preview": "# Getting Started\n\n:::info\n\nThe current **CRACO** version requires **Create React App 5** (`react-scripts 5.x.x`). If us"
},
{
"path": "website/docs/plugin-api/getting-started.md",
"chars": 1316,
"preview": "---\ndescription: Get started with CRACO plugin development\n---\n\n# Getting Started\n\nCRACO has a nice plugin API. You can "
},
{
"path": "website/docs/plugin-api/hooks.md",
"chars": 8110,
"preview": "---\ndescription: Hooks available to CRACO plugins\n---\n\n# Hooks\n\nThere are four hooks available to a plugin:\n\n- [`overrid"
},
{
"path": "website/docs/plugin-api/utility-functions/miscellaneous.md",
"chars": 2963,
"preview": "# Miscellaneous\n\nOther useful utility functions\n\n```js\nconst { throwUnexpectedConfigError } = require('@craco/craco');\n`"
},
{
"path": "website/docs/plugin-api/utility-functions/webpack-asset-modules.md",
"chars": 4926,
"preview": "# Webpack Asset Modules\n\nUtility functions for [Webpack asset modules](https://webpack.js.org/guides/asset-modules/)\n\n``"
},
{
"path": "website/docs/plugin-api/utility-functions/webpack-loaders.md",
"chars": 4484,
"preview": "# Webpack Loaders\n\nUtility functions for [Webpack loaders](https://webpack.js.org/loaders/)\n\n```js\nconst {\n loaderByNam"
},
{
"path": "website/docs/plugin-api/utility-functions/webpack-plugins.md",
"chars": 2311,
"preview": "# Webpack Plugins\n\nUtility functions for [Webpack plugins](https://webpack.js.org/plugins/)\n\n```js\nconst {\n pluginByNam"
},
{
"path": "website/docs/welcome.md",
"chars": 4099,
"preview": "---\ntitle: Welcome\nslug: /\n---\n\n# CRACO\n\n## What is CRACO?\n\nTo customize most things when using [Create React App](https"
},
{
"path": "website/docusaurus.config.js",
"chars": 6966,
"preview": "// @ts-check\n// Note: type annotations allow type checking and IDEs autocompletion\n\nconst darkCodeThemeStyles =\n requir"
},
{
"path": "website/package.json",
"chars": 1149,
"preview": "{\n \"name\": \"craco-website\",\n \"version\": \"0.0.0\",\n \"private\": true,\n \"scripts\": {\n \"docusaurus\": \"docusaurus\",\n "
},
{
"path": "website/plugins/plugins.md",
"chars": 3247,
"preview": "---\ntitle: Plugins\nslug: /\n---\n\n# Community Maintained Plugins\n\nHere is a list of some CRACO plugins maintained by the c"
},
{
"path": "website/recipes/add-autoprefixer-options.md",
"chars": 204,
"preview": "# Add Autoprefixer options\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n style: {\n postcss: {\n env: {\n "
},
{
"path": "website/recipes/add-postcss-features.md",
"chars": 222,
"preview": "# Add PostCSS features\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n style: {\n postcss: {\n env: {\n "
},
{
"path": "website/recipes/add-stylelint.md",
"chars": 388,
"preview": "# Add Stylelint\n\n```js title=\"craco.config.js\"\nconst path = require('path');\n\nconst StyleLintPlugin = require('stylelint"
},
{
"path": "website/recipes/add-webpack-alias-to-jest.md",
"chars": 436,
"preview": "# Add Webpack alias to Jest\n\n```js title=\"craco.config.js\"\n// You can also use craco-alias plugin: https://github.com/ri"
},
{
"path": "website/recipes/extends-postcss-plugins.md",
"chars": 217,
"preview": "# Extends PostCSS plugins\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n style: {\n postcss: {\n plugins: [\n "
},
{
"path": "website/recipes/set-css-loader-locals-convention.md",
"chars": 434,
"preview": "# Set css-loader locals convention\n\n```js title=\"craco.config.js\"\n/**\n * This example shows how to add the localsConvent"
},
{
"path": "website/recipes/use-a-jest-config-file.md",
"chars": 388,
"preview": "# Use a Jest config file\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n jest: {\n configure: {\n globals: {\n "
},
{
"path": "website/recipes/use-a-postcss-config-file.md",
"chars": 500,
"preview": "# Use a PostCSS config file\n\n```js title=\"craco.config.js\"\nconst { POSTCSS_MODES } = require('@craco/craco');\n\nmodule.ex"
},
{
"path": "website/recipes/use-an-eslint-config-file.md",
"chars": 214,
"preview": "# Use an ESLint config file\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n eslint: {\n mode: 'file',\n },\n};\n```\n"
},
{
"path": "website/recipes/use-an-https-dev-server.md",
"chars": 330,
"preview": "# Use an HTTPS dev server\n\n```js title=\"craco.config.js\"\nconst path = require('path');\nconst fs = require('fs');\n\nconst "
},
{
"path": "website/recipes/use-ant-design.md",
"chars": 391,
"preview": "# Use Ant Design\n\n```js title=\"craco.config.js\"\n// Official documentation available at: https://github.com/FormAPI/craco"
},
{
"path": "website/recipes/use-babel-plugin-react-css-modules.md",
"chars": 492,
"preview": "# Use babel-plugin-react-css-modules\n\n```js title=\"craco.config.js\"\nconst CSS_MODULE_LOCAL_IDENT_NAME = '[local]___[hash"
},
{
"path": "website/recipes/use-dart-sass.md",
"chars": 529,
"preview": "# Use Dart Sass\n\n```js title=\"craco.config.js\"\n/**\n * This example shows how to configure the sass-loader for Dart Sass."
},
{
"path": "website/recipes/use-html-loader.md",
"chars": 933,
"preview": "# Use html-loader\n\n```js title=\"craco.config.js\"\n/**\n * To use this, ensure you have added `html-loader` as a dev depend"
},
{
"path": "website/recipes/use-less-loader.md",
"chars": 274,
"preview": "# Use less-loader\n\n```js title=\"craco.config.js\"\n// Official documentation available at: https://github.com/FormAPI/crac"
},
{
"path": "website/recipes/use-linaria.md",
"chars": 204,
"preview": "# Use Linaria\n\n```js title=\"craco.config.js\"\n// Official documentation available at: https://github.com/jednano/craco-li"
},
{
"path": "website/recipes/use-markdown-loader.md",
"chars": 1009,
"preview": "# Use markdown-loader\n\n```js title=\"craco.config.js\"\n// Import Markdown files as HTML into your React Application\n// <ht"
},
{
"path": "website/recipes/use-mobx.md",
"chars": 156,
"preview": "# Use MobX\n\n```js title=\"craco.config.js\"\nmodule.exports = {\n babel: {\n plugins: [['@babel/plugin-proposal-decorator"
},
{
"path": "website/recipes/use-preact.md",
"chars": 201,
"preview": "# Use Preact\n\n```js title=\"craco.config.js\"\n// Official documentation available at: https://github.com/FormAPI/craco-pre"
},
{
"path": "website/recipes/use-purescript.md",
"chars": 1380,
"preview": "# Use PureScript\n\n```js title=\"craco.config.js\"\n// Use PureScript in React Application\n// <https://www.purescript.org>\n\n"
},
{
"path": "website/recipes/use-ts-loader.md",
"chars": 2408,
"preview": "# Use ts-loader\n\n```js title=\"craco.config.js\"\n// This recipe replaces usage of babel-loader for compilation of TypeScri"
},
{
"path": "website/sidebars.js",
"chars": 1424,
"preview": "// @ts-check\n\n/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */\nconst sidebars = {\n docs: [\n '"
},
{
"path": "website/sidebarsRecipes.js",
"chars": 473,
"preview": "// @ts-check\n\n/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */\nconst sidebarsRecipes = {\n docs: "
},
{
"path": "website/src/components/HomepageFeatures/index.js",
"chars": 1692,
"preview": "import React from 'react';\nimport clsx from 'clsx';\nimport styles from './styles.module.css';\n\nconst FeatureList = [\n {"
},
{
"path": "website/src/components/HomepageFeatures/styles.module.css",
"chars": 138,
"preview": ".features {\n display: flex;\n align-items: center;\n padding: 2rem 0;\n width: 100%;\n}\n\n.featureSvg {\n height: 200px;\n"
},
{
"path": "website/src/css/custom.scss",
"chars": 2278,
"preview": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framewor"
},
{
"path": "website/src/pages/index.js",
"chars": 1612,
"preview": "import React, { useEffect, useState } from 'react';\nimport Layout from '@theme/Layout';\nimport { PlayIcon, StarIcon } fr"
},
{
"path": "website/static/.nojekyll",
"chars": 0,
"preview": ""
},
{
"path": "website/static/CNAME",
"chars": 13,
"preview": "craco.js.org\n"
}
]
About this extraction
This page contains the full source code of the dilanx/craco GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 165 files (216.8 KB), approximately 59.1k tokens, and a symbol index with 181 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.