지난 강의 내용에서는 모노레포 환경을 구축하기 였다면, 이번 강의에서는 monorepo 환경에서 코딩 컨벤션과 공통 로직을 공유하는 방법에 대한 주제로 진행되었다.
모노레포는 여러개의 프로젝트를 하나의 레포에서 관리하기 때문에 코딩 컨벤션을 통일해야 한다
tsconfig 설정 공유
base가 되는 tsconfig를 만들어 놓고 각 프로젝트에서 extend 받아 오버라이딩 할 수 있는 구조로 진행할 것이다.
root 디렉토리에서 tsconfig.base.json 파일을 생성한다.
{
"compilerOptions": {
"strict": true,
"useUnknownInCatchVariables": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true,
"newLine": "lf"
},
"exclude": ["**/node_modules", "**/.*/"]
}
apps/wanted/teconfig.json 에 아래 코드를 추가한다.
여기에는 루트에 설정한 중복 코드가 들어갈 필요가 없다.
extends 와 baseurl, path 경로를 잘 지정해준다.
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./src",
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./*"]
}
},
"exclude": ["**/node_modules", "**/.*/"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.js",
"**/*.cjs",
"**/*.mjs",
"**/*.jsx",
"**/*.json"
]
}
packages/lib/teconfig.json에 아래 코드를 추가한다.
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"target": "ESNext",
"lib": ["ESNext", "dom"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"baseUrl": "./src",
"noEmit": false,
"incremental": true,
"resolveJsonModule": true
},
"exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"],
"include": ["**/*.ts", "**/*.js", "**/.cjs", "**/*.mjs", "**/*.json"]
}
prettier, eslint 설정 공통화
eslint
기본 eslint룰을 만들어 놓고 특정 프로젝트 마다 룰을 끄고 가져갈 수 있다. 순서 중요 ⭐️
(순서가 다를 경우 세팅 환경이 깨질 수 있다! 꼭 의존성 모듈을 먼저 설치하고 sdks 실행하기 )
1. 프로젝트 루트에서 prettier, eslint 를 설치한다.
// root에서...
yarn add prettier eslint eslint-config-prettier eslint-plugin-import eslint-plugin-react eslint-plugin-react-hooks eslint-import-resolver-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
2. yarn berry pnp 환경 에서는 prettier, eslint가 sdks를 바라봐야한다. (node_modules 하고 꽤 다른듯 )
yarn dlx @yarnpkg/sdks
3. .vscode/extenstions.json 에 익스텐션이 추가된 것을 확인한다
root리렉토리에 .eslintrc.js를 추가한다.
module.exports = {
root: true,
env: {
es6: true,
node: true,
browser: true,
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: { jsx: true },
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
],
plugins: ['@typescript-eslint', 'import', 'react', 'react-hooks'],
settings: {
'import/resolver': { typescript: {} },
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' }],
// Hoisting을 전략적으로 사용한 경우가 많아서
'@typescript-eslint/no-use-before-define': 'off',
// 모델 정의 부분에서 class와 interface를 합치기 위해 사용하는 용법도 잡고 있어서
'@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',
],
},
],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object'],
alphabetize: { order: 'asc', caseInsensitive: true },
},
],
'react/prop-types': 'off',
// React.memo, React.forwardRef에서 사용하는 경우도 막고 있어서
'react/display-name': 'off',
'react-hooks/exhaustive-deps': 'error',
'react/react-in-jsx-scope': 'off',
'react/no-unknown-property': ['error', { ignore: ['css'] }],
},
};
apps/wanted/src/eslintrc.json을 삭제해준다
삭제해 주는 이유는 프로젝트 안에 eslintrc.json이 있으면 내부의 설정을 바라보게 되기 때문이다.
추가로 eslint를 각 프로젝트 마다 세팅하는 방법은 링크를 참고하면 된다.
https://tech.kakao.com/2019/12/05/make-better-use-of-eslint/
ESLint 조금 더 잘 활용하기
들어가며 안녕하세요. 카카오 FE플랫폼팀의 Joah입니다. 최근에 팀에서 사용하는 JavaScript 스타일 가이드를 개선하는 업무에 참여했습니다. 업무를 하며 스타일 가이드에서 사용하고 있는 ESLint에
tech.kakao.com
prettier
root 디렉토리에 .prettierrc 파일을 생성한다.
{
"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"
}
.vscode/setting.json 에 eslint 설정을 추가한다.
{
"search.exclude": {
"**/.yarn": true,
"**/.pnp.*": true
},
"typescript.tsdk": ".yarn/sdks/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"eslint.nodePath": ".yarn/sdks",
"prettier.prettierPath": ".yarn/sdks/prettier/index.js",
// 기본 포맷터 prettier로 사용
"editor.defaultFormatter": "esbenp.prettier-vscode",
// 파일 저장시 formatter 실행
"editor.formatOnSave": true,
"editor.rulers": [120],
// 추가되는 내용
"eslint.packageManager": "yarn",
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"]
}
react library 패키지 만들기
packages/ui 폴더 생성 및 package.json 생성
공통 ui 컴포넌트 라이브러리를 만들어서 프로젝트에 공유할 수 있게 된다. 이때 내부 의존성을 가지므로 참조하여 쓰기만 하면 된다.
packages/ui 폴더를 생성하고 ui 폴더 안에 yarn init 으로 package.json을 생성한다.
pakage.json파일을 열어 name을 변경해준다.
{
"name": "@wanted/ui",
"packageManager": "yarn@3.5.0"
}
react dependency 설치
루트로 이동하여 설치!
// install
yarn workspace @wanted/ui add typescript react react-dom @types/node @types/react @types/react-dom -D
ui 패키지 설정
packages/ui/tsconfig.json 을 설정해준다.
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"baseUrl": "./src",
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"module": "esnext",
"jsx": "react-jsx",
"noEmit": false,
"incremental": true
},
"exclude": ["**/node_modules", "**/.*/", "dist", "build"]
}
packages/ui/src/index.js 파일 생성 후 내용을 추가한다.
import { ButtonHTMLAttributes, MouseEventHandler, ReactNode } from 'react';
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
children: ReactNode,
onClick?: MouseEventHandler<HTMLButtonElement>,
};
const Button = (props: ButtonProps) => {
const { children, onClick, ...other } = props;
return (
<button type="button" onClick={onClick} {...other}>
{children}
</button>
);
};
export default Button;
packages/ui/src/Button.tsx 파일 생성 후 내용을 추가한다.
export 할 컴포넌트를 정의해준다.
export { default as Button } from './Button';
packages/ui/package.json 에 main을 추가한다.
main은 엔트리가 된다.
{
"name": "@wanted/ui",
"packageManager": "yarn@3.5.0",
"main": "src/index.ts",
"devDependencies": {
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.0.4"
}
}
apps 에 있는 프로젝트에서 packages/ui의 의존성 설치 후 사용해보자
// root 에서
// @wanted/ui 의존성 설치
yarn workspace @wanted/web add @wanted/ui
프로젝트를 구동한다.
yarn workspace @wanted/web dev
이때 에러가 뜰 것이다!
이 오류는 브러우저에서 typescripts 문법을 해석하지 못해서 발생한다.
따라서 @wanted/web 에서 javascript로 변환해줘야한다.
(next.js 13.1 부터는 이 기능이 내장 되었다고 한다. 따라서 간단한 config 파일 수정으로 가능하다! )
app/wnated/next.config.js 파일을 수정한다.
transpilePackages는 패키지를 브라우저가 알아 먹을 수 있게 바꾸겠다 라는 의미로, 특정 라이브러리를 추가로 더 넣을 수 있다.
추가로 알아둘 상식 !
바벨은 es6 최신 문법으로 짠 코드를 브라우저가 알아 먹을 수 있게 변환하는 작업을 하는데 이를 transfiling 이라고 한다. 이것과 같은 맥락으로 이해하면 좋다
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
transpilePackages: ['@wanted/ui'], // 추가
};
module.exports = nextConfig;
종료 후 다시 구동한다!
추가로 궁금하고 흥미로웠던 질문!
.yarn 을 gitignore에 넣는것이 좋을까? 아니면 그냥 레포에 올려도 될까?
이것의 답은 올려도 되고 안올려도 된다! 이것이다.
하지만 흥미로운 토스의 글이 있다.
https://toss.tech/article/node-modules-and-yarn-berry
node_modules로부터 우리를 구원해 줄 Yarn Berry
토스 프론트엔드 레포지토리 대부분에서 사용하고 있는 패키지 매니저 Yarn Berry. 채택하게 된 배경과 사용하면서 좋았던 점을 공유합니다.
toss.tech
프로젝트를 클론받았을 때 yarn install로 추가 라이브러리를 설치 할 필요 없이 바로 실행할 수 있는 것을 zero-install이라고 한다.
이렇게 하기 위해서는 .yarn cache 파일을 그래도 레포에 올리면 된다. 따라서 상황에 따라서 고르면 된다!
typecheck 넣기
typescript pacage.json 에 script추가
app/wanted/package.json
packages/lib/package.json
packages/ui/package.json
위의 세개의 파일에 typecheck script를 추가한다.
"scripts": {
"typecheck": "tsc --project ./tsconfig.json --noEmit"
},
Button 컴포넌트 브레이킹 체인지 발생 시키기
packages/ui/src/Button.tsx 의 props type에 variant를 추가한다.
import { ButtonHTMLAttributes, MouseEventHandler, ReactNode } from 'react';
export type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
children: ReactNode,
variant: 'contained' | 'outlined', // 이 부분 추가
onClick?: MouseEventHandler<HTMLButtonElement>,
};
const Button = (props: ButtonProps) => {
const { children, onClick, ...other } = props;
return (
<button type="button" onClick={onClick} {...other}>
{children}
</button>
);
};
export default Button;
그리고 커맨드를 입력한다.
yarn workspace @wanted/web typecheck
모든 프로젝트를 typecheck 하는 sciprts 만들어 보기
yarn workspace에서 관리하기 위한 기본 Plugin을 제공하여 준다.
workspace-tools를 설치하여 준다.
yarn plugin import workspace-tools
그리고 root package.json 에 다음 스크립트를 추가한다.
"scripts": {
"g:typecheck": "yarn workspaces foreach -pv run typecheck"
},
스크립트를 실행시켜 보자.
// root 에서
yarn g:typecheck
전체 프로젝트가 실행이 되면서 타입체크 후 에러를 알려준다!
위 이미지와 같이 나오면 모든 에러가 없다는 의미이다! 성공
'Front-end' 카테고리의 다른 글
모노레포 환경 구축하기 (with yarn berry) (0) | 2023.05.05 |
---|---|
[Flutter Error] Permission denied vector_math (0) | 2023.04.09 |
bootstrap grid (0) | 2022.03.14 |
Concurrently? (0) | 2022.01.29 |
Proxy Server? (0) | 2022.01.29 |