React+Typescript+Jest 환경에서 craco를 이용한 path alias 설정


최근에 진행중인 프로젝트에서 create-react-app을 이용해 생성한 리액트 프로젝트에서 path alias설정을 하려다 삽질을 많이 해서 누군가는 도움이 되라는 의미에서 글을 작성합니다.

프로젝트 환경

create-react-apptypescript 템플릿으로 생성한 프로젝트입니다. 정석대로 여기서 path alias를 설정하려면 eject 명령어를 사용해야 하지만 craco(Create React App Configuration Override)를 사용하면 eject 없이 path alias를 설정할 수 있습니다.

프로젝트의 디렉토리 구조는 아래와 같고 $components, $contexts, $hooks를 각각 path alias에 추가하고 싶다고 가정해보겠습니다.

root/
    src/
        components/
        contexts/
        hooks/

craco 설정

우선 아래 명령어를 통해 craco를 설치해줍니다.

npm install @craco/craco

설치가 완료되면 package.json 파일의 scriptsreact-scriptscraco로 바꿔줍니다.

"scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test"
}

그 후에 루트 디렉토리에 아래와 같이 craco.config.js파일을 생성해줍니다.

/* craco.config.js */

const path = require('path/posix');

module.exports = {
    webpack: {},
    jest: {}
};

tsconfig 설정하기

path alias가 잘 작동하도록 설정하기 위해서는 총 3가지 설정을 수정해줘야 합니다. 그 중 두 가지는 craco.config.js파일에서 수정이 가능하고 나머지 한 가지는 tsconfig.json파일에서 수정을 해줘야합니다.

tsconfig.json 파일에서 compilerOptions 옵션의 path 설정을 아래와 같이 해줘야 합니다.

{
    "compilerOptions": {
        "baseUrl": "src",
        "paths": {
            "$components/*": ["components/*"],
            "$contexts/*": ["contexts/*"],
            "$hooks/*": ["hooks/*"]
        }
    }
}

히지만 여기까지 설정하면 괴상하게도 CRA가 스크립트를 실행할 때마다 tsconfig.json파일을 초기화시켜 위 설정을 지워버립니다. (여기서 삽질을 한참 했습니다...) 이를 해결하기 위해서 루트 디렉토리에 tsconfig.paths.json파일을 새로 만들어주고 위 설정을 옮겨줍니다. 그리고 기존의 tsconfig.json파일에 아래 줄을 추가해줍니다.

{
    "extends": "./tsconfig.paths.json"
}

이렇게 하면 CRA가 path 설정을 지우지 않는 것을 방지할 수 있습니다.

Webpack, Jest 설정

이제 craco.config.js파일을 열어서 webpackjest 설정을 추가해줘야합니다.

webpack 설정은 아래와 같이 해줍니다.

webpack: {
    alias: {
        $components: path.resolve(__dirname, 'src/components'),
        $contexts: path.resolve(__dirname, 'src/contexts'),
        $hooks: path.resolve(__dirname, 'src/hooks'),
    },
},

여기까지 설정하면 애플리케이션은 정상적으로 동작하지만 테스트를 실행했을 때 jestpath alias를 인식하지 못하기 때문에 jest에도 추가적인 설정을 해줘야합니다.

jest: {
    configure: {
        moduleNameMapper: {
            '^\$components/(.*)$': '<rootDir>/src/components/$1',
            '^\$contexts/(.*)$': '<rootDir>/src/contexts/$1',
            '^\$hooks/(.*)$': '<rootDir>/src/hooks/$1',
        },
    },
},

이렇게 하면 테스트도 정상적으로 동작하는 것을 확인할 수 있습니다.

주의사항

만약 path alias에 @를 사용하는데 types폴더가 존재하면 충돌이 발생합니다. 타입스크립트가 타입 선언 라이브러리(@types/node)와 혼동하기 때문에 폴더 이름을 바꾸거나 다른 문자를 사용해야 합니다.

만약 $components와 같이 폴더에 path alias를 설정하는 것이 아니라 src폴더에 $를 매핑하려고 하면 $가 아닌 다른 문자를 사용해야 합니다. Webpack에서 $를 다른 목적으로 사용하기 때문에 @와 같은 다른 문자를 사용해야 합니다.