ziglog

    Search by

    1월 1주차 기록

    January 7, 2023 • ☕️☕️☕️ 14 min read

    1월 2주차 아니고?


    배워가기

    yaml에서 &* 특수문자

    & 는 ‘anchor’라고 부르며, 프로퍼티들을 복제(duplicate)하거나 상속(inherit)받을 수 있다. * 로 가져다 쓴다.

    Copy
    .job_template:
      &job_configuration # Hidden yaml configuration that defines an anchor named 'job_configuration'
      image: ruby:2.6
      services:
        - postgres
        - redis
    
    test1:
      <<: *job_configuration # Merge the contents of the 'job_configuration' alias
      script:
        - test1 project
    
    test2:
      <<: *job_configuration # Merge the contents of the 'job_configuration' alias
      script:
        - test2 project

    Ref https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#anchors

    정규표현식 수량연산자 {n}

    • 수량연산자 {n}
      • 앞에 온 토큰과 n개 일치
    • 컴마와 조합 {n,}
      • 앞에 온 토큰과 n개 이상 일치
      • 💡 semver로 브랜치명 검색 시 유용하다

    git cherry-pick 중단하기

    • git cherry-pick --quit: 그냥 cherry-pick 상태를 나감
    • git cherry-pick --abort: cherry-pick한 것들을 버려버리면서 상태에서 나감

    JQL 맛보기

    JQA은 지라에서 쓰는 query language 의 이름이다.

    • !~ 연산자
      • ‘포함하지 않은 것’
      • 예) 지라티켓 제목에 ‘다이얼로그’가 없는 것만 찾고 싶은 경우 summary!~다이얼로그
    • in 연산자
      • (= 과 다르게) 뒤에 오는 여러가지 중 일치하는 것이 하나라도 있으면 참
      • 예) 3개의 프로젝트에서 검색하고 싶은 경우 project in (BSFE, SELF, CUDESIGN)

    Nest.js ConfigModule

    Nest의 configModule은 정적이다. 그렇기 때문에 controller나 service처럼, Module들로 인해 모든 application이 만들어진 다음에야 configModule을 사용할 수 있고, module의 생성 시점에는 configModule을 사용할 수 없다.

    이를 해결하기 위해서는 모듈을 동적으로 만들어야 한다. Nest.js Dynamic Modules

    bolt에서 메세지 보내기

    💡 bolt

    bolt에서 메시지를 보내는 방법은 3가지가 있다.

    • say

      • say는 view only가 불가능하다.
    • app.client.chat.postEphemeral

      • 글로벌 쇼컷의 경우 채널을 특정 하기 어렵다.
      • 멘션에서 사용
    • respond

      • 메세지 쇼컷에서 사용

    cf) 글로벌 쇼컷은 사용자만 보이는 메시지를 보낼 수 없다. 커맨드를 사용하면 가능하다. 커맨드는 ack를 통해 수신했음을 알린다. 하지 않는다면 failed with the error "dispatch_failed" 메세지를 만난다

    Nets.js trailing slash 이슈

    Next.js에서 SSG로 exports한 페이지들을 정적으로 배포할 때, 배포된 페이지에서 새로고침 시 Access Denied가 되면서 접근이 안 될 수가 있다.

    이는 trailing slash로 인해 생기는 이슈인데, /index.html을 명시적으로 표현한 경우 해당 이슈가 생기지 않는다.

    💡 trailing slash

    이때 아래 두 가지 방법을 적용해서 이 문제를 해결할 수 있다.

    1. CloudFront Function 추가 및 적용

      Copy
      // CloudFront Function 예시 (이대로 가져다 써도 상관없다)
      
      function handler(event) {
        var request = event.request;
        var uri = request.uri;
      
        var paths = uri.split("/");
        var lastPath = paths[paths.length - 1];
      
        var isFileRequest = lastPath.indexOf(".") !== -1;
      
        if (isFileRequest) {
          return request;
        }
      
        var indexFileName = "index.html";
        var lastCharacter = uri.slice(-1);
        if (lastCharacter !== "/") {
          indexFileName = "/" + indexFileName;
        }
      
        var newUri = `${uri}${indexFileName}`;
        request.uri = newUri;
      
        return request;
      }
    2. next.config.jstrailingSlash 옵션 설정

      Copy
      // next.config.js
      
      module.exports = {
        trailingSlash: true,
        // ...
      };

    sonarqube quality gate

    서비스를 배포할 수 있을만큼 quality가 보장되었는지를 체크한다. (강제성 부여)

    다음과 같은 조건들을 추가할 수 있다.

    • 새로운 블로커 이슈 없음
    • 새로운 코드에 대한 테스트 커버리지 80% 이상

    Ref https://docs.sonarqube.org/latest/user-guide/quality-gates/

    JSDoc의 @template

    JSDoc의 @template 데코레이터를 사용하여 타입스크립트 제네릭에 설명을 추가할 수 있다.

    Copy
    /**
     * @template T
     * @param {T} x - A generic parameter that flows through to the return type
     * @returns {T}
     */
    function id(x) {
      return x;
    }

    zod 이모저모

    • zod의 extends 를 사용해 타입을 확장할 수 있다.

      Copy
      const UserData = z.object({
        userId: z.string().min(1).max(5),
        fullName: z.string().min(1).max(18),
      });
      
      const Article = UserData.extend({
        title: z.string().min(5),
        date: z.date(),
      });
      
      const UserProfile = UserData.extend({
        isVerified: z.boolean(),
        numberOfArticles: z.number().positive(),
      });
    • zod의 z.parse() 대신, z.safeParse()를 사용해서 에러핸들링을 할 수 있다.

      Copy
      export const getOrders = async () => {
        const response = await api.get<Order[]>("/orders");
      
        const result = OrderSchema.safeParse(response.data);
      
        if (!result.success) {
          throw new Error("Invalid API response /orders", result.error);
        }
      
        return result.data;
      };

      이를 Container Component나 Widget 패턴에 적용하기 위해서는 return하는 모든 지점에 contextHolder를 넣어야 한다 이런 번거로움을 줄이기 위해 MessageProvider 를 작성해 사용할 수 있다. 이 때 Antd에서 제공하는 ConfigProvider 를 사용한다면 ConfigProvder 안에 MessageProvider 가 있어야 한다.

    HAR 파일로 HTTP Request 기록 확인하기

    HAR(HTTP ARchive format) 파일은 웹브라우저와 사이트간의 상호작용을 로그로 남기는 파일로 주로 다음과 같은 이슈를 검토 할 때 사용된다.

    • 성능이슈: 느린 페이지 로딩, 특정 작업을 수행할 때 타임아웃 발생
    • 페이지렌더링: 페이지 포맷 오류, 정보 누락

    HAR 파일로 HTTP Request 기록을 확인할 수 있다

    Ref https://partner.polarisofficecorp.com/atlassian/pages/viewpage.action?pageId=4523071

    Sentry의 release와 dist

    Sentry에는 release와 dist가 있다

    Sentry 이벤트를 release별로 분류하고, dist까지의 조합으로 소스코드와 소스맵을 매칭할 수 있다. 소스에서 Sentry를 init하는 시점에 이 release와 dist를 입력해줄 수 있다.

    init한 release와 dist에 맞게 적절한 소스맵이 업로드되어있지 않으면 스택이 이상하게 찍히거나 아예 찍히지 않을 수 있다. (소급적용 안됨. 제대로 소스맵 업로드 후 날아온 이벤트만 제대로 포착)

    cf1) 리액트 네이티브에서는 init할 때 release와 dist를 앱 정보를 보고 알아서 넣어준다. cf2) 리액트 네이티브에서는 코드푸시와 센트리의 릴리즈를 연동할 수도 있다. 다만 이때는 init 시에 커스텀하게 release와 dist를 입력해주어야 함

    Charles로 요청값과 응답값을 기록/변경/테스트하기

    • 응답값(response) 변경하기
      • map local을 이용한다.
      • sequence 탭에서 우클릭 > map local을 클릭하면 됨
      • API구현이 안되어있을 때 사용할 수 있다.
    • 요청값(request) 변경하기
      • 요청 중 하나를 선택해서 breakpoint를 건다.
      • 이리저리 앱을 테스트해보다가 breakpoint 걸림 → edit request 탭을 클릭 → request 수정

    gitlab merge method 설정

    • merge commit: 모든 머지에 머지 커밋을 만든다.
    • merge commit with semi-linear-history
      • 모든 머지에 머지 커밋을 만들지만, fast-forward 머지가 가능할 때만 머지를 허락한다.
      • fast-forward가 불가능하면 rebase를 해야한다.
    • fast-forward merge
      • 머지커밋을 만들지 않는다.
      • fast-forward가 불가능하면 rebase를 해야한다.

    css overflow 조심하기

    css의 overflow는 단축속성이다.

    overflow-x: scroll만 적용하면 y축도 스크롤이 생긴다. y축 스크롤을 방지하려면 overflow-y: hidden을 추가로 적용해줘야 한다.

    왤까?

    하나의 축을 visible(기본값)로 하고, 다른 축에는 다른 값을 지정할 경우 visible이 auto처럼 동작하기 때문이다.

    그럴바에 단축속성을 사용하여 overflow: scroll hidden으로 작성해주면 된다.

    Ref https://developer.mozilla.org/ko/docs/Web/CSS/overflow

    gitlab-ci.yml의 캐시컨트롤

    • cache: policy
      • 캐시의 업로드/다운로드 방법을 조정하고 싶을 때 사용하는 키워드
      • cache: paths 를 지정하지 않으면 아무것도 캐시되지 않는다.

    정책 종류

    • pull-push 정책 (기본값)
      • Job이 시작되면 캐시를 다운받는다.
      • Job이 끝나면 캐시에 변경사항을 업로드한다.
    • pull 정책
      • Job이 시작되면 캐시를 다운받는다.
      • Job이 끝나도 변경사항에 대해 업데이트하지는 않는다.
      • 병렬로 실행되는 여러 잡이 같은 캐시를 공유하게 하고 싶다면 pull 정책 추천
      • Job 실행 속도를 올려주고, 캐시서버에 부담도 줄여준다.
    • push 정책
      • Job이 시작되도 캐시를 다운받지 않는다.
      • Job이 끝나면 캐시를 업로드한다.
      • 캐시를 빌드하려는 거라면 push 정책 추천

    Ref https://docs.gitlab.com/ee/ci/yaml/

    Rich Text Editor

    Rich Text Editor는 rich text를 수정할 수 있는 에디터다.

    rich text? plain text와 반대되는 개념으로, 링크 기능을 사용할 수 있고 서식이 있는 텍스트를 말한다.

    즉 “What you see is what you get”인 (WYSIWYG) 에디터다.

    prosemirrorlexical 은 rich text editor를 만들 수 있는 툴킷을 제공한다. lexical playground를 이용하여 테스트도 해볼 수 있다.


    이것저것

    • Array.prototype.join() 을 사용할 때 undefined와 null 은 사라진다(Ref)

      Copy
      [1, undefined, 3, null].join(); // '1,,3'
    • gitlab-ci.yml의 interruptible - 새로운 ci run으로 해당 동작이 불필요해졌을 때 취소될 수 있는지 여부를 나타낸다. (Defines if a job can be canceled when made redundant by a newer run.)

    • tsc --noEmit 옵션 - 컴파일 output 파일을 생성하지 않는다. (Ref)

    • HTMLImageElement.decode()

      • 자바스크립트로 이미지를 가져올 때 onload 가 Promise 지원을 안해서 new Promise() 로 함수를 감싸는 귀찮은 작업이 필요하다.
      • decode 함수는 Promise를 리턴하기 때문에 콜백 함수 없이 이미지 속성에 접근이 가능하다.
    • 정보통신 세상에서 ‘Inheritance’가 ‘상속’으로 번역된 국내 기관의 첫 공식 사례는 1997년 CHILL 언어 가이드의 표준화 과정 중 이뤄졌다.

      • CHILL언어?
        • ITU-T (국제전기통신연합 중 통신분야 표준 부문)에서 권고한 통신 처리 시스템용 프로그래밍 언어
        • 스위치 장비 내에서 널리 활용되었고, 표준 유지는 1999년에 중단됐지만 그곳에선 아직 사용되고 있다.
      • 참고로 일본/중국/북한에선 상속이 아니라 계승이라고 번역한다는 사실
    • jest에서는 esm (import export)가 지원되지 않는다. 이때 babel을 설치하고 jest config파일에서 설정하면 사용할 수 있다.

    • standard-version의 CHANGELOG

      • compare 경로: https://{gitlab레포경로}/compare/{target}…{source}
      • target, source는 각각 브랜치, 태그, 커밋이 올 수 있는데, 브랜치, 타겟이름이 같은 경우 우선순위는 브랜치가 높다.
    • typeorm의 bigintnumber 타입과 매핑되지 않는다. 대신 프로퍼티를 string으로 매핑시켜준다. (Ref)

    • 자바스크립트에서 배열의 length에 null을 할당하면 배열이 비워지고 length가 0이된다.

    • ES6 스펙상에는 꼬리 재귀 최적화가 명시되어 있다.

    • propTypes와 비슷하게 defaultProps라는 것도 있다.

      • defaultProps 는 React.FC 와 호환되지 않는다는 이슈도 있었는데 지금 해결됐는지는…

    기타공유

    TypeScript 타입 시스템 뜯어보기

    타입스크립트는 구조적 서브타이핑을 통해 타입 호환성을 지원한다. 다음과 같이 Food 타입과 이를 확장한 Burger 타입이 있고,

    Copy
    type Food = {
      /** 각 영양소에 대한 gram 중량값 */
      protein: number;
      carbohydrates: number;
      fat: number;
    };
    
    type Burger = Food & {
      /** 햄버거 브랜드 이름 */
      burgerBrand: string;
    };

    Food 타입의 값을 인자로 받는 calculateCalorie 함수가 있을 때,

    Copy
    function calculateCalorie(food: Food) {
      return food.protein * 4 + food.carbohydrates * 4 + food.fat * 9;
    }

    calculateCalorie 함수의 인자로 Burger 타입의 객체를 넣어도 에러가 나지 않는다.

    Copy
    const burger: Burger = {
      protein: 29,
      carbohydrates: 48,
      fat: 13,
      burgerBrand: "버거킹",
    };
    
    const calorie = calculateCalorie(burger); // ✅

    비록 상속 관계임을 명시하지는 않았지만 burger 변수는 Food 타입의 프로퍼티를 모두 포함하고 있기 때문이다.

    그러나 구조적 서브타이핑에 기반한 타입 호환이 지원되지 않는 경우가 존재하는데, 바로 리터럴 객체를 사용하는 경우다.

    Copy
    const calorie = calculateCalorie({
      protein: 29,
      carbohydrates: 48,
      fat: 13,
      burgerBrand: "버거킹",
    }); // 🚨 Object literal may only specify known properties, and 'burgerBrand' does not exist in type 'Food'

    이는 타입스크립트 컴파일러 동작 방식과 관련이 있다.

    타입스크립트 컴파일 과정의 타입 검사 단계에는 다음과 같은 로직이 포함되어 있다. (간소화)

    Copy
    const isPerformingExcessPropertyChecks =
      getObjectFlags(source) & ObjectFlags.FreshLiteral;
    
    if (isPerformingExcessPropertyChecks) {
      if (hasExcessProperties(source as FreshObjectLiteralType)) {
        reportError();
      }
    }

    함수에 인자로 들어온 값이 FreshLiteral인지 아닌지 여부에 따라 조건분기가 발생하여 타입 호환 허용 여부가 결정된다는 것을 확인할 수 있다.

    FreshLiteral이 뭘까? 🤔

    모든 object literal은 초기에 “fresh” 하다고 간주되며, 타입 단언 (type assertion) 을 하거나, 타입 추론에 의해 object literal의 타입이 확장되면 “freshness”가 사라지게 된다.

    TypeScript Github PR (2015년 7월) 의 논의에 따르면, fresh object인 경우에는 예외적으로 타입 호환을 허용하지 않기로 했음을 확인할 수 있다.

    이는 코드를 읽는 다른 개발자의 입장에서 함수가 실제 다루는 것보다 더 많은 데이터를 받아들인다는 오해를 불러일으킬 수 있고, 프로퍼티 키에 대한 오타가 발생하더라도 오류가 확인되지 않는 부작용이 있기 때문이다.

    또한 fresh object를 함수에 인자로 전달한 경우, 이는 특정한 변수에 할당되지 않았으므로 어차피 해당 함수에서만 사용되고 다른 곳에서 사용되지 않습니다. 이 경우 유연함에 대한 이점보다는 부작용을 발생시킬 가능성이 높으므로 굳이 구조적 서브타이핑을 지원해야할 이유가 없다!

    그럼에도 개발자가 fresh object에 대해서 타입 호환을 허용하고자 한다면?

    👇 마저 확인하기

    Ref https://toss.tech/article/typescript-type-compatibility

    자바스크립트 성능의 비밀 (V8과 히든 클래스)

    구글이 만든 오픈소스 자바스크립트 엔진인 V8의 특징은, JIT(Just In Time) 컴파일러를 사용한다는 점이다.

    V8 엔진은 Ignition이라는 인터프리터를 사용하여, AST를 입력받아 바이트코드를 생성한다. 생성된 바이트코드는 Turbofan이라는 컴파일러에 의해 사용된다. 코드 실행 중 받는 데이터르 기반으로 코드를 최적화하고 보다 최적화된 버전을 다시 컴파일한다.

    V8은 최적화 프로세스와 AST 사용 외에도 자바스크립트의 성능을 향상시키기 위해 또 다른 트릭을 사용한다.

    자바스크립트는 동적 타입 언어다. 즉, 객체에서 속성을 즉시 추가하거나 제거할 수 있다.

    Copy
    const userObject = {};
    userObject.name = "zig";
    userObject.blog = "https://zigsong.github.io";

    그러나 이 접근 방식은 자바스크립트의 성능을 떨어뜨리는 동적 조회를 더 필요로 하게 만든다. V8 엔진은 히든 클래스를 사용해 이 문제를 해결하고 자바스크립트 실행을 최적화한다.

    새 객체를 생성할 때 V8 엔진은 이에 대한 새로운 히든 클래스를 생성한다. 그런 다음 새 프로퍼티를 추가해 동일한 객체를 수정하면 V8 엔진에서 이전 클래스의 모든 프로퍼티가 포함된 새 히든 클래스를 만들고 새 프로퍼티를 포함한다.

    👇 히든 클래스가 어떻게 생성되는지 더 살펴보기

    Ref https://ui.toast.com/posts/ko_20210909


    마무리

    진짜 1월 첫주차라고?? 한 6일은 더 지낸 것 같다. 1월 5일에는 오늘 1월 11일쯤 되지 않았어? 1월 6일에는 1월 12일쯤 되지 않았어? ㅋㅋㅋ… 그만큼 많은 일들을 처리하며 강제 갓생 살았던 주였나보다. 새해라 그런지 헬스장에도 사람이 너무너무 많았고, TIL도 엄청 많이 올라온다. 활기가 넘쳐 좋다… 🫠


    Relative Posts:

    1월 2주차 기록

    January 14, 2023

    12월 4-5주차 기록

    December 30, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon