고티켓-react 컴포넌트 라이브러리 만들기(3)-rollup
2편까지 우리는 storybook으로 컴포넌트 작업환경을 만들고 ,husky로 협업의 기반 커밋룰을 만들었다.
이제는 빌드를 해야할때다. 스토리북도 빌드가 가능하고, 리액트도 빌드가 가능하고.. 우리가 만든 컴포넌트들도 빌드를해야 배포를 하든 뭘 하든 할수 있다. 그 빌드에 대해서 알아보도록하자.
시리즈다 2편 먼저 첨부해두겠다.
우린 create-react-app 하면 웹팩 세팅이 아주 끝내주게 해서 들어온다. 물론 필자도 eject 명령어를 실행시킨적은 한번 뿐이지만 결국 웹팩이란걸 통해서 빌드와, 개발단계에서의 핫 로딩? 을 할수가 있는것이다. rollup도 마찬가지다. 하지만 rollup을 쓰는이유는 babel을 통해 commonjs 모듈로 바꾸는것 외에도 import,export(es6) 로 빌드를 할수 있다.
먼저 이글을 보고 어떤것을 얻어갈 수 있는지 적어놓겠다.
- javascript 기반 리액트 컴포넌트를 빌드하는 방법
- css ( styledcomponent,sass 아님) 기반 rollup 하는 방법
- rollup을 사용, 설정하는 방법
- 부가적인 reset.css 넣기
rollup
간단하게만 생각하자 빌드할때 설정을 해주고 빌드해서 아웃풋 파일을 만드는 것이다. 아래 레포를 참고를 많이 했다.
롤업은 기본 롤업 과 롤업의 훅을 통한 여러가 플러그인 들이있다. 우선 기본적인 롤업의 구조의 대해서 알아보자.
간단하다. 롤업을 실행시키면된다.
package.json 에 스크립트 ( 아무거나 명령어나 상관없다 난 publish) 를 추가 하고
"scripts": {
"publish": "rollup -c",
}
package.json 이 위치한 디렉토리에 rollup.config.js 파일을 만들자. 틀은 위에 올려진 레포의 파일을 확인해 보면 좋을것 같고.
여기서는 고티켓 프로젝트에서 설정했던 플러그인, config 설정 방식에 대한 ( 중요한 부분들만 알아보도록 하자)
plugin
import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import url from '@rollup/plugin-url';
import image from '@rollup/plugin-image';
import replace from '@rollup/plugin-replace';
import filesize from 'rollup-plugin-filesize';
import css from 'rollup-plugin-css-only';
import svgr from '@svgr/rollup';
import del from 'rollup-plugin-delete';
// package.json 에 main , module 등 설정해논 경로가 있음
import pkg from './package.json';
rollup.config.js 에서 import하는 플러그인 , 설정들
const INPUT_FILE_PATH = 'src/index.js';
const PLUGINS = [
// 바벨로 es5 으로 변환.
babel({
babelHelpers: 'runtime',
exclude: 'node_modules/**'
}),
commonjs(),
resolve(),
css({ output: 'gosrockStyle.css' }),
replace({
values: {
"import 'reset.css';": ''
},
delimiters: ['', '']
}),
filesize(),
url(),
svgr({ icon: true }),
image(),
del({
targets: 'dist/*.svg',
verbose: true,
hook: 'writeBundle',
runOnce: false
})
];
플러그인 모음
INPUT_FILE_PATH
번들링을 하고싶은 파일의 첫 시작점이다. 고티켓 스토리북 레포에서는 스토리북 내에서 리액트 컴포넌트를 만들고 그 컴포넌트들을 export 시켜야한다.
위 예시처럼 src/index.js 에서 만든 컴포넌트를 임포트 시켜서 바로 export 시켜주고 있다. 여기서 주의할점은 자동완성으로 임포트를 시켜버리면 jsx 확장자가 뒤에 안붙는 경우가 있는데 .jsx 확장자를 꼭 붙여줘야한다는 것이다. vscode는 알아먹지만, rollup이 못알아먹는다.
bable
우리는 es 모듈과 commonjs 기반으로도 번들링을 해야하기 때문에 babel 플러그인을 사용한다. 이때 bableHelper을 runtime 속성으로 지정하라는 공식문서의 추천사항이있는데, 번들링된 결과물에 외부참조(externel) 을 bable runtime 패키지를 지정해줘야한다. 뒤에 export config 단에서 자세히 설명하겠다.
commonjs , resolve
둘을 같이 다니는 친구라고 생각하면 좋을것같다. es6 문법으로 작성한 리액트 컴포넌트만 있으면 상관없는데 , 다른 디펜던시 가령 qrcode 를 그려주는 리액트 패키지를 임포트 한 상황에서, 내 번들에 qrcode.react 를 포함 시키고 싶다면. resolve를 시켜줘야한다.
import 는 require 로 변환되고 require은 친절하게도 node_modules에서 파일을 찾아서 임포트를 시켜준다. 하지만 우리는 번들링 즉 우리의 소스가 남이 쓰게 끔 해야한다. 번들링 된 결과물에는 node_modules파일이 없으므로 제 3자가 우리의 패키지를 디펜던시로 추가했다고 가정했을 때 그사람의 디팬던시에 qrcode.react 가 없다면 오류가 날것이다. 그래서 번들링된 결과물에도 qrcode.react를 넣어줘야 하는데 그때 쓰는것이 resolve이다. 그리고 그러한 파일을 끌어오기 위한 commonjs가 필요하다.
css , replace
src/index.js 에 export된 파일들을 올라가면서 css가 임포트가 된 컴포넌트들의 css 파일들을 한데 모아준다. 우리는 reset.css ( 브라우저의 기본 패딩등 속성들을 없애주고 폰트설정)을 기본 설정(src/index.js 에서 임포트중)으로 사용중이고 해당파일을
위의 예시처럼 ( antd 와 비슷한느낌) 으로 명시적으로 css 파일을 임포트 하게 끔 하고싶었다. 또한 번들링된 결과물의 css 파일은 reset.css 와 모든 css 파일을 합친 gosrockStyle.css 파일이므로 src/index.js 에서 storybook 프로젝트의 없애고 싶었던 기본 브라우저 스타일들이 그대로 번들링되어 import "./reset.css" 으로 적히게 되면, dist 폴더(번들링된 결과물) 폴더에는 reset.css 파일이 없으므로 오류가 난다. 그래서 해당 문구를 빌드 전에 replace로 없애주었다.
filesize
따로 설명 해줄게 없는 그냥 번들링된 결과물의 사이즈를 표현해주는 유틸형 플러그인 이다.
url , svgr ,img
이미지와 svg파일들을 같이 번들링 하기위한 플러그인이다. react의 svg 임포트 방식을 사용하게되면 cjs ,esm 파일 형식에 포함이 되버리니 파일이 왜 안옮겨지냐고 궁금해 하진 말자.
del
qrcode.react 를 peerdependency로 추가하고 나서 이상하게 빌드 할때마다 dist 폴터 내부에 svg 파일이 끼어들어갔다. 지워주기 위한 플러그인이고, hook 옵션을 통해 빌드의 어느단계에서 작동시킬지 정할 수 있는데 , rollup hook 엔 굉장히 많은 것들이있었다. buildEnd 로 기술해도 svg파일이 남아있어 , 아예 writeBundle 훅으로 했다. 순서상 거의 끝단계에서 지워주는 것이다.
export config
const EXTERNAL = ['react', 'react-dom', 'prop-types', 'qrcode.react'];
const CJS_AND_ES_EXTERNALS = EXTERNAL.concat(/@babel\/runtime/);
const OUTPUT_DATA = [
{
file: pkg.module,
format: 'es'
},
{
file: pkg.main,
format: 'cjs'
}
];
const config = OUTPUT_DATA.map(({ file, format }) => ({
input: INPUT_FILE_PATH,
output: {
file,
format
},
// runtime 으로 바벨 플러그인 설정
// https://github.com/rollup/plugins/tree/master/packages/babel#babelhelpers
// 익스터널에 적용해야 임포트 시켰을때 해당 임포트 시킨사람의 로컬에서 dependency를 찾아 임포트함
external: ['cjs', 'es'].includes(format) ? CJS_AND_ES_EXTERNALS : EXTERNAL,
plugins: PLUGINS
}));
export default config;
플러그인 대해 기술한뒤에 , 설정을 export 시켜줘야하는데, bable/runtime 을 external로 무조건 추가한것과 기본 EXTERNAL 에 대해 보면 될것 같다. 기본적인 output 파일은 cjs ,es 포맷이고 , external 은 package.json의 peerdependency와 연관이있다. 즉 번들링할 결과물에 다른 패키지를 포함시키고 싶지 않은것들을 기술해 주는것이다 . 앞의 resolve와는 반대되는 개념이다.
추가적으로 주석의 url에 들어가면 bablehelpers의 경우 externel로 둬라고 규정하고있는데 es를 cjs로 변환하면서 내부적으로 바벨 핼퍼 함수들을 계속 생성하기 때문에 애초에 그냥 외부 디팬던시로 두는 것이다. 옵션을 빼고 빌드해보면 알게될것이다.
( pkg.main , module은 package.json 에 아웃풋 디렉토리를 "main": "dist/index.cjs.js", "module": "dist/index.esm.js", 와 같은 형식으로 기술 해 두었다)
이렇듯 롤업을 사용해야하는 이유와 , 플러그인을 통해서 롤업을 설정하는 방식 에 대해 알아보았다. typescript나 sass , styledcomponents 를 사용하는경우 해당 플러그인도 다 있으니 알아보시길 권장드린다. 다음번엔 기회가 된다면 typescript로 스토리북 과 index.d.ts 파일을 꼭 생성해 보고싶다.
package.json script 에서 설정했었던 publish 명령어를 실행시키면 dist 폴더 내부에 생성이 되는것을 알 수 있다. 전체적인 그림이 궁금하다면 아래 레포에서 참고를 해보면 될것같다.