-
기존의
apps/wanted
폴더를 copy + paste 한다. -
폴더 이름을
admin
으로 변경
apps/admin/package.json
name
변경 -->@wanted/admin
- 터미널에서
yarn
으로 갱신시켜준다.
// root 에서
yarn
@wanted/admin
빌드 되는지 확인.
yarn workspace @wanted/admin build
✅ 아래와 같이 나오면 정상!
지난시간 monorepo로 구축하고 브레이킹 체인지를 발생시키고, 체킹하는 방법까지 알아보았습니다.
추가로, jscodeshift
라는 javascript/typescript 수정도구에 대해서 간단히 인덱싱만 하고 지나가겠습니다.
https://github.com/facebook/jscodeshift
https://tanstack.com/query/v4/docs/guides/migrating-to-react-query-4
- import { useQuery } from 'react-query'
- import { ReactQueryDevtools } from 'react-query/devtools'
+ import { useQuery } from '@tanstack/react-query'
+ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
npx jscodeshift ./path/to/src/ \
--extensions=ts,tsx \
--parser=tsx \
--transform=./node_modules/@tanstack/react-query/codemods/v4/replace-import-specifier.js
추가해야 할 파일
apps/wanted/package.json
packages/lib/package.json
packages/ui/package.json
typecheck
script를 추가합니다.
"scripts": {
"typecheck": "tsc --project ./tsconfig.json --noEmit"
},
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
아래와 같이 에러가 체크됨을 알수 있다.
yarn workspace에서 관리하기 위한 기본 plugin을 제공해줍니다. https://yarnpkg.com/api/modules/plugin_workspace_tools.html
yarn plugin import workspace-tools
"scripts": {
"g:typecheck": "yarn workspaces foreach -pv run typecheck"
},
g:*
를 붙여주는건 global 하게 모든 프로젝트를 실행한다는 의미로 붙여주었다.
yarn workspaces foreach
명령어 option 확인
https://yarnpkg.com/cli/workspaces/foreach
-p
: 병렬 실행-v
: workspace name 출력
yarn g:typecheck
실행해보자
// root 에서
yarn g:typecheck
전체 프로젝트가 실행된 것을 알수 있고,
@wanted/web
에 에러를 확인할 수 있음.
apps/wanted/pages/index.tsx
수정한다.
그리고, 다시 실행해보면! 모두 에러가 없음을 확인할 수 있었다.
✅ 아래와 같이 에러가 출력되면 성공!
cd packages/ui
yarn init
package.json 파일을 열어서 name을 `@wanted/ui`로 변경.
{
"name": "@wanted/ui",
"packageManager": "yarn@3.3.0"
}
// root 이동
cd ../../
// 갱신
yarn
// install
yarn workspace @wanted/ui add typescript react react-dom @types/node @types/react @types/react-dom -D
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.ts
, packages/ui/src/Button.tsx
파일 생성
packages/ui/src/Button.tsx
내용추가
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/index.ts
내용 추가
export { default as Button } from './Button';
packages/ui/package.json
main 추가
{
"name": "@wanted/ui",
"packageManager": "yarn@3.3.0",
"main": "src/index.ts",
"devDependencies": {
"@types/node": "^18.11.11",
"@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^4.9.3"
}
}
// root 에서
// @wanted/ui 의존성 설치
yarn workspace @wanted/web add @wanted/ui
// @wanted/web 구동
yarn workspace @wanted/web dev
http://localhost:3000/ 접속해보면 아래와 같은 오류가 난다.
브라우저에서 typescript 문법을 해석하지 못해서 발생한다.
@wanted/web
에서 javascript로 변환(transpile) 해줘야 한다.
// next-transpile-modules 설치
yarn workspace @wanted/web add next-transpile-modules
apps/wanted/next.config.js
파일 수정
// @wanted/ui 패키지를 tranpile 시킨다.
const withTM = require('next-transpile-modules')(['@wanted/ui']);
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
};
module.exports = withTM(nextConfig);
종료하고 다시 실행해본다.
// @wanted/web 구동
yarn workspace @wanted/web dev
✅ 아래와 같이 나왔다면 성공!
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
yarn dlx @yarnpkg/sdks
.vscode/extensions.json
에 익스텐션이 추가 확인
esbenp.prettier-vscode
dbaeumer.vscode-eslint
.vscode/extensions.json
추가되면 위 그림에 보이는 대로
이 확장은 현재 작업 영역의 사용자가 권장한 항목입니다
표시 됩니다.
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.rulers": [
120
],
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'] }],
},
};
"eslint.nodePath": ".yarn/sdks",
// 아래 내용추가
"eslint.packageManager": "yarn",
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
eslint가 정상적으로 동작이 안되면 eslint 서버를 재시작 해본다.
- ⌨️
command + shift + p
ESLint: Restart EsLint Server
선택
{
"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", "**/.*/"]
}
{
"$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
},
"exclude": ["**/node_modules", "**/.*/"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mts",
"**/*.js",
"**/*.cjs",
"**/*.mjs",
"**/*.jsx",
"**/*.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"]
}
// yarn 버전 확인
yarn -v
// yarn 버전 변경
yarn set version berry
yarn set version stable
// yarn 버전 확인
yarn -v
// project 폴더 생성
mkdir yarn-berry-workspace
cd yarn-berry-workspace
// packages 디렉토리 만들기 / 루트 초기화
yarn init -w
./package.json
apps/*
폴더추가
{
"name": "yarn-berry-workspace-test",
"packageManager": "yarn@3.3.0",
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
// 1. create-next-app 프로젝트 생성
cd apps
yarn create next-app
저는 아래와 같이 셋팅 하였습니다.
pacakge.json 수정
name @wanted/web
으로 변경
// 3. root가서 상태 갱신하기
cd ..
yarn
// 4. 실행 확인
yarn workspace @wanted/web run dev
./apps/wanted/pages/index.tsx
을 열어보면 typescript error가 발생합니다.
yarn berry는 npm과 모듈을 불러오는 방식이 다르기 때문에 생기는 문제입니다.
yarn add -D typescript
yarn dlx @yarnpkg/sdks vscode
이미 추가 되어 있다면 skip 합니다.
{
"recommendations": ["arcanis.vscode-zipfs"]
}
packages/lib
폴더 생성하고, 아래 스크립트를 실행한다.
// lib 폴더 이동
cd packages/lib
// pacakge.json 파일 생성
yarn init
아래와 같이packages/lib/package.json
파일 수정한다.
{
"name": "@wanted/lib",
"version": "1.0.0",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"typescript": "^4.9.3"
}
}
packages/lib/tsconfig.json
파일 생성 후 설정값 넣기
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"useUnknownInCatchVariables": true,
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"newLine": "lf",
"module": "ESNext",
"moduleResolution": "node",
"target": "ESNext",
"lib": ["ESNext", "dom"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"baseUrl": "./src",
"noEmit": false,
"incremental": true,
"resolveJsonModule": true,
"paths": {}
},
"exclude": ["**/node_modules", "**/.*/", "./dist", "./coverage"],
"include": ["**/*.ts", "**/*.js", "**/.cjs", "**/*.mjs", "**/*.json"]
}
packages/lib/src/index.ts
파일 생성하기 하고 간단한 코드 넣는다.
export const sayHello = () => {
console.log('hello from lib');
return 'hello from lib';
};
apps/wanted
에 의존성 주입
// root로 이동한다.
cd ../../
// root 실행한다.
yarn workspace @wanted/web add @wanted/lib
apps/wanted/package.json
에 의존성이 추가된 것을 확인 합니다.
@wanted/lib
에 sayHello
함수를 호출해 봅니다.
그리고, @wanted/web을 구동해 봅니다.
yarn workspace @wanted/web run dev
아래와 같이 hello from lib
이 노출된다면 성공 입니다.