1. 통합개발환경 세팅
통합개발환경 세팅을 해보자
TypeScript
ESLint
React
Parcel
React Router
styled-components
styled-reset
usehooks-ts
usestore-ts
Axios
tsyringe
reflect-metadata
jest-dom
MSW
CodeceptJS
순서대로 설치 및 테스트 진행
1. 프로젝트 폴더 생성 및 진입
mkdir shop
cd shop
2. npm 글로벌 설치
npm install -g npm
3. Node 버전 잡기
node --version > .node-version
4. npm 패키지 초기화
npm init -y
5. TS 및 ESLint 설치
Typescript 설치
npm i -D typescript
Typescript 설정 파일 생성
npx tsc --init
tsconfig.json 파일에서 jsx 항목 수정
{
"jsx": "react-jsx"
}
ESLint 설치
npm i -D eslint
ESLint 설정
npx eslint --init
$ > To check syntax, find problems, and enforce code style
$ > JavaScript modules (import/export)
$ > React
$ > Yes(TypeScript)
$ > Browser
$ > Use a popular style guide
$ > XO
$ > JavaScript
$ > Yes
$ > npm
ESLint Airbnb 스타일 적용
Airbnb 스타일 다시 적용(TS에서 Airbnb 지원하지 않기 때문)
npm uninstall eslint-config-xo
npm uninstall eslint-config-xo-typescript
npm i -D eslint-config-airbnb
npm i -D eslint-plugin-import
npm i -D eslint-plugin-react
npm i -D eslint-plugin-react-hooks
npm i -D eslint-plugin-jsx-a11y
.eslintrc.js 파일 수정
module.exports = {
env: {
browser: true,
es2021: true,
jest: true,
},
extends: [
'airbnb',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: ['react', 'react-hooks', '@typescript-eslint'],
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
rules: {
indent: ['error', 2],
'no-trailing-spaces': 'error',
curly: 'error',
'brace-style': 'error',
'no-multi-spaces': 'error',
'space-infix-ops': 'error',
'space-unary-ops': 'error',
'no-whitespace-before-property': 'error',
'func-call-spacing': 'error',
'space-before-blocks': 'error',
'keyword-spacing': ['error', { before: true, after: true }],
'comma-spacing': ['error', { before: false, after: true }],
'comma-style': ['error', 'last'],
'comma-dangle': ['error', 'always-multiline'],
'space-in-parens': ['error', 'never'],
'block-spacing': 'error',
'array-bracket-spacing': ['error', 'never'],
'object-curly-spacing': ['error', 'always'],
'key-spacing': ['error', { mode: 'strict' }],
'arrow-spacing': ['error', { before: true, after: true }],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/*.test.js',
'**/*.test.jsx',
'**/*.test.ts',
'**/*.test.tsx',
],
},
],
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
'react/jsx-filename-extension': [
2,
{
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
],
'jsx-a11y/label-has-associated-control': ['error', { assert: 'either' }],
},
};
prettier 충돌 방지
npm install --save-dev eslint-config-prettier
.eslintrc.js 파일 수정
extends: [
'airbnb',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'prettier',
],
.eslintignore 파일 생성
/node_modules/
/dist/
/.parcel-cache/
6. Jest 설치
npm i -D jest @types/jest
npm i -D @swc/core @swc/jest jest-environment-jsdom
npm i -D @testing-library/react @testing-library/jest-dom@5.16.4
jest.config.js
jest.config.js 생성
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
legacyDecorator: true,
decoratorMetadata: true,
},
},
},
],
},
};
React 설치
npm i react react-dom
npm i -D @types/react @types/react-dom
기본 파일 생성
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.tsx"></script>
</body>
</html>
src/main.tsx
import ReactDOM from 'react-dom/client';
import App from './App';
function main() {
const element = document.getElementById('root');
if (!element) {
return;
}
const root = ReactDOM.createRoot(element);
root.render(<App />);
}
main();
src/App.tsx
export default function App() {
return <div>Hello, world!</div>;
}
Parcel 설치
npm i -D parcel
parcel-reporter-static-files-copy 설치
npm install -D parcel-reporter-static-files-copy
static folder 생성
Root path 에 static 폴더를 만든다. 정적 이미지 등을 해당 폴더에 넣는다.
mkdir static
.gitignore 파일 생성
VisualStudioCode, react, macOS, Node 키워드로 생성(gitignore.io)
아래의 폴더는 반드시 추가할 것
...
/dist/
/.parcel-cache/
package.json 수정
"source": "./index.html",
...
...
"scripts": {
"start": "parcel --port 8080",
"build": "parcel build",
"check": "tsc --noEmit",
"lint": "eslint --fix --ext .js,.jsx,.ts,.tsx .",
"test": "jest",
"coverage": "jest --coverage --coverage-reporters html",
"watch:test": "jest --watchAll"
},
실행 테스트
# 로컬 실행
npm run start
# build
npx parcel build
# 정적 서버 실행
npx servor ./dist
MSW 설치 및 관련 파일 생성
TODO: 추후 보완할 것
src/setupTests.ts
src/mocks/server.ts
src/mocks/handler.ts 파일 추가
jest.config.js 설정 변경
TODO: 추후 보완할 것
setupFilesAfterEnv 의 속성에 setupTests.ts 파일 추가
React Router 설치
npm install react-router-dom
styled-components 설치
npm i styled-components@5.3.10
npm i -D @types/styled-components @swc/plugin-styled-components
npm i styled-reset
/src/.swcrc 파일 생성
{
"jsc": {
"experimental": {
"plugins": [
[
"@swc/plugin-styled-components",
{
"displayName": true,
"ssr": true
}
]
]
}
}
}
main.tsx 수정
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
function main() {
const element = document.getElementById('root');
if (!element) {
return;
}
const root = ReactDOM.createRoot(element);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
}
main();
App.tsx 수정
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import routes from './routes';
const router = createBrowserRouter(routes);
export default function App() {
return <RouterProvider router={router} />;
}
/src/routes.tsx 파일 생성 /src/components/Layout.tsx 파일 생성 /src/pages/HomePage.tsx 파일 생성
import Layout from './components/Layout';
import HomePage from './pages/HomePage';
const routes = [
{
element: <Layout />,
children: [{ path: '/', element: <HomePage /> }],
},
];
export default routes;
/src/styles 폴더 생성
/src/styles/defaultTheme.ts 파일 생성
const defaultTheme = { colors: { background: '#FFFFFF', text: '#000000', primary: '#F00000', secondary: '#00FFFF', }, }; export default defaultTheme;
/src/styles/Theme.ts 파일 생성
import defaultTheme from './defaultTheme'; type Theme = typeof defaultTheme; export default Theme;
/src/styles/styled.d.ts 파일 생성
import 'styled-components'; import Theme from './Theme'; declare module 'styled-components' { export interface DefaultTheme extends Theme {} }
/src/styles/GlobalStyle.ts 파일 생성
import { createGlobalStyle } from 'styled-components'; const GlobalStyle = createGlobalStyle` html { box-sizing: border-box; } *, *::before, *::after { box-sizing: inherit; } html { font-size: 62.5%; } body { font-size: 1.6rem; background: ${(props) => props.theme.colors.background}; color: ${(props) => props.theme.colors.text} } :lang(ko) { h1, h2, h3 { word-break: keep-all; } } `; export default GlobalStyle;
App.tsx 수정
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import routes from './routes';
import { ThemeProvider } from 'styled-components';
import defaultTheme from './styles/defaultTheme';
import { Reset } from 'styled-reset';
import GlobalStyle from './styles/GlobalStyle';
const router = createBrowserRouter(routes);
export default function App() {
return (
<ThemeProvider theme={defaultTheme}>
<Reset />
<GlobalStyle />
<RouterProvider router={router} />
</ThemeProvider>
);
}
helper 만들어주기
/src/utils/test-helpers.tsx
/* eslint-disable import/prefer-default-export */
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as originalRender } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import defaultTheme from '../styles/defaultTheme';
type Option = {
path?: string;
};
export function render(
element: React.ReactElement,
{ path = '/' }: Option = {},
) {
return originalRender(
<MemoryRouter initialEntries={[path]}>
<ThemeProvider theme={defaultTheme}>{element}</ThemeProvider>
</MemoryRouter>,
);
}
Axios 설치
npm i axios
tsyringe, reflect-metadata, usestore-ts 설치
npm i tsyringe reflect-metadata usestore-ts
/src/main.tsx, src/setupTests.ts 파일 수정
// 최상단에 임포트(tsyringe 의존성)
import 'reflect-metadata';
tsconfig.json 수정
...
{
"experimentalDecorators": true
"emitDecoratorMetadata": true
}
...
jest.config.js 파일 수정
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
transform: {
'^.+\\.(t|j)sx?$': [
'@swc/jest',
{
jsc: {
parser: {
syntax: 'typescript',
jsx: true,
decorators: true,
},
transform: {
react: {
runtime: 'automatic',
},
legacyDecorator: true, // 이거
decoratorMetadata: true, // 이거
},
},
},
],
},
};
CodeceptJS 설치
npx codeceptjs init
# > Do you plan to write tests in TypeScript? y
# > Where are your tests located? ./tests/**/*_test.js
# > What helpers do you want to use? Playwright
# > where should logs, screenshots, and reports to be stored? (./output)
# > ? [Playwright] Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron? chromium
# > Do you want localization for tests? English (no localization)
# > [Playwright] Base url of site to be tested (http://localhost)
# > [Playwright] Show browser window n
# > Feature which is being tested (ex: account, login, etc)
# > Filename of a test (_test.js)
부가 설정
npm i -D eslint-plugin-codeceptjs playwright @codeceptjs/configure
codecept.conf.ts 에서 config의 타입 제거 및 수정.
import { setCommonPlugins, setHeadlessWhen } from '@codeceptjs/configure'; setHeadlessWhen(process.env.HEADLESS); setCommonPlugins(); export const config = { name: 'my-project', tests: './tests/**/*_test.ts', output: './output', helpers: { Playwright: { browser: 'chromium', url: 'http://localhost', show: false, }, }, include: { // I: './steps_file', to remove I: './tests/steps_file', }, // added plugins: { retryFailedStep: { enabled: true, }, }, };
tests/steps.d.ts 파일 생성
// <reference types='codeceptjs' /> type steps_file = typeof import('./steps_file'); declare namespace CodeceptJS { interface SupportObject { I: I; } interface Methods extends Playwright, REST, JSONResponse {} interface I extends ReturnType<steps_file> {} namespace Translation { interface Actions {} } }
steps_files.js 파일 tests 폴더로 이동
tsconfig.json 파일 수정
CodeceptJS가 내부적으로 ts-node 사용하기 때문에
{ { ... /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, "ts-node": { "files": true } }
/tests/.eslintrc.js 파일 생성
module.exports = { extends: ['plugin:codeceptjs/recommended'], };
.gitignore 에 output/ 추가
package.json 수정
{
"scripts": {
"start": "parcel --port 8080",
"build": "parcel build",
"serve": "servor dist index.html 8000 --reload",
"check": "tsc --noEmit",
"lint": "eslint --fix --ext .js,.jsx,.ts,.tsx .",
"test": "jest",
"coverage": "jest --coverage --coverage-reporters html",
"watch:test": "jest --watchAll",
"codeceptjs": "codeceptjs run --steps",
"codeceptjs:headless": "HEADLESS=true codeceptjs run --steps"
}
}
Last updated