├── .all-contributorsrc
├── .babel-plugin-macrosrc.js
├── .editorconfig
├── .env.local
├── .env.production
├── .eslintrc.js
├── .gitattributes
├── .gitbook.yaml
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── build.yaml
│ ├── commitlint.yaml
│ ├── lint.yml
│ ├── release.yml
│ └── test.yaml
├── .gitignore
├── .husky
├── commit-msg
├── pre-commit
└── prepare-commit-msg
├── .npmrc
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .versionrc.js
├── .vscode
├── extensions.json
├── launch.json
└── settings.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── RELEASE_PROCESS.md
├── commitlint.config.js
├── docs
├── README.md
├── SUMMARY.md
├── building-blocks
│ ├── README.md
│ ├── async-components.md
│ ├── css.md
│ ├── i18n.md
│ ├── routing.md
│ ├── slice
│ │ ├── README.md
│ │ ├── redux-injectors.md
│ │ ├── redux-saga.md
│ │ ├── redux-toolkit.md
│ │ └── reselect.md
│ └── testing.md
├── deployment
│ ├── aws.md
│ ├── azure.md
│ ├── heroku.md
│ └── netlify.md
├── misc
│ └── faq.md
├── quick-start.md
├── tools
│ ├── commands.md
│ ├── editors.md
│ └── package-managers.md
└── understanding-react-boilerplate.md
├── internals
├── extractMessages
│ ├── __tests__
│ │ └── extractingMessages.test.ts
│ ├── i18next-scanner.config.js
│ ├── jest.config.js
│ └── stringfyTranslations.js
├── generators
│ ├── component
│ │ ├── index.test.tsx.hbs
│ │ ├── index.ts
│ │ ├── index.tsx.hbs
│ │ ├── loadable.ts.hbs
│ │ └── messages.ts.hbs
│ ├── paths.ts
│ ├── plopfile.ts
│ ├── slice
│ │ ├── appendRootState.hbs
│ │ ├── importContainerState.hbs
│ │ ├── index.ts
│ │ ├── index.ts.hbs
│ │ ├── saga.ts.hbs
│ │ ├── selectors.ts.hbs
│ │ └── types.ts.hbs
│ └── utils
│ │ └── index.ts
├── scripts
│ ├── clean.ts
│ ├── create-changelog.script.ts
│ ├── create-cra-app.script.ts
│ ├── create-npm-package.script.ts
│ ├── create-npm-package.ts
│ ├── create-template-folder.ts
│ ├── utils.ts
│ └── verify-startingTemplate-changes.ts
├── startingTemplate
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── app
│ │ │ ├── __tests__
│ │ │ │ └── index.test.tsx
│ │ │ ├── components
│ │ │ │ └── NotFoundPage
│ │ │ │ │ ├── Loadable.tsx
│ │ │ │ │ ├── P.ts
│ │ │ │ │ └── index.tsx
│ │ │ ├── index.tsx
│ │ │ └── pages
│ │ │ │ └── HomePage
│ │ │ │ ├── Loadable.tsx
│ │ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ ├── locales
│ │ │ ├── __tests__
│ │ │ │ └── i18n.test.ts
│ │ │ ├── en
│ │ │ │ └── translation.json
│ │ │ ├── i18n.ts
│ │ │ ├── translations.ts
│ │ │ └── types.ts
│ │ ├── react-app-env.d.ts
│ │ ├── reportWebVitals.ts
│ │ ├── setupTests.ts
│ │ ├── store
│ │ │ ├── __tests__
│ │ │ │ ├── configureStore.test.ts
│ │ │ │ └── reducer.test.ts
│ │ │ ├── configureStore.ts
│ │ │ └── reducers.ts
│ │ ├── styles
│ │ │ ├── __tests__
│ │ │ │ └── media.test.ts
│ │ │ ├── global-styles.ts
│ │ │ └── media.ts
│ │ ├── types
│ │ │ ├── RootState.ts
│ │ │ └── index.ts
│ │ └── utils
│ │ │ ├── @reduxjs
│ │ │ └── toolkit.tsx
│ │ │ ├── loadable.tsx
│ │ │ ├── messages.ts
│ │ │ ├── redux-injectors.ts
│ │ │ └── types
│ │ │ └── injector-typings.ts
│ └── tsconfig.json
└── testing
│ ├── generators
│ ├── componentVariations.ts
│ ├── sliceVariations.ts
│ └── test-generators.ts
│ └── loadable.mock.tsx
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── src
├── app
│ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── index.test.tsx.snap
│ │ └── index.test.tsx
│ ├── components
│ │ ├── A
│ │ │ ├── __tests__
│ │ │ │ └── index.test.tsx
│ │ │ └── index.ts
│ │ ├── FormLabel
│ │ │ ├── __tests__
│ │ │ │ └── index.test.tsx
│ │ │ └── index.ts
│ │ ├── Link
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ └── index.test.tsx
│ │ │ └── index.ts
│ │ ├── LoadingIndicator
│ │ │ ├── __tests__
│ │ │ │ ├── __snapshots__
│ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ └── index.test.tsx
│ │ │ └── index.tsx
│ │ ├── NavBar
│ │ │ ├── Logo.tsx
│ │ │ ├── Nav.tsx
│ │ │ ├── __tests__
│ │ │ │ ├── Logo.test.tsx
│ │ │ │ ├── Nav.test.tsx
│ │ │ │ ├── __snapshots__
│ │ │ │ │ ├── Logo.test.tsx.snap
│ │ │ │ │ ├── Nav.test.tsx.snap
│ │ │ │ │ └── index.test.tsx.snap
│ │ │ │ └── index.test.tsx
│ │ │ ├── assets
│ │ │ │ ├── documentation-icon.svg
│ │ │ │ └── github-icon.svg
│ │ │ └── index.tsx
│ │ ├── PageWrapper
│ │ │ └── index.ts
│ │ └── Radio
│ │ │ └── index.tsx
│ ├── index.tsx
│ └── pages
│ │ ├── HomePage
│ │ ├── Features
│ │ │ ├── GithubRepoForm
│ │ │ │ ├── RepoItem.tsx
│ │ │ │ ├── __tests__
│ │ │ │ │ ├── RepoItem.test.tsx
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── RepoItem.test.tsx.snap
│ │ │ │ │ └── index.test.tsx
│ │ │ │ ├── assets
│ │ │ │ │ ├── new-window.svg
│ │ │ │ │ └── star.svg
│ │ │ │ ├── components
│ │ │ │ │ ├── Input.ts
│ │ │ │ │ ├── TextButton.ts
│ │ │ │ │ └── __tests__
│ │ │ │ │ │ ├── TextButton.test.tsx
│ │ │ │ │ │ └── input.test.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── slice
│ │ │ │ │ ├── __tests__
│ │ │ │ │ ├── __snapshots__
│ │ │ │ │ │ └── saga.test.ts.snap
│ │ │ │ │ ├── saga.test.ts
│ │ │ │ │ ├── selectors.test.ts
│ │ │ │ │ └── slice.test.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── saga.ts
│ │ │ │ │ ├── selectors.ts
│ │ │ │ │ └── types.ts
│ │ │ ├── LanguageSwitch
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ ├── index.tsx
│ │ │ │ └── messages.ts
│ │ │ ├── ThemeSwitch
│ │ │ │ ├── __tests__
│ │ │ │ │ └── index.test.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── assets
│ │ │ │ ├── code-analysis.svg
│ │ │ │ ├── css.svg
│ │ │ │ ├── instant-feedback.svg
│ │ │ │ ├── intl.svg
│ │ │ │ ├── route.svg
│ │ │ │ ├── scaffolding.svg
│ │ │ │ ├── seo.svg
│ │ │ │ ├── state.svg
│ │ │ │ └── ts.svg
│ │ │ └── index.tsx
│ │ ├── Loadable.tsx
│ │ ├── Logos.tsx
│ │ ├── Masthead.tsx
│ │ ├── __tests__
│ │ │ ├── Features.test.tsx
│ │ │ ├── Logos.test.tsx
│ │ │ ├── Masthead.test.tsx
│ │ │ ├── __snapshots__
│ │ │ │ ├── Features.test.tsx.snap
│ │ │ │ ├── Logos.test.tsx.snap
│ │ │ │ ├── Masthead.test.tsx.snap
│ │ │ │ └── index.test.tsx.snap
│ │ │ └── index.test.tsx
│ │ ├── assets
│ │ │ ├── cra-logo.svg
│ │ │ ├── plus-sign.svg
│ │ │ └── rp-logo.svg
│ │ ├── components
│ │ │ ├── Lead.ts
│ │ │ ├── P.ts
│ │ │ ├── SubTitle.ts
│ │ │ ├── Title.ts
│ │ │ └── __tests__
│ │ │ │ ├── Lead.test.tsx
│ │ │ │ ├── P.test.tsx
│ │ │ │ ├── Subtitle.test.tsx
│ │ │ │ ├── Title.test.tsx
│ │ │ │ └── __snapshots__
│ │ │ │ ├── Lead.test.tsx.snap
│ │ │ │ ├── P.test.tsx.snap
│ │ │ │ ├── Subtitle.test.tsx.snap
│ │ │ │ └── Title.test.tsx.snap
│ │ ├── index.tsx
│ │ └── messages.ts
│ │ └── NotFoundPage
│ │ ├── Loadable.tsx
│ │ ├── P.ts
│ │ ├── __tests__
│ │ ├── __snapshots__
│ │ │ └── index.test.tsx.snap
│ │ └── index.test.tsx
│ │ └── index.tsx
├── index.tsx
├── locales
│ ├── __tests__
│ │ └── i18n.test.ts
│ ├── de
│ │ └── translation.json
│ ├── en
│ │ └── translation.json
│ ├── i18n.ts
│ ├── translations.ts
│ └── types.ts
├── react-app-env.d.ts
├── reportWebVitals.ts
├── setupTests.ts
├── store
│ ├── __tests__
│ │ ├── configureStore.test.ts
│ │ └── reducer.test.ts
│ ├── configureStore.ts
│ └── reducers.ts
├── styles
│ ├── StyleConstants.ts
│ ├── __tests__
│ │ └── media.test.ts
│ ├── global-styles.ts
│ ├── media.ts
│ └── theme
│ │ ├── ThemeProvider.tsx
│ │ ├── __tests__
│ │ ├── ThemeProvider.test.tsx
│ │ └── utils.test.ts
│ │ ├── slice
│ │ ├── __tests__
│ │ │ └── slice.test.ts
│ │ ├── index.ts
│ │ ├── selectors.ts
│ │ └── types.ts
│ │ ├── styled.d.ts
│ │ ├── themes.ts
│ │ └── utils.ts
├── types
│ ├── Repo.d.ts
│ ├── RootState.ts
│ └── index.ts
└── utils
│ ├── @reduxjs
│ └── toolkit.tsx
│ ├── __tests__
│ ├── __snapshots__
│ │ └── loadable.test.tsx.snap
│ ├── loadable.test.tsx
│ └── request.test.ts
│ ├── loadable.tsx
│ ├── messages.ts
│ ├── redux-injectors.ts
│ ├── request.ts
│ └── types
│ └── injector-typings.ts
├── template.json
├── tsconfig.json
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "react-boilerplate-cra-template",
3 | "projectOwner": "react-boilerplate",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 80,
10 | "commit": true,
11 | "contributors": [
12 | {
13 | "login": "Can-Sahin",
14 | "name": "Can Sahin",
15 | "avatar_url": "https://avatars2.githubusercontent.com/u/33245689",
16 | "profile": "https://github.com/Can-Sahin",
17 | "contributions": [
18 | "code",
19 | "doc",
20 | "ideas",
21 | "review",
22 | "test"
23 | ]
24 | },
25 | {
26 | "login": "receptiryaki",
27 | "name": "Recep Tiryaki",
28 | "avatar_url": "https://avatars0.githubusercontent.com/u/3495307",
29 | "profile": "https://github.com/receptiryaki",
30 | "contributions": [
31 | "code",
32 | "ideas",
33 | "design"
34 | ]
35 | },
36 | {
37 | "login": "mogsdad",
38 | "name": "David Bingham",
39 | "avatar_url": "https://avatars3.githubusercontent.com/u/1707731",
40 | "profile": "https://github.com/mogsdad",
41 | "contributions": [
42 | "doc"
43 | ]
44 | },
45 | {
46 | "login": "lourensdev",
47 | "name": "Lourens de Villiers",
48 | "avatar_url": "https://avatars.githubusercontent.com/u/5746141",
49 | "profile": "https://github.com/lourensdev",
50 | "contributions": [
51 | "doc"
52 | ]
53 | },
54 | {
55 | "login": "rejochandran",
56 | "name": "Rejo Chandran",
57 | "avatar_url": "https://avatars.githubusercontent.com/u/4696985",
58 | "profile": "https://github.com/rejochandran",
59 | "contributions": [
60 | "code",
61 | "doc",
62 | "test"
63 | ]
64 | },
65 | {
66 | "login": "qeleb",
67 | "name": "Caleb Hoff",
68 | "avatar_url": "https://avatars.githubusercontent.com/u/15345696",
69 | "profile": "https://github.com/qeleb",
70 | "contributions": [
71 | "code",
72 | "doc",
73 | "ideas",
74 | "test"
75 | ]
76 | }
77 | ],
78 | "contributorsPerLine": 8,
79 | "commitConvention": "none"
80 | }
81 |
--------------------------------------------------------------------------------
/.babel-plugin-macrosrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | styledComponents: {
3 | displayName: process.env.NODE_ENV !== 'production',
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [{package,bower}.json]
15 | indent_style = space
16 | indent_size = 2
17 |
18 | [{.eslintrc,.scss-lint.yml}]
19 | indent_style = space
20 | indent_size = 2
21 |
22 | [*.{scss,sass}]
23 | indent_style = space
24 | indent_size = 2
25 |
--------------------------------------------------------------------------------
/.env.local:
--------------------------------------------------------------------------------
1 | BROWSER=none
2 | EXTEND_ESLINT=true
3 | FAST_REFRESH=true
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | GENERATE_SOURCEMAP=false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | const prettierOptions = JSON.parse(
5 | fs.readFileSync(path.resolve(__dirname, '.prettierrc'), 'utf8'),
6 | );
7 |
8 | module.exports = {
9 | extends: ['react-app', 'prettier'],
10 | plugins: ['prettier'],
11 | rules: {
12 | 'prettier/prettier': ['error', prettierOptions],
13 | },
14 | overrides: [
15 | {
16 | files: ['**/*.ts?(x)'],
17 | rules: { 'prettier/prettier': ['warn', prettierOptions] },
18 | },
19 | ],
20 | };
21 |
--------------------------------------------------------------------------------
/.gitbook.yaml:
--------------------------------------------------------------------------------
1 | root: ./docs
2 |
3 | structure:
4 | readme: README.md
5 | summary: SUMMARY.md
6 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Global owners
2 | * @can-sahin @receptiryaki
3 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | ---
5 |
6 | Before opening a new issue, please take a moment to review our [**community guidelines**](https://github.com/react-boilerplate/react-boilerplate-cra-template/blob/master/CONTRIBUTING.md) to make the contribution process easy and effective for everyone involved.
7 |
8 | ## Description
9 |
10 | A clear and concise description of what the bug is.
11 |
12 | ## Steps to reproduce
13 |
14 | Steps to reproduce the behavior:
15 |
16 | (Add link to a demo on https://jsfiddle.net or similar if possible)
17 |
18 | **Expected behavior**
19 | A clear and concise description of what you expected to happen.
20 |
21 | **Screenshots**
22 | If applicable, add screenshots to help explain your problem.
23 |
24 | ## Versions
25 |
26 | - react-boilerplate-cra-template:
27 | - Node/NPM:
28 | - Browser:
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | ---
5 |
6 | **Is your feature request related to a problem? Please describe.**
7 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
8 |
9 | **Describe the solution you'd like**
10 | A clear and concise description of what you want to happen.
11 |
12 | **Describe alternatives you've considered**
13 | A clear and concise description of any alternative solutions or features you've considered.
14 |
15 | **Additional context**
16 | Add any other context or screenshots about the feature request here.
17 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## React Boilerplate CRA Template
2 |
3 | ### ⚠️ Clear this template before you submit (after you read the things below)
4 |
5 | Thank you for contributing! Please take a moment to review our [**contributing guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/CONTRIBUTING.md)
6 | to make the process easy and effective for everyone involved.
7 |
8 | **Please open an issue** before embarking on any significant pull request, especially those that
9 | add a new library or change existing tests, otherwise you risk spending a lot of time working
10 | on something that might not end up being merged into the project.
11 |
12 | Before opening a pull request, please ensure:
13 |
14 | - [ ] You have followed our [**contributing guidelines**](https://github.com/react-boilerplate/react-boilerplate/blob/master/CONTRIBUTING.md)
15 | - [ ] Double-check your branch is based on `dev` and targets `dev`
16 | - [ ] Pull request has tests (we are going for 100% coverage!)
17 | - [ ] Code is well-commented, linted and follows project conventions
18 | - [ ] Documentation is updated (if necessary)
19 | - [ ] Internal code generators and templates are updated (if necessary)
20 | - [ ] Description explains the issue/use-case resolved and auto-closes related issues
21 |
22 | Be kind to code reviewers, please try to keep pull requests as small and focused as possible :)
23 |
24 | **IMPORTANT**: By submitting a patch, you agree to allow the project
25 | owners to license your work under the terms of the [MIT License](https://github.com/react-boilerplate/react-boilerplate/blob/master/LICENSE.md).
26 |
--------------------------------------------------------------------------------
/.github/workflows/commitlint.yaml:
--------------------------------------------------------------------------------
1 | # Run commitlint on Pull Requests and commits
2 | name: commitlint
3 | on:
4 | pull_request:
5 | types: ['opened', 'edited', 'reopened', 'synchronize']
6 | push:
7 | branches:
8 | - master
9 | - dev
10 |
11 | jobs:
12 | lint-pull-request-name:
13 | # Only on pull requests
14 | if: github.event_name == 'pull_request'
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v1
18 | - run: yarn add @commitlint/config-conventional
19 | - uses: JulienKode/pull-request-name-linter-action@v0.1.2
20 | lint-commits:
21 | # Only if we are pushing or merging PR to the master
22 | if: (github.event_name == 'pull_request' && github.base_ref == 'refs/heads/master') || github.event_name == 'push'
23 | runs-on: ubuntu-latest
24 | env:
25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26 | steps:
27 | - uses: actions/checkout@v2
28 | with:
29 | fetch-depth: 30 # Its fine to lint last 30 commits only
30 | - run: yarn add @commitlint/config-conventional
31 | - uses: wagoid/commitlint-github-action@v1
32 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: lint
2 | on:
3 | - push
4 | - pull_request
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - uses: actions/checkout@v2
10 | - run: yarn --frozen-lockfile
11 | - run: yarn run lint
12 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | release:
5 | types:
6 | - created
7 | workflow_dispatch:
8 |
9 | jobs:
10 | createAndTestCRAFromNpm:
11 | strategy:
12 | matrix:
13 | node-version: [16.x, 18.x]
14 |
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v3
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 | - name: Create CRA from npm template
22 | run: yarn create react-app --template cra-template-rb .
23 | - run: yarn run build
24 | - run: yarn run test:generators
25 | - run: yarn run lint
26 | - run: yarn run checkTs
27 | - run: yarn run cleanAndSetup
28 | - run: yarn run build
29 | - run: yarn run test:generators
30 | - run: yarn run lint
31 | - run: yarn run checkTs
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yaml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | - push
4 | - pull_request
5 | - workflow_dispatch
6 |
7 | jobs:
8 | test:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Use Node.js 16.x
13 | uses: actions/setup-node@v3
14 | with:
15 | node-version: 16.x
16 | - run: yarn --frozen-lockfile
17 | - run: yarn run test:coverage
18 | - name: Upload to Coveralls
19 | uses: coverallsapp/github-action@master
20 | with:
21 | github-token: ${{ secrets.GITHUB_TOKEN }}
22 | testInternals:
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - name: Use Node.js 16.x
27 | uses: actions/setup-node@v3
28 | with:
29 | node-version: 16.x
30 | - run: yarn --frozen-lockfile
31 | - run: yarn run test:internals
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Don't check auto-generated stuff into git
2 | coverage
3 | build
4 | node_modules
5 | stats.json
6 | .pnp
7 | .pnp.js
8 |
9 | # misc
10 | .DS_Store
11 | npm-debug.log*
12 |
13 | # yarn
14 | yarn-debug.log*
15 | yarn-error.log*
16 | .pnp.*
17 | .yarn/*
18 | !.yarn/patches
19 | !.yarn/plugins
20 | !.yarn/releases
21 | !.yarn/sdks
22 | !.yarn/versions
23 |
24 | # env
25 | .env.development.local
26 | .env.test.local
27 | .env.production.local
28 |
29 | # boilerplate internals
30 | generated-cra-app
31 | .cra-template-rb
32 | template
33 | .eslintcache
34 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | if yarn git-branch-is dev;
5 | then yarn commitlint --edit $1;
6 | fi
7 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn checkTs
5 | yarn lint-staged
6 | yarn verify-startingTemplate-changes
7 |
--------------------------------------------------------------------------------
/.husky/prepare-commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn devmoji -e
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | save-exact = true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | lts/fermium
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | build/
2 | node_modules/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "arrowParens": "avoid"
9 | }
10 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "processors": ["stylelint-processor-styled-components"],
3 | "extends": [
4 | "stylelint-config-recommended",
5 | "stylelint-config-styled-components"
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/.versionrc.js:
--------------------------------------------------------------------------------
1 | const internalSection = `Internals`;
2 | /*
3 | * Used for creating CHANGELOG.md automatically.
4 | * Anything under the internalSection should be boilerplate internals
5 | * and shouldn't interest the end users, meaning that the template shouldn't be effected.
6 | */
7 |
8 | // Check the descriptions of the types -> https://github.com/commitizen/conventional-commit-types/blob/master/index.json
9 | module.exports = {
10 | types: [
11 | { type: 'feat', section: 'Features', hidden: false },
12 | { type: 'fix', section: 'Bug Fixes', hidden: false },
13 | { type: 'docs', section: 'Documentation', hidden: false },
14 | { type: 'perf', section: 'Performance Updates', hidden: false },
15 |
16 | // Other changes that don't modify src or test files
17 | { type: 'chore', section: internalSection, hidden: false },
18 |
19 | // Adding missing tests or correcting existing tests
20 | { type: 'test', section: internalSection, hidden: false },
21 |
22 | // Changes to our CI configuration files and scripts
23 | { type: 'ci', section: internalSection, hidden: false },
24 |
25 | // A code change that neither fixes a bug nor adds a feature
26 | { type: 'refactor', section: internalSection, hidden: false },
27 |
28 | // Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
29 | { type: 'style', section: internalSection, hidden: false },
30 | ],
31 | skip: {
32 | changelog: true,
33 | },
34 | commitAll: true,
35 | };
36 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode",
5 | "styled-components.vscode-styled-components",
6 | "orta.vscode-jest"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Chrome",
6 | "type": "chrome",
7 | "request": "launch",
8 | "url": "http://localhost:3000",
9 | "webRoot": "${workspaceFolder}/src",
10 | "sourceMapPathOverrides": {
11 | "webpack:///src/*": "${webRoot}/*"
12 | }
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.tsdk": "node_modules/typescript/lib",
3 | "[typescript]": {
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "editor.tabSize": 2
6 | },
7 | "[typescriptreact]": {
8 | "editor.defaultFormatter": "esbenp.prettier-vscode"
9 | },
10 | "[javascript]": {
11 | "editor.defaultFormatter": "esbenp.prettier-vscode"
12 | },
13 | "[json]": {
14 | "editor.defaultFormatter": "esbenp.prettier-vscode"
15 | },
16 | "[html]": {
17 | "editor.defaultFormatter": "esbenp.prettier-vscode"
18 | },
19 | "editor.codeActionsOnSave": {
20 | "source.fixAll": true,
21 | "source.fixAll.eslint": true
22 | },
23 | "editor.formatOnSave": true,
24 | "javascript.format.enable": false
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Maximilian Stoiber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/RELEASE_PROCESS.md:
--------------------------------------------------------------------------------
1 | # RELEASE PROCESS
2 |
3 | The release process is **semi-automated**. The generated changelog requires editing to keep it visually appealing and clear for everyone.
4 |
5 | ## Step by step
6 |
7 | 1. All the development goes into `dev` branch. There are only squash merges allowed there so that its not flooded with everyones commits.
8 | 2. Make a PR to `master` from `dev` and if all checks are good then merge with the title `chore: 🔧 releasing x.x.x`.
9 | 3. Generate the changelog
10 | - `yarn run changelog`
11 | 4. Take a look at the previous changelogs and modify the generated changelog accordingly. Delete and organize the commits and move them under internals section if needed.
12 | 5. Create the release
13 | - `yarn run release`
14 | 6. Publish to npm
15 | - `yarn run publish:npm`
16 | 7. Push the changes to git.
17 | 8. Create release in github by copy pasting the related section from the CHANGELOG.md
18 | 9. There is a `release CI workflow`. Wait for it to be succeeded to see if there any problems with the released version.
19 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | // Use types from .versionrc.js so that when generating CHANGELOG there are no inconsistencies
2 | const standardVersionTypes = require('./.versionrc').types;
3 | const typeEnums = standardVersionTypes.map(t => t.type);
4 |
5 | module.exports = {
6 | extends: ['@commitlint/config-conventional'],
7 | rules: {
8 | 'type-enum': [2, 'always', typeEnums],
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # React Boilerplate CRA Template
2 |
3 | ## What is React Boilerplate CRA Template
4 |
5 | {% hint style="info" %}
6 |
7 | 💁♂️ **TL;DR:** **`CRA`** handles the bootstrapping and **`React Boilerplate`** gets you started with the best tools and practices.
8 |
9 | {% endhint %}
10 |
11 | This is a custom [`create-react-app`] template of [`react-boilerplate`]. React Boilerplate has been developing the ultimate React starter kit for many years now. On the other hand **`CRA`** (`create-react-app`) is currently the people's favorite choice. This template has been created to join their strengths together. **`CRA`** provides the necessary bootstrapping to start your projects but does not provide a guide on how to build it. That is where **`react-boilerplate`** comes in and prepares the bases with the battle-tested techniques and tools to guide you into creating state-of-the-art web applications.
12 |
13 | {% hint style="warning" %}
14 |
15 | This documentation assumes that you are familiar with the [`create-react-app`]. The template is built on top of it. ;)
16 |
17 | {% endhint %}
18 |
19 | ## Let's get started with the documentation
20 |
21 | In the following sections, we will briefly introduce this boilerplate to you and then start diving into details with the [Building Blocks](building-blocks/overview) section.
22 |
23 | {% hint style="info" %}
24 |
25 | Source Code & Repo: [Github](https://github.com/react-boilerplate/react-boilerplate-cra-template)
26 |
27 | NPM Package: [npm](https://www.npmjs.com/package/cra-template-rb)
28 |
29 | {% endhint %}
30 |
31 | [`create-react-app`]: https://github.com/facebook/create-react-app
32 | [`react-boilerplate`]: https://github.com/react-boilerplate/react-boilerplate
33 |
--------------------------------------------------------------------------------
/docs/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | - [Quick Start](quick-start.md)
4 | - [Understanding `react-boilerplate`](understanding-react-boilerplate.md)
5 |
6 | ## Tools
7 |
8 | - [CLI & Scaffolding](tools/commands.md)
9 | - [Editor Configuration](tools/editors.md)
10 | - [Package Managers](tools/package-managers.md)
11 |
12 | ## Building Blocks
13 |
14 | - [Building Blocks](building-blocks/README.md)
15 | - [The `Slice`](building-blocks/slice/README.md)
16 | - [Redux & Toolkit](building-blocks/slice/redux-toolkit.md)
17 | - [Reselect](building-blocks/slice/reselect.md)
18 | - [Redux-Saga](building-blocks/slice/redux-saga.md)
19 | - [Redux Injectors](building-blocks/slice/redux-injectors.md)
20 | - [Async Components](building-blocks/async-components.md)
21 | - [Routing](building-blocks/routing.md)
22 | - [i18n Internationalization & Pluralization](building-blocks/i18n.md)
23 | - [Styling (CSS)](building-blocks/css.md)
24 | - [Testing](building-blocks/testing.md)
25 |
26 | ## Deployment
27 |
28 | - [AWS](deployment/aws.md)
29 | - [Azure](deployment/azure.md)
30 | - [Heroku](deployment/heroku.md)
31 | - [Netlify](deployment/netlify.md)
32 |
33 | ## Misc
34 |
35 | - [FAQ](misc/faq.md)
36 |
--------------------------------------------------------------------------------
/docs/building-blocks/async-components.md:
--------------------------------------------------------------------------------
1 | # Async Components
2 |
3 | To load a component asynchronously, create a `Loadable` file by hand or via component generator with the 'Do you want to load resources asynchronously?' option activated.
4 |
5 | This is the content of the file by default:
6 |
7 | #### `Loadable.tsx`
8 |
9 | ```ts
10 | import { lazyLoad } from 'utils/loadable';
11 |
12 | export const HomePage = lazyLoad(
13 | () => import('./index'),
14 | module => module.HomePage, // Select your exported HomePage function for lazy loading
15 | );
16 | ```
17 |
18 | In this case, the app won't show anything while loading your component. You can, however, make it display a custom loader with:
19 |
20 | ```ts
21 | import React from 'react';
22 | import { lazyLoad } from 'utils/loadable';
23 |
24 | export const HomePage = lazyLoad(
25 | () => import('./index'),
26 | module => module.HomePage,
27 | {
28 | fallback:
Loading...
,
29 | }
30 | );
31 | ```
32 |
33 | Make sure to rename your `Loadable.ts` file to `Loadable.tsx`.
34 | This feature is built into the boilerplate using React's `lazy` and `Suspense` features.
35 |
--------------------------------------------------------------------------------
/docs/building-blocks/routing.md:
--------------------------------------------------------------------------------
1 | # Routing
2 |
3 | `react-router` is the de-facto standard routing solution for React applications.
4 |
5 | ## Why not use [connected-react-router](https://github.com/supasate/connected-react-router)?
6 |
7 | There is a detailed explanation for this decision [here](https://reacttraining.com/react-router/web/guides/deep-redux-integration). In short, the recommendation is to forego keeping routes in the Redux store, simply because it shouldn't be needed. There are other ways of navigating, as explained there.
8 |
9 | ## Usage
10 |
11 | To add a new route, simply import the `Route` component and use it standalone or inside the `Routes` component (all part of [RR6 API](https://reactrouter.com/docs/en/v6/getting-started/overview)):
12 |
13 | ```ts
14 | } />
15 | ```
16 |
17 | Top level routes are located in `src/app/index.tsx`.
18 |
19 | If you want your route component (or any component for that matter) to be loaded asynchronously, use the component generator with 'Do you want to load resources asynchronously?' option activated.
20 |
21 | ## Child Routes
22 |
23 | For example, if you have a route called `about` at `/about`, and want to make a child route called `team` at `/about/our-team`, follow the example in `src/app/index.tsx` to create a `Routes` within the parent component.
24 |
25 | #### `AboutPage/index.tsx`
26 |
27 | ```ts
28 | import { Routes, Route } from 'react-router-dom';
29 |
30 | export function AboutPage() {
31 | return (
32 |
33 |
34 |
35 | );
36 | }
37 | ```
38 |
39 | ## Routing programmatically
40 |
41 | You can use the `react-router hooks`, such as [useNavigate](https://reactrouter.com/docs/en/v6/hooks/use-navigate) or [useParams](https://reactrouter.com/docs/en/v6/hooks/use-params), to change the route, get params, and more.
42 |
43 | ```ts
44 | import { useNavigate } from 'react-router-dom';
45 |
46 | function HomeButton() {
47 | let navigate = useNavigate();
48 |
49 | function handleClick() {
50 | navigate('/home');
51 | }
52 |
53 | return (
54 |
57 | );
58 | }
59 | ```
60 |
61 | {% hint style="info" %}
62 |
63 | You can read more in [`react-router`'s documentation](https://reactrouter.com/docs/en/v6).
64 |
65 | {% endhint %}
66 |
--------------------------------------------------------------------------------
/docs/building-blocks/slice/README.md:
--------------------------------------------------------------------------------
1 | # What is a Slice?
2 |
3 | If you have read the redux-toolkit documentation you are familiar with the `slice` concept now. Here, we are taking it another step further by enriching it with `reselect` and `redux-saga`.
4 |
5 | Slice manages, encapsulates, and operates a `portion` of your application's data. For example, if you have a page that displays a user list, then you can have a slice called 'UsersPageSlice' that contains all the users in its state, also the functions to read it from the store and the functions to update the users in the list. So, in short, a slice is a redux-toolkit slice also containing the relative `reselect` and `redux-saga` operations within its folder. After all, they are all related to managing the same portion of the data.
6 |
7 | A `slice` is independent of the UI component. It can contain any kind of logic and it can be located in any folder. To follow the `folder-by-feature` pattern it is recommended to keep your `slices` closer to your component using it. But, this doesn't mean that it only belongs to that component. You can import and use that slice in whichever component you want.
8 |
9 | The next steps in the documentation describes how to use the slices with some examples.
10 |
11 | Example folder view:
12 |
13 | ```
14 | project
15 | |
16 | ├── app
17 | │ └── src
18 | │ ├── app
19 | │ │ ├── Homepage
20 | │ │ │ ├── index.tsx
21 | │ │ │ ├── slice => Contains the relevant stuff for Homepage data
22 | │ │ │ │ ├── index.ts
23 | │ │ │ │ ├── saga.ts
24 | │ │ │ │ ├── selectors.ts
25 | │ │ │ │ └── types.ts
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/building-blocks/slice/redux-injectors.md:
--------------------------------------------------------------------------------
1 | # Redux Injectors
2 |
3 | [`redux-injectors`](https://github.com/react-boilerplate/redux-injectors) is an official `react-boilerplate` companion library. We built it so that it can be used and maintained independently from `react-boilerplate`. It allows you to dynamically load reducers and sagas as needed, instead of loading them all upfront. This has some nice benefits, such as avoiding having to manage a big global list of reducers and sagas. It also facilitates more effective use of [code-splitting](https://webpack.js.org/guides/code-splitting/).
4 |
5 | You can find the main repo for the library [here](https://github.com/react-boilerplate/redux-injectors) and read the docs [here](https://github.com/react-boilerplate/redux-injectors/blob/master/docs/api.md).
6 |
7 | ## Usage
8 |
9 | ```ts
10 | import {
11 | useInjectSaga,
12 | useInjectReducer,
13 | SagaInjectionModes,
14 | } from 'utils/redux-injectors';
15 | import { saga } from './saga';
16 | import { reducer } from '.';
17 |
18 | export function SomeComponent() {
19 | useInjectReducer({ key: 'SomeComponent', reducer });
20 | useInjectSaga({
21 | key: 'SomeComponent',
22 | saga,
23 | mode: SagaInjectionModes.DAEMON,
24 | });
25 | // ...
26 | }
27 | ```
28 |
29 | {% hint style="info" %}
30 |
31 | **Note:** Importing `redux-injectors` from `utils/redux-injectors` will add extra type-safety.
32 |
33 | {% endhint %}
34 |
--------------------------------------------------------------------------------
/docs/building-blocks/slice/reselect.md:
--------------------------------------------------------------------------------
1 | # Reselect
2 |
3 | `reselect` memoizes ("caches") previous state trees and calculations based upon the said tree. This means repeated changes and calculations are fast and efficient, providing us with a performance boost over standard `useSelector` implementations.
4 |
5 | The [official documentation](https://github.com/reactjs/reselect) offers a good starting point!
6 |
7 | ## Usage
8 |
9 | There are two different kinds of selectors, simple and complex ones.
10 |
11 | ### Simple selectors
12 |
13 | Simple selectors are just that: they take the application state and select a part of it.
14 |
15 | ```ts
16 | export const mySelector = (state: MyRootState) => state.someState;
17 | ```
18 |
19 | ### Complex selectors
20 |
21 | If we need to, we can combine simple selectors to build more complex ones which get nested state parts with `reselect`'s `createSelector` function. We import other selectors and pass them to the `createSelector` call:
22 |
23 | #### `.../slice/selectors.ts`
24 |
25 | ```ts
26 | import { createSelector } from '@reduxjs/toolkit';
27 |
28 | export const mySelector = (state: MyRootState) => state.someState;
29 |
30 | // Here type of `someState` will be inferred ✅
31 | const myComplexSelector = createSelector(
32 | mySelector,
33 | someState => someState.someNestedState,
34 | );
35 |
36 | export { myComplexSelector };
37 | ```
38 |
39 | ### Using your selectors in components
40 |
41 | #### `index.tsx`
42 |
43 | ```ts
44 | import React, { useEffect } from 'react';
45 | import { useSelector } from 'react-redux';
46 | import { selectUsername } from './slice/selectors';
47 |
48 | export function HomePage() {
49 | // Type of the `username` will be inferred ✅
50 | const username = useSelector(selectUsername);
51 | // ...
52 | }
53 | ```
54 |
55 | {% hint style="info" %}
56 |
57 | 🎉 **Good News:** You don't need to write this boilerplate code by hand, the `slice` generator will generate it for you. ✓
58 |
59 | {% endhint %}
60 |
--------------------------------------------------------------------------------
/docs/deployment/azure.md:
--------------------------------------------------------------------------------
1 | # Deploy to Azure
2 |
3 | ### Easy 3-Step Deployment Process
4 |
5 | _Step 1:_ Within Azure Portal, add a 'Web App' resource to your resource group. Select the appropriate version of Node (i.e. 10.14) and verify that the operating system is set to Linux to ensure that Node is being run natively and not via IIS (iisnode).
6 |
7 | {% hint style="info" %}
8 |
9 | Note that following the steps in several of the quick start guides (such as [the Azure QuickStart](https://docs.microsoft.com/en-us/azure/app-service/app-service-web-get-started-nodejs)) may result in a Windows + IIS Node configuration that is incompatible with `react-boilerplate`.
10 |
11 | {% endhint %}
12 |
13 | _Step 2:_ When the resource has finished deploying, go to its deployment center and select Local Git (other methods will work as well, but the following steps assume this approach) and 'App Service' for the build provider. Note the Git Clone Uri that is presented when the wizard is finished.
14 |
15 | _Step 3:_ Within the root of your `react-boilerplate` source folder, execute the following commands to publish to Azure:
16 |
17 | 1. `git remote add azure https://YOUR_RESOURCE_NAME.scm.azurewebsites.net:443/YOUR_RESOURCE_NAME.git`
18 | 2. `git add .`
19 | 3. `git commit -m 'Made some epic changes as per usual'`
20 | 4. `git push azure master`
21 |
--------------------------------------------------------------------------------
/docs/deployment/heroku.md:
--------------------------------------------------------------------------------
1 | # Deploy to Heroku
2 |
3 | This doc is still awaiting help. 😢 If you want to contribute to this documentation, please contact us. (Preferably via a pull request!)
4 |
--------------------------------------------------------------------------------
/docs/deployment/netlify.md:
--------------------------------------------------------------------------------
1 | # Deploy to Netlify
2 |
3 | ### Easy 5-Step Deployment Process
4 |
5 | _Step 1:_ Create a `netlify.toml` file in the root directory of your project and copy this code below. Edit these settings if you did not follow the boilerplate structure. More settings available here (https://docs.netlify.com/configure-builds/file-based-configuration/#sample-file)
6 |
7 | ```
8 | [build]
9 | # Directory to change to before starting a build.
10 | # This is where we will look for package.json/.nvmrc/etc.
11 | base = "/"
12 |
13 | # Directory (relative to root of your repo) that contains the deploy-ready
14 | # HTML files and assets generated by the build. If a base directory has
15 | # been specified, include it in the publish directory path.
16 | publish = "./build"
17 |
18 | # Default build command.
19 | command = "yarn run build"
20 |
21 | # The following redirect is intended for use with most SPAs that handle routing internally.
22 | [[redirects]]
23 | from = "/*"
24 | to = "/index.html"
25 | status = 200
26 | ```
27 |
28 | _Step 2:_ Commit your code and push your latest updates to a GitHub repository.
29 |
30 | _Step 3:_ Register or Login in at [Netlify](https://app.netlify.com/).
31 |
32 | _Step 4:_ In your account | team page click `New site from git` then chose your repository.
33 |
34 | _Step 5:_ Click deploy.
35 |
36 | {% hint style="info" %}
37 |
38 | Note: No need to change any setting in the last step as `netlify.toml` overwrites these settings.
39 |
40 | {% endhint %}
41 |
42 | Now your code will be deployed automatically to Netlify on every push to the default branch of your repository.🥳🥳
43 |
--------------------------------------------------------------------------------
/docs/misc/faq.md:
--------------------------------------------------------------------------------
1 | # FAQ
2 |
3 | - [FAQ](#faq)
4 | - [Using reducers optimistically](#using-reducers-optimistically)
5 | - [Keeping up-to-date with the template](#keeping-up-to-date-with-the-template)
6 |
7 | ## Using reducers optimistically
8 |
9 | If you have components that should be available throughout the app, like a `NavigationBar` (i.e., they aren't route-specific), you need to add their respective reducers to the root reducer with the help of `combineReducers`.
10 |
11 | ```ts
12 | // In src/store/reducers.ts
13 |
14 | ...
15 | import { combineReducers } from '@reduxjs/toolkit';
16 | ...
17 |
18 | import { reducer as navigationBarReducer } from 'components/NavigationBar/slice';
19 |
20 | export function createReducer(injectedReducers: InjectedReducersType = {}) {
21 | const rootReducer = combineReducers({
22 | navigationBar: navigationBarReducer,
23 | ...injectedReducers,
24 | });
25 |
26 | return rootReducer;
27 | }
28 | ```
29 |
30 | ## Keeping up-to-date with the template
31 |
32 | Even though the template is an npm package, it's not possible for you to **just update** the package as you would for a dependency, since you start CRA with this template initially. Instead, it is recommended to keep an eye on the [CHANGELOG](../../CHANGELOG.md) file. All the changes that **concern** the template user will be displayed there, like bug fixes, documentation updates, new features, and so on. You can check each change's commits and file changes to see what has been changed. Then, the decision is yours if you want to apply those to your code.
33 |
--------------------------------------------------------------------------------
/docs/quick-start.md:
--------------------------------------------------------------------------------
1 | # QuickStart
2 |
3 | You have just **3** easy-peasy steps to do :)
4 |
5 | ⚠️ Using [Yarn Package Manager](https://yarnpkg.com) is recommended over `npm`.
6 |
7 | **1)** Create **CRA** app with the custom template
8 |
9 | ```shell
10 | yarn create react-app --template cra-template-rb my-app
11 | ```
12 |
13 | **2)** Start the example application and checkout the features made ready for you.
14 |
15 | ```shell
16 | cd my-app
17 | yarn start
18 | ```
19 |
20 | **3)** When you are done examining the sample application. Clean it and start your own app!!
21 |
22 | ```shell
23 | yarn run cleanAndSetup
24 | ```
25 |
26 | {% hint style="success" %}
27 |
28 | That's it. As easy as it can be. Happy coding! 🎉
29 |
30 | {% endhint %}
31 |
--------------------------------------------------------------------------------
/docs/tools/commands.md:
--------------------------------------------------------------------------------
1 | # CLI & Scaffolding
2 |
3 | ## Cleaning
4 |
5 | ```Shell
6 | yarn cleanAndSetup
7 | ```
8 |
9 | Removes the example app, replacing it with the smallest amount of boilerplate code necessary to start writing your app! Also, it makes some essential changes to your setup to give you a clean and working start.
10 |
11 | {% hint style="warning" %}
12 |
13 | **Note:** This command is self-destructive; once you've run it, it disables itself. This action is for your safety, so you can't irreversibly delete portions of your project by accident.
14 |
15 | {% endhint %}
16 |
17 | ## Generators
18 |
19 | ```Shell
20 | yarn generate
21 | ```
22 |
23 | Allows you to auto-generate boilerplate code for common parts of your application, specifically `component`s, and `redux-toolkit slice`s. You can also run `yarn generate ` to skip the first selection (e.g., `yarn generate component`).
24 |
25 | ```Shell
26 | yarn test:generators
27 | ```
28 |
29 | Test whether the generators are working fine. It generates components and slices with a variety of settings. This command is helpful if you decide to customize generators for your needs.
30 |
31 | ## Production
32 |
33 | ```Shell
34 | yarn start:prod
35 | ```
36 |
37 | - Builds your app (see `yarn run build`)
38 | - Serves the `build` folder locally
39 |
40 | The app is built for optimal performance; assets are minified and served `gzip`-ed.
41 |
42 | ## Unit testing
43 |
44 | ```Shell
45 | yarn test
46 | ```
47 |
48 | Unit tests specified in the `**/__tests__/*.ts` files throughout the application are run.
49 |
50 | All the `test` commands allow an optional `-- [string]` argument to filter the tests run by Jest, useful if you need to run a specific test only.
51 |
52 | ```Shell
53 | # Run only the Button component tests
54 | yarn test -- Button
55 | ```
56 |
57 | ## Linting
58 |
59 | ```Shell
60 | yarn lint
61 | ```
62 |
63 | Lints your Typescript and your CSS.
64 |
65 | ```Shell
66 | yarn lint:fix
67 | ```
68 |
69 | Lints your code and tries to fix any errors it finds.
70 |
71 | ## Extracting translation JSON Files
72 |
73 | ```Shell
74 | yarn extract-messages
75 | ```
76 |
77 | ## Typescript
78 |
79 | ```Shell
80 | yarn checkTs
81 | ```
82 |
83 | Checks for TypeScript errors.
84 |
--------------------------------------------------------------------------------
/docs/tools/editors.md:
--------------------------------------------------------------------------------
1 | # Setting Up Your Editor
2 |
3 | You can edit React Boilerplate using any editor or IDE, but there are a few extra steps that you can take to make sure your coding experience is as good as it can be.
4 |
5 | ## VS Code
6 |
7 | We provide a `.vscode` folder out-of-the-box which includes the **recommended extensions**, **debugger configuration**, and **settings**
8 |
9 | They are highly suggested for the best Developer Experience. Extensions are responsible for:
10 |
11 | - [Eslint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
12 | - [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
13 | - [Styled Components](https://marketplace.visualstudio.com/items?itemName=styled-components.vscode-styled-components)
14 | - [Jest](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest)
15 |
16 | These are the basic building blocks in the boilerplate.
17 |
18 | ## Chrome Extensions
19 |
20 | For a better debugging and development experience we suggest [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en). In the boilerplate, Redux is configured with this extension in mind so that you can debug and monitor your state changes very easily.
21 |
--------------------------------------------------------------------------------
/docs/tools/package-managers.md:
--------------------------------------------------------------------------------
1 | # Using a Package Manager
2 |
3 | ## Switching from NPM to Yarn
4 |
5 | While you may be familiar with `npm`, or even use it as your package manager of choice, it is not the recommended package manager for this project. This is because `Yarn` is faster, more reliable, and more extensible than `npm`. If you are not familiar with `Yarn`, you can read more about it [here](https://yarnpkg.com/en).
6 |
7 | To download `Yarn` using `npm`, run the following command:
8 |
9 | ```bash
10 | npm install -g yarn
11 | ```
12 |
13 | This will install `Yarn Classic/v1` globally on your machine.
14 |
15 | ## Upgrading to Yarn 3
16 |
17 | ### Why Yarn 3
18 |
19 | `Yarn 3` includes a host of benefits over `Yarn Classic/v1`, including:
20 |
21 | 1. Even **faster** installs
22 | 1. More verbose & readable output
23 | 1. `Yarn version` is stored directly in the repo to keep everyone on the same version
24 | 1. `Yarn plugins` can be added to extend the functionality of `Yarn`
25 | 1. `PnP` (Plug and Play) mode can be used to improve performance and stability (if supported by your project)
26 | 1. Helpful, new commands like `yarn dedupe`, `yarn info`, & more
27 |
28 | > Yarn does not use `PnP` by default. If you would like to use `PnP`, you can read more about it [here](https://yarnpkg.com/features/pnp).
29 |
30 | > Read more about the differences between `Yarn Classic/v1` and `Yarn 3` [here](https://yarnpkg.com/getting-started/migration).
31 |
32 | ### Upgrading
33 |
34 | To upgrade to `Yarn 3` from `Yarn Classic/v1`, run the following command:
35 |
36 | ```bash
37 | yarn set version berry
38 | yarn install
39 | ```
40 |
41 | ## Yarn Plugins
42 |
43 | Now, you can start getting plugins to extend the functionality of `Yarn 3`.
44 |
45 | ### Yarn-Typescript Plugin
46 |
47 | ```bash
48 | yarn plugin import typescript
49 | ```
50 |
51 | This will install the `Yarn-TypeScript` plugin, which automatically adds `@types/` packages into your dependencies when you add a package that doesn't include its own types
52 |
53 | ### Interactive-Tools
54 |
55 | ```bash
56 | yarn plugin import interactive-tools
57 | ```
58 |
59 | This will install the `Interactive-Tools` plugin, which includes the `yarn dedupe` & `yarn upgrade-interactive` commands. These commands will help you to remove duplicate packages from your `node_modules` folder, and upgrade your dependencies to the latest versions.
60 |
--------------------------------------------------------------------------------
/internals/extractMessages/i18next-scanner.config.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | const path = require('path');
3 | const typescript = require('typescript');
4 | const compilerOptions = require('../../tsconfig.json').compilerOptions;
5 |
6 | const stringfyTranslationObjects = require('./stringfyTranslations.js');
7 |
8 | module.exports = {
9 | input: [
10 | 'src/app/**/**.{ts,tsx}',
11 | '!**/node_modules/**',
12 | '!src/app/**/*.test.{ts,tsx}',
13 | ],
14 | output: './',
15 | options: {
16 | debug: false,
17 | removeUnusedKeys: false,
18 | func: {
19 | list: ['t'],
20 | extensions: [''], // We dont want this extension because we manually check on transform function below
21 | },
22 | lngs: ['en', 'de'],
23 | defaultLng: 'en',
24 | defaultNs: 'translation',
25 | resource: {
26 | loadPath: 'src/locales/{{lng}}/{{ns}}.json',
27 | savePath: 'src/locales/{{lng}}/{{ns}}.json',
28 | jsonIndent: 2,
29 | lineEnding: '\n',
30 | },
31 | keySeparator: '.', // char to separate keys
32 | nsSeparator: ':', // char to split namespace from key
33 | interpolation: {
34 | prefix: '{{',
35 | suffix: '}}',
36 | },
37 | },
38 | transform: function transform(file, enc, done) {
39 | const extensions = ['.ts', '.tsx'];
40 |
41 | const { base, ext } = path.parse(file.path);
42 | if (extensions.includes(ext) && !base.includes('.d.ts')) {
43 | const content = fs.readFileSync(file.path, enc);
44 | const shouldStringfyObjects = base === 'messages.ts';
45 | parseContent(content, this.parser, shouldStringfyObjects);
46 | }
47 |
48 | done();
49 | },
50 | };
51 | function parseContent(content, parser, shouldStringfyObjects = true) {
52 | const { outputText } = typescript.transpileModule(content, {
53 | compilerOptions: compilerOptions,
54 | });
55 | let cleanedContent = outputText;
56 | if (shouldStringfyObjects) {
57 | cleanedContent = stringfyTranslationObjects(outputText);
58 | }
59 | parser.parseFuncFromString(cleanedContent);
60 | }
61 |
62 | module.exports.parseContent = parseContent;
63 |
--------------------------------------------------------------------------------
/internals/extractMessages/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testMatch: [
3 | '**/__tests__/**/*.+(ts|tsx|js)',
4 | '**/?(*.)+(spec|test).+(ts|tsx|js)',
5 | ],
6 | transform: {
7 | '^.+\\.(ts|tsx)$': 'ts-jest',
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/internals/extractMessages/stringfyTranslations.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This is custom intermediate function to convert translations objects to i18next resource strings
3 | * `i18next-scanner expects strings such like:
4 | *
5 | * {t('a.b')}
6 | *
7 | * but translations object enables us to write the same thing as:
8 | *
9 | * {t(translations.a.b)}
10 | *
11 | * So, this function converts them into strings like the first one so that scanner recognizes
12 | */
13 |
14 | function stringfyTranslationObjects(content) {
15 | let contentWithObjectsStringified = content;
16 | const pattern = /_t\((.+?)[),]/gim;
17 | const matches = content.matchAll(pattern);
18 | for (const match of matches) {
19 | if (match.length < 1) {
20 | continue;
21 | }
22 | const key = match[1];
23 | let keyAsStringValue = '';
24 | if (["'", '"', '`'].some(x => key.includes(x))) {
25 | keyAsStringValue = key;
26 | } else {
27 | keyAsStringValue = stringifyRecursively(content, key);
28 | keyAsStringValue = `'${keyAsStringValue}'`;
29 | }
30 | contentWithObjectsStringified = replaceTranslationObjectWithString(
31 | contentWithObjectsStringified,
32 | key,
33 | keyAsStringValue,
34 | );
35 | }
36 | return contentWithObjectsStringified;
37 | }
38 |
39 | // Recursively concatenate all the `variables` until we hit the imported translations object
40 | function stringifyRecursively(content, key) {
41 | let [root, ...rest] = key.split('.');
42 | const pattern = `${root} =(.+?);`;
43 | const regex = RegExp(pattern, 'gim');
44 | let match = regex.exec(content);
45 | if (match && match.length > 1) {
46 | const key = match[1].trim();
47 | root = stringifyRecursively(content, key);
48 | } else if (isImportedTranslationObject(content, root)) {
49 | root = null;
50 | }
51 |
52 | if (root != null) {
53 | return [root, ...rest].join('.');
54 | } else {
55 | return [...rest].join('.');
56 | }
57 | }
58 |
59 | function isImportedTranslationObject(content, key) {
60 | const pattern = `import {.*?${key}.*?} from.+locales/translations.*`;
61 | return RegExp(pattern, 'gim').test(content);
62 | }
63 |
64 | function replaceTranslationObjectWithString(content, key, keyAsStringValue) {
65 | return content.replace(`_t(${key}`, `t(${keyAsStringValue}`);
66 | }
67 |
68 | module.exports = stringfyTranslationObjects;
69 |
--------------------------------------------------------------------------------
/internals/generators/component/index.test.tsx.hbs:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { render } from '@testing-library/react';
3 |
4 | import { {{ properCase componentName }} } from '..';
5 |
6 | {{#if wantTranslations}}
7 | jest.mock('react-i18next', () => ({
8 | useTranslation: () => {
9 | return {
10 | t: str => str,
11 | i18n: {
12 | changeLanguage: () => new Promise(() => {}),
13 | },
14 | };
15 | },
16 | }));
17 | {{/if}}
18 |
19 | describe('<{{ properCase componentName }} />', () => {
20 | it('should match snapshot', () => {
21 | const loadingIndicator = render(<{{ properCase componentName }} />);
22 | expect(loadingIndicator.container.firstChild).toMatchSnapshot();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/internals/generators/component/index.tsx.hbs:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * {{ properCase componentName }}
4 | *
5 | */
6 | {{#if wantMemo}}
7 | import React, { memo } from 'react';
8 | {{else}}
9 | import * as React from 'react';
10 | {{/if}}
11 | {{#if wantStyledComponents}}
12 | import styled from 'styled-components/macro';
13 | {{/if}}
14 | {{#if wantTranslations}}
15 | import { useTranslation } from 'react-i18next';
16 | import { messages } from './messages';
17 | {{/if}}
18 |
19 | interface Props {}
20 |
21 | {{#if wantMemo}}
22 | export const {{ properCase componentName }} = memo((props: Props) => {
23 | {{else}}
24 | export function {{ properCase componentName }}(props: Props) {
25 | {{/if}}
26 | {{#if wantTranslations}}
27 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
28 | const { t, i18n } = useTranslation();
29 | {{/if}}
30 |
31 | return (
32 | {{#if wantStyledComponents}}
33 |