initial commit

This commit is contained in:
andres
2023-05-26 18:59:42 +02:00
commit 735ae87ca9
36 changed files with 6487 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies
node_modules
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Swap the comments on the following lines if you don't wish to use zero-installs
# Documentation here: https://yarnpkg.com/features/zero-installs
!.yarn/cache
#.pnp.*
# Testing
coverage
# Production
build
# Miscellaneous
*.local

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/cards-front.iml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

58
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,58 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="80" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

6
.idea/jsLinters/eslint.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/cards-front.iml" filepath="$PROJECT_DIR$/.idea/cards-front.iml" />
</modules>
</component>
</project>

8
.idea/prettier.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
<option name="myRunOnSave" value="true" />
<option name="myFilesPattern" value="{**/*,*}.{js,ts,jsx,tsx,vue,astro,css,scss,json}" />
</component>
</project>

3
.idea/sonarlint/issuestore/index.pb generated Normal file
View File

@@ -0,0 +1,3 @@
9
README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d

View File

@@ -0,0 +1,3 @@
9
README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d

27
README.md Normal file
View File

@@ -0,0 +1,27 @@
# vite-template-redux
Uses [Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), and [React Testing Library](https://github.com/testing-library/react-testing-library) to create a modern [React](https://react.dev/) app compatible with [Create React App](https://create-react-app.dev/)
```sh
npx degit reduxjs/redux-templates/packages/vite-template-redux my-app
```
## Goals
- Easy migration from Create React App or Vite
- As beginner friendly as Create React App
- Optimized performance compared to Create React App
- Customizable without ejecting
## Scripts
- `dev`/`start` - start dev server and open browser
- `build` - build for production
- `preview` - locally preview production build
- `test` - launch test runner
## Inspiration
- [Create React App](https://github.com/facebook/create-react-app/tree/main/packages/cra-template)
- [Vite](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react)
- [Vitest](https://github.com/vitest-dev/vitest/tree/main/examples/react-testing-lib)

14
index.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>React Redux App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

61
package.json Normal file
View File

@@ -0,0 +1,61 @@
{
"name": "vite-template-redux",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest",
"format": "prettier --write .",
"lint": "eslint .",
"type-check": "tsc"
},
"dependencies": {
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16",
"@mui/material": "^5.13.2",
"@reduxjs/toolkit": "^1.8.1",
"axios": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.1",
"react-router-dom": "^6.11.2"
},
"devDependencies": {
"@testing-library/dom": "^9.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.2.5",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"@types/testing-library__jest-dom": "^5.14.5",
"@vitejs/plugin-react": "^4.0.0",
"eslint": "^8.0.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-prettier": "^4.2.1",
"jsdom": "^21.1.0",
"prettier": "^2.7.1",
"prettier-config-nick": "^1.0.2",
"typescript": "^5.0.2",
"vite": "^4.0.0",
"vitest": "^0.30.1"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error",
"react/jsx-no-target-blank": "off"
}
},
"prettier": "prettier-config-nick"
}

5722
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

39
src/App.css Normal file
View File

@@ -0,0 +1,39 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-float infinite 3s ease-in-out;
}
}
.App-header {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
}
.App-link {
color: rgb(112, 76, 182);
}
@keyframes App-logo-float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(10px);
}
100% {
transform: translateY(0px);
}
}

14
src/App.test.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { render } from "@testing-library/react"
import { Provider } from "react-redux"
import { store } from "./app/store"
import App from "./App"
test("renders learn react link", () => {
const { getByText } = render(
<Provider store={store}>
<App />
</Provider>,
)
expect(getByText(/learn/i)).toBeInTheDocument()
})

58
src/App.tsx Normal file
View File

@@ -0,0 +1,58 @@
import { Counter } from "./features/counter/Counter"
import { createBrowserRouter, RouterProvider } from "react-router-dom"
import "./App.css"
import { store } from "@/app/store"
import { Provider } from "react-redux"
import { createTheme, ThemeProvider } from "@mui/material"
import { useAppDispatch, useAppSelector } from "@/app/hooks"
import { useEffect } from "react"
import { appActions } from "@/features/app/app.slice"
export const Test = () => {
const isLoading = useAppSelector((state) => state.app.isLoading)
const error = useAppSelector((state) => state.app.error)
const dispatch = useAppDispatch()
function handleErrorButtonClicked() {
dispatch(appActions.setError({ error: "new error" }))
}
useEffect(() => {
setTimeout(() => {
dispatch(appActions.setIsLoading({ isLoading: false }))
}, 3000)
}, [dispatch])
if (isLoading) return <div>loading...</div>
return (
<div>
<button onClick={handleErrorButtonClicked}>create error</button>
{!!error && <h2>{error}</h2>}
<Counter />
</div>
)
}
const router = createBrowserRouter([
{
element: <Test />,
path: "/",
},
{
element: <div>hello</div>,
path: "/hello",
},
])
const theme = createTheme()
function App() {
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</Provider>
)
}
export default App

6
src/app/hooks.ts Normal file
View File

@@ -0,0 +1,6 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"
import type { RootState, AppDispatch } from "./store"
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

19
src/app/store.ts Normal file
View File

@@ -0,0 +1,19 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
import counterReducer from "../features/counter/counterSlice"
import { appReducer } from "@/features/app/app.slice"
export const store = configureStore({
reducer: {
counter: counterReducer,
app: appReducer,
},
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>

View File

@@ -0,0 +1,27 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
const slice = createSlice({
name: "app",
initialState: {
error: null as null | string,
isLoading: true,
isAppInitialized: false,
},
reducers: {
setIsLoading: (state, action: PayloadAction<{ isLoading: boolean }>) => {
state.isLoading = action.payload.isLoading
},
setError: (state, action: PayloadAction<{ error: string | null }>) => {
state.error = action.payload.error
},
setIsAppInitialized: (
state,
action: PayloadAction<{ isInitialized: boolean }>,
) => {
state.isAppInitialized = action.payload.isInitialized
},
},
})
export const appReducer = slice.reducer
export const appActions = slice.actions

View File

@@ -0,0 +1,79 @@
.row {
display: flex;
align-items: center;
justify-content: center;
}
.row > button {
margin-left: 4px;
margin-right: 8px;
}
.row:not(:last-child) {
margin-bottom: 16px;
}
.value {
font-size: 78px;
padding-left: 16px;
padding-right: 16px;
margin-top: 2px;
font-family: "Courier New", Courier, monospace;
}
.button {
appearance: none;
background: none;
font-size: 32px;
padding-left: 12px;
padding-right: 12px;
outline: none;
border: 2px solid transparent;
color: rgb(112, 76, 182);
padding-bottom: 4px;
cursor: pointer;
background-color: rgba(112, 76, 182, 0.1);
border-radius: 2px;
transition: all 0.15s;
}
.textbox {
font-size: 32px;
padding: 2px;
width: 64px;
text-align: center;
margin-right: 4px;
}
.button:hover,
.button:focus {
border: 2px solid rgba(112, 76, 182, 0.4);
}
.button:active {
background-color: rgba(112, 76, 182, 0.2);
}
.asyncButton {
composes: button;
position: relative;
}
.asyncButton:after {
content: "";
background-color: rgba(112, 76, 182, 0.15);
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
opacity: 0;
transition: width 1s linear, opacity 0.5s ease 1s;
}
.asyncButton:active:after {
width: 0%;
opacity: 1;
transition: 0s;
}

View File

@@ -0,0 +1,68 @@
import { useState } from "react"
import { useAppSelector, useAppDispatch } from "@/app/hooks"
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from "./counterSlice"
import styles from "./Counter.module.css"
export function Counter() {
const count = useAppSelector(selectCount)
const dispatch = useAppDispatch()
const [incrementAmount, setIncrementAmount] = useState("2")
const incrementValue = Number(incrementAmount) || 0
return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className={styles.button}
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
)
}

View File

@@ -0,0 +1,6 @@
// A mock function to mimic making an async request for data
export function fetchCount(amount = 1) {
return new Promise<{ data: number }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500),
)
}

View File

@@ -0,0 +1,34 @@
import counterReducer, {
CounterState,
increment,
decrement,
incrementByAmount,
} from "./counterSlice"
describe("counter reducer", () => {
const initialState: CounterState = {
value: 3,
status: "idle",
}
it("should handle initial state", () => {
expect(counterReducer(undefined, { type: "unknown" })).toEqual({
value: 0,
status: "idle",
})
})
it("should handle increment", () => {
const actual = counterReducer(initialState, increment())
expect(actual.value).toEqual(4)
})
it("should handle decrement", () => {
const actual = counterReducer(initialState, decrement())
expect(actual.value).toEqual(2)
})
it("should handle incrementByAmount", () => {
const actual = counterReducer(initialState, incrementByAmount(2))
expect(actual.value).toEqual(5)
})
})

View File

@@ -0,0 +1,85 @@
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit"
import { RootState, AppThunk } from "../../app/store"
import { fetchCount } from "./counterAPI"
export interface CounterState {
value: number
status: "idle" | "loading" | "failed"
}
const initialState: CounterState = {
value: 0,
status: "idle",
}
// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
"counter/fetchCount",
async (amount: number) => {
const response = await fetchCount(amount)
// The value we return becomes the `fulfilled` action payload
return response.data
},
)
export const counterSlice = createSlice({
name: "counter",
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = "loading"
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = "idle"
state.value += action.payload
})
.addCase(incrementAsync.rejected, (state) => {
state.status = "failed"
})
},
})
export const { increment, decrement, incrementByAmount } = counterSlice.actions
// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)`
export const selectCount = (state: RootState) => state.counter.value
// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState())
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount))
}
}
export default counterSlice.reducer

13
src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

1
src/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><g fill="#764ABC"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

10
src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from "react"
import ReactDOM from "react-dom/client"
import App from "./App"
import "./index.css"
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

2
src/setupTests.ts Normal file
View File

@@ -0,0 +1,2 @@
/// <reference types="vitest/globals" />
import "@testing-library/jest-dom"

1
src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

24
tsconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"paths": {
"@/*": ["./src/*"]
},
"types": ["testing-library__jest-dom"]
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

9
tsconfig.node.json Normal file
View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

24
vite.config.ts Normal file
View File

@@ -0,0 +1,24 @@
import { defineConfig } from "vitest/config"
import react from "@vitejs/plugin-react"
import path from "path"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
server: {
open: true,
},
resolve: {
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
},
build: {
outDir: "build",
sourcemap: true,
},
test: {
globals: true,
environment: "jsdom",
setupFiles: "src/setupTests",
mockReset: true,
},
})