├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── .stylelintrc.json ├── .vscode ├── .extensions.json └── .settings.json ├── README.md ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── src ├── App.jsx ├── assets │ └── images │ │ ├── app_screenshot.gif │ │ ├── favicon.svg │ │ └── logo.svg ├── index.css ├── index.html ├── main.jsx └── tests │ └── example.spec.js ├── tailwind.config.js └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | max_line_length = 80 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | max_line_length = 0 17 | trim_trailing_whitespace = false 18 | 19 | # Matches multiple files with brace expansion notation 20 | # Set default charset 21 | [*.{js,jsx,ts,tsx,py}] 22 | charset = utf-8 23 | 24 | [*.py] 25 | indent_style = space 26 | indent_size = 4 27 | 28 | # Tab indentation (no size specified) 29 | [Makefile] 30 | indent_style = tab 31 | 32 | # Matches the exact files either package.json or .travis.yml 33 | [{package.json,.travis.yml}] 34 | indent_style = space 35 | indent_size = 2 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "es2021": true, 6 | "jest/globals": true, 7 | "node": true 8 | }, 9 | "plugins": [ 10 | "react", 11 | "jsx-a11y", 12 | "jest", 13 | "import", 14 | "unused-imports" 15 | ], 16 | "extends": [ 17 | "airbnb", 18 | "airbnb/hooks", 19 | "plugin:import/recommended", 20 | "plugin:jest/recommended", 21 | "plugin:jsx-a11y/recommended", 22 | "prettier" 23 | ], 24 | "parserOptions": { 25 | "ecmaFeatures": { 26 | "jsx": true 27 | }, 28 | "ecmaVersion": "latest", 29 | "sourceType": "module" 30 | }, 31 | "overrides": [ 32 | { 33 | "files": [ 34 | "src/**/*.{js,jsx}" 35 | ], 36 | "rules": { 37 | "react/prop-types": "off", 38 | "import/order": [ 39 | "error", 40 | { 41 | "groups": [ 42 | "builtin", 43 | "external", 44 | "internal", 45 | [ 46 | "parent", 47 | "sibling" 48 | ], 49 | "index", 50 | "object", 51 | "type" 52 | ], 53 | "pathGroups": [ 54 | { 55 | "pattern": "./**/**\\.css", 56 | "group": "type", 57 | "position": "after" 58 | } 59 | ], 60 | "pathGroupsExcludedImportTypes": [ 61 | "builtin" 62 | ], 63 | "alphabetize": { 64 | "order": "asc", 65 | "caseInsensitive": true 66 | }, 67 | "newlines-between": "always", 68 | "warnOnUnassignedImports": true 69 | } 70 | ] 71 | } 72 | } 73 | ], 74 | "rules": { 75 | "jsx-a11y/click-events-have-key-events": "warn", 76 | "jsx-a11y/label-has-associated-control": "warn", 77 | "jsx-a11y/no-noninteractive-element-interactions": "warn", 78 | "jsx-quotes": [ 79 | "error", 80 | "prefer-double" 81 | ], 82 | "no-unused-vars": "warn", 83 | "react/function-component-definition": "off", 84 | "react/react-in-jsx-scope": "off", 85 | "react/prop-types": "warn", 86 | "semi": "error" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pnpm-debug.log* 7 | node_modules 8 | dist 9 | public 10 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSameLine": false, 4 | "bracketSpacing": true, 5 | "jsxSingleQuote": false, 6 | "plugins": ["./node_modules/prettier-plugin-tailwindcss"], 7 | "printWidth": 80, 8 | "quoteProps": "as-needed", 9 | "semi": true, 10 | "singleQuote": true, 11 | "tabWidth": 2, 12 | "trailingComma": "none" 13 | } 14 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["stylelint-scss", "@namics/stylelint-bem"], 3 | "extends": [ 4 | "stylelint-config-standard-scss", 5 | "stylelint-config-idiomatic-order", 6 | "stylelint-config-prettier-scss" 7 | ], 8 | "rules": { 9 | "font-family-name-quotes": null, 10 | "plugin/stylelint-bem-namics": { 11 | "patternPrefixes": [], 12 | "helperPrefixes": [] 13 | }, 14 | "scss/at-rule-no-unknown": [ 15 | true, 16 | { 17 | "ignoreAtRules": ["tailwind", "layer"] 18 | } 19 | ], 20 | "selector-class-pattern": null 21 | 22 | }, 23 | "overrides": [ 24 | { 25 | "customSyntax": "postcss-scss", 26 | "files": ["**/*.scss"] 27 | }, 28 | { 29 | "customSyntax": "@stylelint/postcss-css-in-js", 30 | "files": ["**/*.jsx", "**/*.tsx"], 31 | "rules": { 32 | "function-name-case": [ 33 | "lower", 34 | { 35 | "ignoreFunctions": ["/.*/"] 36 | } 37 | ], 38 | "selector-class-pattern": [ 39 | "^([a-z][a-z0-9]*)(-[a-z0-9]+)*((__([a-z][a-z0-9]*)(-[a-z0-9]+)*)?(--([a-z][a-z0-9]*)(-[a-z0-9]+)*)?)$" 40 | ], 41 | "value-keyword-case": [ 42 | "lower", 43 | { 44 | "ignoreFunctions": ["/.*/"], 45 | "ignoreKeywords": ["/.*/"], 46 | "ignoreProperties": ["/.*/"] 47 | } 48 | ] 49 | } 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /.vscode/.extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "editorconfig.editorconfig", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "orta.vscode-jest", 7 | "stylelint.vscode-stylelint", 8 | "styled-components.vscode-styled-components" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": true, 6 | "source.fixAll.stylelint": true 7 | }, 8 | "css.validate": false, 9 | "scss.validate": false, 10 | "stylelint.validate": ["css", "postcss", "scss"] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vite React Starter 2 | 3 | ![Vite](https://img.shields.io/badge/-Vite-646CFF?logo=vite&logoColor=white&style=for-the-badge) 4 | ![Babel](https://img.shields.io/badge/Babel-F9DC3e?style=for-the-badge&logo=babel&logoColor=black) 5 | ![React](https://img.shields.io/badge/-React-61DAFB?logo=react&logoColor=white&style=for-the-badge) 6 | ![React Router](https://img.shields.io/badge/React_Router-CA4245?style=for-the-badge&logo=react-router&logoColor=white) 7 | ![ESLint](https://img.shields.io/badge/ESLint-4B3263?style=for-the-badge&logo=eslint&logoColor=white) 8 | ![Prettier](https://img.shields.io/badge/-Prettier-F7B93E?logo=prettier&logoColor=white&style=for-the-badge) 9 | ![Stylelint](https://img.shields.io/badge/-Stylelint-263238?logo=stylelint&logoColor=white&style=for-the-badge) 10 | ![SASS](https://img.shields.io/badge/SASS-hotpink.svg?style=for-the-badge&logo=SASS&logoColor=white) 11 | ![TailwindCSS](https://img.shields.io/badge/tailwindcss-%2338B2AC.svg?style=for-the-badge&logo=tailwind-css&logoColor=white) 12 | ![Jest](https://img.shields.io/badge/-Jest-C21325?logo=jest&logoColor=white&style=for-the-badge) 13 | ![Testing Library](https://img.shields.io/badge/-Testing%20Library-E33332?logo=testing%20library&logoColor=white&style=for-the-badge) 14 | 15 | > Opinionated Vite starter template. 16 | 17 | ![screenshot](./src/assets/images/app_screenshot.gif) 18 | 19 | ## Description 20 | 21 | An starter template for Vite React 18 projects including a bunch of useful tools and libraries enforcing best practices and autofix on save. 22 | 23 | For styling it comes with SASS, Emotion, and TailwindCSS ready to use. Choose your favorite CSS framework and get started. It also includes the @namics/stylelint-bem plugin for BEM style validation. 24 | 25 | ## Built With 26 | 27 | - [Vite](https://vitejs.dev/) Next generation frontend tooling. 28 | - [Babel](https://babeljs.io/) The compiler for next generation JavaScript. 29 | - [React Router](https://reactrouter.com/) Declarative Routing for React.js 30 | - [ESLint](https://eslint.org/) Find and fix problems in your JavaScript code. 31 | - [Prettier](https://prettier.io/) Opinionated code formatter. 32 | - [Stylelint](https://stylelint.io/) A mighty, modern linter that helps you avoid errors and enforce conventions in your styles. 33 | - [@emotion/react](https://emotion.sh/) Emotion is a library designed for writing css styles with JavaScript. 34 | - [@emotion/styled](https://emotion.sh/) Styled is a way to create React components that have styles attached to them. 35 | - [Sass](https://sass-lang.com/) Syntactically Awesome Style Sheets. 36 | - [TailwindCSS](https://tailwindcss.com/) Rapidly build modern websites without ever leaving your HTML. 37 | - [Jest](https://jestjs.io/) Delightful JavaScript Testing. 38 | - [Testing Library](https://testing-library.com/) The React Testing Library is a very light-weight solution for testing React components 39 | 40 | ### Other Plugins 41 | 42 | - [prop-types](https://www.npmjs.com/package/prop-types) Runtime type checking for React props and similar objects. 43 | - [react-error-boundary](https://www.npmjs.com/package/react-error-boundary) Simple reusable React error boundary component. 44 | - [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb) Airbnb's extensible shared config. 45 | - [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import) Linting support of ES2015+ (ES6+) import/export syntax. 46 | - [eslint-plugin-jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y) Enforce accessibility best practices for React components. 47 | - [eslint-plugin-unused-imports](https://www.npmjs.com/package/eslint-plugin-unused-imports) Report and remove unused es6 modules. 48 | - [postcss](https://www.npmjs.com/package/postcss) PostCSS is a tool for transforming CSS with JavaScript plugins. 49 | - [stylelint-config-idiomatic-order](https://www.npmjs.com/package/stylelint-config-idiomatic-order) Order your styles based on idiomatic-css. 50 | 51 | ## Getting Started 52 | 53 | To get a local copy up and running follow these simple example steps. 54 | 55 | ### Prerequisites 56 | 57 | - Recommended `node` : `>=16.13.0` 58 | - `npm` or `pnpm` or `yarn` 59 | 60 | I advice to use `pnpm` for managing dependencies. It's faster and more reliable than `npm`. To install [pnpm](https://pnpm.io/) just run: 61 | 62 | - `corepack enable` 63 | - `corepack prepare pnpm@7.0.0-rc.3 --activate` 64 | 65 | After that the syntax is the same as `npm` e.g. `npm install` becomes `pnpm install`. 66 | 67 | ### Setup 68 | 69 | 1. Download or fork this project 70 | 2. Extract the content to a new directory, rename it and cd the directory. 71 | 3. Install all dependencies using: 72 | 73 | - `npm install` or `pnpm install` or `yarn` 74 | 75 | ## Scripts 76 | 77 | ### Start dev server 78 | 79 | - `npm run dev` or `pnpm run dev` or `yarn run dev` and open the browser at `http://localhost:3000` 80 | 81 | ### Build for production 82 | 83 | - `npm run build` or `pnpm run build` or `yarn run build` 84 | 85 | ### Locally preview production build 86 | 87 | After creating the production build, run: 88 | 89 | - `npm run preview` or `yarn run preview` 90 | 91 | ### Start server 92 | 93 | - `npm run serve` or `pnpm run serve` or `yarn run serve` and open the browser at `http://localhost:4173` 94 | 95 | ## Connect With Me 96 | 97 | 98 | 99 | |   |   | 100 | | ------------ | ---------------------------------------------------- | 101 | | **GitHub** | [@fabri4c](https://github.com/fabri4c) | 102 | | **Twitter** | [@fabri_4c](https://twitter.com/fabri_4c) | 103 | | **LinkedIn** | [@fabri4c](https://www.linkedin.com/in/fabri4c/) | 104 | 105 | ## Show your support 106 | 107 | You can give a ⭐️ if you like this project! 108 | 109 | ## Acknowledgments 110 | 111 | The ideas and inspiration from this project are coming from the following: 112 | 113 | - [ESLint docs](https://eslint.org/docs/user-guide/configuring/) 114 | - [Prettier docs](https://prettier.io/docs/en/index.html) 115 | - [Stylelint docs](https://stylelint.io/user-guide/configure/) 116 | - [starter-vite-react](https://github.com/warugaki-web-developer/starter-vite-react) 117 | - [Vitamin](https://github.com/wtchnm/Vitamin) 118 | 119 | ## License 120 | 121 | No License. You can use this starter as you wish. 122 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starter-vite-react", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Opinionated Vite starter template with ESLint, Prettier, Stylelint and more.", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/fabgrel10/vite-react-starter" 9 | }, 10 | "keywords": [ 11 | "vite", 12 | "react", 13 | "starter", 14 | "template" 15 | ], 16 | "scripts": { 17 | "dev": "vite", 18 | "build": "vite build --emptyOutDir", 19 | "serve": "vite preview", 20 | "lint:eslint": "eslint . --ext .js,.jsx", 21 | "fix:eslint": "eslint --fix . --ext .js,.jsx", 22 | "lint:stylelint": "stylelint **/*.{css,scss,jsx}", 23 | "fix:stylelint": "stylelint --fix ./src/**/*.{css, scss,jsx}", 24 | "test": "jest" 25 | }, 26 | "dependencies": { 27 | "@emotion/react": "^11.10.4", 28 | "@emotion/styled": "^11.10.4", 29 | "prop-types": "^15.8.1", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0", 32 | "react-error-boundary": "^3.1.4", 33 | "react-router-dom": "^6.3.0", 34 | "sass": "^1.54.5" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.18.13", 38 | "@emotion/babel-plugin": "^11.10.2", 39 | "@emotion/babel-preset-css-prop": "^11.10.0", 40 | "@namics/stylelint-bem": "^7.0.0", 41 | "@stylelint/postcss-css-in-js": "^0.38.0", 42 | "@testing-library/dom": "^8.17.1", 43 | "@testing-library/jest-dom": "^5.16.5", 44 | "@testing-library/react": "^13.3.0", 45 | "@testing-library/user-event": "^14.4.3", 46 | "@vitejs/plugin-react": "^2.0.1", 47 | "autoprefixer": "^10.4.8", 48 | "babel-loader": "^8.2.5", 49 | "eslint": "^8.23.0", 50 | "eslint-config-airbnb": "^19.0.4", 51 | "eslint-config-prettier": "^8.5.0", 52 | "eslint-plugin-import": "2.26.0", 53 | "eslint-plugin-jest": "27.0.1", 54 | "eslint-plugin-jsx-a11y": "6.6.1", 55 | "eslint-plugin-react": "^7.31.1", 56 | "eslint-plugin-react-hooks": "^4.6.0", 57 | "eslint-plugin-unused-imports": "^2.0.0", 58 | "jest": "^29.0.1", 59 | "postcss": "^8.4.16", 60 | "postcss-scss": "^4.0.4", 61 | "postcss-syntax": "^0.36.2", 62 | "prettier": "^2.7.1", 63 | "prettier-plugin-style-order": "^0.2.2", 64 | "prettier-plugin-tailwindcss": "^0.1.13", 65 | "react-test-renderer": "^18.2.0", 66 | "stylelint": "^14.11.0", 67 | "stylelint-config-idiomatic-order": "^8.1.0", 68 | "stylelint-config-prettier-scss": "^0.0.1", 69 | "stylelint-config-standard-scss": "^5.0.0", 70 | "stylelint-scss": "^4.3.0", 71 | "tailwindcss": "^3.1.8", 72 | "typescript": "^4.8.2", 73 | "vite": "^3.0.9", 74 | "webpack": "^5.74.0" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | syntax: 'postcss-scss', 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {} 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import logo from './assets/images/logo.svg'; 2 | 3 | function App() { 4 | return ( 5 |
6 |
7 | logo 8 |

Vite React Starter 💯

9 |

10 | Vite + React
11 | ESLint + Prettier + Stylelint 12 |
13 | Sass + Emotion + Tailwind 14 |
15 | Jest + Testing Library 16 |

17 |
18 |
19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /src/assets/images/app_screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fabri4c/vite-react-starter/8f896f096b9800edfa4b9cb701d5ee8b2976f1ff/src/assets/images/app_screenshot.gif -------------------------------------------------------------------------------- /src/assets/images/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* If you don't plan to use TailwindCSS in your project, you can remove these imports */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | .app { 7 | text-align: center; 8 | } 9 | 10 | .app-logo { 11 | height: 40vmin; 12 | pointer-events: none; 13 | } 14 | 15 | @media (prefers-reduced-motion: no-preference) { 16 | .app-logo { 17 | animation: app-logo-spin infinite 20s linear; 18 | } 19 | } 20 | 21 | .app-header { 22 | display: flex; 23 | min-height: 100vh; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | background-color: #282c34; 28 | color: white; 29 | font-size: calc(10px + 2vmin); 30 | } 31 | 32 | .app-link { 33 | color: #61dafb; 34 | } 35 | 36 | @keyframes app-logo-spin { 37 | from { 38 | transform: rotate(0deg); 39 | } 40 | 41 | to { 42 | transform: rotate(360deg); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | import App from './App'; 5 | 6 | import './index.css'; 7 | 8 | ReactDOM.createRoot(document.getElementById('root')).render( 9 | 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /src/tests/example.spec.js: -------------------------------------------------------------------------------- 1 | function filterByTerm(inputArr, searchTerm) { 2 | return inputArr.filter((arrayElement) => arrayElement.url.match(searchTerm)); 3 | } 4 | 5 | describe("Filter function", () => { 6 | test("it should filter by a search term (link)", () => { 7 | const input = [ 8 | { id: 1, url: "https://www.url1.dev" }, 9 | { id: 2, url: "https://www.url2.dev" }, 10 | { id: 3, url: "https://www.link3.dev" } 11 | ]; 12 | 13 | const output = [{ id: 3, url: "https://www.link3.dev" }]; 14 | 15 | expect(filterByTerm(input, "link")).toEqual(output); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './src/components/**/*.jsx', 4 | './src/pages/**/*.jsx', 5 | './src/**/*.jsx', 6 | './src/index.html' 7 | ], 8 | theme: { 9 | extend: {} 10 | }, 11 | plugins: [] 12 | }; 13 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [ 7 | react({ 8 | jsxImportSource: '@emotion/react', 9 | babel: { 10 | plugins: ['@emotion/babel-plugin'] 11 | } 12 | }) 13 | ] 14 | }); 15 | --------------------------------------------------------------------------------