[npm] npm 패키지 생성부터 배포까지!

eunniverse·2024년 7월 24일

글쓰게된 계기

npm 패키지 만들어야지! 했던 적이 있다. 그러나 다른 프로젝트하느라 바쁘다는 핑계로 미뤘는데, nextJS로 개인 플젝을 하다가 오 이건 라이브러리로 만들어도 되겠는데? 하는 간단한 컴포넌트가 있어서 이틀만에 후다닥 만들었다! 물론 rollup 은 초면이라 매-우 헤맸지만 배운 것도 정-말 많아서 쓰게된 글이다 ㅎㅎ

어떤 라이브러리를 만들었나?

우선 내가 만든 라이브러리는 react-scroll-header-loading 이다. javascript 의 scroll event 를 기반으로 scroll 위치만큼 상단 로딩바를 움직이는 컴포넌트이다.

자세한건 여기 확인하면 된당 ㅎㅎ (많이 써주셨으면 좋겠다......)

npm 패키지 생성부터!

1. NPM 정보 설정

cd npm-project
npm init

npm 정보를 설정하는 명령어를 입력하면 패키지명, 버전, 패키지 설명, git url, 작성자, license 를 설정한다. 입력한 데이터로 pakcage.json이 생성된다.

package name: (npm_install_test)
version: (1.0.0)
description:
git repository:
author:
license: (ISC)

2. 라이브러리 개발 후 NPM 로그인

npm login

NPM에 라이브러리를 배포하려면 로그인해야한다. 위의 명령어를 입력하면 OTP를 입력하라는 화면으로 이동하고 메일로 받은 OTP 번호를 입력하면 된다!

3. NPM 배포

npm publish

위의 명령어를 입력하면 npmjs.com 에서 본인 계정 > packages 에 배포한 라이브러리를 확인할 수 있다!

webpack vs rollup vs esbuild

라이브러리 배포를 위해 어떤 빌드도구를 사용할까 고민이 되었다. 그래서 가장 많이 사용하는 3가지 툴을 비교해보았다.

Webpack

  • 특징
    1. 모듈 번들링
      JavaScript, CSS, 이미지 등 리소스를 하나의 번들 파일로 만든다.
    2. 플러그인과 로더
      광범위한 플러그인과 로더 시스템을 지원하여 복잡한 빌드 요구 사항을 처리할 수 있다.
    3. Code Splitting
      동적으로 필요한 코드만 로드할 수 있는 기능을 지원하여 성능 최적화를 할 수 있다.
    4. 많은 플러그인과 로더가 존재하고, 복잡한 요구사항을 처리할 수 있다.
    5. 설정 파일이 다소 복잡한 편이며 빌드 속도가 느린 편이다.

Rollup

  • 특징
    1. ES 모듈을 사용하여 트리 쉐이킹과 같은 기능을 지원한다.
      📌 트리 쉐이킹 : 사용되지 않는 코드를 제거하여 최적화된 번들을 생성하는 방식
    2. 번들 크기가 작고 빌드가 빠르다.
    3. 설정 파일이 간단하다.
    4. CommonJS 모듈과의 호환성 문제를 처리가 필요할 수 있다.
    5. Webpack 대비 플러그인 생태계가 작아서 직접 구현을 해야할 수도 있다.

ESBuild

  • 특징
    1. Go 언어로 작성되어 있으며, 병렬 처리를 통해 빌드가 빠르다.
    2. ES 모듈과 CommonJS 모듈을 지원한다.
    3. 설정이 간단하고 직관적이다.
    4. 비교적 새로운 도구라서 생태계가 작은 편이며, 문서가 부족하다.
    5. 최신 JS 기능을 지원한다. (빠르게 발전하고 있는 편..)

언제 사용하면 좋을까?

복잡한 애플리케이션 => Webpack 추천👍
라이브러리 or 패키지 => Rollup 추천👍
빠르게 개발해야한다 => ESBuild 추천👍

npm 라이브러리를 개발하는 나는 Rollup을 사용하기로 결정했다!

rollup 사용해서 빌드하기

1. rollup, babel 설치 및 설정

빌드를 위해 rollup을, ES5로 변환하기 위해 babel을 설치했다. 설치 스크립트는 다음과 같다.

// 최신 JavaScript 코드를 구형 브라우저나 환경에서도 동작하도록 변환
npm install --save-dev @babel/core

// 브라우저 호환성을 보장하기 위해 필요한 변환을 자동으로 설정
npm install --save-dev @babel/preset-env

// JSX와 기타 React 관련 최신 JavaScript 기능 변환 
npm install --save-dev @babel/preset-react

// 드 크기 감소 및 폴리필 관리
npm install --save-dev @babel/plugin-transform-runtime

// Rollup 빌드 프로세스에서 Babel을 통합하여 ES6/7 코드를 변환
npm install --save-dev @rollup/plugin-babel 

// Node.js 모듈이나 기존 CommonJS 모듈을 사용하는 프로젝트에서 Rollup을 사용하도록 처리하는 모듈
npm install --save-dev @rollup/plugin-commonjs 

// Rollup이 Node.js 모듈을 해석하고 가져올 수 있도록 하는 플러그인
npm install --save-dev @rollup/plugin-node-resolve 

// javaScript와 기타 자산을 효율적으로 번들링하는 모듈 번들러
npm install --save-dev rollup 

// Peer dependencies를 외부 모듈로 처리하는 Rollup 플러그인
npm install --save-dev rollup-plugin-peer-deps-external

// Terser를 사용하여 JavaScript 코드를 최소화(minify)하는 Rollup 플러그인
npm install --save-dev rollup-plugin-terser

2. rollup.config.js 추가

// // Node.js 모듈을 해석하고 가져오기 위한 플러그인
import resolve from '@rollup/plugin-node-resolve';

// CommonJS 모듈을 ES 모듈로 변환하기 위한 플러그인
import commonjs from '@rollup/plugin-commonjs';

// Babel을 Rollup과 통합하여 코드를 변환하기 위한 플러그인
import babel from '@rollup/plugin-babel';

// Peer dependencies를 외부 모듈로 처리하기 위한 플러그인
import peerDepsExternal from 'rollup-plugin-peer-deps-external';

// Terser를 사용하여 코드를 최소화하기 위한 플러그인
import { terser } from 'rollup-plugin-terser'; 

export default {
    input: './index.js', // 번들링을 시작할 파일 지정 : 번들을 생성할 입력 파일

    // 출력 파일 설정
    output: {
        file: 'dist/index.esm.js', // 번들 파일이 저장될 경로
        format: 'es', // 번들 파일의 모듈 형식 지정 : ES 모듈 형식으로 출력
        sourcemap: true // 소스 맵 생성하여 디버깅을 쉽게 하도록 설정 
      					//(소스 맵을 생성하여 디버깅 시 원본 소스 코드와 매핑)
    },

    // 외부 종속성 설정
    external: ['react'], // 번들에 react 제외

    plugins: [
        peerDepsExternal(), // peerDependencies를 외부 모듈로 처리하여 번들 크기를 줄임
        resolve(),  // Node.js 모듈을 해석하고 가져오기 위한 플러그인

        // Babel을 사용하여 코드 변환
        babel({
            exclude: 'node_modules/**', // node_modules 는 변환 대상 제외
            babelHelpers: 'bundled', // Babel 헬퍼 함수를 번들에 포함
            presets: ['@babel/preset-env', '@babel/preset-react'] // Babel 프리셋 설정 
          // @babel/preset-env는 최신 JavaScript 기능을 변환하고, @babel/preset-react는 JSX를 변환함
        }),

        // CommonJS 모듈 -> ES 모듈로 변환
        commonjs(),

        // JavaScript 코드 최소화 
        terser()
    ]
};

3. package.json 에 scripts 추가

...
"scripts": {
    // rollup.config.js 를 사용하여 빌드한다는 의미
  	"build": "rollup -c"
  },
...

4. .babelrc 설정

{
  // Babel이 사용할 프리셋 설정
  "presets": [
    "@babel/preset-env", // 최신 JavaScript 기능을 변환하기 위한 프리셋
    "@babel/preset-react" // JSX와 기타 React 관련 코드를 변환하기 위한 프리셋
  ],
  // Babel이 사용할 플러그인 설정
  "plugins": [
    "transform-commonjs", // CommonJS 모듈을 변환하기 위한 플러그인
    ["@babel/plugin-transform-runtime", {
      // Babel의 헬퍼 함수를 번들에 포함하지 않고, 별도로 모듈화하여 재사용 가능하게 함
      "helpers": false,
      // async/await 등의 기능을 변환하기 위해 regenerator를 사용하도록 설정
      // ES6 이상의 비동기 기능을 ES5 환경에서 사용할 수 있게 해줌
      "regenerator": true
    }]
  ]
}
❓ babel helper 의 역할

Babel이 JavaScript 코드를 변환할 때 공통적으로 필요한 기능들을 제공한다.

🔍 다른 객체로 확장하거나 병합할 때 사용하는 _extends
🔍 ES6 클래스 생성자를 변환할 때 사용하는 _classCallCheck
🔍 ES6 클래스의 정적 및 인스턴스 메서드를 정의할 때 사용하는 _createClass
🔍 부모 클래스와 자식 클래스 간의 상속 관계를 설정할 때 사용하는 _inherits
🔍 객체 디스트럭처링에서 사용하는 _objectWithoutProperties 가 있다.

❓ .babelrc 란??

Babel의 설정 파일로, Babel이 자바스크립트 코드를 변환할 때 사용할 설정을 지정한다. 이 파일은 프로젝트의 루트 디렉토리에 위치하며, JSON 형식으로 Babel 프리셋(presets)과 플러그인(plugins)을 정의한다. .babelrc를 사용하면 프로젝트에 필요한 Babel 설정을 쉽게 관리할 수 있다.

5. 빌드하기

npm run build

위의 명령어를 통해 빌드를 하면 output에 정의한대로 dist 아래 빌드된 파일이 생성된다. 그러면 이제 테스트를 해보자!

6. 테스트하기

npm 에 배포하기 전에 작성한 모듈을 로컬 환경에서 테스트할 수 있다.
개발이 완료된 프로젝트 경로에서 링크를 생성하여 다른 프로젝트에서 해당 패키지를 사용하면 된다.

npm link

개발이 완료된 프로젝트 경로에서 링크를 생성한다.

🧷 6.2) 다른 프로젝트에서 사용하기
// npm link {패키지명}
npm link react-scroll-header-loading

이렇게하면 아주 간편하게 테스트를 할 수 있다!!

Today I Learned!

역시 삽질을 하면 배우는 게 많다. 이번 프로젝트를 하면서도 정-말 많이 배웠다. 그 내용을 간단하게 정리하려 한다.

🧷 export default 를 n번 사용하는 방법

JavaScript 모듈에서는 파일당 하나의 default export만을 지원한다. 따라서 만약 n개 이상의 default export가 필요하다면, 아래와 같은 방법으로 해결해야 한다.

1. 객체를 사용하여 여러 export를 내보내기

function ComponentA() {
  // ComponentA code
}

function ComponentB() {
  // ComponentB code
}

export default {
  ComponentA,
  ComponentB,
};

2. Named Export 사용하기

// src/myModule.js

export default function DefaultComponent() {
  // DefaultComponent code
}

export function NamedComponentA() {
  // NamedComponentA code
}

export function NamedComponentB() {
  // NamedComponentB code
}
// src/useMyModule.js
import DefaultComponent, { NamedComponentA, NamedComponentB } from './myModule';
🧷 Element type is invalid: ~~~...

위의 오류를 모두 표현하자면 다음과 같다.

expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

나의 친구 chatGPT 에게 물어보니 React 컴포넌트를 사용하려할 때 발생한다했고, 나의 경우에는 import 와 export 가 불일치했기 때문에 문제가 되었다. named export 와 default export 중 하나만 사용을 해야하는데, 나는 둘다 사용하고 있어서 named export 로 통일해주었다.

🧷 Warning: React does not recognize the ~~~...

Warning: React does not recognize the loadingBarTopColor prop on a DOM element.

위의 warning 은 prop이 HTML 표준이 아니라서 나는 경고 메시지였다. 그래서 컴포넌트 구조를 변경했고, 문제가 해결되었다!


const LoadingBar = ({ loadingBarTopColor = 'red' ... }) => {
  const loadingBarStyle = { 
 	 // 여기서 loadingBarTopColor 사용
     ...
  };
    
  return <div style={loadingBarStyle} {...props} />;
}

만들어본 후기

진짜 정말 재밌는 프로젝트였다. 다운로드 수가 올라가는 것도 보이고, 내가 만든걸 누군가 쓴다라고 생각하니까 성취감이 컸다. 스스로 개발을 좋아하는가에 대한 물음을 던질 때가 많은데, 이번 경험을 통해 느꼈다. 나는 개발을 좋아하고, 계속해서 무언가를 만들고 싶다. 또 다른 걸 한번 만들어봐야징!!!!!


✔️ 혹시나 header loading bar 가 필요하다면 여기로 들어가서 한번씩 써봐주세요..!
❤️피드백도 대환영입니다❤️

profile
능력이 없는 것을 두려워 말고, 끈기 없는 것을 두려워하라

0개의 댓글