rollup 환경 구축하기
- 리액트 컴포넌트 배포를 위해 환경을 구축하며 작성한 내용이어서 배포를 염두에 두고 작성한 부분이 많습니다. 😄
rollup
을 이용한 환경을 구축합니다. rollup
의 대부분의 기능은 webpack
으로도 물론 가능합니다. (오히려 webpack
이 더 편한 부분도 있지 않나 하는 생각도 들었습니다. ) 그렇지만, rollup
을 사용하면 cjs
, esm
모듈을 쉽게 구축할 수 있습니다. 또, 라이브러리를 배포하기 위한 환경 구성에서 레퍼런스가 다른 번들러보다 많았다는 점도 좋았습니다.
체크리스트
우리는 rollup
을 사용해 어떤 환경을 구축해야 할까요? 저는 간단히 다음과 같이 생각하며 시작했습니다.
- javascript (es6이상 문법에 대한 트랜스파일링)
- react, ts
- jest로 테스트
- cjs, esm 모듈로 구분해 배포할 수 있을 것
javascript
rollup
은 웹팩과 비슷한, 모듈 번들러 입니다. 다만, 바벨의 기능을 rollup-config
에서 간단히 적용할 수 있습니다.
yarn add -D rollup rollup-plugin-babel @babel/core @babel/preset-env
-
@bable/core
: 바벨의 기본적인 기능을 가지고 있습니다.es6
자바스크립트를 트랜스파일링 합니다. -
@babel/preset-env
: 이를 통해, 다양한 환경들에 특정시킬 수 있습니다. (특정 브라우저, 또는 비율로 )module.exports = { "presets": [ [ "@babel/preset-env", { "targets": ">= 0.25%, not dead, ie >= 10", "useBuiltIns": "entry", // 이 부분은 usage로 바꾸면 실제 사용하는 코드만을 변환합니다. "corejs":3 } ] ] } // 위와 같은 방식으로, 타겟을 지정해 사용할 수 있습니다.
-
@bable/cli
: 커맨드 인터페이스로, 바벨을 실행시킵니다. 우리는 rollup을 사용해 실행할 것이므로, 따로 설치하지 않았습니다. 대신, 아래로 실행합니다. -
@rollup/plugin-babel
:rollup
에서babel
을 사용할 수 있도록 하는 플러그인입니다.
위를 설치해주고, rollup.config.mjs
를 작성해 주겠습니다.
(Error: Node tried to load your configuration file as CommonJS even though it is likely an ES module
오류를 쉽게 해결하고자, .mjs
로 작성했습니다. )
// rollup.config.mjs import babel from '@rollup/plugin-babel'; export default { input: './src/index.js', output: [ { file: './dist/esm/index.js', format: 'es', sourcemap: true, }, { file: './dist/cjs/index.js', format: 'cjs', sourcemap: true, }, ], // input, output을 설정해주고 우리는 esm, cjs를 모두 생성하기 위해 위처럼 진행해줍니다. plugins: [ // 바벨 트랜스파일러 설정 babel({ presets: [ [ '@babel/preset-env', // { // targets: '>= 0.25%, not dead, ie >= 10', // useBuiltIns: 'usage', // corejs: 3, // 속성을 넣을 수 있어요! 저는 일단 제거했습니다. // }, ], ], }), ], }; // babel을 따로 파일로 관리할 수 있습니다. // 저는, 한 파일에 몰아 두는게 rollup의 컨셉이라 생각해 위처럼 진행했씁니
이제는, 이 빌드를 진행하게 되는 pacakge.json
들을 수정해 줍시다.
// package.json { "main": "dist/cjs/index.js", // csj를 받아옵니다. require에 대응됩니다. "module": "dist/esm/index.js", // exm을 받아옵ㄴ디ㅏ. import에 대응됩니다. "devDependencies": { "@babel/core": "^7.20.2", "@babel/preset-env": "^7.20.2", "@rollup/plugin-babel": "^6.0.2", "rollup": "^3.3.0", "rollup-plugin-babel": "^4.4.0" }, "scripts": { "build": "rollup -c", "watch": "rollup -cw" } }
src
폴더에 간단한 파일을 두고, 빌드시켜보면 dist
폴더에 번들이 생성된 것을 확인할 수 있습니다.
react
react
의 jsx
를 읽기 위해 babel
을 사용합니다.
yarn add -D @babel/preset-react yarn add -P react react-dom // 우리는 , react를 사용하지 않습니다. 받는 쪽에서 필요한 디펜던시를 제공하기 위해 // peer로 설치합니다.
-
@bable/preset-react
는react - jsx
을 트랜스파일링합니다.여러가지 옵션 중
runtime
을 수정할 필요가 있습니다.classic | automatic
, defaults toclassic
classic, automatic
설정이 있는데,automatic
으로 설정하지 않으면 트랜스파일 과정에서 자동으로 제공되는import react -
가 생성되지 않습니다. 이 부분을automatic
으로 설정했습니다.
// rollup.config.js export default { input: "./src/index.jsx", // 이제 jsx를 받으니까 수정합시다! ... plugins: [ babel({ presets: [ "@babel/preset-env", ["@babel/preset-react", { runtime: "automatic" }], ], }), ], };
역시 index.jsx
에 간단한 리액트 컴포넌트를 작성하고, 빌드를 해 보면 잘 빌드가 되는 것을 확인할 수 있습니다.
const Button = () => { return <button>버튼</button>; }; export default Button; // cjs import { jsx } from 'react/jsx-runtime'; var Button = function Button() { return /*#__PURE__*/jsx("button", { children: "\uBC84\uD2BC" }); }; export { Button as default }; //# sourceMappingURL=index.js.map
typescript
이제 타입스크립트를 지원해야 합니다. 그런데, rollup
에서 제공하고 있는 typescript
플러그인에는 문제가 있습니다. dist
루트에 index.d.ts
를 생성하지 못합니다. 여기 에 그 해결법이 존재했지만 제대로 적용되지 않았고 (모노레포를 만들 때에 사용해야만 했고 경로관련 문제가 해결하기 어려웠습니다.) 그래서 기존 bable
을 이용했습니다.
yarn add -D @babel/preset-typescript typescript tslib @babel/plugin-transform-runtime @types/react @types/react-dom
- 다른 플러그인은, 보시면 알 수 있지만
@babel/plugin-transform-runtime
플러그인은 다음과 같습니다.bableHelpers: 'runtime'
과 함께 쓰이며 전역에 폴리필을 만들지 않고 패키지에서 폴리필을 가져옵니다. 이 부분을 적용하지않으면rollup
에서 오류가 발생해 추가했습니다.
그리고, rollup.config.js
를 수정합니다.
... import babel from "@rollup/plugin-babel"; const extensions = [".js", ".jsx", ".ts", ".tsx"]; export default { input: "./src/index.ts", output: [ { file: "./dist/esm/index.js", format: "es", sourcemap: true, }, { file: "./dist/cjs/index.js", format: "cjs", sourcemap: true, }, ], watch: { exclude: "node_modules/**", include: "./src/*", }, plugins: [ babel({ presets: [ "@babel/env", ["@babel/preset-react", { runtime: "automatic" }], "@babel/preset-typescript", ], plugins: ["@babel/plugin-transform-runtime"], babelHelpers: "runtime", exclude: ["node_modules/**", "dist"], extensions, }), ], };
추가적인 rollup plugin
yarn add -D @rollup/plugin-node-resolve @rollup/plugin-commonjs rollup-plugin-peer-deps-external
rollup/plugin-node-resolve
:node_modules
에서 써드파티 모듈을 사용할때 사용합니다.rollup-plugin-peer-deps-external
: 피어 디펜던시로 설치된 라이브러리를 번들링에 포함하지 않습니다.import
를 통해 불러옵니다.rollup-plugin-commonjs
:cjs
형태로 이루어진 모듈의 코드를es6
로 포함하여 결과물에 포함합니다.
jest
jest
에도 ts
를 사용하기 위해 ts-jest
를 사용했습니다.
yarn add -D @testing-library/dom @testing-library/jest-dom @testing-library/react @testing-library/user-event jest jest-environment-jsdom @types/jest ts-jest ts-node @types/jest
import type { JestConfigWithTsJest } from 'ts-jest'; const jestConfig: JestConfigWithTsJest = { preset: 'ts-jest', testEnvironment: 'jsdom', moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], moduleNameMapper: { '^@src/(.*)$': '<rootDir>/src/$1', }, watchPathIgnorePatterns: ['node_modules', 'dist'], }; // jest.config.ts
그리고, 간단한 컴포넌트 테스트를 진행해보면 잘 진행 되는 것을 확인할 수 있습니다.
한가지 문제가 있는데, 여기서 react, react-dom
을 디펜던시로 추가해야 합니다. dom
을 테스트 하기 위한 tesing-library/dom
이 react-dom
에서 몇가지 유틸리티를 가지고 오는 메세지가 출력되어 추가할 수 밖에 없었습니다. 더 좋은 해결방법이 있었을지.. 🥲
이로써 환경설정은 마무리 했습니다. 저는 이를 기반으로 lerna
+ yarn workspace
를 이용해 리액트 컴포넌트 라이브러리를 모노레포로 구성해보려고 합니다.