diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0abbe11 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,22 @@ + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +max_line_length = 80 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[*.{js,jsx,ts,tsx}] +charset = utf-8 + +[{package.json}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..e728fcc --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,27 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: [ + 'airbnb', + 'airbnb-typescript', + 'airbnb/hooks', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], + ignorePatterns: ['node_modules/'], + overrides: [], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + plugins: ['react', '@typescript-eslint', 'prettier'], + rules: { + 'react/react-in-jsx-scope': 0, + 'react-hooks/exhaustive-deps': 'warn', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6181974 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Linter cache files +.eslintcache +.stylelintcache diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..7e15468 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm run lint-staged diff --git a/.postcssrc.json b/.postcssrc.json new file mode 100644 index 0000000..ab3bed5 --- /dev/null +++ b/.postcssrc.json @@ -0,0 +1,6 @@ +{ + "syntax": "postcss-scss", + "plugins": { + "autoprefixer": {} + } +} diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..df2df8a --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +.vscode/ +node_modules/ +package-lock.json +package.json +README.md +.eslintignore diff --git a/.prettierrc.cjs b/.prettierrc.cjs new file mode 100644 index 0000000..c0b4d65 --- /dev/null +++ b/.prettierrc.cjs @@ -0,0 +1,10 @@ +module.exports = { + printWidth: 60, + tabWidth: 2, + useTabs: false, + semi: true, + singleQuote: true, + trailingComma: 'all', + bracketSpacing: true, + arrowParens: 'avoid', +}; diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..4cb79e7 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,23 @@ +{ + "plugins": ["stylelint-scss"], + "extends": [ + "stylelint-config-standard-scss", + "stylelint-config-prettier-scss" + ], + "rules": { + "font-family-name-quotes": null, + "scss/at-rule-no-unknown": [ + true, + { + "ignoreAtRules": ["layer"] + } + ], + "selector-class-pattern": null + }, + "overrides": [ + { + "customSyntax": "postcss-scss", + "files": ["**/*.scss"] + } + ] +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..0e25de0 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "dbaeumer.vscode-eslint", + "Syler.sass-indented", + "ZixuanChen.vitest-explorer" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bfc4f26 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,36 @@ +{ + "editor.quickSuggestions": { + "strings": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "[jsonc]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": true + }, + "[markdown]": { + "editor.wordWrap": "on", + "editor.formatOnSave": true, + "editor.renderWhitespace": "all", + "editor.acceptSuggestionOnEnter": "off" + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..eb630ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Gabriel Pereira Woitechen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d260a6 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# ⚡ [Reactivite](https://github.com/armanatz/Reactivite) ⚡ + +A minimal starter for React with TypeScript support with out of the box formatting and linting. Utilizes the blazing fast Vite build tool. + +## Features + +- [React](https://reactjs.org) with [TypeScript](https://www.typescriptlang.org) using the [Vite](https://vitejs.dev) build tool. +- Supports [Sass](https://sass-lang.com/) with [automatic type declaration file creation](https://github.com/activeguild/vite-plugin-sass-dts) when using SCSS Modules. +- Utilizes [PostCSS](https://postcss.org/) with the [Autoprefixer](https://github.com/postcss/autoprefixer) plugin to optimize CSS and SCSS code. +- Supports TypeScript [absolute imports](https://github.com/aleclarson/vite-tsconfig-paths). +- Uses [ESLint](https://eslint.org) (with ESLint AirBnB Config), [stylelint](https://stylelint.io) and [Prettier](https://prettier.io) for code linting and formatting. +- Runs pre-commit command using [Husky](https://github.com/typicode/husky) to run [lint-staged](https://github.com/okonet/lint-staged). +- Unit and integration testing using [Vitest](https://vitest.dev/) and [Testing Library](https://testing-library.com/). +- Comes with a light VSCode setting file to enable formatting on save for JSON, JavaScript, TypeScript, TypeScript React, and Markdown files. +- Recommends useful extensions to install when using VSCode as your editor. + +## Getting Started + +Either use this repo as a [GitHub template](https://github.com/armanatz/Reactivite) or use [degit](https://github.com/Rich-Harris/degit) to make a clean copy of this repo: + +``` +npx degit armanatz/Reactivite#master my-app +``` + +After that, navigate to your project folder and install dependencies: + +``` +// If using NPM as your package manager +npm i + +// If using Yarn as your package manager +yarn +``` + +Then you can run the local development server using: + +``` +// If using NPM as your package manager +npm run dev + +// If using Yarn as your package manager +yarn dev +``` + +To build your application, run: + +``` +// If using NPM as your package manager +npm build + +// If using Yarn as your package manager +yarn build +``` + +Build files will be located in the `dist` folder once generated. + +## Available Scripts + +- `format`: Formats all files with Prettier. +- `lint`: Type-checks all files with TypeScript, and then lints all files using ESLint and StyleLint. +- `preview`: Allows for a local preview of the production build of the application. +- `test`: Runs unit and integration tests based on file changes tracked in git using Vitest. +- `test:ci`: Runs all unit and integration tests in CI mode using Vitest. +- `test:watch`: Runs Vitest test runner in watch mode. +- `validate`: Runs all linting commands defined in `lint`, and then runs `test:ci` for testing. diff --git a/index.html b/index.html new file mode 100644 index 0000000..0401886 --- /dev/null +++ b/index.html @@ -0,0 +1,29 @@ + + + + + + + + + My App + + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..b8cbb59 --- /dev/null +++ b/package.json @@ -0,0 +1,82 @@ +{ + "name": "my-app", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "build": "tsc && vite build", + "dev": "vite --port 3000 --strictPort --host", + "format": "prettier -uw --cache --ignore-path .gitignore \"./**/*.{js,ts,tsx,json,cjs}\"", + "lint": "run-p run-tsc run-eslint run-stylelint", + "lint-staged": "lint-staged", + "prepare": "husky install", + "preview": "vite preview", + "run-eslint": "eslint --cache --fix --ignore-path .gitignore --ext .js,.cjs,.ts,.tsx .", + "run-stylelint": "stylelint --cache --fix --ignore-path .gitignore **/*.{css,scss}", + "run-tsc": "tsc", + "test": "vitest", + "test:ci": "vitest run", + "test:watch": "vitest watch", + "validate": "run-p lint test:ci" + }, + "dependencies": { + "classnames": "^2.3.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@nabla/vite-plugin-eslint": "^1.4.1", + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^14.4.3", + "@types/react": "^18.0.17", + "@types/react-dom": "^18.0.6", + "@typescript-eslint/eslint-plugin": "^5.38.1", + "@typescript-eslint/parser": "^5.38.1", + "@vitejs/plugin-react": "^2.1.0", + "autoprefixer": "^10.4.12", + "eslint": "^8.24.0", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-airbnb-typescript": "^17.0.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.6.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.31.8", + "eslint-plugin-react-hooks": "^4.6.0", + "husky": "^8.0.1", + "jsdom": "^20.0.0", + "lint-staged": "^13.0.3", + "npm-run-all": "^4.1.5", + "postcss": "^8.4.16", + "postcss-scss": "^4.0.5", + "postcss-syntax": "^0.36.2", + "prettier": "^2.7.1", + "sass": "^1.55.0", + "stylelint": "^14.13.0", + "stylelint-config-prettier-scss": "^0.0.1", + "stylelint-config-standard-scss": "^5.0.0", + "stylelint-scss": "^4.3.0", + "ts-node": "^10.9.1", + "typescript": "^4.6.4", + "vite": "^3.1.0", + "vite-plugin-sass-dts": "^1.1.42", + "vite-tsconfig-paths": "^3.5.1", + "vitest": "^0.23.4" + }, + "browserslist": { + "production": "Edge >= 18, Firefox >= 60, Chrome >= 61, Safari >= 11, Opera >= 48", + "development": [ + "last 1 chrome version", + "last 1 firefox version" + ] + }, + "lint-staged": { + "*": "prettier -uw --cache", + "*.{css,scss}": "stylelint --cache --fix", + "*.{ts,tsx}": [ + "eslint --cache --fix", + "vitest related --run --coverage=false" + ] + } +} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..b01d239 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/src/App.module.scss b/src/App.module.scss new file mode 100644 index 0000000..ecedcfc --- /dev/null +++ b/src/App.module.scss @@ -0,0 +1,41 @@ +.container { + text-align: center; +} + +.logo { + height: 40vmin; + pointer-events: none; + + @media (prefers-reduced-motion: no-preference) { + animation: logo-spin infinite 20s linear; + } +} + +.header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.link { + color: #61dafb; +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} + +.btn { + font-size: calc(10px + 2vmin); +} diff --git a/src/App.module.scss.d.ts b/src/App.module.scss.d.ts new file mode 100644 index 0000000..d260a16 --- /dev/null +++ b/src/App.module.scss.d.ts @@ -0,0 +1,14 @@ +declare const classNames: { + readonly container: 'container'; + readonly logo: 'logo'; + readonly header: 'header'; + readonly link: 'link'; + readonly btn: 'btn'; +}; +export default classNames; +export type ClassNames = + | 'container' + | 'logo' + | 'header' + | 'link' + | 'btn'; diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..d6e7acc --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,56 @@ +import { useState } from 'react'; + +import logo from './logo.svg'; + +import styles from './App.module.scss'; + +export default function App() { + const [count, setCount] = useState(0); + + return ( +
+
+ logo +

Hello Vite + React!

+

+ +

+

+ Edit App.tsx and save to test HMR + updates. +

+

+ + Learn React + + {' | '} + + Vite Docs + +

+
+
+ ); +} diff --git a/src/index.scss b/src/index.scss new file mode 100644 index 0000000..ee21d05 --- /dev/null +++ b/src/index.scss @@ -0,0 +1,13 @@ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', + 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', + 'Droid Sans', 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, + 'Courier New', monospace; +} diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 0000000..cce5bc3 --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; + +import './index.scss'; + +import App from './App'; + +ReactDOM.createRoot( + document.getElementById('root') as HTMLElement, +).render( + + + , +); diff --git a/src/logo.svg b/src/logo.svg new file mode 100644 index 0000000..e422c50 --- /dev/null +++ b/src/logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000..0f508f2 --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,6 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import matchers from '@testing-library/jest-dom/matchers'; + +import { expect } from 'vitest'; + +expect.extend(matchers); diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0dea90b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "allowJs": false, + "allowSyntheticDefaultImports": true, + "baseUrl": "src", + "esModuleInterop": false, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "module": "ESNext", + "moduleResolution": "Node", + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ESNext", + "typeRoots": ["node_modules/@types"], + "useDefineForClassFields": true + }, + "include": [".eslintrc.cjs", "vite.config.ts", "src"], + "exclude": ["node_modules"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..ba43a28 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,26 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/// +/// + +import { defineConfig } from 'vite'; +import eslintPlugin from '@nabla/vite-plugin-eslint'; +import react from '@vitejs/plugin-react'; +import sassDts from 'vite-plugin-sass-dts'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig(({ mode }) => ({ + plugins: [ + eslintPlugin(), + react(), + sassDts(), + tsconfigPaths(), + ], + test: { + clearMocks: true, + css: false, + environment: 'jsdom', + globals: true, + include: ['src/**/__tests__/*'], + setupFiles: ['./src/setupTests.ts'], + }, +}));