ziglog

    Search by

    2월 첫주차 기록

    February 4, 2023 • ☕️☕️ 8 min read

    코드리뷰는 재미써


    배워가기

    gitlab ci 이모저모

    • job과 job 간에 파일을 공유하려면 job artifacts - dependencies에 가져올 다른 job artifact를 넣어주어야 한다.

    • gitlab ci의 파이프라인은 두 가지 종류가 있다

      • MR 파이프라인과 브랜치 파이프라인이다
      • MR 파이프라인을 트리거하기 위해서는, 파이프라인 rules의 조건(if)에 $CI_PIPELINE_SOURCE == 'merge_request_event'  를 명시해주어야 한다.

    Ref

    테스트

    • 테스트의 준비-실행-검증 구절에서 보통 실행 구절은 한 줄이어야 한다. 실행 구절이 여러 줄이면, 단일 작업을 수행하는 데 여러 개의 메서드 호출이 필요하다는 것이다. 이는 API에 문제가 있는 것이며, 클라이언트에서 모순이 발생할 수 있다.

    • jest에서는 describe.each() 메서드로 테스트를 매개변수화할 수 있다

      Copy
      describe.each([
        [1, 1, 2],
        [1, 2, 3],
        [2, 1, 3],
      ])(".add(%i, %i)", (a, b, expected) => {
        test(`returns ${expected}`, () => {
          expect(a + b).toBe(expected);
        });
      
        test(`returned value not be greater than ${expected}`, () => {
          expect(a + b).not.toBeGreaterThan(expected);
        });
      });

    Ref https://jestjs.io/docs/api#describeeachtablename-fn-timeout

    react-query queryClient의 위치

    SSR에서 react-query를 사용해주기 위해서, 서버에서 query를 prefetch하고 클라이언트에서 hydration해주는 방법을 선택했었다. query hydration을 사용하기 위해서는 react-query 공식 문서에서 QueryClient를 리액트의 상태로 관리하여 데이터가 서로 다른 유저와 요청 사이에 공유되지 않도록 작성하는 방법을 권장하고 있었다.

    Copy
    // _app.jsx
    import {
      Hydrate,
      QueryClient,
      QueryClientProvider,
    } from '@tanstack/react-query'
    
    export default function MyApp({ Component, pageProps }) {
      const [queryClient] = React.useState(() => new QueryClient())
    
      return (
        <QueryClientProvider client={queryClient}>
          <Hydrate state={pageProps.dehydratedState}>
            <Component {...pageProps} />
          </Hydrate>
        </QueryClientProvider>
      )

    그런데… useQuery를 호출하는 컴포넌트들에서 무한 API 호출이 발생하는 버그가 터졌다 😇

    이유와 해결책을 살펴보자.

    react-query의 queryClient는 일종의 옵저버 패턴으로, 내부에 query 데이터에 대한 캐시값이나 클라이언트 내의 정보를 담고 있다. API를 호출하게 되면 queryClient에 캐시 정보와 여러 정보를 업데이트 하게 된다. 그리고 client의 값이 변경될 때마다 컴포넌트는 새로 mount한다.

    위 코드에서 queryClient는 useState로 관리되기 때문에 상태를 변경할 수 없는 상태이므로, useQuery로 호출한 상태의 queryClient를 불러오지 못한다. react-query 내부적으로 제대로 mount가 안되고, API를 호출했으나 응답이 오지 않으니 지속적으로 API를 호출하게 된다.

    어느 순간 애플리케이션에서 SSR hydration을 사용하지 않게 되었는데, QueryClient는 여전히 useState로 관리되고 있어서 발생한 문제였다.

    그래서 다음과 같이 queryClient를 React 컴포넌트 밖으로 빼버렸다.

    Copy
    const [queryClient] =  new QueryClient({
      defaultOptions: {
        queries: {
          suspense: true,
          useErrorBoundary: true,
          retry: false,
        },
      },
    })
    
    const App = ({ Component, pageProps }: AppProps) => {
      return ...

    Nest.js HttpException, ExceptionFilter

    Nest.js는 앱 전체에서 처리되지 않은 예외처리들을 담당하는 exceptions layer가 있다. 이 레이어는 처리되지 않은 애플리케이션 코드를 잡아 사용자 친화적인 응답으로 바꿔준다.

    이 동작은 HttpException 타입의 예외를 처리하는 global exception filter로 수행된다. 알 수 없는 형태의 에러라면, 빌트인 exception filter는 500 Internal server error를 JSON 형식으로 반환한다.

    다음처럼 기본으로 제공하는 HttpException을 사용할 수도 있고,

    Copy
    @Get()
    async findAll() {
      throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
    }

    HttpException을 상속하여 커스텀 Exception을 사용할 수도 있다.

    Copy
    export class ForbiddenException extends HttpException {
      constructor() {
        super("Forbidden", HttpStatus.FORBIDDEN);
      }
    }

    HttpException 뿐만 아니라 exception layer 전반을 제어하기 위해 Exception filter를 사용할 수도 있다. Exception filter를 이용하면 에러 발생 시 로깅 등의 추가적인 작업을 할 수 있다.

    Copy
    import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
    import { Request, Response } from 'express';
    
    @Catch(HttpException)
    export class HttpExceptionFilter implements ExceptionFilter {
      catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse<Response>();
        const request = ctx.getRequest<Request>();
        const status = exception.getStatus();
    
        response
          .status(status)
          .json({
            statusCode: status,
            timestamp: new Date().toISOString(),
            path: request.url,
          });
      }
    }

    컨트롤러에서 사용할 땐 이렇게

    Copy
    @Post()
    @UseFilters(new HttpExceptionFilter())
    async create(@Body() createCatDto: CreateCatDto) {
      throw new ForbiddenException();
    }

    또는 앱 전역에서 globalFilter로 사용할 수 있다

    Copy
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      app.useGlobalFilters(new HttpExceptionFilter());
      await app.listen(3000);
    }
    bootstrap();

    Ref https://docs.nestjs.com/exception-filters

    Nest.js logger

    Nest는 앱 부트스트랩 및 여러 다른 예외 처리의 상황에서 문자열 기반의 로거를 제공한다. 이 기능은 nest에 내장된 Logger class를 사용한다.

    cf) 더 향상된 로깅을 위해 Node.js의 로깅 패키지인 Winston을 사용할 수도 있다.

    nest에서 제공하는 LoggerService를 상속하여 커스텀 로거를 만들 수도 있다.

    Copy
    import { LoggerService } from '@nestjs/common';
    
    export class MyLogger implements LoggerService {
      log(message: any, ...optionalParams: any[]) {}
      error(message: any, ...optionalParams: any[]) {}
      warn(message: any, ...optionalParams: any[]) {}
      debug?(message: any, ...optionalParams: any[]) {}
      verbose?(message: any, ...optionalParams: any[]) {}
    }

    Ref https://docs.nestjs.com/techniques/logger

    Vanilla Extract

    Vanilla Extract는 타입스크립트를 지원하는 zero-runtime 스타일시트로, 타입스크립트를 전처리기로 사용하여 빌드 타임에 타입 안정적인 정적 CSS 파일을 생성한다.

    • 타입스크립트로 타입 안정성을 갖춘 정적 CSS 생성
    • global theme이나 여러 개의 theme을 만들 수 있다.
    • 프레임워크에 종속적이지 않다.
    • 익스텐션에도 사용될 수 있다.

    파일 확장자명이 .css.ts로 끝나는 건 신기하다.

    Copy
    import { style } from "@vanilla-extract/css";
    
    export const className = style({
      display: "flex",
      flexDirection: "column",
      selectors: {
        "&:nth-child(2n)": {
          background: "aliceblue",
        },
      },
      "@media": {
        "screen and (min-width: 768px)": {
          flexDirection: "row",
        },
      },
    });

    Ref https://vanilla-extract.style/

    fetch

    Fetch API를 사용하지 못하는 환경이 있다.

    • Browsers: Opera Mini & IE
    • Server: Node v17 or lower

    만약 sdk나 라이브러리를 만들 때 Fetch API가 필요하다면, fetch polyfill을 굳이 구현할 필요는 없다. 사용하는 쪽에서 fetch를 사용하지 않을 수도 있기 때문에, 필요 시 라이브러리를 사용하는 쪽에서 구현하는 것이 더 낫다.

    또한 만약 라이브러리에서 특정 polyfill(node-fetch)들을 디펜던시로 가지고있다면 node가 아닌 deno, bun 등의 다른 런타임에서 문제를 일으킬 수 있다.

    용어

    • DPR, Device Pixel Ratio 기기 픽셀 비율

      • 화소밀도(pixel density)를 표현하는 단위로, 단위 길이 안에 존재하는 픽셀의 개수를 상대적으로 나타내는 단위 (1x, 2x, 3x)
    • Alpha Compositing 알파 채널 기법

      • png 에서 사용하는 이미지 변환 기법으로, 핵심 이미지 레이어를 제외한 배경 이미지 레이어를 제거하여 전체 이미지를 투명하게 만들 수 있다.
    • Raster graphics 래스터 이미지

      • 작은 점을 무수히 찍어 만든 이미지로, 사이즈가 커지면 그만큼의 추가정보가 필요해서 확장성이 떨어진다.
    • Art direction

      • 같은 이미지의 크기만 다르게 하는 것이 아니라 이미지의 특징이나 가치가 기기 특성에 따라 표현되도록 하는 정교한 작업

    배포전 테스트하기 (npm pack)

    1. 배포할 패키지 빌드 npm run build
    2. 배포할 패키지 패킹 npm pack
    • tgz 압축파일이 만들어진다.
    1. 패키지를 사용할 레포로 이동하기
    2. tgz 경로 지정해서 설치하기 npm i {tgz파일경로}
    • 이때 패키지가 변경됐는지 구분이 어렵다면 README.md 같은 곳에 표식을 남겨둔다.

    이것저것

    • 객체지향 프로그래밍과 함수형 프로그래밍
      • 객체지향 프로그래밍에서 추상화의 단위가 클래스라면 함수형 프로그래밍에서 추상화 단위는 함수다.
      • 객체지향 프로그래밍에서 협업의 방법이 참조나 이벤트 등을 통한 연결이라면 함수형 프로그래밍에서의 협업 방법은 함수의 인자와 결과값이다.
    • MouseEvent.shiftKey
      • 마우스 이벤트에서 shiftKey를 눌렀는지 알 수 있다.
      • 💡꿀팁! shift + 마우스를 이용하면 텍스트 드래그를 좀더 쉽게 할 수 있다. 커서에서부터 shift를 누르고 클릭한 지점까지 선택된다.
    • requestIdleCallback()
      • 브라우저의 idle 타입(태스크 큐에 작업이 없을 때) callback으로 전달받은 함수를 실행한다.
      • 우선순위가 낮은 작업을 실행하고자 할 때 사용한다.
    • storybook v.7
      • storybook ver 7부터 내부 빌드툴이 esbuild로 변경되었다. (원래는 webpack이었다.)
      • storybook은 preview와 app manager로 나눌 수 있는데, 7부터는 app-manager를 pre-bundle하여 더욱 빠르다.
    • Github에서는 프로젝트를 스캔해서 문제가 있는 버전의 패키지를 사용하면 알람을 보내준다. (Dependabot)
    • Github CodeQL은 코드를 정적으로 분석하여 취약점이 있다면 안내해준다.
      • CWE(Common Weakness Enumeration)을 찾아서 해결해준다고 한다.
      • 오픈소스에서는 무료로 사용할 수 있으며, (프로젝트 > Settings > Code security and analysis > Code Settings), Typescript도 사용 가능하다.

    기타공유

    WWW상 유통되는 컨텐츠 언어 비율

    영어가 약 60%, 한국어가 1% 남짓으로 나름 많다.

    천재만재 인도 개발자들이 많을 줄 알았는데, Hindi는 0.1%밖에 안 된다. (근데 인도어가 ‘Indian’이 아니고 ‘Hindi’라는 것도 처음 알았다.) 유튜브만 많이 하시나보다.

    Ref https://w3techs.com/technologies/overview/content_language


    마무리

    이번주도 신규 프로젝트 코드리뷰 열심히 하고 😎 주말에 알차게 놀고 뻗었다

    사실 안 뻗었다 절거운 인생 또 힘찬 월요일~!


    Relative Posts:

    2월 2주차 기록

    February 11, 2023

    1월 4주차 기록

    January 28, 2023

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon