Code splitting시 주의해야할 점(side effect)

김종식·2023년 1월 13일
0
post-thumbnail

javascript에서 Code splitting을 해서 Dynamic하게 import를 진행할 때에 주의해야 할 점이 있다.

이 글에서는 Next.JS의 dynamic을 사용하여 예를들어 설명해보겠다.

import dynamic from 'next/dynamic';
import { useState } from 'react';

import { Button } from '@/components/ui';

const DynamicComponent = dynamic(() => import('@/components/test/DynamicComponent'));
export default function TestPage() {
  const [isRender, setIsRender] = useState(false);

  return (
    <div>
      <Button onClick={() => setIsRender((p) => !p)}>BUTTON</Button>
      {isRender && <DynamicComponent />}
    </div>
  );
}

일반적으로 dynamic import는 위와 같은 상황에 사용하게된다.

실제로 이를 dev tool에서 확인해보면 Render 버튼을 눌렀을때에 DynamicComponent를 lazy하게 요청하여 렌더시키는 것을 확인할 수 있다.

하지만 이 때에 주의해야 할 점이 있다.

자바스크립트 개발자들이 많이 쓰는 export 방식중 하나인데

대충 이러한 구조로 export를 해주는 방식이다. 이는 실제로 코드의 import 부분을 깔끔하게 만들어주며 여러가지 방법으로 응용하여 사용할 수 있어 많은 사람들이 사용하고 있는 방식이다.

하지만 여기서 문제가 발생하게된다.

import dynamic from 'next/dynamic';
import { useState } from 'react';

import { NormalComponent } from '@/components/test';
import { Button } from '@/components/ui';

const DynamicComponent = dynamic(() => import('@/components/test/DynamicComponent'));
export default function TestPage() {
  const [isRender, setIsRender] = useState(false);

  return (
    <div>
      <Button onClick={() => setIsRender((p) => !p)}>BUTTON</Button>
      <NormalComponent />
      {isRender && <DynamicComponent />}
    </div>
  );
}

만약 위의 코드를 실행한다면 DynamicComponent는 lazy하게 가져와질까?

정답은 아니요 이다.

일단 매 번 빌드를 하거나 하는것이 아닌이상 대부분의 데브환경에서는 위와 같은 경우 특수한 경우를 제외하고는 dynamic import가 거의 불가능하다고 보는것이 맞을 것 같으며(확실하지는 않음)

프로덕션 환경을 기준으로 설명하자면 Webpack과 같은 번들러에서 해당 파일에 SideEffect가 존재한다고 판단하여 트리 쉐이킹대상에서 제외하여 트리 쉐이킹 이 발생하지 않아 생기는 이슈이다.

그렇다면 SideEffect가 뭐기에 이런 일이 발생하는 것이며 번들러는 왜 이 파일을 트리 쉐이킹 대상에서 제외하는것일까?

SideEffect는 CS적인 설명으로는 어떠한 함수에서 결과 값 외에 다른 어떠한 상태를 변경시키는 것이라고 설명할 수 있을 것 같다.

SideEffect는 위의 설명만 보자면 개발자가 의도하지 않은 문제를 발생시킬 수 있는 뭔가 불안한 느낌을 받겠지만 이는 반드시 나쁜것만은 아니며 실제로 개발을 하다보면 많은 경우에 SideEffect를 발생시키는 자신을 확인할 수 있을것이다.

SideEffect는 개발자가 의도하였을수도 의도하지 않았을수도 있다고 말할 수 있을 것 같다. (아마 대부분의 경우에는 의도하였을것이다.)

이러한 SideEffect에 대해 함부로 트리 쉐이킹을 했을때에 몇가지의 문제가 발생할 수 있게된다.

SideEffect가 발생하는 파일 혹은 함수에서 어떠한 모듈을 import하여 가져오기만 해도 실행되는 경우도 있으며(polyfill, css 등) 파일 내부에서 개발자가 의도적으로 어떠한 글로벌한 함수를 바꾸거나 하는 경우도 존재할 수 있게된다. 하지만 이를 번들러가 빌드 과정에서 트리 쉐이킹해버린다면 이 파일 혹은 함수가 개발자가 의도하지 않는대로 동작할 가능성이 존재하게 되어버리는 것이다.

하지만 번들러는 이러한 것들에 대해 개발자가 의도한 것인지 의도하지 않은것인지에 대해 알 수가 없어 개발자가 따로 파일을 지정해주거나 하지 않는이상은 이렇게 SideEffect가 존재하는 파일 자체를 트리 쉐이킹에서 제외하여 묶어서 번들링을 진행하게되어 결과적으로 트리 쉐이킹이 발생하지 않게 되는것이다.

이를 통해 번들링 과정에서 개발자가 의도하지 않거나 잘못된 부분에 대한 위험성을 방지할 수 있게 된다.

즉 위의 index.ts파일은 안에서 NormalComponent만 import하여 가져오더라도 아래에 export되어있는 DynamicComponent가 존재하기에 번들러가 SideEffect의 가능성이 있다고 판단하여 트리 쉐이킹을 발생시키지 않고 묶어서 번들링을 진행하게되고 NormalComponent를 호출하였을 때 DynamicComponent도 같이 가져와 lazy loading이 발생하지 않는것이다.

물론 대부분의 경우에는 SideEffect 옵션을 false로 하여 모든 파일에 대해 트리 쉐이킹을 하도록 해도 정상적으로 동작하도록 개발을 하겠지만 혹시모를 위험성을 방지하는 역할을 하기에 이러한SideEffect옵션이 존재하게 되는 것이다.

여기서 SideEffect의 룰들을 Package.json 파일에서 핸들링 할 수 있는데

"sideEffects": false

와 같이 선언하여 모든 파일에 대해 SideEffect 체크를 하지 않도록 설정해 준다던가

"sideEffects": [ "dist/store.js", "dist/polyfill.js" ] 

와 같은 구문을 추가하여 어떠한 디렉터리만 SideEffect를 검사하지 않도록 적용시키는 등의 다양한 옵션 설정이 가능하며
함수의 앞에

/*#__PURE__*/ noSideEffects();

와 같이 지정하여 순수한 함수임을 번들러에게 알려주는 방법도 존재하며 찾아보면 더욱 많은 방법이 존재하는것을 확인할 수 있을것이다.

SideEffect옵션은 단순하게 사용하자면 그럴수도 있지만 어떠한 경우에는 트리 쉐이킹 한 패키지임에도 불구하고 결국 모든 것들이 연결되어 있어 의미없는 트리 쉐이킹을 진행하거나 이로 인해 해결하기 어려운 에러가 발생하는 등 사용하기전에 한 번쯤은 생각해보고 사용하는것을 권장한다.

SideEffect옵션을 False로 하여 프로덕션 환경에서 정상적으로 Code Splitting이 적용된 모습

profile
웹개발자 / 잘못된 정보에 대한 피드백은 언제나 환영입니다. ^^

0개의 댓글