
해당 글은 다음 글을 번역 및 정리한 글입니다.
https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-7/
만약 여러 파일들을 가진 프로젝트를 사용한다고 하면, 우리는 이런 식으로 import해와서 해당 모듈을 사용한다.
import { foo } from "./some/other-file";
export function myCoolCode() {
// Pretend that this is super smart code :)
const result = foo();
return result;
}
하지만 이런 파일이 한두개라면 괜찮겠지만, 라이브러리들을 보면 정말 많은 모듈들이 있다. 또한 각 모듈마다 파일을 나누게 된다면 우리가 사용하는 import 문은 점점 더 많아지게 된다.
하지만 이렇게 import를 하지 않기 위해서는 모듈들을 한 곳에 모두 모아놓고 관리할 수도 있으나, 그렇게 되면 파일 자체가 여러 역할을 지니기 때문에 나중에 유지보수성이 낮아지게 된다.사이즈가 점점 더 커질 때마다 이 문제는 점점 더 심해질 것이다.
import { foo } from "../foo";
import { bar } from "../bar";
import { baz } from "../baz";
결국 우리는 각각의 디렉토리에서 모듈들을 하나하나씩 다 import문을 쓰게 되된다. 불필요하게 import가 길어지면서 코드가 예뻐보이지 않을 수 있다.
이러한 문제를 해결하기 위해 배럴 파일(barrel file) 이라는 패턴을 사용할 수 있다.
배럴 파일은 여러 개의 모듈 디렉토리 위에 하나의 index.js를 두고 export 하는 방식이다.
// feature/index.js
export * from "./foo";
export * from "./bar";
export * from "./baz";
이렇게 상위에서 모든 것들을 다시금 export해주는 파일을 만들게 되면 여러 모듈들을 한번에 import 해올 수 있기 때문에, 위 예시처럼 3개의 import 문이 하나로 합쳐질 수 있다.
import { foo, bar, baz } from "../feature";
여러 개의 모듈을 한번에 모아서 한 곳에서 받아오면 이만큼 보기 좋은게 없다!
실제로 리액트의 경우도 이런 방식으로 배럴 파일을 두고 있다.
// react/packages/react/index.js
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
...
// Export all exports so that they're available in tests.
// We can't use export * from in Flow for some reason.
export {
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
__COMPILER_RUNTIME,
Children,
Component,
Fragment,
Profiler,
PureComponent,
StrictMode,
Suspense,
cloneElement,
createContext,
createElement,
createRef,
use,
forwardRef,
isValidElement,
lazy,
memo,
cache,
startTransition,
unstable_DebugTracingMode,
unstable_LegacyHidden,
unstable_Activity,
unstable_Scope,
unstable_SuspenseList,
unstable_TracingMarker,
unstable_getCacheForType,
unstable_useCacheRefresh,
useId,
useCallback,
useContext,
useDebugValue,
useDeferredValue,
useEffect,
experimental_useEffectEvent,
useImperativeHandle,
useInsertionEffect,
useLayoutEffect,
useMemo,
useOptimistic,
useSyncExternalStore,
useReducer,
useRef,
useState,
useTransition,
useActionState,
version,
} from './src/ReactClient';
그렇기 때문에 우리는 이런 식으로 간편하게 import문을 통해서 여러 훅들을 가져와 사용한다.
import { useState } from "react";
정말 깔끔하고 보기 좋지 않은가?
이걸 보고 각각의 여러 모듈들을 가진 파일들에 대해 index를 두고 import하려는 사람들은 성능에 대해서 생각해볼 필요가 있다.
만약 이러한 패턴이 가독성이 좋아보여서 자신의 폴더 구조 또한 배럴 파일 패턴을 적용하기 위해서 많은 파일들이 있는 디렉토리에서 index.js를 두고 다시금 export 한다고 생각해보자.
그렇게 된다면, 이 index.js들은 디렉토리 계층 구조에서 한 계단씩 계속 올라가면서도 index.js 배럴 파일이 생길 것이고, 마지막 루트에서는 하나의 index.js에 모든 모듈들을 담을 수 있게 된다.
아유 깔끔하니 야무지네~..~

라고만 생각한다면 나중에 큰코 다칠 수 있다.
우리는 이 index.js 파일을 통해 하나의 모듈을 import 한다고 했을 때, 내가 필요한 모듈만을 import해와서 사용한다고 생각한다. 하지만 이는 반은 맞고 반은 틀리다.
내가 필요한 모듈만을(X) import해와서 사용한다(O)
배럴 파일에 있는 모듈들 중에 하나를 import해와서 사용하는 경우, 배럴 파일에 있는 모든 모듈들은 import를 해오는 과정에서 함께 import된다.
이 말의 의미는 무엇인가?
결국 전역변수가 있다면 이에 따른 예외가 발생할 수 있다는 것이다.
// a.js
globalThis.foo = 123;
// b.js
console.log(globalThis.foo); // should log: 123
// index.js
import "./a";
import "./b";
여기에서 이런 식으로 b를 실행하게 되면 123이 출력되는 것을 알 수 있다.
결국 이는 곧 전역 변수를 생성했을 때, 하나라도 충돌이 날 수 있다는 가능성을 포함한다. 그렇기에 우리의 예상과는 다른 동작이 일어날 수 있다.
결국 위 예시를 통해서도 알 수 있듯이, 내가 b 모듈만 가져와서 사용한다고 하더라도, 배럴 파일에서 가져오는 과정에서 배럴 파일이 import하는 모든 모듈들 또한 함께 딸려오게 된다.
이 문제는 테스트를 할 때 더 안좋은 방향으로 작용한다.
jest와 같은 테스트러너는 테스트 파일마다 새로운 자식 프로세스에서 실행되는데, 만약 테스트 파일이 여러개라면 모듈 그래프를 각 파일마다 처음부터 그리고 import해오는 과정이 반복적으로 발생하기 때문에 이에 대한 비용을 모두 지불해야 한다.
모듈 그래프를 그리는데 6초가 걸린다고 가정하면, 테스트 파일이 100개일 때는 10분동안 테스트만 돌려야 한다는 뜻이다.
문제는 모듈 그래프를 그리는 비용 뿐만이 아니다. import cycle linitng rules에 대한 린팅 과정에서 생기는 비용 또한 부가적으로 발생한다.
import cycle liniting rules
모듈들이 순환 참조하는 상황을 방지하기 위해 정해놓는 린팅 규칙으로, 모듈 간의 의존성을 명확하게 유지하기 위해 사용한다.
린터는 파일 별로 실행되기 때문에 결국 테스트파일 100개가 생기면 여기에 따라서도 린터가 각각 생성될 수밖에 없다. 그렇게 되면 린팅도 각각 import되는 모든 모듈에 대해 검사를 진행하는 과정에서 시간은 기하급수적으로 증가한다.

레퍼런스에서는 순환참조하는 파일을 만든 후에, 모듈 그래프를 그리는 비용을 측정한 그래프를 첨부했다. 모듈은 비어있는 상태임에도 불구하고 모듈이 많아지면 많아질수록 비용은 엄청나게 높아지는 것을 볼 수 있다.
0.15s * 100 / 4 = 3.75s 오버헤드0.31s * 100 / 4 = 7.75s 오버헤드3.12s * 100 / 4 = 1:18m 오버헤드16.81s * 100 / 4 = ~7:00m 오버헤드48.44s * 100 / 4 = ~20:00m 오버헤드그래서 최근 현업에서는 배럴 파일의 사용을 최대한 지양하고 있다고 한다. 우리 또한 이러한 배럴 파일이 성능에 미치는 영향을 제대로 알고 사용할 필요가 있다.
Drive Mad redefines browser-based racing with its revolutionary combination of precise vehicle dynamics and meticulously designed tracks.