├── .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 |
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 |
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 |
--------------------------------------------------------------------------------