기존에 있던 포트폴리오를 리액트 버전으로 바꾸면서 컴포넌트 라이브러리를 만들어보기로 결심하였다.
나의 포트폴리오는 react, gsap, locomotive-scroll, scss를 사용하였고 이와 관련된 설정을하여 npm 패키지로 만들어서 배포하는 과정을 기록해본다😈
+ TypeScript 추가_
$ npm init
프로젝트 디렉토리를 생성하고 npm 패키지를 초기화해준다.
react는 import하여 사용하지 않고 package.json
에 peerDependencies를 추가하여 내가 만드는 라이브러리를 사용하는 쪽에서 설치한 리액트를 사용하겠다고 명시해야 한다.
{
"peerDependencies": {
"gsap": "^3.12.5",
"locomotive-scroll": "^4.1.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
}
}
peerDependencies은 패키지가 특정 버전의 라이브러리와 호환되도록 명시한다.
$ npm i -D @babel/core @babel/preset-env
$ npm i -D @babel/preset-react
jsx 파일을 해석할 수 있도록 바벨 관련 패키지를 설치한다.
@babel/preset-env
최신 JavaScript 기능을 사용하는 코드를 구형 환경에서도 동작하도록 변환
@babel/preset-react
React JSX 문법을 JavaScript 코드로 변환
npm i -D rollup
npm i -D @rollup/plugin-babel
npm i -D @rollup/plugin-commonjs
npm i -D @rollup/plugin-node-resolve
npm i -D rollup-plugin-peer-deps-external
npm i -D rollup-plugin-terser
Rollup은 자바스크립트 번들러의 한 종류로 ES6 모듈로 빌드가 가능하여 필요한 부분만 가져올 수 있다는 장점이 있다.
@rollup/plugin-babel
rollup에서 babel 사용할 수 있도록
@rollup/plugin-commonjs
commonjs 형태의 모듈을 es모듈로 변환
@rollup/plugin-node-resolve
node_modules 폴더에서 모듈을 찾아서 번들링
rollup-plugin-peer-deps-external
peerDependencies로 정의된 패키지를 번들링에서 제외
rollup-plugin-terser
rollup 번들을 최소화(압축)하는 데 사용되는 플러그인
npm i -D node-sass
npm i -D rollup-plugin-postcss
node-sass
scss를 css로 컴파일
rollup-plugin-postcss
rollup 번들링 과정에서 css 파일 처리
npm i -D typescript
npm i -D tslib
npm i -D @rollup/plugin-typescript
package.json
파일에 아래 내용을 작성하였다.
{
"name": "프로젝트명",
"version": "1.0.0",
"description": "프로젝트 설명",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"scripts": {
"build": "rollup -c"
},
"keywords": ["키워드"],
"author": "소유자",
...
"main": "dist/cjs/index.js"
빌드된 CommonJS 버전의 JavaScript 파일이 위치하는 경로
require('your-package')와 같은 방식으로 패키지를 사용할 때
"module": "dist/esm/index.js"
빌드된 ES 모듈 버전의 JavaScript 파일이 위치하는 경로
import Button from 'your-package'와 같은 방식으로 패키지를 사용할 때
"build": "rollup -c"
Rollup을 사용하여 빌드 실행
루트경로에 .babelrc
파일을 생성하여 아래 내용을 작성하였다.
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
Babel 프리셋을 사용하여 JavaScript 코드를 변환하도록 설정한다.
TypeScript 사용한다면
"@babel/preset-typescript"
를 추가해주면 된다.
{
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"]
}
루트경로에 rollup.config.js
파일을 생성하여 아래 내용을 작성하였다.
import babel from "@rollup/plugin-babel";
import commonjs from "@rollup/plugin-commonjs";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import postcss from "rollup-plugin-postcss";
import { terser } from "rollup-plugin-terser";
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import pkg from "./package.json";
export default {
input: "src/index.js", // 기준 파일
output: [
{
file: pkg.main, // package.json "main"에 설정해준 경로
format: "cjs", // Commonjs
sourcemap: true,
},
{
file: pkg.module, // package.json "module"에 설정해준 경로
format: "esm", // ES Module
sourcemap: true,
},
],
external: ["react", "react-dom", "gsap", "locomotive-scroll"], // 외부모듈로 지정
plugins: [
nodeResolve(),
commonjs(),
babel({
babelHelpers: "bundled",
exclude: "node_modules/**",
presets: ["@babel/preset-env", "@babel/preset-react"],
extensions: [".js", ".jsx"],
}),
postcss({
extract: false, // CSS가 JavaScript 파일에 인라인으로 포함
modules: false, // CSS Modules 기능이 비활성화
use: ["sass"], // .sass 파일을 CSS로 컴파일
extensions: [".css", ".scss"], // Rollup이 .css와 .scss 파일을 처리
}),
terser(),
peerDepsExternal(),
],
};
TypeScript를 사용한다면
...
import typescript from "@rollup/plugin-typescript";
...
export default {
...
plugins: [
...
babel({
babelHelpers: "bundled",
exclude: "node_modules/**",
presets: [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript",
],
extensions: [".js", ".jsx", ".ts", ".tsx"],
}),
typescript({
tsconfig: "./tsconfig.json",
}),
...
],
};
typescript를 import 해주고 위와 같이 타입스크립트 설정을 추가해주면 된다.
전체코드 : https://github.com/hellojoyworldz/react-portfolio-component-library/blob/1.1.1/rollup.config.js
TypeScript 컴파일러가 프로젝트를 어떻게 처리할지 설정하는 파일이다.
TypeScript를 사용한다면 root 경로에 추가해줘야 한다.
{
"compilerOptions": {
"target": "ES5",
"module": "ESNext",
"lib": ["DOM", "ESNext"],
"jsx": "react",
"declaration": true,
"declarationDir": "dist/types",
"outDir": "dist/esm",
"strict": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
target
TypeScript 코드를 컴파일할 때 어떤 JavaScript 버전으로 변환할지 지정
module
"ESNext"로 설정했으므로, 최신 ECMAScript 모듈 시스템을 사용
lib
프로젝트에서 사용할 수 있는 라이브러리들을 지정
DOM은 브라우저 환경에서 사용할 수 있는 API를 포함하고, ESNext는 최신 ECMAScript 기능들을 포함
jsx
JSX를 지원하는 옵션
.tsx 파일에서 JSX 문법을 사용할 수 있음
declaration
.d.ts
타입 정의 파일이 생성
컴파일된 JavaScript 코드에 대한 타입 정보를 제공
declarationDir
생성된 .d.ts
파일들이 저장될 디렉토리를 지정
outDir
컴파일된 JavaScript 파일들이 저장될 디렉토리
strict
엄격한 타입 검사를 활성화하는 옵션
moduleResolution
모듈을 어떻게 찾을지를 설정
node로 설정하면 Node.js 스타일의 모듈 해석을 사용하고, 이는 node_modules 디렉토리에서 패키지를 찾는 등의 규칙을 따른다.
resolveJsonModule
.json
파일을 모듈처럼 import할 수 있게 해주는 옵션
esModuleInterop
CommonJS와 ES Module 간의 호환성을 제공하는 옵션
skipLibCheck
라이브러리 파일(.d.ts
파일)의 타입 검사를 건너뜀
이는 빌드 속도를 높이는 데 도움이 될 수 있다.
루트경로에 .npmignore
파일을 생성하여 아래 내용을 작성하였다.
.gitignore 같이 npm 패키지에 등록할 때 필요하지 않은 파일이나 폴더를 설정하면된다.
/node_modules
rollup.config.js
tsconfig.json
.babelrc
(TypeScript를 사용한다면 확장자는 바꿔서 파일을 생성하면 된다!)
rollup.config.js
input에 "src/index.js" 로 설정해주었으니 이 경로가 빌드되는 기준이 된다. 일단 테스트 컴포넌트를 생성해여 build 해보자!
src/TestButton.jsx
import React, { useEffect } from 'react';
import './TestButton.scss';
export const TestButton = ({ label, onClick }) => {
useEffect(() => {
console.log('Button component mounted');
}, []);
return (
<button className="my-button" onClick={onClick}>
{label}
</button>
);
};
import React from 'react'
-> 무조건 꼭 있어야 한다.
리액트훅도 써보고 props도 써보자! 나중에 console.log도 잘 찍히는지 확인해보고
src/TestButton.scss
.my-button {
color:#fff;
border: 0px;
background:red;
}
scss도 사용해야하니까 테스트
src/index.js
export { TestButton } from "./TestButton.jsx";
$ npm run build
빌드를 해보면 rollup.config.js 파일에서 설정한 output 경로에 빌드파일이 생긴다.
(dist 폴더가 생성되면서 cjs, esm 폴더 안에 index.js 파일이 생김. 설정해주기 나름이다.)
$ npm create vite@latest
리액트 프로젝트를 생성한 후 package.json에 패키지를 추가하여 간단한게 테스트 해보면 된다.
package.json
파일에
...
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sample-library": "file:../sample-library"
},
...
"sample-library": "file:../sample-library"
내가 만든 컴포넌트 패키지를 추가하고, file로 경로를 설정해주었다.
$ npm install
그러고 난 다음에 install 해주고 App.js에서 import 하여 테스트를 해본다.
테스트가 끝난 후 진짜 만들고싶은 컴포넌트 라이브러리를 만들어 나갔다.
라이브러리를 완성한 다음 빌드 후 테스트하면 어디가 잘못됐는지 확인하기 어려우므로 컴포넌트를 하나씩 만들고 빌드하면서 테스트하였다.
완성된 컴포넌트 라이브러리 폴더구조는 이러하다.
assets/styles 폴더에 reset, common, variable 스타일을,
Components 폴더에 컴포넌트를 모아두었다.
이미지는 github에 올려 이미지경로를 가져와서 사용하였기 때문에 constants/path.js에 IMG_PATH 변수를 지정해놓고 공통으로 사용하였다.
https://www.npmjs.com/signup
npm 사이트 계정을 만들어야 한다.
금방 만들 수 있다. 쉽다.
(로그인 할 때 마다 이메일로 1회용 비밀번호를 받아야하나보다. 사용하는 이메일 쓰는게 좋을듯)
$ npm login
$ npm whoami
$ npm logout
$ npm login
npm 로그인을 해준다.
(아이디와 비밀번호, 이메일까지 성공적으로 입력하면 이메일로 1회용 비밀번호가 발송된다. 마지막으로 1회용 비밀번호까지 입력해주면 완료)
$ npm whoami
로그인이 성공했다면, 사용자의 아이디가 표시된다.
$ npm logout
만약 로그아웃이 하고싶다면
$ npm publish --access public
$ npm info 패키지이름
$ npm publish --access public
두근두근 패키지 배포를 해보자!
그런데 계속 403 에러가 나는거다. 저와같이 403에러가 난다면..?
package.json에 name을 변경해보세요! 패키지 이름이 중복되서 나타나는 에러였답니다!
$ npm info 패키지이름
나같은 실수를 하지 않으려면 등록된 패키지인지 확인해보는게 좋겠다
내가 배포한 react-portfolio-component-library로 검색해보았다.
이렇게 패키지 정보가 나온다.
없을법한 아무 패키지이름을(sdfesdfsdfwew) 검색해보았다.
'sdfesdfsdfwew@latest' is not in this registry.
레지스트리에 없다고 404 에러가 뜬다.
.npmignore 파일에 .idea를 추가해주지 않아 패키지에 배포가 되어버렸다.
이렇게 컴포넌트 라이브러리를 수정한 후 다시 배포하기 위해선 어떻게 해야할까?
$ npm version patch
$ npm publish
$ npm version patch
1.0.0 -> 1.0.1
(하위 호환성을 유지하면서 버그 수정이 있을 때 증가)
$ npm version minor
1.0.0 -> 1.1.0
(하위 호환성을 유지하면서 새로운 기능이 추가될 때 증가)
$ npm version major
1.0.0 -> 2.0.0
(하위 호환성을 깨는 변경이 있을 때 증가)
$ npm publish
알맞게 버전업 하고 다시 배포해주면 끝!
이렇게 나의 첫번째 npm 패키지 등록이 끝났다 후후
컴포넌트 다시 정리하고 기본값을 설정해주어 최대한 라이브러리처럼 사용할 수 있도록 수정하였다. 사용법 정리도 아직 안했지만 나의 목표는 일단은 삽질 그만하고 빠르게 npm 패키지에 등록하는게 목표였으므로 뿌듯하다 👏
추후에 내가 해결하기 힘들었던 문제들을 어떻게 해결했는지 적어야겠다.
내 react-portfolio-component-library 링크!
깃허브 링크
타입스크립트 적용 전: https://github.com/hellojoyworldz/react-portfolio-component-library/tree/1.0.4
타입스크립트 적용 후 : https://github.com/hellojoyworldz/react-portfolio-component-library
이것은 샘플GIF!