개요
리액트로 작성된 프론트 코드의 CI/CD 를 하다가 오류가 발생했고 디버깅을 하다가 원인을 알아내서 간단히 작성해보려고 한다.
원인 분석; 왜 Vite 나 Webpack 에서 발생하는가?
웹팩(Webpack)이나 Vite 는 모듈 번들러로 자바스크립트 파일들을 하나로 묶어 애플리케이션에서 사용할 수 있게 모듈이나 파일 경로를 해석하는데 이때 올바르지 않은 경로가 참조되면 Module not found 오류가 발생한다.
보통 상대 경로(../../components/Button)가 잘못 작성되었거나, 프로젝트 구조 변경 시 경로 수정이 누락된 경우에 발생한다. 또한, 웹팩이나 Vite가 특정 확장자를 찾지 못했을 때도 이 오류가 발생할 수 있다.
결국, 이 오류의 발생 주체는 모듈 번들러에서 파일을 찾지 못했기 때문이다.
해결책; 우회하기
1. 처음부터 작성을 잘 해둔다. 뒤에 파일 형식을 잘 지켜서
경로를 명확히 하고, 프로젝트 구조를 체계적으로 관리하여 오류를 방지한다.
2. resolve 옵션에 extentions 필드로 확장자를 자동으로 처리할 수 있게 구성한다.
웹번들러의 resolve 옵션의 extensions 필드를 설정하여 특정 확장자를 자동으로 처리할 수 있다.
파일을 import할 때 확장자를 생략할 수 있고, 자동으로 해당 확장자를 가진 파일을 찾아준다.
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/components'),
},
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], // TypeScript 파일 확장자도 추가
},
};
Alias 이용하여 절대경로 이용
alias 옵션의 경우에는 프로젝트 내 특정 디렉토리를 나타내는 단축 경로를 나타내서 ../../assets/* 또는 ../../../components/*와 같은
복잡한 경로 를 작성하기 쉽게 해주고 다수의 import 문을 수정할 필요가 없어진다.
리팩터링 시에 유지보수도 간편해지는것 같다.
import Button from "../../../components/Button";
// 아래와같이 표현이 가능하다!
import Button from "@/components/Button";
만약 TypeScript 를 이용하게 된다면 컴파일러 옵션을 이용하여 확장자, 절대 경로 모두를 이용할수 있게되는데 moduleResolution옵션과 ts 의 import 경로 설정을 해주면 쉽게 구현가능하다.
일단 이용하는 스택은 Vite, TS 이다.
먼저 TS 컴파일러와 IDE가 참고하는 설정파일 tsconfig.json 코드를 확인해보면
{
"compilerOptions": {
"baseUrl": ".", // 프로젝트의 루트 디렉토리
"paths": {
"@/*": ["src/*"] // '@'를 'src' 폴더로 매핑
},
"jsx": "react-jsx", // React 프로젝트의 JSX 설정
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler", // app에서도 bundler를 사용하므로 상위로 이동
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"], // app과 상위 둘 다 사용하므로 상위로 이동
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
baseUrl과 paths 옵션을 통해 TypeScript가 모듈 경로를 해석하는 방법을 설정하는데 이때 paths 설정을 통해 프로젝트 내에서 alias를 사용할수 있게 된다.
위 코드에서 작성한 @/*를 src/*로 매핑하면, @/components/Button과 같이 짧은 경로로 파일을 import할수 있게 되고 해당 설정은 TS 컴파일러 동작시에 작동한다.
따라서 분명 vite.config.ts 에 잘 작성했는데 실제 사용하려고 @/~ 을 이용해 import 를 했을때 참조오류가 발생하는것이다.
config.app.json는 하위의 설정(동작 어플리케이션) 으로 상위의 설정을 상속받아 작동하는데 모듈화가 되어있고 큰 프로젝트라면 적절하게 작성해주도록하자.
Vite는 번들러로서 모듈을 해석할 때 TypeScript 설정을 자동으로 인식하지 않는다.(아쉽게도)
따라서 Vite는 독립적으로 설정된 alias를 사용하여 파일 경로를 알아야하기 때문에 vite.config.ts 파일에 명시적으로 alias를 설정해주도록 하자
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
});
이후에는 간단히 src 내부 컴포넌트를 하나 만들어보고 이를 app.tsx에서 작동 테스트를 해보았다.
혹시 이렇게 해도 오류가 발생하거나 확장자 매핑 문제가 발생하면 "compilerOptions": { "allowImportingTsExtensions } 옵션이 true 인지 확인해보자