이전 글에 이어서, 이번에는 배포 중 만났던 이슈들에 대해 글로 정리해보려 합니다. (feat. 페이먼츠 모듈 페어인 로건 선생님)
최근에 페이먼츠 모듈을 라이브러리 형태로 묶어 NPM에 배포하는 작업을 진행하게 되었어요. 처음 해보는 패키지 배포였기에, 예상치 못한 문제들을 마주쳤고 특히 타입 빌드 관련해서 꽤 헤맸습니다. 이번 글에서는 그 과정에서 마주친 이슈들과 해결 과정을 정리해보려 합니다. 🙌
패키지는 Vite + TypeScript + React 환경에서 작성되었고, 빌드와 타입 선언 파일 생성을 위한 설정은 다음과 같았습니다.
tsconfig.json
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
vite.config.ts
import * as path from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import dts from "vite-plugin-dts";
export default defineConfig({
build: {
lib: {
entry: path.resolve(__dirname, "src/lib/index.ts"),
name: "index",
fileName: "index",
},
rollupOptions: {
external: ["react"],
output: {
globals: {
react: "React",
},
},
},
commonjsOptions: {
esmExternals: ["react"],
},
},
plugins: [react(), dts()],
});
npm run build를 하면 분명 index.js는 잘 만들어지는데, index.d.ts는 어디에도 없었습니다. 타입 선언 파일이 없다 보니, 패키지를 다른 프로젝트에서 사용할 때 타입 인식이 되지 않는 문제가 발생했어요.
처음엔 npm 타입스크립트 빌드, npm build 타입 선언 파일 같은 키워드로 열심히 검색해봤지만, 딱 맞는 해결법이 보이지 않았습니다.
결국, GPT에게 도움을 요청했고 다음과 같은 조언을 받았습니다!
“tsconfig에서 declaration: true와 emitDeclarationOnly: true, outDir 등을 설정해봐라”
요약하자면 위와 같은 조언을 받고 tsconfig를 손보기 시작했습니다.
기존에는 tsconfig.json에서 "noEmit": true
로 설정되어 있어서, 타입 선언 파일이 생성되지 않는 상태였더라고요. 처음엔 이것이 원인이라고 생각해서 tsconfig 설정을 계속 살펴보느라 꽤 많은 시간을 쏟았습니다.
하지만 noEmit
옵션을 해제하고 tsconfig를 수정한 이후에도, 여전히 dist 폴더에 .d.ts 파일이 생성되지 않았습니다.
"types": "dist/index.d.ts",
"main": "dist/index.js",
"module": "dist/index.js",
"files": [
"dist"
],
package.json설정도 잘 해줬고, 문제가 없다고 생각했는데 결과는 똑같았어요.
도저히 안되겠다 싶어 주변분들에게 SOS를 청했습니다. (두번째 동환님 샤라웃.. 진짜 감사합니다. 근데 동환님은 제 블로그 안봄 ㅋ.ㅋ)
알고 보니 vite-plugin-dts
는 Vite 환경에서 .d.ts 파일을 별도로 빌드해주는 플러그인이었습니다. 하지만 저는 이 플러그인을 설치만 해놓고 제대로 된 설정을 하지 않은 상태였던 것이죠.
처음엔 tsconfig 문제라고 생각했지만, 자세히 들여다보니 tsconfig가 아니라 vite-plugin-dts 설정 부족이 문제의 원인이었습니다.
vite-plugin-dts
는 TypeScript의 tsc 빌드와는 별개로 타입 선언 파일을 생성합니다. Vite는 기본적으로 .ts 파일의 타입 선언을 생성하지 않기 때문에, 해당 라이브러리를 배포하려면 해당 플러그인의 설정이 사실상 필수인 것이죠.
처음엔 “dts? 그냥 플러그인 이름인가 보다~ “하고 넘겼는데, 이제는 dist에 .d.ts 파일이 생겨야 한다는 걸 알고 나니까 이름 자체가 너무 직관적으로 느껴지네요. 이래서 뭔가를 ‘제대로’ 알아야 한다는 말이 괜히 있는 게 아니구나 싶었습니다.
공식문서를 찾아보던 중, 이것저것 보고 있었는데 눈에 띄는 단어가 있더라구요. 읽어보니 공식문서에서 tsconfigPath
를 지정하라는 것을 알 수 있었습니다.
그래서 단순히 dst
에 tsconfigPath
만 추가해봤습니다.
export default defineConfig({
//..
plugins: [
react(),
dts({
tsconfigPath: "./tsconfig.app.json",
}),
],
});
결과적으로 저 한문장을 추가함으로써 dist 폴더에 .d.ts 파일들이 제대로 생성되기 시작했습니다 🎉
패키지를 배포한 후, 사용하는 쪽에서 이미지가 불러와지지 않는 문제를 마주했습니다.
로컬에서는 잘 되던 SVG 아이콘이, 실제 패키지를 설치해 사용하니 경로를 못찾고 엑박이 뜨는 상황이었어요.
기존 구조
<img src="/close.svg" />
기존 프로젝트에서는 public/ 폴더 아래에 svg 파일을 넣고, 위과 같이 사용하고 있었습니다. 심지어 패키지를 빌드했을 때 dist/public/close.svg
가 정상적으로 생성된 것도 확인했지만, 여전히 설치한 프로젝트에서는 이미지를 불러오지 못했습니다.
문제의 원인은 경로 해석 방식의 차이였습니다. public 디렉토리는 Vite 개발 서버에서는 루트 경로(/)로 인식되지만, 패키지로 배포했을 때는 이 경로를 해석할 수 없습니다. 패키지를 사용하는 쪽의 프로젝트에는 이 이미지 경로(/close.svg)가 없기 때문입니다.
아무리 dist/public/close.svg
파일이 존재해도, 외부 프로젝트에서는 그 파일에 접근할 수 있는 루트 경로(/)의 맥락이 달라지기 때문에 결과적으로 이미지를 참조할 방법이 없어 이미지가 깨지게 됩니다.
이 문제를 해결하기 위해, 이미지를 import해서 사용하는 방식으로 전환했습니다.
하지만 .svg 파일을 그대로 import하려고 하면 TypeScript가 인식하지 못해 에러가 발생합니다.
따라서 svg.d.ts 파일을 만들고 다음과 같이 선언해주었습니다. 또 이미지를 assets라는 폴더를 만들고 위치를 해당 폴더 내부로 이동시켰어요.
declare module "*.svg";
이렇게 선언을 해주면 TypeScript는 .svg 파일을 JS 모듈처럼 인식할 수 있게 됩니다. 결과적으로 Vite는 해당 이미지를 자동으로 빌드 결과물에 포함시키고, 패키지 내에서 유효한 상대 경로를 가진 정적 자산으로 변환해줍니다.
결과적으로 svg를 사용해서 이미지를 사용자에게 보여주기 성공했습니다.
import closeIcon from './assets/close.svg';
// 사용할 때
<img src={closeIcon} alt="닫기" />
처음 해보는 패키지 배포였지만, 이번 경험 덕분에 타입 선언의 중요성과 도구들의 작동 방식을 좀 더 명확하게 이해할 수 있었던 것 같아요. 그리고 무엇보다도 공식 문서를 끝까지 읽는 것이 얼마나 중요한지 다시금 느꼈습니다.
처음에는 검색과 GPT의 답변에만 의존했지만, 결국 마지막 열쇠는 공식 문서에 명확하게 적혀 있었던 설정 한 줄이었습니다.
GPT의 도움도 물론 큰 힘이 됐지만, 도구에만 의존하지 않고 스스로 문서를 읽고 이해하는 과정이 있어야 진짜 성장할 수 있다는 걸 이번에 뼈저리게 느꼈습니다.
끗-
공식문서 좋은데요???? 👍