@tanstack/react-router + @tanstack/react-query 조합을 써보려고 @tanstack/react-query를 5버전으로 올린 후(queryOptions와 useSuspenseQuery hook을 써보기 위함) queryOptions를 사용해 custom function을 만들었더니 갑자기 type 참조를 못하겠다며 우수수수수수수 오류가 자기 주장을 해왔다. returnType에 대한 타입참조를 찾지 못해 반환값 추정이 안된단다...😬
일단 문제가 되는 부분은 다행히(?) 모두 types 참조를 못하고 있다는 에러만을 보여주고 있었다.
명확히 어디서 문제가 발생했는지는 솔직히 잘 모르겠지만? pnpm workspace 참조의 문제, typescript bundler resolution 문제, monorepo의 구조적 문제 정도를 의심해봤다. 일단 react-query v5 + vite(react-ts-swc)에서만 문제가 발생해서 가장 유력한건 typescript 설정이었다. 일단 그래서 구글링을 열심히 해봤다.
여기서도 보면 비슷하게 이야기를 나누고 있다. 어쨌든 tanstack 측에서는 tsup이 문제인거 같다고 한다.
https://github.com/TanStack/query/issues/6318
그러니까 원래는 UseQueryReturnType에 대한 dts 파일을 생성한 후, 이를 index.d.ts 진입점에서 다시 같은 이름으로 export를 해줘야하는데, tsup이 트랜스파일 및 번들링을 진행하면서 특정 이름으로 alias를 시켜 간접적으로 export를 한 후 index.d.ts 진입점에서 재반환을 시키고 있다는 것이다. 이 간접 참조 및 alias가 TypeScript의 인식에 혼란을 주고 있다고 한다.
그래서 node_modules로 들어가서 모듈 코드를 까봤는데..? 내 패키지엔 alias가 없는데..? 애초에 다른 패키지 얘기기도 했지만 오류 양상이 비슷해서 봤었는데 내 case의 경우 저 내용이 직접적인 원인은 아닌것 같다. 일단 저 discussion에서는 TypeScript 5.4.0-beta 버전으로 마이그레이션하면 이슈가 없어질거라는데 굳이 beta 버전을 깔고싶지 않았고 alias 케이스가 아니어서 다른 코멘트의 내용을 기반으로 tsconfig 수정 고군분투를 해봤다.
일단 여러가지 시도중에서 유의미했던 방법은 4가지가 있다.
1. tsconfig.json의 moduleResultion을 'node'로 설정하기
import { queryOptions } from '@tanstack/react-query';
import type * as _T from '@tanstack/react-query'; // 이 부분 추가
export type User = {
access_token: string;
email: string;
marketing: boolean;
};
const fetcher = (...args: Parameters<typeof fetch>) => fetch(...args).then(res => res.json());
export const workspaceQueryOptions = (name: string) =>
queryOptions({
queryKey: ['workspace', name],
queryFn: () => fetcher(`/api/settings/${name}`),
staleTime: 30 * 60 * 1000, // 30 min
});
export const workspaceAPITokenQueryOptions = (name: string) =>
queryOptions({
queryKey: ['workspace', name, 'apiToken'],
queryFn: () => fetcher(`/api/workspace/${name}/api_token`),
staleTime: Infinity,
});
export const userQueryOptions = () =>
queryOptions<User>({
queryKey: ['auth'],
queryFn: () => fetcher('/api/account'),
staleTime: 10 * 60 * 1000, // 10 min
});
.types
처럼 일반적인 이름에 paths mapping을 걸면 해당 명칭을 쓸 수 없는 점 때문에 적절하지 않다고 판단했다.{
"extends": "tsconfig/vite.json",
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": true,
"lib": ["esnext", "DOM", "DOM.Iterable"],
"module": "es2022",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"./types": ["node_modules/@tanstack/react-query/build/modern/types.d.ts"], // 여기랑
"@tanstack/core/*": ["../../node_modules/.pnpm/@tanstack+query-core@5.22.2/node_modules/@tanstack/query-core/*"], // 여기를 추가
"@/*": ["./src/*"]
},
"skipDefaultLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"references": [{ "path": "./tsconfig.node.json" }]
}
{
"extends": "tsconfig/vite.json",
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": true,
"lib": ["esnext", "DOM", "DOM.Iterable"],
"module": "es2022",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "Bundler",
"declaration": false,
"declarationMap": false,
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
"skipDefaultLibCheck": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"],
"references": [{ "path": "./tsconfig.node.json" }]
}
오늘의 결론은..버전은 함부로 올리지말자..?