- 프로젝트: loplat UI
- 키워드: bundling optimization, rollup, tree shaking, code splitting, commonJS, es modules, import cost
- 상황
- 팀원분이 완성해둔 loplat ui 라이브러리를 실제 프로젝트에서 사용하고자한다.
- 아래와 같이 사용하고 싶은 컴포넌트를 import 한다.
import { Button } from 'loplat-ui'; import { black400 } from 'loplat-ui';
- 사용할 컴포넌트에 해당하는 코드만 불러오는 것이 아니라, 번들링 된 코드를 모두 불러와 과도한 import cost가 든다.
black400이라는 palette const를 불러오는 것이므로 1kB도 되지않는 작은 크기여야만 한다.
- 해결과정
- 기존 rollup.config.js 파일을 보고 문제의 원인을 찾아보았다.
export default { input: "src/index.ts", output: [ { file: packageJson.main, format: "cjs", sourcemap: true }, { file: packageJson.module, format: "esm", sourcemap: true } ], plugins: [ peerDepsExternal(), resolve(), commonjs(), typescript({ useTsconfigDeclarationDir: true }), postcss({ extensions: ['.css'] }) ] };
결과물로 나오는 index.js가 cjs format, 즉 commonJS 모듈 방식의 파일이었다. 사내의 리액트 프로젝트에서 cjs 포맷이 필요한 경우는 없기 때문에 es module 방식인 'esm' format만 남기고 제거하였다.
if (process.env.NODE_ENV === 'production') { require('./production.js') } else { require('./development.js') }
commonJS는 코드를 불러오는 require 문이 동기적으로 발생한다. 따라서 위와 같은 코드를 번들링한다고 가정했을 때, 어떤 파일을 필요로할지 정적으로 판단할 수 없으므로 production.js와 development.js를 모두 포함한 번들링 결과가 만들어진다.
이는 commonJS에서 기본적으로 tree-shaking이 불가능한 이유이다. 반대로 es modules 에서는 파일의 맨 위에서 import 해야만 하기 때문에 정적으로 코드를 분석할 수 있다.
import { Button } from 'loplat-ui'; import { subtract } from 'lodash-es';
- esm format만 남기고 빌드한 결과이다. 빌드 결과물인 index.js를 살펴보면
exports 를 이용하는 cjs 결과와는 달리 export 를 이용하여 컴포넌트를 내보내는 것을 확인할 수 있다.// es modules ... var Add = React__default.memo(function (_a) { var _b = _a.size, size = _b === void 0 ? 18 : _b, _c = _a.fillColor, fillColor = _c === void 0 ? '#9DAAB7' : _c, className = _a.className, style = _a.style; var uniqueId = String(Math.random().toString(36).substr(2, 9)); return ...; }); ... export { Add as AddIcon, Alert as AlertIcon, ... } // commonJS // exports.AddIcon = Add; // exports.AlertIcon = Alert;
- 하지만 여전히 tree shaking 이 되지 않는 것이 확인되었다.
- index.js 라는 하나의 파일에 모든 라이브러리 코드가 들어있어, tree shaking을 하기엔 아직 힌트(hint)가 부족하다고 판단했다. 그래서 컴포넌트마다 output을 번들링하여 결과물을 쪼갰다.
import multiInput from 'rollup-plugin-multi-input'; ... export default { // NOTE: tree shaking 을 위해 esm 파일들을 code splitting 하여 빌드한다. input: ['src/**/index.ts', 'src/**/index.tsx', 'src/**/generated/*.tsx'], output: [ { dir: 'dist', format: 'esm', sourcemap: true, }, ], ...
이때, 'rollup-plugin-multi-input' 이라는 플러그인을 이용하여 input에 모든 컴포넌트를 넘겨주었다. - 아래는 번들링 결과이다.
index.js에서 전체 컴포넌트에 대한 모든 로직과 코드(2번에서 var Add = ...)가 있는 것이 아니라, 각각의 컴포넌트로 쪼개진 js 파일을 단순히 export함을 알 수 있다.
즉 import { Button } from 'loplat-ui'; 라고 했을 때, components/Button/index.js 에 해당하는 코드만 가져온다면 원하는대로 tree-shaking이 될 것이라 기대했다. - 하지만 결과는 여전히 실패였다.
다음글 '번들링 최적화를 통해 import cost 줄이기(2)'에서 tree-shaking 지원을 위한 마지막 단계를 알아보자.
- 기존 rollup.config.js 파일을 보고 문제의 원인을 찾아보았다.
'프론트엔드' 카테고리의 다른 글
svg mask fill 속성은 white로 지정하기 (0) | 2021.12.03 |
---|---|
번들링 최적화를 통해 import cost 줄이기(2) (0) | 2021.11.28 |
Chrome과 안드로이드 기기를 유선/무선 연결하기 (0) | 2021.11.23 |
safari inset, intersection observer 크로스 브라우징하기 (0) | 2021.11.18 |
css animation steps로 Spinner 구현하기 (0) | 2021.11.17 |