index.ts가 정말 필요할까? Barrel Export의 장단점을 통해 알아본다.

namm'm'm·2025년 8월 2일

Project

목록 보기
6/8

1. 인트로

Next.js에서 FSD 아키텍처를 적용시키던 중 배럴 익스포트(Barrel Export)의 필요성을 느꼈다.

But, 누군가는 배럴 파일의 위험성에 대해서도 소개하고 있길래, 사용할까말까 고민해봤다.

2. 배럴 익스포트? 배럴파일(index.ts)?

배럴 익스포트란, 다른 파일들을 재수출(re-export)하는 파일을 만드는 패턴이다.

간단히말해, 여러 파일을 한 곳에서 모아서 내보내는 방식이며, 이때 사용하는 파일(보통 index.ts 혹은 index.js)을 배럴 파일이라고 한다.

배럴 익스포트(index.ts)의 유무에 따른 차이점을 확인해보자.

2.1 배럴파일 적용전

BlogList에서 useBlogList와 BlogCard로부터 UI및 기능들을 import 해야하는 상황이다.

📂blog-list
    ├─📂model
    │     └─useBlogList.ts
    └─📂ui
          └─BlogCard.tsx
    

📂widgets
    └─BlogList.tsx 

배럴 파일 없을 때는 직접 하나하나 import 해야한다.

// widgets/BlogList.tsx
import { useBlogList } from '@/features/blog-list/model/useBlogList';
import { BlogCard } from '@/features/blog-list/ui/BlogCard';

2.2 배럴파일 적용후

📂blog-list
    ├─📂model
    │     └─useBlogList.ts
    ├─📂ui
    │     └─BlogCard.tsx
    │
    └─index.ts (배럴파일 생성)

📂widgets
    └─BlogList.tsx 

배럴파일 생성 ( 배럴파일에서 미리 이것저것 export 준비)

// features/blog-list/index.ts (배럴파일) 생성
export { useBlogList } from './model/useBlogList';
export { BlogCard } from './ui/BlogCard';

사용할 때는 개별 하나하나 import할필요없이 배럴파일만 import하면된다.

// widgets/BlogList.tsx
import { BlogCard, useBlogList } from '@/features/blog-list';

3. 배럴 익스포트의 장점

3.1 Import 문 간소화

// Before
import { BlogData } from '@/entities/blogs/model/types';
import { blogApi } from '@/entities/blogs/api/blogApi';
... 길어지는 import// After
import { BlogData, blogApi ...한줄컷 } from '@/entities/blogs';

3.2 모듈의 Public API 명확화

어떤 것들이 외부에 노출되는지 명확하게 정의할 수 있음.

// entities/blogs/index.ts - 블로그 entities의 Public API
export type { BlogData, BlogListResponse } from './model/types';
export { blogApi } from './api/blogApi';
// 내부 구현 상세는 숨길수있다

3.3 리팩토링 용이성

내부 폴더 구조가 바뀌어도 배럴파일만 수정하면 됨. 사용하는 쪽 코드는 건드리지 않아도 됨.

4. 배럴 익스포트의 단점

배럴 익스포트가 문제가 되는 케이스들을 살펴보자.

4.1 번들 크기 증가 이슈

// shared/ui/index.ts
export * from './Button';
export * from './Modal';
export * from './Table';
export * from './Chart';
export * from './Calendar';
// ... 그외 여러 컴포넌트

// 실사용하는 컴포넌트에서 Button만 필요하다면? 
import { Button } from '@/shared/ui';
// 모든 컴포넌트가 번들에 포함되며 성능이슈를 발생시킨다.

4.2 모듈 그래프 구성 비용

배럴 파일이 많아지면 import 체인이 거미줄처럼 복잡해짐.

// feature/index.js
export * from "./foo";
export * from "./bar";  
export * from "./baz";

// 사용하는 곳
import { foo, bar, baz } from "../feature";

이런 패턴이 코드베이스 전체에 퍼지면서 모든 폴더에 index.js 파일이 생김. 결국 하나의 모듈을 import하면 배럴 파일 → 또 다른 배럴 파일 → 또 다른 배럴 파일 순으로 연쇄적으로 import하게 됨.

최종적으로는 거미줄처럼 얽힌 import문을 통해 프로젝트의 모든 파일을 import하게 되는 상황 발생.

4.3 테스트 러너에서의 오버헤드

Jest 같은 테스트 러너는 각 테스트 파일을 별도 프로세스에서 실행함. 이는 모든 테스트 파일이 모듈 그래프를 처음부터 다시 구성해야 한다는 뜻임.

블로그에서 제시한 계산:

  • 모듈 그래프 구성에 6초가 걸리는 프로젝트
  • 테스트 파일 100개
  • 총 오버헤드: 10분

이 시간 동안에는 실제 테스트나 다른 코드가 실행되지 않음. 순전히 엔진이 소스 코드를 준비하는 시간임.

5. Barrel파일이 필요없다는 글, 반박해보자

https://github.com/yeonjuan/dev-blog/blob/master/JavaScript/speeding-up-the-javascript-ecosystem-the-barrel-file-debacle.md

  • 대규모 일때에 해당하는 문제점이라는 것! 쪼끄만 프로젝트에서는 개발편의성이나 챙기자
  • 모든 배럴 파일을 동일하게 취급하고 있긴하다. 블로그에서는 무지성 export만을 고려하고 있다.
// 이런 건 문제없음
export { Button } from './Button';
export { Input } from './Input';

// 이런 게 문제 (무지성 export)
export * from './components'; // 10000개 컴포넌트
export * from './utils';      // 10000개 유틸함수
  • DX(Developer Experience) 개발자의 편의성도 고려한다면, 배럴익스포트를 완전히 사용하지 않는것은 과연 옳을까?

6. 현명한 배럴 익스포트 전략

그럼 어떻게 해야 할까? 프로젝트 규모와 상황에 맞는 전략을 세워보자.

6.1 프로젝트 규모별 접근법

소규모 프로젝트

  • 선택적 사용: 핵심 모듈들만 배럴 익스포트 적용 (성능 이슈 거의 없음)
// 현재 나의 프로젝트 수준
entities/blogs/index.ts
features/blog-list/index.ts
// 이정도만 사용하자

중간규모 프로젝트

  • 신중한 적용: 정말 필요한 곳에만 사용하며, 번들크기와 빌드시간을 체크해보자

대규모 프로젝트

  • 할수있다리: 나중에 꼭 경험해보자

6.2 성능 최적화된 패턴

명시적 export 사용

// ✅ 필요한 모듈만 export해서 명시하자
export type { BlogData, BlogListResponse } from './model/types';
export { blogApi } from './api/blogApi';

// ❌ 지양하자
export * from './model/types';
export * from './api/blogApi';

작은 단위로 분할해서 사용하자

// entities/blogs/model/index.ts (분할 1번)
export type { BlogData, BlogListResponse } from './types';

// entities/blogs/api/index.ts (분할 2번)
export { blogApi } from './blogApi';

// entities/blogs/index.ts (2개로분할된걸 최상위에서 export)
export * from './model';
export * from './api';

조건부 배럴 익스포트

자주 함께 사용되는 것들만 묶고, 무거운 모듈은 별도로 import하도록 함.

// shared/ui/index.ts
// 기본적인 것들만 등록해두기
export { Button } from './Button';
export { Input } from './Input';

// 무거운 컴포넌트는 별도로 import해서 사용해버리기
// import { Chart } from '@/shared/ui/Chart';

7. 프로젝트 사용예시

TypeScript 환경에서 FSD아키텍처를 사용하고 있으며, 배럴 파일을 적용해보려한다.

  1. Entities 계층 배럴파일 생성
// entities/blogs/index.ts
export type { BlogData, BlogListResponse } from './model/types';
export { blogApi } from './api/blogApi';
  1. Features 계층 배럴파일 생성
// features/blog-list/index.ts
export { useBlogList } from './model/useBlogList';
export { BlogCard } from './ui/BlogCard';
  1. widgets/blog-section.tsx에서의 사용예시
'use client';
import { BlogCard, useBlogList } from '@/features/blog-list';
import TempBlogSection from '@/widgets/blog-temp-section';

export default function BlogSection() {
  const { blogs, error, loading, handleBlogClick, refetch } = useBlogList();

  if (error) {
    return <TempBlogSection />;
  }
  if (loading) {
    <div>
      <h1>Loading blogs...</h1>
      <button onClick={refetch}>새로고침</button>
    </div>;
  }

  return (
    <div className="w-full px-4 py-12 ">
      <div className="max-w-6xl mx-auto">
        <h2 className="font-paperlogy text-4xl md:text-5xl font-bold text-foreground mb-12">Blog</h2>

        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 font-paperlogy tracking-wider">
          {/* 여기에 블로그 입력 */}
          {blogs.length === 0 ? <p className="text-foreground">블로그를 준비중입니다.</p> : blogs.map((blog) => <BlogCard key={blog.id} blog={blog} onClick={handleBlogClick} />)}
        </div>
      </div>
    </div>
  );
}

8. 마무리

어떻게 마무리 해야할지 모르겠다.
배럴 익스포트의 문제점을 유의미하게 격어보지는 못했기 때문에 아직은 DX적인 부분을 좀더 고려하여 개발을 진행해보려 한다.
이후 규모가 큰 프로젝트를 만났을 때, 직접 경험해보며 느껴보고 싶다.
마지막으로 참고했던 블로그 글 namgigi!

https://github.com/yeonjuan/dev-blog/blob/master/JavaScript/speeding-up-the-javascript-ecosystem-the-barrel-file-debacle.md

0개의 댓글