<다크모드로 보시는 걸 추천드립니다>
2023년 8월 인티그레이션에서 한달 간의 인턴쉽을 마치며 작성하는 회고글;
Storybook
이라는 디자인 시스템 플랫폼을 활용하여Vue로 작성된 UI 컴포넌트를 React로 변환
한다.- Storybook의
composition
기능을 활용하여vue framework과 react framework를 하나의 공간에서 관리
할 수 있도록 한다. (multi-framework 작업)
인티그레이션에는 기존에 디자인 시스템 관리를 위해 사용하고 있던 Medistream UI Storybook 이 존재했다.
→ 하지만 기존 Storybook은 Vue 프레임워크로만 작성되어 있었다.
우리는 이번 프로젝트를 통해
1) React로 변환한 별도의 스토리북을 생성한 뒤,
2) Composition 기능을 활용하여 기존의 Vue 스토리북과 합치는 것을 목표로 했다.
(2개 이상의 프레임워크를 하나의 스토리북으로 합쳐서 한번에 관리할 수 있도록 하는 작업)
합치는 작업은 Storybook에서 제공하는 Composition
기능을 활용하면 가능해보여서 React 스토리북부터 생성하고 테스트 해보기로 했다.
(리액트는 사실 프레임워크가 아니라 라이브러리라고 하지만, 본 회고글에서는 이해의 편의를 위하여 프레임워크라고 정의하겠다.)
Vite?
이전까지는 리액트를 활용한 프로젝트를 생성할 때 늘create-react-app
(CRA) 모듈을 사용했었다.
하지만 이번 프로젝트를 통해Vite
이라는 모듈을 처음 접해보게 되었다. Vite란 무엇이며, CRA와는 무슨 차이가 있기에 인티그레이션에서는 Vite를 채택하여 사용 중인 건지 궁금했다.
그래서 찾아보니 Vite는 Vue CLI를 대체하는 프론트엔드 개발 툴로, 프론트 개발 속도의 향상에 중점을 둔 데브 서버라는 것을 알 수 있었다.
- 📘 TIL - Vite란 무엇인가
Vite docs를 천천히 살펴보며 Vite 모듈 + React 프레임워크 기반 프로젝트를 생성한 뒤에는 Vue로 작성된 UI Component를 React로 마이그레이션 하는 작업을 진행했다.
💡 Vite를 사용하면서 주의했어야 했던 점
→ Vue3를 사용하던 와중 발생한 오류. 이건
The requested module '/node_modules/.cache/.vite-storybook/deps/vue.js?v=41c5bf38' does not provide an export named 'default'
import Vue from 'vue'
라는 코드 줄에서 발생한 오류인데, V3는 기존에 V2가 Vue를 import해와서 사용하던 방식을 더 이상 채택하지 않기 때문에 기존 방식과 어긋나서 import ↔ export 경로가 달라졌기 때문에 발생한 에러 메세지이다.Storybook에서 제공하는 Composition 기능을 활용하는 데에는 두 가지 방식이 있다.
- Compose published Storybooks
- Compose local Storybooks
간단히 말해, 1) 배포된 다수의 스토리북을 합치는 방식과 2) 로컬에 존재하는 스토리북을 합치는 방식이 있는 것이다.
로컬에 존재하는 스토리북을 합치는 방식은 포트번호를 달리하여 관리하는 것이기 때문에, 하나의 주소창에 모든 프레임워크 컴포넌트를 보고자 하는 우리의 목표 방향과는 달라 보였다.
// Local
// .storybook/main.js
export default {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
refs: {
react: {
title: 'React',
url: 'http://localhost:7007', // 프레임워크 별로 별도의 포트번호를 설정한다
},
angular: {
title: 'Angular',
url: 'http://localhost:7008', // 프레임워크 별로 별도의 포트번호를 설정한다
},
},
};
또한, 기존 Vue storybook은 이미 배포가 되어있는 상태였기 때문에 React storybook도 배포를 한 뒤 composition 기능을 테스트해보는 것으로 진행했다.
Storybook을 배포하기 위해서 Storybook 측에서 추천하는 배포 툴이 있는데 이것이 바로 Chromatic이다.
Chromatic 공식 문서를 보면서 따라하면 배포하는 것은 크게 어렵지 않다.
배포를 성공한 뒤에는 main.js
파일을 다음과 같이 수정하면 된다.
// Published
// .storybook/main.js
export default {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
refs: {
'framework-name': {
title: 'Storybook Design System',
url: 'https://master--5ccbc373887ca40020446347.chromatic.com/',
// Chromatic으로 배포된 url 주소
},
},
};
우리가 첫 시도를 통해 겪은 시행착오들은 다음과 같다.
기존 Vue Storybook은 Chromatic으로 배포된 것이 아니었다. (aws, s3 사용)
→ 크게 문제될 것은 없어보였음. 그래서 이대로 진행하기로.
aws로 배포된 기존 Vue Storybook의 main.js
에 Chromatic으로 배포된 React Storybook 을 얹어보았다. (Composition 기능 활용)
// Vue Storybook - main.js
refs: {
react: {
title: "React",
url: "https://react--<appid>.chromatic.com/",
},
},
이렇게 코드를 작성해주니 Vue storybook 링크에 React Storybook 이 함께 나타나는 데에는 성공했다!
🛠 하지만 이후의 유지보수와 관련해서 문제가 생기게 되었다.
우선 이런 식으로 Storybook을 결합한다고 하면 레포는 총 2개인 셈이다.
- React Storybook repo & Vue Storybook repo
그러나 우리가 궁극적으로 목표로 하는 방향은 단일 레포에서 React 와 Vue를 모두 함께 관리할 수 있도록 하는 것이다.
→ 🔆 협업이 중요한 개발 문화에서는 코드를 다룰 때 누가 봐도 이용, 유지 및 보수하기에 용이해야 한다. 하지만 레포가 2개로 나뉘어져 있으면 어디서 무엇을 어떻게 다뤄야 하는 건지 파악하기가 까다로워지게 된다. 그런 불편함을 감소시키고 효율성을 높이기 위해 단일 레포 안에서 한번에 관리가 가능하도록 하자는 게 이번 프로젝트의 방향성이 된 것.
단일 레포 안에서 React와 Vue를 모두 관리하기 위해서는 Root Storybook을 하나 새로 생성하기로 했다.
root
ㄴ .storybook
story는 없고, react와 vue를 composition하는 storybook.
ㄴ stories
root-storybook이 동작하기 위한 최소 하나 이상의 story가 존재한다.
ㄴ components
UI 컴포넌트 구성하는 jsx, vue 파일들이 모여있다.
ㄴ [+] react-storybook
components 내 jsx를 import하는 story들이 모여있다.
ㄴ [+] vue-storybook
components 내 vue를 import하는 story들이 모여있다.
→ 우리가 생각한 구조는 이런 구조다. Root Storybook이 존재하고, 그 안에 react-storybook과 vue-storybook을 추가하는 것.
(Root Storybook이 작동하기 위해서 최소 1개의 story가 있어야 하기 때문에 stories
폴더 안에 docs를 정리해둔 Welcome.stories.mdx
를 생성해두었음.)
어쨌든 스토리북은 story 파일이 특정 프레임워크로 작성된 파일을 import 해오는 구조로 되어있기 때문에 story 파일들만 프레임워크별로 관리를 하고(storybook-react
, storybook-vue
), 컴포넌트 파일들은 별도의 components
폴더를 생성하여 한 군데에 모아두는 게 편리할 것이라고 생각했다.
이런 구조로 만들기 위해서는 기존에 chromatic으로 배포한 React repo
내 react project를 제거하고, 새롭게 Root repo
내 추가해준 React storybook과 Vue storybook을 chromatic으로 다시 배포해주었다.
Chromatic
으로 배포한 React-Storybook Repo (삭제 필요)aws
로 배포되어있던 Vue-Storybook Repo (무시)aws
로 배포Chromatic
으로 배포Chromatic
으로 배포// Root-Storybook > main.js
refs: {
vue: {
title: "Vue",
url: "https://<vue>.chromatic.com/",
},
react: {
title: "React",
url: "https://<react>.chromatic.com/",
},
},
다시 배포한 React-storybook과 Vue-storybook의 chromatic url을 Root-storybook의 main.js
에 추가해주면 된다.
🤔 여기서 겪은 시행착오가 또 있다.
코드에 수정사항이 있을 때마다 git push
후 재배포를 위해 Chromatic Build를 새로 진행하는데, Build 후 배포할 때마다 배포 url이 바뀐다는 점이다.
→ 우리는
main.js
에 url 주소를 꽂아넣은 것이기 때문에, url이 재배포할 때마다 바뀐다면 곤란한데..!
이런 문제를 해결하기 위해 존재하는 게 Chromatic permalink이다.
docs를 살펴보면 permalink 설정하는 방법 역시 어렵지 않다.
https://<branch>--<appid>.chromatic.com
라는 주소를 사용한다면 재배포를 해도 해당 주소에 전부 반영이 된다.
<branch>
에는 내가 작업을 해서 하고 있는 브랜치명을 작성하면 된다. 만약에 develop이라는 브랜치에서 코드 수정 작업을 진행하고 있다면 https://develop—<appid>.chromatic.com
이 되는 것이다.<appid>
는 Chromatic app의 project > Manage > Collaborate 탭 속 Permalink 구간에서 확인할 수 있다.우리는 작업의 편의성을 위해 React Storybook 을 관리하는 React
브랜치와 Vue Storybook을 관리하는 Vue
브랜치를 따로 파서 각각의 permalink
를 설정해준 후, Root-storybook의 main.js
에 추가해줬다. (이제 Root-storybook이라는 하나의 레포에서 react와 vue 코드 모두 관리가 가능해진 셈)
// permalink 적용 후 main.js
refs: {
vue: {
title: "Vue",
url: "https://vue--<appid>.chromatic.com/",
},
react: {
title: "React",
url: "https://react--<appid>.chromatic.com/",
},
},
우리는 React-storybook과 Vue-storybook를 각각 관리하기 위해 Root-storybook의 package.json
속 scripts
를 정의해주었다.
// Root-storybok > package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"storybook": "storybook dev -p 6006",
"storybook:react": "cd storybook-react && npm install && npm run storybook",
"storybook:vue": "cd storybook-vue && npm install && npm run storybook",
"deploy:react": "cd storybook-react && npm install && npm run build-storybook && npx chromatic --project-token=<react-token>",
"deploy:vue": "cd storybook-vue && npm install && npm run build-storybook && npx chromatic --project-token=<vue-token>"
},
storybook:react
/ storybook:vue
: 로컬 작업 환경 실행
→ 위 명령어를 통해 로컬 환경을 실행시킨 후 작업을 진행한다.
deploy:react
/ deploy:vue
: chromatic build 및 배포 (chromatic 토큰 필요)
→ 수정된 사항을 반영하기 위해 chromatic 재배포를 시행한다.
git add / commit / push
: Root-storybook repo 내 최신 코드를 반영하고 유지시키기 위해 로컬 작업 내역을 리모트로 푸시한다.
이렇게 진행하면 우리가 Chromatic을 배포할 때마다 사용하는 Chromatic project token이 scripts
에 그대로 노출이 되기 때문에 토큰값을 환경변수로 처리할 필요가 있어 보였다.
// Package.json
"deploy:react": "cd storybook-react && npm install && npm run build-storybook && npx chromatic --project-token=<react-token>",
"deploy:vue": "cd storybook-vue && npm install && npm run build-storybook && npx chromatic --project-token=<vue-token>"
<react/vue-token>
부분에 실제 chromatic token이 명시되어 있음. → 환경변수로 변경 필요.Bitbucket repository > Repository settings > repository variables 에 들어가면 환경변수를 추가해줄 수 있다.
REACT_STORYBOOK_TOKEN
추가VUE_STORYBOOK_TOKEN
추가이후 환경변수의 값은 코드 내에서 $REACT_STORYBOOK_TOKEN
와 같은 형태로 접근 가능하다.
🐞 package.json에서 환경변수 접근하기
이 방식대로 사용했으나, 어째서인지 package.json의 script에서 토큰값을 환경변수로 처리하는데 자꾸 실패했다. 그래서 우선 보안을 위해 script에서는 deploy 명령어를 삭제하고 파이프라인에서만 사용하기로 했다.
우리는 token 값을 package.json
의 scripts
에서 사용 중이었으나, 배포 자동화를 위해서는 pipeline에 적용시켜야 한다.
- npx chromatic --project-token $REACT_STORYBOOK_TOKEN --exit-zero-on-changes
- npx chromatic --project-token $VUE_STORYBOOK_TOKEN --exit-zero-on-changes
토큰값이 들어갈 부분에 $REACT_STORYBOOK_TOKEN
과 같이 환경변수를 끼워넣어 줬더니 정상적으로 작동하는 것을 확인할 수 있었다.
pipelines:
branches:
develop:
- step:
name: Build
deployment: Production
image: node:18
script:
- npm install
- npm run build-storybook
artifacts:
- storybook-static/**
- step:
name: Deploy to S3
image: atlassian/pipelines-awscli
script:
- aws s3 sync --delete ./storybook-static s3://ui.integrationcorp.co.kr
- aws cloudfront create-invalidation --distribution-id E2J4QL6EK16P4D --paths "/*"
react:
- step:
name: Build & Deploy
deployment: Production
image: node:18
script:
- npm install
- cd storybook-react
- npm install
- npm run build-storybook
- npx chromatic --project-token $REACT_STORYBOOK_TOKEN --exit-zero-on-changes
artifacts:
- storybook-react/storybook-static/**
vue:
- step:
name: Build & Deploy
deployment: Production
image: node:18
script:
- npm install
- cd storybook-vue
- npm install
- npm run build-storybook
- npx chromatic --project-token $VUE_STORYBOOK_TOKEN --exit-zero-on-changes
artifacts:
- storybook-vue/storybook-static/**
tags:
production-*:
- step:
name: Build
deployment: Production
image: node:18
script:
- npm install
- npm run build-storybook
artifacts:
- storybook-static/**
- step:
name: Deploy to S3
image: atlassian/pipelines-awscli
script:
- aws s3 sync --delete ./storybook-static s3://ui.integrationcorp.co.kr
- aws cloudfront create-invalidation --distribution-id E2J4QL6EK16P4D --paths "/*"
이번 프로젝트를 진행하면서 Storybook을 활용한 디자인 시스템에 대해 배우고 구조를 설계해보는 과정을 직접 경험해볼 수 있어서 정말 특별하고 좋았다.
사실은 Storybook의 Composition 기능을 이용해서 multi-framework 구조를 설계한다는 전례는 구글링을 해봐도 찾아보기가 어려웠다. 그렇기 때문에 팀원 분들과 함께 머리를 맞대어 가며 직접 새로운 구조를 설계해보고, 결국엔 목표로 했던 바를 이뤄냈다는 게 이루어 말할 수 없는 쾌감으로 다가오는 것 같다.
항상 구글링하면 쉽게 나오거나 이미 남들도 알고 있는 지식을 카피해서 활용하는 것만 해보다가 ‘이게 과연 될까?’ 싶었던 부분을 스스로 뚫어낸 느낌이라고 해야할까.
이제 누군가가 storybook 디자인 시스템을 multi-framework로 생성하고 관리하고 싶다고 요구한다면 뚝-딱 만들어내 줄 수 있을 것 같다. 😎
사실 이번 프로젝트 때 가장 크게 배우고 느낀 점은 공식 문서의 중요성이다.
프로젝트의 최종 목표였던 “Storybook Composition 기능을 활용하여 다수의 framework를 하나의 레포에서 관리한다”
라는 전례를 찾아보기 어려웠기 때문에 구글링을 해서 비슷한 문제를 겪고 있는 다른 사람들의 사례를 참고할 수가 없었다. 그렇기에 전적으로 공식 문서에 의존할 수 밖에 없었고, 무언가 막힐 때마다 문제가 무엇인지 파악하기 위해 공식 문서를 샅샅이 파헤쳐 보고 하나씩 해결해 나아간 과정이 정말 뿌듯하고 즐거웠다.
Vue로 작성된 코드를 React로 마이그레이션하는 과정 속에서 남들이 봤을 때 이해하기 쉽도록 코드를 짜는 것에 대한 중요성을 뼈저리게 느낄 수 있었다.
간단한 예시로, 단순한 변수명을 명명하는데 있어서도 통일성 있게 설정을 해야 추후에 다른 팀원이 내 코드를 보았을 때 어떤 동작을 하는 코드인지 알아보기 쉽다는 점을 깨달았다.
윗글은 함수를 정의하는데 변수명이 이랬다 저랬다 해서 코드를 작성한 장본인인 나마저도 헷갈릴 지경에 이르렀었는데, 경린님이 바로 잡을 수 있도록 알맞은 길잡이를 제공해주셨던 TIL 글이다. 어찌보면 너무나도 당연한데 코드가 제대로 작동하는 데에만 급급해서 리팩토링에는 신경쓰지 않았던 나 자신을 크게 반성하게 되는 계기가 되었다.
아쉬웠던 점은, Vue로 작성된 UI 컴포넌트를 React로 마이그레이션하는 과정에 있다.
나는 기존에 React로만 프로젝트를 진행했었기에 이번 프로젝트를 통해 Vue로 작성된 코드를 처음 접해보게 되었다. 그래서 React로 마이그레이션 하기 위해서는 Vue와 React의 구조적 차이는 무엇인지, React에서 하는 역할을 Vue에서는 어떤 식으로 처리하는지 등에 대해 파악하는 게 우선이라고 생각했다.
간략하게나마 차이점을 파악한 후 ChatGPT의 도움을 얻어가며 몇 가지의 컴포넌트를 마이그레이션 처리하는데에는 성공했으나, Vue의 상태관리 등의 생태계를 완전히 파악하지 못한 게 아쉽다.
React로 변환하는 작업만 진행했을 뿐, Vue 코드를 직접 작성해보고 겪어보지 못했기에 Vue를 파악하는데 한계가 있었던 것 같다.
기회가 된다면 Vue로 코드를 처음부터 직접 다 작성해보며 배워서 React와 Vue의 장단점을 좀 더 뚜렷하게 파악할 수 있으면 좋을 것 같다는 생각이 든다. 🤓
이번 인턴쉽을 통해 부트캠프에서 진행한 프로젝트 및 사이드 프로젝트와는 비교할 수 없을만큼 많은 배움과 성장을 경험할 수 있었다. 불과 4주 뿐이었는데도 말이다.
궁금했던 점들이 실제 업무에 어떻게 활용되는지를 직접 확인해보며 ‘아, 이건 이런 방식으로 활용되는 거구나’ 라는 인사이트를 얻을 수 있었으며, 문제가 발생하면 직접 부딪혀보고 해결해 나아가면서 문제 해결 능력도 크게 향상 시킬 수 있었다.
또한 회사에서 사용될 가능성이 있는 디자인 시스템의 구조를 직접 설계하면서, 특정 방향성을 선택하고 목표를 설정하는 이유를 실용적인 측면에서 깊이 이해하고 받아들일 수 있는 경험을 할 수 있었다.
Composition 기능을 활용하여 Storybook 디자인 시스템을 단일 레포 내 multi-framework로 관리하겠다는 확실한 방향성 및 목표가 설정되었고, 이러한 방향성 설정의 이유를 이해한 후에는 팀원들의 편의성을 위해 이 프로젝트를 무조건 성공해내고 싶다는 욕심이 생겼었다. 그래서 자연스레 더욱 열정적으로 작업에 임하게 되었던 것 같다. (나의 흔적을 남기자!)
프로젝트 실무를 직접 경험하고 해결해보며 얻은 것들은 내게 큰 자신감과 역량을 부여해 주었으며, 새로운 기술과 개념을 배울 수 있는 기회를 가지게 되어 끊임없이 발전할 동기를 얻게 해주었다. 덕분에 프로젝트 기간 동안 목표한 바를 이루어낼 수 있었고, 이번 인턴쉽을 통해 얻은 소중한 경험과 지식은 미래 성장에 있어서 중요한 밑거름이 될 것이라고 장담한다. 🤍