팀 FE 프로젝트에서 MSA (MicroService Architecture) 를 지향하는 개발을 위해 monorepo를 도입하기로 하였다.
작업을 위해 포스팅을 열심히 찾아봤지만 대부분 ts를 사용하거나 lerna, nx, turborepo를 사용해서 ts를 기반한 방법만 보였다..
지금은 js만 사용 하고 있기에 위 방법들에서 ts를 걷어내려 했지만 쉽지 않아서 yarn workspace를 활용해 처음 부터 설계하였다.
일반적으로 하나의 프로젝트당 한개의 저장소를 사용하는 것을 multi-repo 라고 하고 MonoRepo는 하나의 저장소에 여러개의 프로젝트가 구성된 것을 의미한다.
전체 프로젝트를 도입하기 위해 root 디렉토리를 생성하고 yarn 작업 폴더를 선언해준다.
mkdir project
cd project
yarn init
생성된 package.json 파일을 수정해준고 yarn init으로 디렉토리를 yarn 패키지 폴더로 설정해 준다.
생성된 package.json을 아래처럼 수정해준다.
{
"name": "**프로젝트 이름**",
"private": "true",
"version": "1.0.0",
"workspaces": [
"packages/*" // 프로젝트 폴더
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT"
}
프로젝트가 들어갈 폴더를 아래처럼 선언해주고 packages라는 폴더를 생성해준다.
...
"workspaces": [
"packages/*" // 프로젝트 폴더
],
...
monorepo를 사용하는 가장 큰 이유는 여러 프로젝트에서 공통의 UI 또는 Utils를 중복으로 사용하기 위함이다.
import React from "react";
export const Button = props => {
return <button {...props}>{props.children}</button>;
};
packages 폴더로 진입한후 CRA 프로젝트를 생성한다
npx create-react-app cra
/* yarn 으로 프로젝트가 맞춰져있어서 개발환경마다 생성시 에러가 날수도있음 아래 명령어로 npm으로 우회해서 설치해도 상관없음! */
npx create-react-app cra --use-npm
Monorepo에서 프로젝트 실행은 root에서 사용해야한다 그럼으로 root 폴더의 Package.json에 script를 추가한다.
"scripts": {
"client": "yarn workspace ProjectName"
},
여기서 ProjectName은 cra로 생성한 폴더에있는 package.json에 name 속성이다.
해당 자식 Project의 package.json에 script를 root에서 사용할 수 있도록 해준다. yarn client 뒤에 이어서 자식 project의 명령어를 이어서 써주면 된다.
script를 추가 하였으면 root에서 yarn/yarn install 명령어로 root 디렉토리에서 의존성 모듈을 설치한다.
다음 명령어로 프로젝트를 실행한다
yarn client start
or
yarn client start
생성해둔 Common Components를 Webpack 모듈을 마이그레이션 해야하는데 CRA는 Webpack config를 지원하지 않고 Caraco 모듈을 활용해서 마이그레이션을 해야한다.
const path = require("path");
const { getLoader, loaderByName } = require("@craco/craco");
const absolutePath = path.join(__dirname, "common(공통 컴포넌트 폴더 명)");
module.exports = {
webpack: {
alias: {},
plugins: [],
configure: (webpackConfig, { env, paths }) => {
const { isFound, match } = getLoader(
webpackConfig,
loaderByName("babel-loader")
);
if (isFound) {
const include = Array.isArray(match.loader.include)
? match.loader.include
: [match.loader.include];
match.loader.include = include.concat[absolutePath];
}
return webpackConfig;
}
}
};
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "craco eject"
},
import { Button } from "common/Button";
import "./App.css";
function App() {
return (
<div className="App">
<header className="App-header">
<h1>모노레포</h1>
<Button>버튼!</Button>
</header>
</div>
);
}
export default App;