서비스워커 이해하기 + Storybook +msw

Ryan Cho·2024년 11월 14일
0

서비스워커 (Service Worker)란?

서비스워커는 웹 브라우저의 백그라운드에서 실행되는 js파일이다.
네트워크 요청을 가로채거나 캐싱을 통해 오프라인 상태에서도 앱이 동작가능하게 한다.

				클라이언트 --- 프록시(서비스워커) --- 서버	

이처럼 클라이언트와 서버사이의 일종의 프록시 역할을 수행한다.

어떻게 사용 가능한가?

서비스워커는 오프라인 지원, 푸시알림, 백그라운드 동기화 등 여러 작업을 수행할 수 있지만, 이번 프로젝트에서는 BFF서버가 준비되지 않은 상태에서 mock data를 실제 api처럼 설계하고, 실제로 api요청을 보내지만 서비스워커의 오프라인 지원으로 네트워크 요청을 가로채서 mock data로 데이터를 출력하는 방식을 사용한다.

여기서 사용된게 msw(Mock Service Worker)이다.

msw 장점

코드의 작성방법은 나중에 다루도록 하고 왜 중요한지를 먼저 이해하자.

독립적인 FE개발이 가능하다. 단순히 mock data를 이용해 개발을 하는데 그치는게 아닌, msw는 api의 응답을 제어할 수 있기때문에 다양한 테스트 시나리오를 설정 할 수 있다(성공, 실패, 지연 등). 또한 무엇보다 실제 서버와 동일한 환경으로 설계를 하여 당장은 mock data를 사용하더라도 언제든지 실제 api를 적용시킬 수 있다는 유연함이 있다.

스토리북

모든 FE개발은 스토리북을 기준으로 진행된다.

먼저 스토리북의 계층구조에 대해 이해해야한다.

(.storybook/main.tsx) - .storybook/preview.tsx - 각각의 stories.tsx(meta-단일Story)

main.tsx는 최상단의 계층이지만 주로 스토리북 전체의 설정을 다루고 일반적인 storybook의 최상단은 preview.tsx라고 다루겠다.

컴포넌트와 stories.tsx컴포넌트를 혼동하지 말 것

스토리북으로 개발이 아직 익숙하지 않아서 헷갈리지만. FE개발자는 스토리북 서버를 띄우고 스토리북을 기준으로 개발을 진행한다.
따라서 스토리북 서버에 보여주는건 틀이 되는 기존 컴포넌트와, 동적으로 데이터를 다루는건 preview.tsx와 컴포넌트.stories.tsx파일임을 잊지 말자.

msw+스토리북

msw라는 서비스워커를 등록하고 그 서비스워커가 브라우저의 api요청을 가로채서 목데이터로 보여준다는건 알고있다. 원리가 어떻게 될까?
여기서 일일이 코드를 다루지는 않겠다. 이해만 하자.

useQuery를 사용한다고 가정했을 때 queryFn을 우리가 최종적으로 사용해야 할 api함수를 사용한다고 가정하자.

// 실제 컴포넌트

...
const {data} = useQuery({
	queryKey:['...'],
    queryFn: getData
})

//getData함수
const getData = async() => {
	await fetch(dummyURL)
}

getData의 dummyURL은 아무 의미없는 url이다.
BFF서버에서 실제 api주소가 나오기 전까지의 테스트를 위한 url일 뿐이다.
따라서 실제 dev서버에서 해당 컴포넌트는 박살이 날것이다.

하지만 우리는 스토리북에서 개발을 진행한다는 사실을 기억하자

그럼 mockAPI를 동일한 dummyURL로 만든다.

const getSuccessMockData = ...(dummyURL) // 성공 스토리북 decorator
const getErrorMockData = ...(dummyURL) // 실패 스토리북 decorator
const getLoadingMockData = ...(dummyURL) // 로딩 스토리북 decorator

preview.tsx 또는 개별 스토리북 파일에 msw를 등록한다

const worker = setupWorker() // 서비스워커 등록

또한 props로 데이터를 변경시키는게 아니고 api호출에 대해 데이터를 렌더링하고있기 때문에 명시적으로 argType과 args를 설정하고 그에 따라 decorators옵션을 목api로 지정하여 동적으로 스토리북을 렌더링 할 수 있다.

initialize({ // 'msw-storybook-addon' 설치
	serviceWorker: {
    	url: '/mockServiceWorker.js'
    }
})
const worker = getWorker()

...
argTypes: {
  [dummyURL]: { control: 'object' },
},
args: {
  [dummyURL]: getSuccessMockData,
},
decorators: [(Story, ctx) => { // ctx를 debugger해보면 args를 가지고 있음
  worker.resetHandlers(
    ctx.args.dummyURL // dummyURL 기반으로 핸들러 재정의
  );
  return <Story />;
}],

완벽한 로직은 아니지만 전체적인 흐름은 위와 같다.

목api 예시

export const getSuccessMockData = (data: WLResponseListCardsResponse) => {
  return http.get(dummyURL, () => {
    return new HttpResponse(JSON.stringify(data), {
      status: 200
    })
  })
}

export const getErrorMockData = () => {
  return http.get(dummyURL, () => {
    return HttpResponse.error()
  })
}

export const getLoadingMockData = () => {
  return http.get(dummyURL, async () => {
    await delay(99999)
  })
}

위의 getErrorMockData, getLoadingMockData는 각 스토리북의 decorator설정에서 worker.resetHandlers()에 해당 api를 이용해 덮어씌움으로 구현해 볼 수 있다.

msw초기화

pnpm dlx msw init public/ --save
// public 디렉토리에 mockServiceWorker.js 생성

작동 원리

진짜 우리의 컴포넌트는 queryFn을 통해 비동기 데이터를 반환한다. 스토리북에서도 해당 api를 통해 데이터를 반환해야한다.
하지만 존재하지 않는 url이기에 터질것임.

따라서 storybook에서 우리는 worker를 등록했고,
dummyURL을 명시적으로 arg에 등록(스토리북에서 동적으로 데이터 수정을 위함).
또한 resetHandlers메서드를 통해 dummyURL로 호출되는 모든 API 요청을 msw 가로채고 목 데이터를 반환.

결론

이와 같은 로직으로 실제 api가 나오지 않더라도 FE에서는 계속 개발을 진행하고 스토리북을 이용해서 테스트를 할 수 있다.

profile
From frontend to fullstack

0개의 댓글