앞선 [TS] '"react-router-dom"' has no exported member named 'createBrowserRouter'. Did you mean 'BrowserRouter'? 오류 해결하기 문제에서 @types/react-router-dom을 제거함으로 해결했지만 또 다른 오류가 발생하는 것을 맞닥뜨릴 수 있었다. 이전 포스팅에서 해결방법을 간단히 설명했지만, 왜 이 방법으로 해결이 될 수 있었는지를 포스팅하려고한다.
react-router-dom의 타입 선언 파일을 찾을 수 없어서, TS 환경에서 작업을 하지 못한다.
근본적인 원인은 타입스크립트 환경 설정 파일에서 moduleResolution이 classic으로 설정되어있기 때문이다.(명시적으로 설정하지 않았지만 target을 es6로 설정함에 따라 자동설정됨) 이 모듈해석 방식에 따르면 원하는 타입 선언을 가져올 수 없기 때문에 문제가 발생했다.
원인을 이해하기위해 moduleResolution이 무엇이고, 왜 현재상황에서 이 값이 classic이면 안되는지 알아보자. (항상 classic이면 안되는 것이 아니기 때문에 이해하는 것이 중요하다.)
ModuleResolution
은 타입스크립트 모듈 해석 전략이며, 기본적으로 node.js의 전략을 따른다고한다. 쉽게 말하면 우리가 파일안에다가 require 또는 import를 이용해 모듈을 가져와 사용할텐데, 이 모듈을 어디로부터 가져오게 할건지를 의미하는 것이다.
이 속성에 대한 값은 ‘node’(module - node16, nodenext, 이외) or ‘classic’ (module - AMD, UMD, System, ES6/2015)가 있다.
명시적으로 설정하지 않으면 module에 따라 정해진다. 만약 module이 설정되지 않았다면 module은 target에 따라 정해진다.
만약 지금 나와 같은 오류가 발생했다면 아마 classic으로 값이 설정되있거나 module이 ES6/2015 또는 target이 es6/es2015로 설정된 상태일 것이다.
짧게 표현하자면 의존관계가 아래와 같다.
두 방식 모두 기본적으로 아래와 같이 모듈을 해석한다.
- 상대경로
- 비상대경로
- 테스트 결과
moduleResolution: classic 으로 설정됐을 때 react-router-dom 타입 선언을 탐색하는 로그이다.
======== Resolving module 'react-router-dom' from '/Users/hyeonjeong/[프로젝트 루트]/client/src/test.ts'. ========
Explicitly specified module resolution kind: 'Classic'.
// 현재 경로부터 루트 경로에 있는 ts, tsx, d.ts 파일 탐색
File '/Users/hyeonjeong/[프로젝트 루트]/client/src/react-router-dom.ts' does not exist.
File '/Users/hyeonjeong/[프로젝트 루트]/client/src/react-router-dom.tsx' does not exist.
File '/Users/hyeonjeong/[프로젝트 루트]/client/src/react-router-dom.d.ts' does not exist.
// ...
File '/react-router-dom.ts' does not exist.
File '/react-router-dom.tsx' does not exist.
File '/react-router-dom.d.ts' does not exist.
Searching all ancestor node_modules directories for preferred extensions: Declaration.
// node_modules 폴더 => @types 내 선언 파일 탐색. 이를 루트 경로까지 반복 탐색
Directory '/Users/hyeonjeong/[프로젝트 루트]/client/src/node_modules' does not exist, skipping all lookups in it.
File '/Users/hyeonjeong/[프로젝트 루트]/client/node_modules/@types/react-router-dom.d.ts' does not exist.
// ...
Directory '/node_modules' does not exist, skipping all lookups in it.
// js, jsx 파일을 루트 경로까지 탐색
File '/Users/hyeonjeong/[프로젝트 루트]/client/src/react-router-dom.js' does not exist.
File '/Users/hyeonjeong/[프로젝트 루트]/client/src/react-router-dom.jsx' does not exist.
======== Module name 'react-router-dom' was not resolved. ========
마지막 줄을 보면 react-router-dom 타입 선언을 찾지못했음을 알 수 있다.
node_modules 내에서 찾다가, 만약 못찾으면 상위 node_modules 내부를 탐색한다.
- 상대 경로
1. 일치하는 경로내에서 주어진 파일을 확인한다. (ts → tsx → d.ts)
2. 일치하는 폴더내에서 package.json이 있다면 type에 정의된 파일을 참조한다.
3. 일치하는 경로내에서 주어진 폴더를 확인한다. (index.ts → index.tsx → index.d.ts)
- from '.moduleA' 라면 moduleA/index.ts, moduleA/index.tsx, moduleA/index.d.ts를 확인하게됌
- 비상대 경로
- 테스트 결과
moduleResolution: node 으로 설정됐을 때 react-router-dom 타입 선언을 탐색하는 로그이다.
======== Resolving module 'react-router-dom' from 'Users/hyeonjeong/[프로젝트 루트]/client/src/test.ts'. ========
Explicitly specified module resolution kind: 'Node10'.
Loading module 'react-router-dom' from 'node_modules' folder, target file types: TypeScript, Declaration.
**Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.**
Directory 'Users/hyeonjeong/[프로젝트 루트]/client/src/node_modules' does not exist, skipping all lookups in it.
Found 'package.json' at 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/package.json'.
File 'Users/hyeonjeong/[프로젝트 루트]client/node_modules/react-router-dom.ts' does not exist.
File 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom.tsx' does not exist.
File 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom.d.ts' does not exist.
'package.json' does not have a 'typesVersions' field.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field './dist/index.d.ts' that references 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/dist/index.d.ts'.
File 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/dist/index.d.ts' exists - use it as a name resolution result.
Resolving real path for 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/dist/index.d.ts', result 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/dist/index.d.ts'.
======== Module name 'react-router-dom' was successfully resolved to 'Users/hyeonjeong/[프로젝트 루트]/client/node_modules/react-router-dom/dist/index.d.ts' with Package ID 'react-router-dom/dist/index.d.ts@6.15.0'. ========
마지막 줄을 통해 react-router-dom 패키지 내부에서 성공적으로 타입 선언을 찾은 것을 확인할 수 있다.
중간중간 나오는 분석 로그를 보는 방법은 tsc [파일명] --traceResolution
를 터미널에 입력하면된다. 모듈을 해석하는 과정을 순차적으로 보여주는 명령어다.
근데 로그가 너무 길어서 터미널에서 일부 로그가 잘린다하면 tsc [파일명] --traceResolution > resolution.txt
을 입력하여 파일에다 작성해서 모든 로그를 볼 수 있다.
원인을 알고나서 classic으로 설정될 경우 react-router-dom 내의 선언 파일에 도달하지 못한다는 것을 알았다. 하지만 node는 모듈 해석 방식에 따라 가능했기 때문에 moduleResolution을 'node'로 설정해주어서 해결했다.
Module Resolution - Typescript
TypeScript: TSConfig Reference - Docs on every TSConfig option
타입스크립트 성능을 위한 팁 - yceffort님의 블로그