rollup으로 번들링하는 과정

처음 rollup으로 번들링하는 과정에서 겪은 문제들과 해결방법

monorepo를 사용해 패키지들을 분리하는 과정에서 이슈를 확인했다.

번들링 하지않고, 패키지들을 index.ts에서 직접 내보내 사용했을 때, 사용하지 않는 코드들도 함께 포함되는 문제가 있었다. 이 문제점을 확인하고 tree-shaking을 적용하기 위해 rollup을 사용하게 되었다.


tree-shaking

tree-shaking은 최종 번들링한 결과에서 사용하지 않는 코드를 제거하는 기능이다. 불필요한 코드가 없어지기 때문에 번들 파일의 크기를 줄일 수 있다.

tree-shaking은 ES6 부터(esm) 지원되는 기능이다.


rollup

먼저 약 10개정도 되는 패키지들을 각각 설정하는건 비효율 적이라 생각했다. 그래서 공통적으로 사용할 rollup.config.mjs 파일을 만들어 사용하기로 했다.

import path from 'path';
import { readFileSync } from 'fs';
import { DEFAULT_EXTENSIONS } from '@babel/core';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import builtins from 'builtin-modules';
import json from '@rollup/plugin-json';
import { defineConfig } from 'rollup';
import terser from '@rollup/plugin-terser';

const extensions = [...DEFAULT_EXTENSIONS, '.ts', '.tsx'];

export function generateRollupConfig({ packageDir }) {

  {...}

  const plugins = [
    resolve({
      extensions,
    }),
    commonjs(),
    babel({
      exclude: 'node_modules/**',
      extensions,
      babelHelpers: 'bundled',
    }),
    json(),
    typescript({
      compilerOptions: {
        outDir: 'dist',
        module: 'esnext',
        moduleResolution: 'bundler',
      },
      tsconfig: path.join(packageDir, 'tsconfig.json'),
    }),
    terser(),
  ];

  const generateConfig = (output) => {
    return {
      input,
      output,
      external,
      plugins,
    };
  };

  return defineConfig([
    generateConfig([
      {
        dir: output,
        format: 'cjs',
        entryFileNames: '[name].js',
      },
      {
        dir: output,
        format: 'esm',
        entryFileNames: '[name].mjs',
        preserveModules: true,
        preserveModulesRoot: path.dirname(input),
      },
    ]),
  ]);
}
  • @rollup/plugin-babel : rollup + babel 연동

*Note. @rollup/plugin-commonjs 보다 뒤에

  • @rollup/plugin-commonjs : CommonJS 모듈을 ES6으로 변환하여 롤업 번들에 포함

*Note. @rollup/plugin-node-resolve와 함께 사용

  • @rollup/plugin-node-resolve : node_modules에 외부 모듈 확인

*Note. @rollup/plugin-commonjs와 함께 사용

  • @rollup/plugin-terser : 간결하고 축소된 번들 생성 (띄어쓰기나 주석 제거)
  • @rollup/plugin-url : data-URIs, ESM으로 가져옴
  • @rollup/plugin-virtual : 메모리에서 가상 모듈을 로드

*Note. Use this plugin before any others such as node-resolve or commonjs, so they do not alter the output.

  • @svgr/rollup : svgr 플러그인

  • builtin-modules : node.js 빌트인 모듈

  • external : 특정 모듈을 번들에 포함하지 않고, 외부 모듈로 설정


각 패키지별 번들링

import path from 'path';
import { generateRollupConfig } from '@repo/rollup-config';

const __dirname = path.resolve();

export default generateRollupConfig({ packageDir: __dirname });

utils에서 내보내는 함수 중 프로젝트별로 사용하는곳과 사용하지 않는 곳이 있었는데, 초기에 번들링과정을 거치지 않았을 때 프로젝트 빌드시 해당 함수를 각 프로젝트에 모두 포함되었다.

번들링 과정을 확인하기 위해 vite.config.js에

{...}

    build: {
      rollupOptions: {
        output: {
          manualChunks: {
            utils: ['@repo/utils']
          },
        },
      },
    },

{...}

옵션을 추가해 비교해보았다.

bundle-test bundle-test

전, 후 용량에 차이가 있다는걸 확인했고, js파일을 확인해 사용하는 함수만 포함되는었는지 확인해보았다.


처음 rollup을 사용해 패키지들을 번들링하면서 tree-shaking이라는 개념도 알게되었고, cjs와 esm의 차이점도 알게되었다.

다른 기업들의 오픈소스들을 뒤져가며 작업을 진행하면서 많은 것을 배울 수 있었던 시간이었다.

rollup 설정에 대해서 아직 완벽하게 이해하지는 못했으며, 내가 의도한대로 작동하는지도 조금 확신이 들지 않지만 이번 기회에 조금 더 깊게 공부해보고 계속해서 확인해보며 수정해나가야겠다. 개인적으로는 최적화해 나가는 과정이 매우 재밌었던 작업이었다!