This boilerplate will help you to quick start your project.
- Just download this repo and change the project name OR click on
use this template
button to create the new repo. - Init the commitizen before committing anything in your new project.
npx commitizen init cz-conventional-changelog --yarn --dev --exact --force
🔥 react
🚢 redux
💥 babel
yarn lint
to run both css & ts lintyarn test
to run tests and type checkyarn dist
to build your projectyarn clean
to remove the dist and coverage folderyarn watch
to run your project locallyyarn type-check
to run tsc to check typesyarn bundle:analyze
to analyze the bundle sizeyarn lint:css
to run the css lintyarn lint:ts
to run the ts lintyarn test:update
to upgrate snapshotsyarn test:watch
to watch testsyarn test:jest
to run test onlyyarn docker-watch
to be executed as the startup command for the Docker development container (no need to manually run)yarn image-build
to build the Docker development containeryarn image-remove
to remove the Docker development containeryarn docker-dev
to run the Docker development container with hot reloadingyarn docker-stop
to stop the running Docker development containeryarn docker-update-deps
to remove and rebuild the container image with updated dependenciesyarn docker-exec
to open an interactive ash shell inside the container
- Create project directory
- Create npm package
- Create git repository
- Add react libraries
- Add Eslint
- Add Prettier
- Add editorconfig file
- Add Typescript
- Add Vscode config files
- Add Babel
- Add index.html
- Add Webpack
- Add React-hot-loader
- Add Webpack plugins
- Add stylelint
- Add Jest
- Add commitizen & commitlint
- Add Redux
- Add SVG Loader
`mkdir my-project && cd my-project`
`npm init`
`git init`
`yarn add react react-dom`
- add eslint
yarn add -D eslint
Explaining the important bits:
parser: '@typescript-eslint/parser'
tells ESLint to use the parser package you installed (@typescript-eslint/parser
).- This allows ESLint to understand TypeScript syntax.
- This is required, or else ESLint will throw errors as it tries to parse TypeScript code as if it were regular JavaScript.
plugins: ['@typescript-eslint']
tells ESLint to load the plugin package you installed (@typescript-eslint/eslint-plugin
).- This allows you to use the rules within your codebase.
extends: [ ... ]
tells ESLint that your config extends the given configurations.eslint:recommended
is ESLint's inbuilt "recommended" config - it turns on a small, sensible set of rules which lint for well-known best-practices.plugin:@typescript-eslint/eslint-recommended
is a configuration we provide which disables a few of the recommended rules from the previous set that we know are already covered by TypeScript's typechecker.plugin:@typescript-eslint/recommended
is our "recommended" config - it's just likeeslint:recommended
, except it only turns on rules from our TypeScript-specific plugin.
Plugins
There are many plugins, each covering a different slice of the JS development world. Below are just a few examples, but there are many, many more.
- Jest testing:
eslint-plugin-jest
- React best practices:
eslint-plugin-react
andeslint-plugin-react-hooks
So finally install yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-jest eslint-plugin-react eslint-plugin-react-hooks
- eslint file would be like below:
{
"root": true,
"env": {
"es6": true,
"browser": true,
"jest/globals": true
},
"settings": {
"react": {
"version": "detect"
}
},
"parserOptions": {
"ecmaVersion": 11,
"jsx": true,
"project": "./tsconfig.json"
},
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "jest", "react-hooks", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jest/recommended",
"plugin:react/recommended",
"prettier/@typescript-eslint"
],
"rules": {
"arrow-parens": ["error", "as-needed"],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/jsx-no-bind": "warn",
"react/prop-types": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/explicit-function-return-type": "off"
}
}
- Add .eslintignore file
node_modules
dist
coverage
- Add lint script in package.json
{
"script": {
"lint": "run-p -c lint:*",
"lint:ts": "eslint 'src/**/*.{ts,tsx}'"
}
}
`yarn add -D prettier eslint-config-prettier eslint-plugin-prettier`
add plugin in the esconfig file
{
// ...
"plugins": ["@typescript-eslint", "jest", "react-hooks", "prettier"],
"extends": [
//...
"prettier/@typescript-eslint"
]
//..
}
Add .preetierrc
{
"bracketSpacing": true,
"printWidth": 140,
"semi": true,
"trailingComma": "none",
"singleQuote": true,
"tabWidth": 4,
"arrowParens": "avoid"
}
.editorconfig
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
max_line_length = 140
quote_type = single
spaces_around_brackets = true
spaces_around_operators = true
trim_trailing_whitespace = true
# identation
indent_style = space
indent_size = 4
yarn add -W typescript
- Add tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "src",
"esModuleInterop": true,
"isolatedModules": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"lib": ["es2017", "es2015", "dom", "es2018.promise"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"resolveJsonModule": true,
"rootDir": "../",
"skipLibCheck": true,
"strict": true,
"strictFunctionTypes": false,
"strictNullChecks": false,
"target": "esnext",
"types": ["jest", "testing-library__jest-dom"],
"paths": {
"@components": ["components"],
"@components/*": ["components/*"],
"@theme": ["theme"],
"@theme/*": ["theme/*"],
"@utils": ["utils"],
"@utils/*": ["utils/*"],
"@test-utils": ["utils/test-utils"],
"@styled": ["utils/styled"],
"@store": ["store"],
"@store/*": ["store/*"]
}
},
"include": ["src"],
"exclude": ["node_modules", ".commitlintrc"]
}
- Add settings.json file
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true,
"source.fixAll.stylelint": true
},
"eslint.validate": ["html", "javascript", "javascriptreact", "typescript", "typescriptreact"],
"cSpell.words": ["Medly"]
}
- Add extensions.json file
{
"recommendations": [
"eamodio.gitlens",
"stylelint.vscode-stylelint",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"jpoissonnier.vscode-styled-components",
"streetsidesoftware.code-spell-checker"
]
}
`yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript`
Create babel.config.js file
module.exports = function (api) {
const presets = ['@babel/preset-env', '@babel/react', '@babel/typescript'],
plugins = [];
return { presets, plugins };
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Boilerplate</title>
</head>
<body>
<div id="root" />
</body>
<noscript>Your browser does not support JavaScript!</noscript>
</html>
`yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin`
Create webpack.config.js file
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SRC = path.resolve(__dirname, './src/index');
module.exports = {
entry: SRC,
output: {
path: path.join(__dirname, '/dist'),
filename: 'bundle.js',
publicPath: '/'
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
},
devServer: {
historyApiFallback: true
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
};
Add script in package.json "start": "webpack-dev-server --mode development --hot"
This will hot reload the app, keeping the components state.
-
install:
yarn add -D react-hot-loader @hot-loader/react-dom
-
add plugin in babel.config.json
//... if (api.env() === 'development') { plugins.push('react-hot-loader/babel'); } //...
-
add alias in webpack
resolve: { alias: { 'react-dom': '@hot-loader/react-dom', } }
-
wrap your app in hot();
export default hot(App)
Few useful webpack plugins
yarn add -D circular-dependency-plugin clean-webpack-plugin compression-webpack-plugin copy-webpack-plugin fork-ts-checker-webpack-plugin tsconfig-paths-webpack-plugin webpack-bundle-analyzer webpack-merge
webpack.common.js
const path = require('path');
const webpack = require('webpack');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const CircularDependencyPlugin = require('circular-dependency-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const SRC = path.resolve(__dirname, '../../src');
const DIST = path.resolve(__dirname, '../../dist');
const ENTRY = path.resolve(__dirname, '../../src/index');
const STATIC = path.resolve(__dirname, '../../public/static');
const INDEX_HTML = path.resolve(__dirname, '../../public/index.html');
module.exports = {
entry: [ENTRY],
performance: {
maxAssetSize: 500000,
maxEntrypointSize: 500000
},
resolve: {
extensions: ['.js', '.ts', '.tsx', '.json'],
symlinks: false,
plugins: [new TsconfigPathsPlugin()]
},
module: {
rules: [
{
test: /\.tsx?$/,
include: SRC,
exclude: [/node_modules/, /\.spec.tsx?$/, /\.test.tsx?$/, /__snapshots__/, /__tests__/],
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
workerParallelJobs: 50
}
},
'babel-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: INDEX_HTML,
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new CopyPlugin([{ from: STATIC, to: DIST }]),
new ForkTsCheckerWebpackPlugin(),
new CircularDependencyPlugin({
exclude: /node_modules/,
failOnError: true
}),
new webpack.HashedModuleIdsPlugin(),
new CompressionPlugin()
]
};
webpack.dev.js
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const DIST = path.resolve(__dirname, '../../dist');
module.exports = merge(common, {
mode: 'development',
output: {
path: DIST,
filename: '[name].[hash].js',
chunkFilename: '[name].[hash].js',
publicPath: '/'
},
resolve: {
alias: {
'react-dom': '@hot-loader/react-dom'
}
},
devServer: {
hot: true,
inline: true,
overlay: true,
historyApiFallback: true,
disableHostCheck: true,
stats: { children: false }
}
});
webpack.prod.js
const path = require('path');
const common = require('./webpack.common.js');
const merge = require('webpack-merge');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const DIST = path.resolve(__dirname, '../../dist');
const config = {
mode: 'production',
output: {
path: DIST,
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
publicPath: '/'
},
optimization: {
moduleIds: 'hashed',
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: 10,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `vendor~${packageName.replace('@', '')}`;
}
}
}
}
}
};
if (process.env.BUNDLE_ANALYZE) config.plugins = [new BundleAnalyzerPlugin()];
module.exports = merge(common, config);
yarn add -D stylelint stylelint-config-prettier stylelint-config-standard stylelint-config-styled-components stylelint-custom-processor-loader stylelint-prettier babel-plugin-styled-components
- add
.stylelintrc
file
{
"syntax": "scss",
"plugins": ["stylelint-prettier"],
"extends": ["stylelint-config-standard", "stylelint-config-styled-components", "stylelint-prettier/recommended"],
"rules": {
"prettier/prettier": true,
"value-keyword-case": null,
"declaration-empty-line-before": "never",
"unit-no-unknown": [
true,
{
"ignoreUnits": ["`"]
}
],
"function-name-case": null
}
}
- add plugin in babel.config.js
//...
plugins = [
[
'babel-plugin-styled-components',
{
pure: true,
displayName: true,
minify: true,
transpileTemplateLiterals: true
}
]
];
//...
- add loader for ts and tsx file
[
{
loader: 'thread-loader',
options: {
workers: 2,
workerParallelJobs: 50
}
},
'babel-loader',
'stylelint-custom-processor-loader'
];
- add script in package.json file for linting
{
"lint": "run-p -c lint:**",
"lint:css": "stylelint 'src/**/*.tsx'",
"lint:ts": "eslint 'src/**/*.{ts,tsx}'"
}
yarn add -D jest @testing-library/react @testing-library/jest-dom jest-styled-components
- Add config/jest.config.js
module.exports = {
collectCoverage: true,
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
statements: 100
}
},
collectCoverageFrom: [
'<rootDir>/src/**/*.(ts|tsx)',
'!<rootDir>/**/index.(ts|tsx)',
'!<rootDir>/src/App.tsx',
'!<rootDir>/src/theme/**',
'!<rootDir>/src/icons/**',
'!<rootDir>/src/store/sagas.ts',
'!<rootDir>/src/utils/styled.ts',
'!<rootDir>/node_modules/**',
'!<rootDir>/src/**/types.(ts|tsx)',
'!<rootDir>/src/**/types/**',
'!<rootDir>/src/utils/fetch.ts',
'!<rootDir>/src/utils/test-utils.tsx'
],
coverageDirectory: '<rootDir>/coverage/',
moduleFileExtensions: ['.mjs', 'ts', 'tsx', 'js', 'jsx', 'svg'],
roots: ['<rootDir>/src/'],
rootDir: '../../',
setupFilesAfterEnv: ['<rootDir>/config/jest/setupAfterEnv.js'],
testEnvironment: 'jsdom',
testMatch: ['**/*.(spec|test).(ts|tsx)'],
verbose: true,
moduleNameMapper: {
'\\.(css|less)$': '<rootDir>/config/jest/__mocks__/styleMock.js',
'^@styled': '<rootDir>/src/utils/styled',
'^@store(.*)$': '<rootDir>/src/store$1',
'^@test-utils': '<rootDir>/src/utils/test-utils',
'^@components(.*)$': '<rootDir>/src/components$1',
'^@theme(.*)$': '<rootDir>/src/theme$1',
'^@utils(.*)$': '<rootDir>/src/utils$1'
}
};
- Add localStorage file
const storageMock = () => {
const storage = {};
return {
setItem: (key, value) => {
storage[key] = value || '';
},
getItem: key => {
return key in storage ? storage[key] : null;
},
removeItem: key => {
delete storage[key];
}
};
};
window.localStorage = storageMock();
- Add setupAfterEnv file
import '@testing-library/jest-dom/extend-expect';
import 'jest-styled-components';
- Add style mock file for loading css of all the libraries
module.exports = {};
- Add jest-dom types in tsconfig file
{
"types": ["jest", "testing-library__jest-dom"]
}
yarn add -W husky commitizen @commitlint/cli @commitlint/config-conventional cz-conventional-changelog
- Add .commitlintrc file
{
"extends": [
"@commitlint/config-conventional"
]
}
- Add configs in package.json
{
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"husky": {
"hooks": {
"prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
}
yarn add redux react-redux redux-saga
-
add other dev dependencies
yarn add -D @types/react-redux @babel/plugin-transform-runtime redux-devtools-extension redux-mock-store
-
configure store
import { applyMiddleware, combineReducers, createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import reduxSaga from 'redux-saga';
import { rootSaga } from './sagas';
import { initialState as userInitialState, user } from './user';
export const initialState = {
user: userInitialState
},
sagaMiddleware = reduxSaga(),
rootReducer = combineReducers({
user
}),
store = createStore(rootReducer, {}, composeWithDevTools(applyMiddleware(sagaMiddleware)));
sagaMiddleware.run(rootSaga);
export type AppState = ReturnType<typeof rootReducer>;
- provide this store to app
import { store } from '@store';
import { ThemeProvider } from 'styled-components';
import { defaultTheme } from '@theme';
import React from 'react';
import * as ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
ReactDOM.render(
<Provider store={store}>
<ThemeProvider theme={defaultTheme}>
<>
<App />
</>
</ThemeProvider>
</Provider>,
document.getElementById('root')
);
-
Add these packages
yarn add -D @svgr/webpack babel-plugin-inline-react-svg
-
Add
inline-react-svg
in babel config plugins. -
Add Svg loader in webpack config
{
"test": /\.svg$/,
"use": [
{
"loader": "@svgr/webpack",
"options": {
"icon": true
}
}
]
}
- Add svg in extensions in jest config
moduleFileExtensions: ['.mjs', 'ts', 'tsx', 'js', 'jsx', 'svg'],