

파이썬 프로젝트를 진행하면서 csv 파일을 만들고 그 파일을 읽고 파싱해서 원하는 데이터를 추출해내는 로직이 있었다.
라이브러리를 배포해보고 싶은 것이 이 프로젝트를 만든 가장 큰 이유이고 파이썬에서 사용했던 로직을 참고삼아 라이브러리로 만들어 보았고 그 과정을 정리해보았다.
mkdir parse-csv-file
cd parse-csv-file
npm init -y
-y 플래그는 프로젝트에 대한 기본 양식 설정을 다 생략하고 default로 만들겠다는 뜻, yes 라는 뜻{
"name": "parse-csv-file",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
}
name - NPM에 배포할 때 사용되는 이름version - 배포할 때 사용되는 버전, Semantic Version 체계 사용type - 값으로module / commonjs 를 사용할 수 있으며 불러오기/내보내기 옵션 설정할 때 사용commonjs = CJS - require/module.exports를 사용하고 동기적으로 동작
module = ESM - import/export를 사용하고 비동기적으로 동작 (Top-level Await 지원)
ESM에서는 CJS를 import 할 수 있지만 CJS에서는 Top-level Await을 지원하지 않기 때문에 ESM을 require 할 수 없다
main - 라이브러리를 사용할 때 기본 진입점. index.js 를 기본 진입점으로 사용license - 라이브러리의 라이센스를 의미keywords - npm에 보여질 키워드author - 만든이description - 라이브러리 설명 exports 필드는 node 12버전에 추가된 필드로 cjs 와 esm 을 동시에 지원
{
"name": "parse-csv-file",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
"exports": {
"./lib": {
"types": "./dist/lib/index.d.ts", // typescript를 사용하는 경우 사용될 파일을 명시한 conditional 필드
"require": "./dist/lib/index.cjs", // cjs 환경에서 사용될 파일을 명시한 conditional 필드
"import": "./dist/lib/index.js" // esm 환경에서 사용될 파일을 명시한 conditional 필드
}
}
}
exports 필드는 기존에 main 필드를 참고해서 라이브러리를 불러왔었는데 esm이나 commonjs 두 개의 방식을 모두 지원하는 경우에는 main에서 두 파일을 모두 적을 수가 없어 나온 필드
exports가 있으면 main, module, types 등보다 exports 필드를 우선시 함.
exports 필드는 모두 . 으로 시작되는 상대 경로로 작성
해당 경로는 라이브러리의 subPath 를 의미하고 그 안의 객체에는 import, require, types, default 같은 conditional 필드 존재
현재의 경우에는 최상위 경로에는 아무것도 없고 lib 폴더 내부에 코드가 존재하므로 ./lib 으로 표시
수정
exports 문에 ./lib 경로로 export를 해줘도 실제 빌드해서 만드는 파일은 dist파일 바로 아래 경로에 있기 때문에 exports를 ./ 경로로 수정
npm i typescript @types/node -D
typescript와 node의 타입을 지원해주는 @types/node 설치
{
"compilerOptions": {
"target": "es6" /* 최신 브라우저는 es6을 대부분 지원한다. */,
"module": "ES6" /* 모듈 시스템을 지정한다. */,
"lib": ["es5", "es6", "dom"] /* 타입스크립트가 어떤 버전의 JS의 빌트인 API를 사용할 건지에 대한 것을 명시해 준다. */,
"declaration": true /* 타입스크립트가 자동으로 타입정의 (d.ts) 파일을 생성해 준다. */,
"outDir": "dist" /* 컴파일된 결과물을 어디에 저장할지에 대한 것을 명시해 준다. */,
"strict": true /* 타입스크립트의 엄격한 모드를 활성화한다. */
"moduleResolution": "bundler" /* 모듈 해석 방법을 지정한다. */
},
"include": ["src/lib/index.ts"] /* 컴파일할 대상을 명시해 준다. */
}
이 후 npm run tsc 로 컴파일을 해도 되지만 build script로 작성
{
"name": "parse-csv-file",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"build": "npm run build:tsc",
"build:tsc": "npm run tsc"
"tsc": "tsc" // npm run tsc 가 동작하지 않을 땐 직접 tsc 입력
},
"keywords": [],
"author": "",
"license": "ISC",
"description": ""
"exports": {
"./lib": {
"types": "./dist/lib/index.d.ts", // typescript를 사용하는 경우 사용될 파일을 명시한 conditional 필드
"require": "./src/index.cjs", // cjs 환경에서 사용될 파일을 명시한 conditional 필드
"import": "./src/index.js", // esm 환경에서 사용될 파일을 명시한 conditional 필드
}
}
}
npm run build 를 실행하면 tsconfig.json에 명시해 준 outDir 경로에 dist 폴더가 생성되고 dist 폴더 안에 index.js 와 index.d.ts 파일이 생성 됨
{
"name": "parse-csv-file",
"version": "1.0.4",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "npm run build:tsc",
"build:tsc": "npm run tsc",
"tsc": "tsc"
},
"keywords": [],
"author": "",
"license": "MIT",
"exports": {
"./lib": {
"types": "./dist/lib/index.d.ts",
"require": "./dist/lib/index.cjs",
"import": "./dist/lib/index.js"
}
}
}
폴더와 컴파일 된 결과물들에 맞춰서 main, types, exports 필드도 변경
컴파일 된 결과물인 dist/index.js 를 main에 넣어줌
exports 필드 안에는 types 필드를 추가해 index.d.ts 파일 명시
import 필드에는 ./dist/lib/index.js 파일 명시
라이브러리 코드를 수정할 때마다 esm, cjs 를 둘 다 고칠 수는 없기 때문에 esbuild 라이브러리를 이용해 한 번에 컴파일
npm i esbuild -D
// build.js
import esbuild from "esbuild";
// 공통으로 사용할 옵션들
// https://esbuild.github.io/api/#build 에서 다양한 옵션들을 확인할 수 있다.
const baseConfig = {
entryPoints: ["src/lib/index.ts"], // 컴파일할 파일
outdir: "dist", // 컴파일된 파일이 저장될 경로
bundle: true, // 번들링 여부
sourcemap: true, // 소스맵 생성 여부
platform: "node" // 코드 내부에서 fs 모듈을 불러와 쓰는데 명시 안하면 빌드 시 에러
};
Promise.all([
// 한 번은 cjs
esbuild.build({
...baseConfig,
format: "cjs",
outExtension: {
".js": ".cjs",
},
}),
// 한 번은 esm
esbuild.build({
...baseConfig,
format: "esm",
}),
]).catch(() => {
console.log("Build failed");
process.exit(1);
});
커맨드 라인에서도 사용 가능하지만 스크립트 파일로 작성도 가능.
다양한 옵션들을 조정하기 위해 build.js 파일 생성 및 옵션 설정
{
"scripts": {
"prepack": "npm run build",
"build": "npm run clean && npm run build:tsc && npm run build:js",
"build:tsc": "npm run tsc --emitDeclarationOnly",
"build:js": "node build.js",
"clean": "rm -rf dist"
}
}
배포하기 전에 확실히 build 명령어를 실행하고 배포하기 위해 prepack 스크립트 추가
prepack 스크립트는 npm publish를 실행하기 전에 실행되는 스크립트
빌드 전 dist 폴더를 삭제하는 스크립트와 tsc 명령어가 js 파일도 생성해주니 tsc에
—emitDeclarationOnly 플래그로 d.ts 파일만 생성하도록 수정
esbuild는 타입스크립트 d.ts 컴파일을 지원하지 않아 tsc와 esbuild를 같이 사용해야 함
https://esbuild.github.io/content-types/#es-module-interopTypeScript 구성 옵션 declaration(즉, 파일 생성 .d.ts)은 지원되지 않습니다. TypeScript로 라이브러리를 작성하고 컴파일된 JavaScript 코드를 다른 사람이 사용할 수 있는 패키지로 게시하려는 경우 유형 선언도 게시하고 싶을 것입니다. 이는 esbuild가 어떤 유형 정보도 유지하지 않기 때문에 esbuild가 대신 할 수 있는 일이 아닙니다. TypeScript 컴파일러를 사용하여 생성하거나 직접 수동으로 작성해야 할 가능성이 큽니다.
// package.json
{
...,
"files": [
"dist",
"src"
]
}
라이브러리에 포함될 파일 또는 폴더 명시해주는 필드
npm publish 를 통해 빌드 및 배포