├── .eslintignore ├── .nvmrc ├── .stylelintignore ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── test.yml │ ├── license.yml │ ├── pr-size-labeler.yml │ └── reviewdog.yml ├── .prettierignore ├── .npmrc ├── .gitattributes ├── tsconfig.prod.json ├── .husky └── pre-commit ├── __mocks__ └── svg.js ├── src ├── vite-env.d.ts ├── globals.d.ts ├── index.css ├── vitest.d.ts ├── styles.ts ├── setupTests.ts ├── App.spec.tsx ├── index.tsx ├── App.css ├── favicon.svg ├── hooks │ └── useGnuiTheme.tsx ├── App.tsx └── logo.svg ├── prettier.config.js ├── stylelint.config.js ├── graphql.config.js ├── scripts ├── code-version.js └── circular-deps.js ├── lint-staged.config.js ├── knip.jsonc ├── lockfile-lint.config.js ├── index.html ├── tsconfig.json ├── .eslintrc.js ├── README.md ├── .gitignore ├── package.json ├── vite.config.ts ├── LICENSE └── docs └── CRA_MIGRATION_GUIDE.md /.eslintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v18.13.0 -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nordcloud/augeas 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | .vscode 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | legacy-peer-deps=true 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json linguist-generated=true 2 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "jsx": "react-jsx" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | export GIT_HOOKS=true 5 | npx --no-install lint-staged -r --concurrent true 6 | -------------------------------------------------------------------------------- /__mocks__/svg.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | export default "SvgrURL"; 4 | export const ReactComponent = "div"; 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # What 2 | 3 | - What was done in this Pull Request 4 | - How was it before 5 | - Any other information that may be useful for the dev to review Pull Request 6 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | /// 4 | /// 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | module.exports = { 4 | ...require("@nordcloud/eslint-config-pat/prettier.config.js"), 5 | }; 6 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | module.exports = { 4 | extends: ["@nordcloud/eslint-config-pat/stylelint.config.js"], 5 | }; 6 | -------------------------------------------------------------------------------- /graphql.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022-2023 Nordcloud Oy or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | /** @type {import("graphql-config").IGraphQLConfig} */ 6 | module.exports = { 7 | schema: "", 8 | documents: "src/**/*.{graphql,ts,tsx}", 9 | }; 10 | -------------------------------------------------------------------------------- /scripts/code-version.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | const { execSync } = require("child_process"); 4 | 5 | function codeVersion() { 6 | return execSync("git rev-parse --short HEAD").toString().trim(); 7 | } 8 | 9 | module.exports = { codeVersion }; 10 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | module.exports = { 4 | "*.{ts,tsx}": ["prettier --write", "eslint --fix", "stylelint"], 5 | "*.graphql": ["prettier --write", "eslint --fix"], 6 | "*.{json,json5,md,yml,js,css,html}": ["prettier --write"], 7 | }; 8 | -------------------------------------------------------------------------------- /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | /// 4 | 5 | import { CSSProp } from "styled-components"; 6 | 7 | interface DefaultTheme {} 8 | 9 | declare module "react" { 10 | interface HTMLAttributes extends DOMAttributes { 11 | css?: CSSProp; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /knip.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/knip@next/schema.json", 3 | "entry": ["src/index.tsx!"], 4 | "project": ["src/**/*.{ts,tsx}!", "scripts/**/*.{js,ts}", "vite.config.ts"], 5 | "rules": { 6 | "enumMembers": "warn" 7 | }, 8 | "ignoreDependencies": [ 9 | "eslint-plugin-react", 10 | "@vitest/coverage-v8", 11 | "@testing-library/jest-dom" 12 | ], 13 | "ignore": ["**/*.d.ts", "**/generated", "src/setupTests.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023 Nordcloud Oy or its affiliates. All Rights Reserved. 2 | 3 | name: Build 4 | on: 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | build: 9 | name: Run Vite build 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version-file: package.json 16 | 17 | - run: npm ci 18 | - run: npm audit signatures 19 | - run: npm run build 20 | -------------------------------------------------------------------------------- /src/vitest.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Nordcloud Oy or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | import type { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers"; 6 | import type { Assertion, AsymmetricMatchersContaining } from "vitest"; 7 | 8 | type CustomMatchers = jest.Matchers & TestingLibraryMatchers; 9 | 10 | declare module "vitest" { 11 | interface Assertion extends CustomMatchers {} 12 | interface AsymmetricMatchersContaining extends CustomMatchers {} 13 | } 14 | -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | import { css, styled } from "styled-components"; 4 | import { theme } from "@nordcloud/gnui"; 5 | 6 | export const Wrapper = styled.div({ 7 | textAlign: "center", 8 | }); 9 | 10 | export const Box = styled.div({ 11 | background: "#1e1c32", 12 | padding: theme.spacing.spacing06, 13 | }); 14 | 15 | export const buttonCss = css` 16 | padding: ${theme.spacing.spacing02}; 17 | margin-bottom: ${theme.spacing.spacing04}; 18 | `; 19 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | import * as matchers from "@testing-library/jest-dom/matchers"; 4 | import { configure } from "@testing-library/react"; 5 | import { expect, vi } from "vitest"; 6 | 7 | expect.extend(matchers); 8 | 9 | beforeAll(() => { 10 | global.ResizeObserver = vi.fn().mockImplementation(() => ({ 11 | observe: vi.fn(), 12 | unobserve: vi.fn(), 13 | disconnect: vi.fn(), 14 | })); 15 | }); 16 | 17 | configure({ 18 | asyncUtilTimeout: 3000, 19 | }); 20 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. 2 | 3 | name: Test 4 | on: 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | test: 9 | name: Tests 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 20 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: 16 | node-version-file: package.json 17 | - run: npm ci 18 | - run: npm audit signatures 19 | - run: npm run validate:all 20 | - run: npm run test 21 | -------------------------------------------------------------------------------- /lockfile-lint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Nordcloud Oy or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | /** @see https://github.com/lirantal/lockfile-lint/tree/main/packages/lockfile-lint#file-based-configuration */ 6 | module.exports = { 7 | type: "npm", 8 | path: "package-lock.json", 9 | allowedHosts: "npm", 10 | validateHttps: true, 11 | validateIntegrity: false, 12 | validatePackageNames: true, 13 | allowedPackageNameAliases: [ 14 | "string-width-cjs:string-width", 15 | "strip-ansi-cjs: strip-ansi", 16 | "wrap-ansi-cjs:wrap-ansi", 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /src/App.spec.tsx: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | import { screen, render } from "@testing-library/react"; 4 | import userEvent from "@testing-library/user-event"; 5 | import { App } from "./App"; 6 | 7 | test("increases counter", async () => { 8 | render(); 9 | 10 | const counter = screen.getByRole("button", { name: /count is/i }); 11 | 12 | expect(counter.textContent).toMatchInlineSnapshot(`"count is: 0"`); 13 | await userEvent.click(counter); 14 | expect(counter.textContent).toMatchInlineSnapshot(`"count is: 1"`); 15 | }); 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | import { StrictMode } from "react"; 4 | import { createRoot } from "react-dom/client"; 5 | import { SetGlobalStyle } from "@nordcloud/gnui"; 6 | import { GNUIThemeProvider } from "~/hooks/useGnuiTheme"; 7 | import { App } from "./App"; 8 | import "./index.css"; 9 | 10 | const container = document.getElementById("root"); 11 | const root = createRoot(container as HTMLElement); // createRoot(container!) if you use TypeScript 12 | 13 | root.render( 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /scripts/circular-deps.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* Copyright (c) 2021 Nordcloud Oy or its affiliates. All Rights Reserved. */ 4 | 5 | const madge = require("madge"); 6 | 7 | const MAX_CIRCULARS = 0; 8 | 9 | const entryFile = process.cwd() + "/src/index.tsx"; 10 | const tsConfig = process.cwd() + "/tsconfig.json"; 11 | 12 | madge(entryFile, { 13 | tsConfig, 14 | fileExtensions: ["ts", "tsx"], 15 | detectiveOptions: { 16 | ts: { 17 | skipTypeImports: true, 18 | }, 19 | }, 20 | }).then((res) => { 21 | const circulars = res.circular(); 22 | 23 | if (circulars.length > MAX_CIRCULARS) { 24 | console.error("Circular dependencies detected", "\n", circulars); 25 | process.exit(1); 26 | } 27 | 28 | process.exit(0); 29 | }); 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "useUnknownInCatchVariables": false, 18 | "noUncheckedIndexedAccess": true, 19 | "jsx": "react-jsxdev", 20 | "paths": { 21 | "~/*": ["./src/*"] 22 | }, 23 | "baseUrl": ".", 24 | "types": ["vitest/globals"] 25 | }, 26 | "include": ["./src"] 27 | } 28 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | width: auto; 9 | } 10 | 11 | @media (prefers-reduced-motion: no-preference) { 12 | .App-logo { 13 | animation: App-logo-spin infinite 20s linear; 14 | } 15 | } 16 | 17 | .App-header { 18 | background-color: #fff; 19 | min-height: 100vh; 20 | display: flex; 21 | flex-direction: column; 22 | align-items: center; 23 | justify-content: center; 24 | font-size: calc(10px + 2vmin); 25 | color: #000; 26 | } 27 | 28 | .App-link { 29 | color: #61dafb; 30 | } 31 | 32 | @keyframes App-logo-spin { 33 | from { 34 | transform: rotate(0deg); 35 | } 36 | to { 37 | transform: rotate(360deg); 38 | } 39 | } 40 | 41 | button { 42 | font-size: calc(10px + 2vmin); 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/license.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. 2 | 3 | name: Action 4 | on: 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | licenses: 9 | name: Check licenses 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v3 14 | with: 15 | token: ${{ secrets.GHTOKEN }} 16 | - id: changed-files 17 | uses: jitterbit/get-changed-files@v1 18 | - uses: nordcloud/addlicense@master 19 | with: 20 | directory-pattern: '${{ steps.changed-files.outputs.all }}' 21 | - name: Commit changes 22 | uses: EndBug/add-and-commit@v7.0.0 23 | with: 24 | message: 'Update license headers' 25 | add: '${{ steps.changed-files.outputs.all }}' 26 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | require("@nordcloud/eslint-config-pat/patch/modern-module-resolution"); 4 | 5 | module.exports = { 6 | extends: [ 7 | "@nordcloud/eslint-config-pat/profile/web-app", 8 | "@nordcloud/eslint-config-pat/mixins/react", 9 | "@nordcloud/eslint-config-pat/mixins/graphql/operations", 10 | "plugin:react/jsx-runtime", 11 | ], 12 | 13 | parserOptions: { tsconfigRootDir: __dirname }, 14 | 15 | settings: { 16 | react: { 17 | version: "detect", // React version. "detect" automatically picks the version you have installed. 18 | }, 19 | }, 20 | 21 | ignorePatterns: ["*.js", "vite.config.ts"], 22 | 23 | overrides: [ 24 | { 25 | files: ["scripts/**/*.js"], 26 | rules: { 27 | "@typescript-eslint/no-var-requires": "off", 28 | }, 29 | }, 30 | ], 31 | }; 32 | -------------------------------------------------------------------------------- /.github/workflows/pr-size-labeler.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. 2 | 3 | name: Pull Request Size 4 | on: [pull_request] 5 | 6 | jobs: 7 | labeler: 8 | if: github.event.pull_request.draft == false 9 | runs-on: ubuntu-latest 10 | permissions: 11 | issues: write 12 | pull-requests: write 13 | name: Label the PR size 14 | steps: 15 | - uses: codelytv/pr-size-labeler@v1 16 | with: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | xs_label: size/xs 19 | xs_max_size: '20' 20 | s_label: size/s 21 | s_max_size: '100' 22 | m_label: size/m 23 | m_max_size: '400' 24 | l_label: size/l 25 | l_max_size: '800' 26 | xl_label: size/xl 27 | fail_if_xl: 'false' 28 | message_if_xl: '' 29 | files_to_ignore: package-lock.json 30 | -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/hooks/useGnuiTheme.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Nordcloud Oy or its affiliates. All Rights Reserved. 3 | */ 4 | 5 | import { createContext, useContext, useMemo } from "react"; 6 | import { useThemeSwitcher, THEME_OPTIONS } from "@nordcloud/gnui"; 7 | 8 | const initialState = { 9 | currentTheme: THEME_OPTIONS.LIGHT, 10 | toggleTheme: () => undefined, 11 | setTheme: () => undefined, 12 | }; 13 | 14 | type Context = { 15 | currentTheme: THEME_OPTIONS; 16 | toggleTheme: () => void; 17 | setTheme: (newTheme: THEME_OPTIONS) => void; 18 | }; 19 | 20 | const GNUIThemeContext = createContext(initialState); 21 | GNUIThemeContext.displayName = "GNUIThemeContext"; 22 | 23 | export function GNUIThemeProvider({ children }: { children: React.ReactNode }) { 24 | const { currentTheme, setTheme } = useThemeSwitcher(); 25 | 26 | const nextTheme = 27 | currentTheme === THEME_OPTIONS.LIGHT 28 | ? THEME_OPTIONS.DARK 29 | : THEME_OPTIONS.LIGHT; 30 | 31 | const contextValue = useMemo(() => { 32 | const toggleTheme = () => setTheme(nextTheme); 33 | 34 | return { 35 | currentTheme, 36 | setTheme, 37 | toggleTheme, 38 | }; 39 | }, [currentTheme, setTheme, nextTheme]); 40 | 41 | return ( 42 | 43 | {children} 44 | 45 | ); 46 | } 47 | 48 | export function useGNUITheme() { 49 | const context = useContext(GNUIThemeContext); 50 | 51 | if (context === undefined) { 52 | throw new Error("useGNUITheme must be used within a GNUIThemeProvider"); 53 | } 54 | return context; 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vite template for frontend projects 2 | 3 | This is a basic setup for our frontend projects, it includes [**Migration guide**](./docs/CRA_MIGRATION_GUIDE.md) from deprecated **Create React App** setup. 4 | 5 | ## Components 6 | 7 | 1. Frontend application based on [**Vite**](https://vitejs.dev/) 8 | 2. Code is written in [**Typescript**](https://basarat.gitbook.io/typescript/getting-started) 9 | 3. [**Prettier**](https://prettier.io/) formatter 10 | 4. Linting implemented with [**Stylelint**](https://stylelint.io/) and [**ESLint**](https://eslint.org/) 11 | 5. Testing setup consists of [**Vitest**](https://vitest.dev/) and [**Testing Library**](https://testing-library.com/) 12 | 6. Support for styling with [**Styled Components**](https://styled-components.com/) 13 | 7. [**GNUI**](https://github.com/nordcloud/GNUI) component library 14 | 8. Additional checks that improve codebase maintenance (**circular dependencies, unused exports**) 15 | 9. **Git hooks** support 16 | 10. **Alias import** support 17 | 18 | ## Setup 19 | 20 | ### 1. Make sure you use a proper Node.js and npm versions 21 | 22 | Check `engines` field in `package.json` to see currently supported versions 23 | 24 | Automatic setup for [NVM](https://github.com/nvm-sh/nvm) users 25 | 26 | ```bash 27 | nvm install 28 | nvm use 29 | ``` 30 | 31 | ### 2. Install packages 32 | 33 | ```bash 34 | npm install 35 | ``` 36 | 37 | ### 3. Add [env file](https://vitejs.dev/guide/env-and-mode.html#env-files) 38 | 39 | Create `.env` file 40 | 41 | ```bash 42 | REACT_APP_CLIENT_TOKEN= 43 | REACT_APP_ENV=development 44 | SERVER_OPEN_BROWSER=false 45 | ``` 46 | 47 | ## [Create React App migration guide](./docs/CRA_MIGRATION_GUIDE.md) 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # Logs 4 | logs 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Optional npm cache directory 10 | .npm 11 | 12 | # Dependency directories 13 | /node_modules 14 | /jspm_packages 15 | /bower_components 16 | 17 | # Yarn Integrity file 18 | .yarn-integrity 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # dotenv environment variables file(s) 24 | .env 25 | .env.* 26 | 27 | # production 28 | /build 29 | 30 | # generated 31 | 32 | 33 | ### SublimeText ### 34 | # cache files for sublime text 35 | *.tmlanguage.cache 36 | *.tmPreferences.cache 37 | *.stTheme.cache 38 | 39 | # workspace files are user-specific 40 | *.sublime-workspace 41 | 42 | # project files should be checked into the repository, unless a significant 43 | # proportion of contributors will probably not be using SublimeText 44 | # *.sublime-project 45 | 46 | 47 | ### VisualStudioCode ### 48 | /.vscode 49 | .vscode/* 50 | .history 51 | ### Vim ### 52 | *.sw[a-p] 53 | 54 | ### WebStorm/IntelliJ ### 55 | /.idea 56 | modules.xml 57 | *.ipr 58 | 59 | 60 | ### System Files ### 61 | .DS_Store 62 | 63 | # Windows thumbnail cache files 64 | Thumbs.db 65 | ehthumbs.db 66 | ehthumbs_vista.db 67 | 68 | # Folder config file 69 | Desktop.ini 70 | 71 | # Recycle Bin used on file shares 72 | $RECYCLE.BIN/ 73 | 74 | # Thumbnails 75 | ._* 76 | 77 | # Files that might appear in the root of a volume 78 | .DocumentRevisions-V100 79 | .fseventsd 80 | .Spotlight-V100 81 | .TemporaryItems 82 | .Trashes 83 | .VolumeIcon.icns 84 | .com.apple.timemachine.donotpresent 85 | 86 | # Tests 87 | /coverage 88 | 89 | # dependencies 90 | /node_modules 91 | /.pnp 92 | .pnp.js -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 2 | 3 | import { useState } from "react"; 4 | import { Spinner, Text, theme } from "@nordcloud/gnui"; 5 | import { useGNUITheme } from "~/hooks/useGnuiTheme"; 6 | import { Wrapper, Box, buttonCss } from "~/styles"; 7 | import Logo from "./logo.svg?react"; 8 | import "./App.css"; 9 | 10 | export function App() { 11 | const [count, setCount] = useState(0); 12 | const { currentTheme } = useGNUITheme(); 13 | 14 | return ( 15 | 16 |
17 | 18 | 23 | Hello Vite + React! 24 | 25 | 32 | 33 | Edit App.tsx and save to test HMR updates. 34 | 35 | Current theme: {currentTheme} 36 | 37 | 43 | Learn React 44 | 45 | {" | "} 46 | 52 | Vite Docs 53 | 54 | 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. 2 | 3 | name: Reviewdog 4 | on: [pull_request] 5 | 6 | jobs: 7 | reviewdog: 8 | if: github.event.pull_request.draft == false 9 | name: Reviewdog 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: read 13 | pull-requests: write 14 | timeout-minutes: 30 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: actions/setup-node@v3 18 | with: 19 | node-version-file: package.json 20 | 21 | - run: npm ci 22 | - run: npm audit signatures 23 | 24 | - name: Prettier 25 | uses: EPMatt/reviewdog-action-prettier@v1 26 | with: 27 | github_token: ${{ secrets.GITHUB_TOKEN }} 28 | reporter: github-pr-review 29 | level: warning 30 | fail_on_error: false 31 | prettier_flags: '**/*.+(js|json|graphql|ts|tsx|md|yml|css|html)' 32 | 33 | - name: ESLint 34 | uses: reviewdog/action-eslint@v1 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | reporter: github-pr-review 38 | filter_mode: added 39 | level: warning 40 | fail_on_error: false 41 | eslint_flags: . --report-unused-disable-directives 42 | 43 | - name: Stylelint 44 | uses: reviewdog/action-stylelint@v1 45 | with: 46 | github_token: ${{ secrets.GITHUB_TOKEN }} 47 | reporter: github-pr-review 48 | filter_mode: file 49 | level: warning 50 | fail_on_error: false 51 | stylelint_input: '**/*.+(ts|tsx)' 52 | 53 | - name: TypeScript 54 | uses: EPMatt/reviewdog-action-tsc@v1 55 | with: 56 | github_token: ${{ secrets.GITHUB_TOKEN }} 57 | reporter: github-pr-check 58 | filter_mode: nofilter 59 | level: warning 60 | fail_on_error: false 61 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pat-frontend-template", 3 | "version": "1.0.0", 4 | "description": "Frontend template based on Vite", 5 | "author": "Nordcloud Engineering", 6 | "license": "Apache-2.0", 7 | "homepage": "https://github.com/nordcloud/pat-frontend-template#readme", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/nordcloud/pat-frontend-template.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/nordcloud/pat-frontend-template/issues" 14 | }, 15 | "keywords": [ 16 | "vite", 17 | "reactjs", 18 | "typescript", 19 | "styled-components", 20 | "vitest", 21 | "testing-library", 22 | "template", 23 | "boilerplate", 24 | "eslint", 25 | "prettier", 26 | "stylelint" 27 | ], 28 | "scripts": { 29 | "dev": "vite", 30 | "dev:clean": "npm run dev -- --force", 31 | "build": "tsc && vite build", 32 | "serve": "vite preview --port 3000", 33 | "format": "prettier --ignore-path .gitignore --write \"**/*.+(js|json|ts|tsx)\"", 34 | "test": "vitest run", 35 | "test:watch": "vitest watch", 36 | "test:silent": "npm run test -- --silent", 37 | "test:commit": "npm run test:silent -- --changed", 38 | "test:coverage": "vitest run --coverage", 39 | "check:types": "tsc --pretty --skipLibCheck --noEmit", 40 | "check:format": "prettier --check \"**/*.+(js|json|ts|tsx)\"", 41 | "check:circular-deps": "./scripts/circular-deps.js", 42 | "check:knip": "knip", 43 | "lint": "eslint --ignore-path .gitignore --ext .ts,.tsx .", 44 | "lint:quiet": "npm run lint -- --quiet", 45 | "lint:css": "stylelint '**/*.+(ts|tsx)'", 46 | "lint:css:quiet": "npm run lint:css -- --quiet", 47 | "lint:lockfile": "lockfile-lint", 48 | "validate:all": "run-p check:types check:format check:circular-deps check:knip lint:quiet lint:css:quiet lint:lockfile", 49 | "postinstall": "patch-package && husky install" 50 | }, 51 | "engines": { 52 | "node": "~18.12.0 || ~18.13.0", 53 | "npm": "~8.19.0" 54 | }, 55 | "dependencies": { 56 | "@nordcloud/gnui": "^8.19.0", 57 | "react": "^18.2.0", 58 | "react-dom": "^18.2.0", 59 | "styled-components": "^6.0.7" 60 | }, 61 | "devDependencies": { 62 | "@nordcloud/eslint-config-pat": "^8.0.0", 63 | "@testing-library/jest-dom": "^6.1.2", 64 | "@testing-library/react": "^14.0.0", 65 | "@testing-library/user-event": "^14.4.3", 66 | "@types/node": "^18.11.9", 67 | "@types/react": "^18.2.21", 68 | "@types/react-dom": "^18.2.7", 69 | "@vitejs/plugin-react": "^4.0.4", 70 | "@vitest/coverage-v8": "^0.34.2", 71 | "babel-plugin-styled-components": "^1.12.1", 72 | "eslint": "^8.49.0", 73 | "husky": "^8.0.3", 74 | "jsdom": "^22.1.0", 75 | "knip": "^2.19.11", 76 | "lint-staged": "^14.0.1", 77 | "lockfile-lint": "^4.12.1", 78 | "madge": "^6.1.0", 79 | "npm-run-all": "^4.1.5", 80 | "patch-package": "^8.0.0", 81 | "prettier": "^3.0.3", 82 | "stylelint": "^13.13.1", 83 | "stylelint-config-recommended": "^5.0.0", 84 | "stylelint-config-styled-components": "^0.1.1", 85 | "stylelint-processor-styled-components": "^1.10.0", 86 | "typescript": "^5.2.2", 87 | "vite": "^4.4.9", 88 | "vite-plugin-checker": "^0.6.2", 89 | "vite-plugin-env-compatible": "^1.1.1", 90 | "vite-plugin-html": "^3.2.0", 91 | "vite-plugin-svgr": "^4.1.0", 92 | "vitest": "^0.34.2" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | /* Copyright (c) 2021-2023 Nordcloud Oy or its affiliates. All Rights Reserved. */ 5 | 6 | import path from "path"; 7 | import { defineConfig, loadEnv } from "vite"; 8 | import react from "@vitejs/plugin-react"; 9 | import envCompatible from "vite-plugin-env-compatible"; 10 | import svgrPlugin from "vite-plugin-svgr"; 11 | import { createHtmlPlugin } from "vite-plugin-html"; 12 | import checker from "vite-plugin-checker"; 13 | import { configDefaults } from "vitest/config"; 14 | import { codeVersion } from "./scripts/code-version"; 15 | 16 | const ENV_PREFIX = "REACT_APP_"; 17 | 18 | // https://vitejs.dev/config/ 19 | export default defineConfig(({ mode }) => { 20 | const env = loadEnv(mode, "env", [ENV_PREFIX, "SERVER"]); 21 | const isProd = env.REACT_APP_ENV === "production"; 22 | 23 | return { 24 | plugins: [ 25 | envCompatible({ prefix: ENV_PREFIX }), 26 | checker({ 27 | overlay: false, 28 | typescript: true, 29 | }), 30 | createHtmlPlugin({ 31 | inject: { 32 | data: { 33 | env: { 34 | NODE_ENV: process.env.NODE_ENV, 35 | REACT_APP_CLIENT_TOKEN: env.REACT_APP_CLIENT_TOKEN, 36 | REACT_APP_ENV: env.REACT_APP_ENV, 37 | }, 38 | }, 39 | }, 40 | minify: true, 41 | }), 42 | svgrPlugin({ 43 | svgrOptions: { 44 | icon: true, 45 | // ...svgr options (https://react-svgr.com/docs/options/) 46 | }, 47 | }), 48 | react({ 49 | babel: { 50 | plugins: [ 51 | [ 52 | "babel-plugin-styled-components", 53 | { 54 | displayName: true, 55 | fileName: false, 56 | }, 57 | ], 58 | ], 59 | }, 60 | }), 61 | ], 62 | resolve: { 63 | alias: { 64 | "~": path.resolve(__dirname, "src"), 65 | }, 66 | }, 67 | server: { 68 | port: 3000, 69 | open: env.SERVER_OPEN_BROWSER === "true", 70 | }, 71 | define: { 72 | "process.env.CODE_VERSION": JSON.stringify(codeVersion()), // Useful for uploading sourcemaps 73 | }, 74 | build: { 75 | outDir: "build", 76 | sourcemap: isProd ? "hidden" : true, 77 | }, 78 | test: { 79 | globals: true, 80 | environment: "jsdom", 81 | setupFiles: "src/setupTests.ts", 82 | testTimeout: 10000, 83 | teardownTimeout: 180000, 84 | clearMocks: true, 85 | include: ["src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], 86 | coverage: { 87 | src: ["src"], 88 | include: ["src/**/*.{ts,tsx}"], 89 | exclude: [ 90 | ...(configDefaults.coverage.exclude ?? []), 91 | "src/setupTests.ts", 92 | "src/**/__mockups__/**", 93 | "src/**/__tests__/**", 94 | "src/**/*.spec.ts", 95 | "src/**/*.spec.tsx", 96 | ], 97 | reporter: ["text", "lcovonly"], 98 | provider: "v8", 99 | all: true, 100 | lines: 45, 101 | branches: 35, 102 | functions: 35, 103 | statements: 45, 104 | }, 105 | reporters: process.env.CI ? ["default"] : ["default", "verbose"], 106 | }, 107 | }; 108 | }); 109 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/CRA_MIGRATION_GUIDE.md: -------------------------------------------------------------------------------- 1 | # CRA -> Vite migration guide 2 | 3 | A step-by-step guide for migrating a project from v4 of [Create React App](https://create-react-app.dev/) to [Vite](https://vitejs.dev/) with `ts-jest` setup. 4 | 5 | ## Table of Contents 6 | 7 | - [Remove Create React App](#1-remove-create-react-app) 8 | - [Install Vite](#2-install-vite) 9 | - [Update tsconfig](#update-tsconfig-path-aliasing-included) 10 | - [Update package.json](#update-packagejson) 11 | - [Create config file](#create-config-file) 12 | - [Environment variables](#3-environment-variables) 13 | - [Static analysis](#4-static-analysis) 14 | - [Babel plugins](#5-babel-plugins) 15 | - [SVG components](#6-svg-components) 16 | - [Index.html](#7-indexhtml) 17 | - [Testing environment setup](#8-testing-environment-setup) 18 | - [Install packages](#install-packages) 19 | - [Jest config setup](#jest-config-setup) 20 | - [Mock SVG files](#mock-svg-files) 21 | - [Add scripts](#add-scripts) 22 | - [Alternative setups](#alternative-setups) 23 | - [Additional features](#9-additional-features) 24 | - [Troubleshooting](#troubleshooting) 25 | 26 | ## 1. Remove Create React App 27 | 28 | - uninstall packages 29 | 30 | ```bash 31 | npm uninstall react-scripts 32 | ``` 33 | 34 | - remove package.json entries: 35 | 36 | **package.json** 37 | 38 | ```json 39 | "scripts": { 40 | "start": "react-scripts start", 41 | "build": "react-scripts build", 42 | "eject": "react-scripts eject", 43 | "test": "react-scripts test", 44 | }, 45 | "jest": { 46 | "collectCoverageFrom": [ 47 | "src/**/*.{js,jsx,ts,tsx}" 48 | ], 49 | "coveragePathIgnorePatterns": [ 50 | "/node_modules/", 51 | ] 52 | }, 53 | ``` 54 | 55 | ## 2. Install Vite 56 | 57 | ```bash 58 | npm install -D vite @vitejs/plugin-react 59 | ``` 60 | 61 | ### Update `tsconfig` (path aliasing included) 62 | 63 | **tsconfig.json** 64 | 65 | ```json 66 | { 67 | "compilerOptions": { 68 | "target": "ESNext", 69 | "useDefineForClassFields": true, 70 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 71 | "allowJs": true, 72 | "skipLibCheck": true, 73 | "esModuleInterop": true, 74 | "allowSyntheticDefaultImports": true, 75 | "strict": true, 76 | "forceConsistentCasingInFileNames": true, 77 | "module": "ESNext", 78 | "moduleResolution": "Node", 79 | "resolveJsonModule": true, 80 | "isolatedModules": true, 81 | "noEmit": true, 82 | "jsx": "react-jsx", 83 | "paths": { 84 | "~/*": ["./src/*"] 85 | }, 86 | "baseUrl": "." 87 | }, 88 | "include": ["./src"] 89 | } 90 | ``` 91 | 92 | ### Update `package.json` 93 | 94 | ```json 95 | "scripts": { 96 | "dev": "vite", 97 | "dev:clean": "npm run dev -- --force", 98 | "build": "tsc && vite build", 99 | "serve": "vite preview --port 3000", 100 | } 101 | ``` 102 | 103 | ### Create config file 104 | 105 | Add **vite.config.ts** at the root of your project 106 | 107 | ```typescript 108 | import path from "path"; 109 | import { defineConfig } from "vite"; 110 | import react from "@vitejs/plugin-react"; 111 | 112 | // https://vitejs.dev/config/ 113 | export default defineConfig(({ mode }) => { 114 | return { 115 | plugins: [react()], 116 | resolve: { 117 | alias: { 118 | "~": path.resolve(__dirname, "src"), 119 | }, 120 | }, 121 | server: { 122 | port: 3000, 123 | }, 124 | build: { 125 | outDir: "build", 126 | }, 127 | }; 128 | }); 129 | ``` 130 | 131 | ### Create vite-env.d.ts 132 | 133 | Add **vite-env.d.ts** in the `src` folder of your project 134 | 135 | ```typescript 136 | /// 137 | ``` 138 | 139 | 140 | ## 3. Environment variables 141 | 142 | Vite handles environment variables in a different way than CRA, [Read here](https://vitejs.dev/guide/env-and-mode.html), 143 | to avoid complete refactoring and preserve backwards-compatibilty we can use a custom [plugin](https://github.com/IndexXuan/vite-plugin-env-compatible). 144 | 145 | ```bash 146 | npm install -D vite-plugin-env-compatible 147 | ``` 148 | 149 | **vite.config.ts** 150 | 151 | ```typescript 152 | import envCompatible from "vite-plugin-env-compatible"; 153 | 154 | const ENV_PREFIX = "REACT_APP_"; 155 | 156 | // https://vitejs.dev/config/ 157 | export default defineConfig(({ mode }) => { 158 | return { 159 | plugins: [envCompatible({ prefix: ENV_PREFIX })], 160 | }; 161 | }); 162 | ``` 163 | 164 | ## 4. Static analysis 165 | 166 | By default, type checking & linting in CRA was built into dev server, we can implement this in Vite with a [plugin](https://github.com/fi3ework/vite-plugin-checker). 167 | I will omit running `eslint` here since it can pollute terminal & browser console output with many warnings, it's also pretty slow in larger codebases. 168 | Additional linting can be handled by IDE setup, git hooks & PR checks (e.g. github actions). 169 | 170 | ```bash 171 | npm install -D vite-plugin-checker 172 | ``` 173 | 174 | **vite.config.ts** 175 | 176 | ```typescript 177 | import checker from "vite-plugin-checker"; 178 | 179 | // https://vitejs.dev/config/ 180 | export default defineConfig(({ mode }) => { 181 | return { 182 | plugins: [ 183 | checker({ 184 | overlay: false, 185 | typescript: true, 186 | }), 187 | ], 188 | }; 189 | }); 190 | ``` 191 | 192 | ## 5. Babel plugins 193 | 194 | We were adding custom babel plugins to CRA webpack setup via [craco](https://github.com/gsoft-inc/craco). 195 | This is how we can handle it in Vite (example includes usage of [Styled Components plugin](https://styled-components.com/docs/tooling#babel-plugin)): 196 | 197 | ```typescript 198 | // https://vitejs.dev/config/ 199 | export default defineConfig(({ mode }) => { 200 | return { 201 | plugins: [ 202 | react({ 203 | babel: { 204 | plugins: [ 205 | [ 206 | "babel-plugin-styled-components", 207 | { 208 | displayName: true, 209 | fileName: false, 210 | }, 211 | ], 212 | ], 213 | }, 214 | }), 215 | ], 216 | }; 217 | }); 218 | ``` 219 | 220 | ## 6. SVG Components 221 | 222 | CRA allows to transform `.svg` files into React components. 223 | 224 | ```tsx 225 | import { ReactComponent as Logo } from "./logo.svg?react"; 226 | ``` 227 | 228 | In order to make this work with Vite we will need a [plugin](https://github.com/pd4d10/vite-plugin-svgr) 229 | 230 | ```bash 231 | npm install -D vite-plugin-svgr 232 | ``` 233 | 234 | ```typescript 235 | import svgrPlugin from "vite-plugin-svgr"; 236 | 237 | // https://vitejs.dev/config/ 238 | export default defineConfig(({ mode }) => { 239 | return { 240 | plugins: [ 241 | svgrPlugin({ 242 | svgrOptions: { 243 | icon: true, 244 | // ...svgr options (https://react-svgr.com/docs/options/) 245 | }, 246 | }), 247 | ], 248 | }; 249 | }); 250 | ``` 251 | 252 | Add additional declaration to the `src/vite-env.d.ts` file 253 | 254 | ```typescript 255 | /// 256 | ``` 257 | 258 | 259 | ## 7. Index.html 260 | 261 | - Move `index.html` from `/public` to the root of the project 262 | - Remove any `%PUBLIC_URL%` references from the file 263 | 264 | ```html 265 | 266 | 267 | 268 | 269 | 270 | ``` 271 | 272 | - Add Entry script tag to 'index.html' 273 | 274 | ```html 275 | 276 | 277 | ``` 278 | 279 | - Embedding JavaScript: 280 | CRA supported [ejs](https://ejs.co/) out of the box, we will need additional [plugin for Vite](https://github.com/anncwb/vite-plugin-html): 281 | 282 | ```bash 283 | npm install -D vite-plugin-html 284 | ``` 285 | 286 | **vite.config.ts** 287 | 288 | ```typescript 289 | import html from "vite-plugin-html"; 290 | 291 | // https://vitejs.dev/config/ 292 | export default defineConfig(({ mode }) => { 293 | return { 294 | plugins: [ 295 | html({ 296 | inject: { 297 | data: { 298 | env: { 299 | NODE_ENV: process.env.NODE_ENV, 300 | REACT_APP_CLIENT_TOKEN: process.env.REACT_APP_CLIENT_TOKEN, 301 | REACT_APP_ENV: process.env.REACT_APP_ENV, 302 | }, 303 | }, 304 | }, 305 | minify: true, 306 | }), 307 | ], 308 | }; 309 | }); 310 | ``` 311 | 312 | Conditionally append scripts and pass environment variables: 313 | 314 | **index.html** 315 | 316 | ```html 317 | 318 | 319 | <% if (env.NODE_ENV === 'production') { %> 320 | 321 | 327 | <% } %> 328 | 329 | 330 | ``` 331 | 332 | ## 8. Testing environment setup 333 | 334 | Create React App used `babel` to transform the code for `jest`, we're gonna use [ts-jest](https://kulshekhar.github.io/ts-jest/docs/babel7-or-ts/) in our setup. 335 | 336 | ### Install packages 337 | 338 | ```bash 339 | npm install -D jest ts-jest jest-watch-typeahead identity-obj-proxy 340 | ``` 341 | 342 | ### Jest config setup 343 | 344 | Create `jest.config.js` file and add below config: 345 | 346 | ```typescript 347 | // jest.config.js 348 | /** @type {import('@jest/types').Config.InitialOptions} */ 349 | const config = { 350 | verbose: true, 351 | roots: ["/src"], 352 | setupFilesAfterEnv: ["/src/setupTests.ts"], 353 | collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"], 354 | coveragePathIgnorePatterns: ["/node_modules/"], 355 | testMatch: [ 356 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 357 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}", 358 | ], 359 | testEnvironment: "jsdom", 360 | preset: "ts-jest", 361 | globals: { 362 | "ts-jest": { 363 | isolatedModules: true, 364 | }, 365 | }, 366 | transform: { 367 | "\\.(ts|js)x?$": "ts-jest", 368 | }, 369 | transformIgnorePatterns: [ 370 | "[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$", 371 | ], 372 | resetMocks: true, 373 | moduleNameMapper: { 374 | ".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": 375 | "identity-obj-proxy", 376 | "\\.svg$": "/__mocks__/svg.js", 377 | "~(.*)$": "/src/$1", 378 | }, 379 | moduleDirectories: ["node_modules", "/node_modules", "."], 380 | watchPlugins: [ 381 | "jest-watch-typeahead/filename", 382 | "jest-watch-typeahead/testname", 383 | ], 384 | testTimeout: 10000, // optional 385 | }; 386 | 387 | module.exports = config; 388 | ``` 389 | 390 | ### Mock SVG files 391 | 392 | Create `__mocks__/svg.js`: 393 | 394 | ```javascript 395 | export default "SvgrURL"; 396 | export const ReactComponent = "div"; 397 | ``` 398 | 399 | ### Add scripts 400 | 401 | **package.json** 402 | 403 | ```json 404 | "scripts": { 405 | "test": "jest", 406 | "test:watch": "npm run test -- --watch", 407 | "test:all": "npm run test -- --silent --watchAll=false", 408 | "test:coverage": "npm run test:all -- --coverage", 409 | } 410 | ``` 411 | 412 | ### Alternative setups 413 | 414 | - [Babel setup](https://jestjs.io/docs/tutorial-react#setup-without-create-react-app) 415 | - [Vitest](https://vitest.dev/) - unit-test framework powered by Vite 416 | - [esbuild-jest](https://github.com/aelbore/esbuild-jest) 417 | - [@swc/jest](https://swc.rs/docs/usage/jest) 418 | 419 | ## 9. Additional features 420 | 421 | ### Open tab when server starts 422 | 423 | Add possibility to configure opening a new tab when dev server is launched (it was turned on by default in CRA): 424 | 425 | **.env** 426 | 427 | ```bash 428 | SERVER_OPEN_BROWSER=true 429 | ``` 430 | 431 | **vite.config.ts** 432 | 433 | ```typescript 434 | import { defineConfig, loadEnv } from "vite"; 435 | 436 | const ENV_PREFIX = ["REACT_APP_", "SERVER"]; 437 | 438 | // https://vitejs.dev/config/ 439 | export default defineConfig(({ mode }) => { 440 | const env = loadEnv(mode, "env", ENV_PREFIX); 441 | 442 | return { 443 | server: { 444 | port: 3000, 445 | open: env.SERVER_OPEN_BROWSER === "true", 446 | }, 447 | }; 448 | }); 449 | ``` 450 | 451 | ### Compile time replacement 452 | 453 | There are cases when we want to replace certain variables with some values at compile time (e.g. insert code version extracted from git for uploading source maps), 454 | we've used [craco](https://github.com/gsoft-inc/craco) and [Define plugin](https://webpack.js.org/plugins/define-plugin/) to achieve that in CRA: 455 | 456 | **craco.config.js** 457 | 458 | ```typescript 459 | module.exports = { 460 | webpack: { 461 | plugins: [ 462 | new DefinePlugin({ 463 | "process.env.CODE_VERSION": JSON.stringify(codeVersion()), 464 | }), 465 | ], 466 | }, 467 | }; 468 | ``` 469 | 470 | It can be easily replicated using Vite: 471 | 472 | **vite.config.ts** 473 | 474 | ```typescript 475 | // https://vitejs.dev/config/ 476 | export default defineConfig(({ mode }) => { 477 | return { 478 | define: { 479 | "process.env.CODE_VERSION": JSON.stringify(codeVersion()), 480 | }, 481 | }; 482 | }); 483 | ``` 484 | 485 | ## Troubleshooting 486 | 487 | ### `require is not defined` runtime error 488 | 489 | The cause of this is that modules(probably some npm package) marked as ESM use `require` in their code (e.g. to dynamically load other modules). 490 | To resolve this, add this to `vite.config.ts` 491 | 492 | ```typescript 493 | // https://vitejs.dev/config/ 494 | export default defineConfig(({ mode }) => { 495 | return { 496 | build: { 497 | commonjsOptions: { 498 | transformMixedEsModules: true, 499 | }, 500 | }, 501 | }; 502 | }); 503 | ``` 504 | --------------------------------------------------------------------------------