1. 라이브러리 정하기
정말 대략적인 기획은 뽑았으니 나머지는 차차 해가는 걸로 합시다.
그렇다면 이제 프로젝트를 시작해봐야 합니다.
근데 문제가 있습니다.
회사 프로젝트만 하고 사이드 프로젝트를 잘 안 하다 보니까 뭐가 트렌드고 뭐가 좋은지를 잘 모르겠어요.
평소 공부하던 영상 외에 진짜 뭐가 괜찮은지 찾아보겠습니다.
일단 React로 시작하는 건 제가 그냥 정했습니다.
Angular는 언제 사라질까 무섭고, Vue는 제가 계속 개발을 유지한다는 가정 하에
프로젝트가 커질수록 좀 성능면 문제가 있지는 않을까 싶어서요.
다른 건 아직 생태계가 너무 작아서 제가 배우는데 더 오래 걸릴 것 같습니다.
React를 시작하는 방법은 크게 CRA, Webpack Vite, Next.js(SSR) 등이 있는데요.
CRA는 이제 지원도 중단되어 바로 버리고, Webpack은 ESBuilder에 비해 꽤 느립니다.
Next.js는 SSR을 지원하지만 제가 SSR이 필요하지는 않습니다.
저는 SEO가 크게 중요하지 않고, 돈이 없어서 좋은 서버를 둘 수 없어 서버 부하가 오면 큰 일 나거든요.
에디터다 보니까 사용자 인터렉션이 꽤 필요한 건 당연하고요.
그럼 Vite로 React를 시작하는 건 정했습니다.
이제 스타일을 정해야 하는데, 동적 스타일링이 좀 많을 것 같아 CSS In JS를 사용할까 합니다.
라이브러리가 많은데, style 태그를 계속 생성하는 다른 라이브러리에 비해 Emotion은 한 번만 생성하고
CSSOM에 직접 주입하여 성능 최적화가 꽤 잘 이루어져 있다고 하네요.
Emotion으로 해보겠습니다.
2. Vite로 React Typescript 프로젝트 시작하기
자 그럼 시작해 볼까요
제 프로젝트 이름은 AppEditor입니다.
npm create vite@latest AppEditor
위 명령어를 통해 vite로 프로젝트를 시작합니다.
그럼 진행하는 게 맞는지, 어떤 프로젝트를 시작할 건지, 언어는 뭔지를 설정할 수 있습니다.
저는 React 프로젝트, TypeScript+SWC를 선택했습니다.
여기서 SWC는 Speedy Web Compiler로 Rust 기반으로 작성된 JS 컴파일러라고 합니다.
Webpack이 Go 언어 기반으로 작성되어 성능이 빨라진 게 ESBuilder라면
기존 Babel이 Rust 언어 기반으로 작성되어 빠른 성능을 가지게 된 게 SWC라고 보시면 되겠습니다.
자 프로젝트를 시작했으면, 개발로 들어가기 전에 초기 설정을 좀 해줘야 합니다.
사용할 ESLint, 디렉터리 구조, 라이브러리, 코드 스타일 규칙을 미리 설정해 줘야
개발이 보다 빠르고 편리하게 진행될 수 있습니다.
3. ESlint 설정하기
ESlint는 JS 코드의 패턴을 맞춰주는 도구입니다.
JS의 다양한 사용성을 통해 코드 스타일이 일과되지 못하거나
문제 될 우려가 있는 코드의 사용을 제한하고 수정해 줄 수 있는 도구로 정적 코드 분석 도구이지만,
코드 스타일 유지에 도움을 주는 용도로 많이 사용됩니다.
ESlint 설정은 기본으로 되어있기는 하지만 저는 Toss에서 아이디어를 따온 김에 설정 양식도 좀 뺏어보겠습니다.
https://github.com/toss/slash에서 eslintrc.js 파일을 통해 쉽게 확인이 가능하네요.
처음 프로젝트를 생성하면 위처럼 eslint가 설정되어 있을 텐데, 제 마음대로 아래처럼 작성해 주었습니다.
import globals from 'globals';
import typescriptParser from '@typescript-eslint/parser';
import typescriptESLint from '@typescript-eslint/eslint-plugin';
import reactHooksESLint from 'eslint-plugin-react-hooks';
import reactESLint from 'eslint-plugin-react';
import importESLint from 'eslint-plugin-import';
export default [
{
ignores: ['node_modules/', 'dist/'], // lint 적용 제외 경로
},
{
files: ['**/*.{ts,tsx,js,jsx}'],
languageOptions: {
parser: typescriptParser,
ecmaVersion: 2020,
sourceType: 'module',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
globals: globals.browser,
},
plugins: {
'@typescript-eslint': typescriptESLint,
react: reactESLint,
import: importESLint,
'react-hooks': reactHooksESLint,
},
settings: {
'import/resolver': {
typescript: {},
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs'],
moduleDirectory: ['node_modules', 'src/'],
},
},
react: {
version: 'detect',
},
},
rules: {
'no-implicit-coercion': 'error',
'no-warning-comments': [
'warn',
{
terms: ['TODO', 'FIXME', 'XXX', 'BUG'],
location: 'anywhere',
},
],
curly: ['error', 'all'],
eqeqeq: ['error', 'always', { null: 'ignore' }],
// TypeScript-specific rules
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-parameter-properties': 'off',
'@typescript-eslint/no-var-requires': 'warn',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/naming-convention': [
'error',
{ format: ['camelCase', 'UPPER_CASE', 'PascalCase'], selector: 'variable', leadingUnderscore: 'allow' },
{ format: ['camelCase', 'PascalCase'], selector: 'function' },
{ format: ['PascalCase'], selector: 'interface' },
{ format: ['PascalCase'], selector: 'typeAlias' },
],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
'@typescript-eslint/no-unused-vars': ['error', { ignoreRestSiblings: true }],
'@typescript-eslint/member-ordering': [
'error',
{
default: [
'public-static-field',
'private-static-field',
'public-instance-field',
'private-instance-field',
'public-constructor',
'private-constructor',
'public-instance-method',
'private-instance-method',
],
},
],
// React-specific rules
'react/prop-types': 'off',
'react/display-name': 'off',
'react-hooks/exhaustive-deps': 'error',
'react/react-in-jsx-scope': 'off',
'react/no-unknown-property': ['error', { ignore: ['css'] }],
},
},
];
Toss Slash 라이브러리의 설정을 가져오면서 조금 변경해 주었습니다.
4. Prettier 설정하기
ESlint가 코드의 문법을 검토하면서 안정적인 코드 스타일을 지켜주는 도구였다면,
Prettier는 코드의 포맷을 일관되게 지켜주는 도구입니다.
들여 쓰기, 공백, 한 줄에 삽입 가능한 최대 코드 길이, 문장 규칙 등을 설정하여
여러 사람이 프로젝트를 작성해도 일관성 있게 양식을 갖춰주어 가독성을 높이는데 도움을 줍니다.
제가 작성할 Prettier는 아래와 같습니다.
export default {
arrowParens: 'avoid',
bracketSameLine: false,
bracketSpacing: true,
endOfLine: 'lf',
jsxSingleQuote: false,
printWidth: 120,
proseWrap: 'preserve',
quoteProps: 'as-needed',
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
};
이렇게 prettier.js를 작성해 두고 이걸 이제 ESlint와 함께 사용해서 서로 충돌을 없애고 잘 동작하도록 해보겠습니다.
먼저 아까 작성한 eslint.config.js 파일에 prettier와 관련된 eslint plugin과
방금 작성한 prettier.config.js 파일의 내용을 읽어오는 내용의 임포트를 추가합니다.
import prettierConfig from './prettier.config.js';
import prettierESLint from 'eslint-plugin-prettier';
이어서 아래에 plugins과 rules에 각각 추가해 주면 되는데 아래처럼 작성이 가능합니다.
plugins: {
prettier: prettierESLint,
},
rules: {
'prettier/prettier': ['error', prettierConfig]
}
5. Emotion 라이브러리 추가하기
Emotion 라이브러리는 공식 사이트를 통해 아래처럼 npm install을 해주면 추가가 가능합니다.
다만, jsx로 해석하라는 Pragma를 작성하라거나, ESlint가 문법을 오류로 인식하는 일이 발생해서 몇 설정이 필요합니다.
먼저 Pragma 제거를 위해 아래처럼 tsconfig.json과 vite.config.js를 수정해 줍시다.
tsconfig에 아래 내용을 추가하고,
"jsxImportSource": "@emotion/react"
vite.config.js의 plugins를 아래처럼 수정하면
plugins: [
react({
jsxImportSource: '@emotion/react',
}),
tsconfigPaths(),
],
이제부터 Pragma를 작성하지 않아도 emotion의 css를 사용하는 부분들을 알아서 jsx로 인식하고 빌드해 줍니다.
ESlint 설정은 또 아까 그 파일입니다.
eslint.config.js 파일에서 아래의 import, plugins, rules를 수정하면 됩니다.
import { rules as emotionESLint } from '@emotion/eslint-plugin';
plugins: {
'@emotion': { rules: emotionESLint },
}
rules: {
'@emotion/pkg-renaming': 'error',
}
6. Git Hooks 설정하기
Git Hooks는 Git의 특정 이벤트에 따라 미리 설정해 둔 스크립트를 실행할 수 있는 기능입니다.
npm 라이브러리 중 Husky라는 라이브러리는 이 Hooks 설정을 매우 편하게 도와주는 라이브러리이며,
저는 이걸 통해서 Hooks를 설정해 보겠습니다.
npm install --save-dev husky
위 명령어로 Husky 라이브러리를 package.json에 추가하고
npx husky init
명령어로 Husky를 시작하면, package.json에 prepare script가 husky라고 추가되고. husky라는 폴더가 생성됩니다.
이제 누구나 해당 레포지토리를 받아 시작할 때 자동으로 husky 내용이 추가됩니다.
저는 브랜치 맨 앞에 feat, chore 등 작업 내용을 알리고 커밋 메시지에 미리 삽입될 수 있는
간단한 커밋 컨벤션만 추가하려 합니다.
따라서 아래의 세 개 파일을 작성해 주었습니다.
commit-msg
#!/usr/bin/env sh
# 커밋 메시지 파일 경로
COMMIT_MSG_FILE=$1
# 커밋 메시지 파일에서 첫 번째 줄을 읽어옴
FIRST_LINE=$(head -n 1 "$COMMIT_MSG_FILE")
# 첫 번째 줄이 존재하고 길이가 10 이하인 경우, 에러 메시지 출력
if [ -n "$FIRST_LINE" ] && [ ${#FIRST_LINE} -le 10 ]; then
echo 'Please write a valid title for your commit message.'
exit 1
fi
# 정상적으로 커밋 메시지가 작성된 경우
exit 0
pre-commit
#!/usr/bin/env sh
# 현재 브랜치명 가져오기
BRANCH_NAME=$(git symbolic-ref --short HEAD)
# feat, chore, bugfix, refactor로 시작하는지 확인
if echo "$BRANCH_NAME" | grep -Eq '^(feat/|chore/|bugfix/|refactor/)'; then
# 브랜치명이 규칙을 따를 경우 lint 실행
npm run lint
# lint가 실패하면 커밋 차단
if [ $? -ne 0 ]; then
echo "커밋 불가: lint 오류가 발생했습니다."
exit 1
fi
else
echo "커밋 불가: 브랜치명은 feat, chore, bugfix, refactor 중 하나로 시작해야 합니다."
exit 1
fi
prepare-commit-msg
#!/usr/bin/env sh
# 현재 브랜치명 가져오기
BRANCH_NAME=$(git symbolic-ref --short HEAD)
# 커밋 메시지 파일 경로
COMMIT_MSG_FILE=$1
# 브랜치명에서 '/' 이전 부분을 접두사로 추출
PREFIX=$(echo "$BRANCH_NAME" | cut -d'/' -f1)
# 접두사가 존재하고, 커밋 메시지가 해당 접두사로 시작하지 않는 경우 메시지 수정
if [ -n "$PREFIX" ]; then
if ! grep -q "^₩[$PREFIX₩]" "$COMMIT_MSG_FILE"; then
sed -i.bak "1s/^/[$PREFIX] /" "$COMMIT_MSG_FILE"
fi
fi
7. 다음 이야기
드디어 프로젝트 초기 설정이 끝났습니다.
이제 npm run dev를 통해 프로젝트 실행이 가능하고 원하는 양식으로 바로 개발이 가능합니다.
제 레포지토리를 공개해서 좀 보여드리면 편하겠지만, 일단은 private 레포지토리로 진행하려 합니다.
제가 배포까지는 할 생각인데, 배포했을 때 잘되면 유료화도 좀 생각해보고 있거든요.
그래도, 코드의 일부들은 블로그에서 확인이 가능하니 필요하신 내용은 참고해서 구현하시면 될 것 같습니다.
다음 글에서는 디렉터리 구조 좀 나누고, 잡아둔 기획에 따라 뷰만 살짝 구현해 보겠습니다.
너무 길어졌는데 읽어주셔서 감사합니다.
'사이드 프로젝트' 카테고리의 다른 글
[클라이언트] 4. 뷰 작성 (3) | 2024.11.14 |
---|---|
[기획] 2. 프로젝트 목표 설정하기 (0) | 2024.11.04 |
[기획] 1. 프로젝트 시작하기 (2) | 2024.11.03 |