builder

번들 사이즈 이슈 해결(eslint,tsconfig)

하리하링웹 2024. 7. 24. 21:27

개요

웹 애플리케이션에서 초기 렌더링이 늦어지는 문제의 원인은 매우 다양하다. 이번에 해결한 이슈는 그 중 하나인 번들 사이즈 관련 문제이다.

 

일반적으로 개발자들은 다양한 오픈소스 번들러나, 경우에 따라서는 직접 개발한 번들러를 사용하여 자바스크립트 파일을 번들링한다. 그렇다면 왜 번들링 과정이 필요할까?

 

적절히 큰 규모의 프로젝트에서는 코드의 양이 수 기가바이트, 많으면 수십 기가바이트에 이를 수 있다. 사용자가 프로그램에 접속할 때 마다 모든 코드를 한 번에 가져오는 것은 현실적으로 부담이 너무 크기 때문에, 번들러에서는 이 과정에서 중복되는 코드 혹은 현재 상황에 필요 없는 코드를 제거하는 트리쉐이킹(Tree-shaking) 과정을 거친다. 이를 통해 큰 사이즈의 코드들을 아주 작은 단위로 줄여서 가져오게 된다.

 

초기 번들 사이즈는 웹 애플리케이션의 초기 렌더링 속도에 매우 큰 영향을 미치므로, 이러한 최적화 과정은 필수적이다.

 

우리 회사에서 문제가 된 부분은 번들 사이즈가 너무 커서 초기 로딩 속도가 느려진다는 점이었다. 이를 해결하기 위해 번들 사이즈를 줄이는 작업을 진행했다. 말로는 간단해 보일 수 있지만, 실제로 개발 과정에서는 많은 부분을 수정해야했다.

수정과정

번들 사이즈를 줄이기 위해 먼저 번들 사이즈가 늘어난 원인을 찾아야 했다.

 

원인은 의외로 쉽게 찾을 수 있었는데, 우리 회사에서는 추상화된 데이터 모델과 각종 정의들을 모두 하나의 번들로 묶고 있었다. 문제는 다른 모듈에서 추상화된 데이터 모델의 일부를 import하는 과정에서 무언가 잘못되어, 각 모듈의 번들 모두에 추상화된 데이터 모델들이 모여있는 번들의 전체 코드가 포함된 것이었다.

 

원인이 명확했기에 이를 제거하기만 하면 해결이 되는 상황이었으며, 이를 위해 처리해야 할 항목들을 아래와 같이 정리했다.

  1. 현재 회사에서는 Rollup 기반 자체 번들러를 구현하여 사용하고 있기에, 이 코드를 수정하여 문제가 되는 추상화된 데이터 모델 번들 전체를 가져오는 문제를 없앤다. (빌더 수정)
  2. 번들링 시에 추상화된 데이터 모델 번들 전체를 가져오는 문제를 막기 위해, 프로젝트 코드 규칙을 지키지 않고 사용 중인 import 구문들을 사용하지 못하게 만든다. (tsconfig, eslint 설정 수정)
  3. 기존 잘못된 import 구문들을 모두 변경한다. (기존 코드 수정)

여기서 3번 항목은 너무 많은 작업이 필요했기에, 다른 팀에서 인원을 뽑아 따로 진행해주었다.

빌더 수정

빌더 수정 과정 자체는 건드릴 것이 없었다. 애초에 원인이 된 부분은 2번 항목이다. 효율적인 번들링을 위해 정의해놓은 프로젝트 규칙대로 import가 되지 않아 이러한 문제가 발생했기에, 빌더 부분은 사실상 수정한 내용이 없다고 말할 수 있다.

설정 수정

빌더에서 규칙에서 어긋난 import를 호환되게 만들거나, 내부 규칙을 잘 지켜달라고 개발자들에게 요청하는 방법도 있지만 이는 좋은 방법이 아니다. 매번 내부 규칙을 설명할 수 없고, 사람이기 때문에 아무리 교육하더라도 실수가 발생할 수 있다. 또한 예외처리를 통해 문제를 해결하는 것도 다른 더 나은 해결책이 있을 때는 그다지 바람직하지 않기 때문이다.

따라서, 아예 규칙에 어긋난 import가 불가능하도록 프로젝트를 설정하는 것이 가장 좋은 방법이다.

tsconfig 수정

이를 위해 먼저 설정해야 하는 것은 tsconfig이다. 타입스크립트 기반 프로젝트에서 VSCode의 import 자동 완성 기능은 tsconfig에 의존하고 있다. tsconfig의 paths 설정에 따라 import할 때의 텍스트가 결정된다. 예를 들어보겠다.

[규칙 O]
import { some } from "company.some.module"
[규칙 X]
import { some } from "../../some/paths"
import { some } from "some.module.expand"

 

우리 회사에서는 위와 같이 company.some.module과 같은 방식으로 다른 모듈을 import하도록 규칙을 정의했다. 하지만 기존 tsconfig가 아래와 같이 설정되어 있어, 타 모듈을 import할 때 VSCode가 규칙과 다른 방식의 import도 추천해 규칙이 깨지는 경우가 발생했다.

[기존 tsconfig.json]
"paths": {
  "company.some.module": ["some/paths"],
  "some.module.expand": ["some/paths"]
}

 

위와 같은 설정으로 인해 한 개의 모듈에 대해 company.some.module, some.module.expand 두 개의 path 정의와 상대 경로까지 세 가지의 import 경우의 수가 나와 문제가 발생했다. 이를 방지하기 위해 설정을 아래와 같이 수정하였다.

[새로운 tsconfig.json]
"paths": {
  "company.some.module": ["some/paths"]
}

 

이는 코드 한 줄을 지운 단순한 수정처럼 보이지만, 실제 프로젝트에서는 각 모듈의 관계를 고려하여 내부의 export를 담당하는 index.ts 파일들의 관계 정의, 파일 위치 수정 등 다양한 수정을 추가적으로 진행했다. 물론 근본적인 원인은 기존 tsconfig였기에, 위와 같이 잘못된 import를 추천할 수 있는 문제의 코드를 제거했다. 이를 통해 개발자가 자동 완성 기능을 사용할 때 회사의 규칙대로 정상적으로 import를 진행할 수 있게 되었다.

eslint 설정

하지만 아직 한 가지 문제가 남아있었다. 개발자가 VSCode의 자동 완성 기능만 사용한다면 문제가 없겠지만, 직접 import 경로를 작성하거나 상대 경로로 import할 수 있기 때문이다. 이는 VSCode의 기능만으로는 한계가 있어, 직접 eslint 규칙을 만들어 해당 케이스로 import를 했을 경우 에러 메시지를 출력하거나 자동 수정(auto fix)이 가능하도록 제공해야 한다.

회사가 기존에 사용하는 eslint-plugin이 있기에 해당 레포지토리로 가서 import를 체크하는 규칙 코드를 추가했다. 만약 eslint-plugin 같은 것이 없다면 처음부터 만들어야 하는데, 동작 원리만 알고 시간만 적당히 있다면 부담없이 만들 수 있을 것이다. eslint에서 방법을 잘 제공해주기에 eslint의 가이드라인에 맞춰 코드 분석 및 변환, 규칙 코드 작성, 설정 등록의 순서로 만들면 된다.

 

이 경우는 테스트 코드를 먼저 작성했다. 테스트 코드를 먼저 작성하는 방식이 항상 옳다고는 할 수 없지만, eslint의 경우 정해진 규칙 내에서 동작하도록 만들어야 하기 때문에 테스트 코드를 통해 발생할 수 있는 모든 경우를 정의해준 뒤 코딩하는 것이 훨씬 편하고 개발자의 실수로 인한 에러도 방지할 수 있다. 물론 이는 어떤 것을 개발하느냐에 따라 다르기에 어떤 것이 더 우선인지는 본인이 잘 판단하여 정하면 된다.

 

 

규칙을 잘 만들고 테스트도 잘 동작하게 만들었다면, 프로젝트의 eslint.config에 아래와 같이 플러그인과 규칙을 등록해주면 프로젝트에 적용이 된다.

plugins: ["@company-eslint"],
rules: {
  "@company-eslint/check-import": ["error"],
}

 

마찬가지로 각 프로젝트에 따라 더욱 상세한 린트 설정을 추가하는 것이 좋다.

 

추가적으로 우리 회사의 경우 VSCode extension 형태로 eslint-plugin의 버전을 관리하고 있기에, eslint 모듈의 package.json 버전을 올린 뒤 VSCode를 새로고침하여 새 버전을 다운로드하도록 유도하였다.

 

이제 VSCode에 새로 만든 규칙이 추가된 eslint가 적용되면서, 개발자가 직접 import하여 생기는 문제도 방지할 수 있게 되었다. eslint와 더불어 husky와 같은 라이브러리를 사용해 git commit 시에 eslint를 돌려 잘못된 코드가 올라가지 못하게 하는 규칙도 같이 추가하는 것이 좋다.

기존 코드 수정

설정 작업이 완료되었으니, 전체 린트 체크를 한 뒤 발생한 에러들을 수정해야 하는 작업이 남았다. 파일이 매우 많으며, 단순 수정으로 해결할 수 없는 코드들은 파일 위치를 옮기는 작업도 함께 진행해야 했다. 혼자 하기에는 시간이 너무 오래 걸리기에, 다른 팀에서 지원을 받아 금방 끝낼 수 있었다.

node [eslint경로] -c [eslint config 파일 경로] --quiet --ignore-pattern **/@test/**/*.ts --ignore-pattern **/__tests__/**/* --ignore-pattern **/@test/**/*.tsx [린트 체크 대상 경로]

 

위와 같이 CLI로 직접 실행할 수 있으며, package.json에 정의되어 있다면 해당 명령어를 실행시켜줘도 된다. 우리 회사 프로젝트의 경우는 모듈이 많고 Turbo와 같은 통합 모듈 관리 툴을 사용하지 않았기에, CLI로 직접 실행시켜 린트를 진행하였다. 추가적으로 아래 명령어와 같이 입력해 에러 메시지를 파일로 추출하여 수정 팀에 전달하였다.

[eslint 실행 명령어] > lint-errors.txt

결론

이번 작업을 통해 번들 사이즈를 줄이는 것뿐만 아니라 번들링 과정에서 중복 코드를 제거하는 데도 성공하여 빌드 시간을 단축할 수 있었다. 이후 벤치마크 작업을 진행해 보았는데 예상 외로 이 수정 하나로 상상 이상의 효과를 얻을 수 있었다.

아래는 벤치마크 결과이다.

                                                                                                                   

   AS-IS  TO-BE
Develop 환경 번들 총 용량 60MB 31MB
Product 환경 번들 총 용량 25MB 12MB

 

용량과 더불어 빌드 시간 또한 약 50% 정도 향상 되었으며, 이 때까지 진행한 빌더 관련 작업중 가장 유의미한 결과 값을 얻을 수 있었다.

번들 사이드에 대해서만 생각하고 작업을 하였지만 번들 사이즈 감소뿐 아니라, 전체적인 빌드 성능 향상이라는 두 마리 토끼를 잡은 보람찬 작업이였다.