저번 작업을 통해 경로 별칭을 해제하고 실제 경로를 가져올 수 있었다.
여기서 문제점은 이 파일들의 확장자가 없다는 것이다.
(모자이크가 심하게 되어있지만 확장자가 없다)
예를 들어 App.tsx에서 src/pages 안에 있는 Component1.tsx를 import 하는 경우 실제로는 import Component1 from src/pages/Component1이 사용된다.
이 시리즈의 글은 개발 과정을 작성한 것으로 내용 중 코드 또는 판단이 이상한 부분이 있다면 높은 확률로 뒷부분에서 수정될 것입니다.
확장자를 어떻게 추가할 수 있을까?
확장자를 어떻게 추가할 수 있을지 고민을 많이 했던 것 같다.
JS, TS, JSX, TSX에 상관없이 사용할 수 있도록 하는 것이 목표였으므로 일괄적인 확장자를 적용할 수 없었다.
또한, JSX와 TSX 확장자를 붙이는 것도 문제가 발생하는 것이 커스텀 훅 중에는 일반적인 TS, JS 파일도 섞여있다는 것을 보면 임의로 설정해서 확장자를 붙이면 안된다.
이를 해결하기 위해서 파일의 전제 목록을 가져와서 대조하기로 했다.
targetDir에 대한 정보를 가져오고 있기 때문에 해당 디렉토리 내에서 모든 파일의 정보를 미리 목록으로 만들어두는 것이다.
src/pages/Component1이 실제 파일에서 import 되었다면 전체 파일 목록은 src/pages/Component1.tsx라는 데이터를 갖고 있을 것이다.
이때 두 경로를 비교한 뒤 해당하는 데이터가 있다면 전체 파일 목록에서 확장자가 포함된 경로를 얻을 수 있을 것이다.
import fs from 'node:fs';
import path from 'node:path';
export function getAllFiles(dirPath, arrayOfFiles) {
const files = fs.readdirSync(dirPath);
arrayOfFiles = arrayOfFiles || [];
files.forEach((file) => {
const filePath = path.join(dirPath, file);
if (fs.statSync(filePath).isDirectory()) {
arrayOfFiles = getAllFiles(filePath, arrayOfFiles);
} else {
arrayOfFiles.push(filePath);
}
});
return arrayOfFiles;
}
모든 파일을 가져오면 위와 같은 형식의 데이터를 얻을 수 있다.
확장자를 포함한 targetDir의 모든 데이터를 가져온 모습이다.
이제 모든 파일의 정보를 알고 있기 때문에 문자열 비교를 통해 해당하는 파일의 확장자를 설정할 수 있다.
만약, 같은 이름의 다른 확장자가 존재한다면?
일반적으로 같은 파일 이름을 사용하는 경우는 없을 것으로 보이지만 만약 같은 파일 이름을 갖는 서로 다른 확장자 파일의 경우 import 할 때 확장자를 명시하는 것을 전제로 한다.
(파일의 확장자를 명시하지 않는 경우 모듈을 잘못 불러오는 경우가 있는 것으로 확인)
따라서, 이 프로젝트에서는 이름이 같지만 다른 확장자를 갖는 파일이 없거나 확장자를 명시하고 있음을 전제로 구현하고자 한다.
우선 경로 구분자를 /로 다루는 것이 편하기 때문에 경로 구분자를 수정하는 작업을 수행한다.
const filePath = path.join(dirPath, file).replace(/\\/g, '/');
이후 모든 파일 목록을 대상으로 일치하는 문자열을 검사하여 확장자를 포함한 데이터를 가져올 수 있도록 한다.
export function appendExtensions(resolvedPaths, allFiles) {
return resolvedPaths.map((resolvedPath) => {
const matchedFile = allFiles.find((file) => file.startsWith(resolvedPath));
return matchedFile;
});
}
위와 같이 확장자를 적절히 붙일 수 있음을 볼 수 있다.
위에서 undefined에 해당하는 것은 react-router-dom과 @tanstack/react-query에 해당한다.
프로젝트내에 존재하지 않거나 targetDir 안에 없는 경우 또는 npm 라이브러리인 경우는 기본적으로 undefined를 반환하도록 했다.
(node_modules가 포함되는 경우 depth가 매우 깊어지고 방향성에도 맞지 않다고 판단. 추후 option을 추가한다면 node_modules의 이름 정도를 트리에 포함시키는 기능을 추가할 수 있을 것 같다)
번외: cli 입력 옵션 받기
"test:ui": "vitest --ui --coverage",
일반적으로 cli 기반 프로그램을 위와 같이 사요하는 경우가 많다.
옵션을 설정할 수 있으며 옵션의 순서가 중요하지 않고 어떤 내용이 포함되어 있는지만 관심이 있다.
이를 만들기 위해 필요한 개념은 이전에 봤던 provess.argv를 이용해 프로그램 실행 시 인자를 넘길 수 있도록 하는 것이다.
현재 프로젝트에 적용해본다면 아래와 같이 입력할 수 있다.
"asd": "import-visualizer --root src/App.tsx --targetDir src"
위와 같이 스크립트를 입력한 경우 process.argv를 출력해보면 아래와 같다.
첫 번째 인자는 실행 환경의 경로, 두 번째 인자는 실행 파일 경로, 세 번째 인자부터 사용자가 전달한 인자가 되는 모습을 볼 수 있다.
이 라이브러리에서 제공하는 옵션은 크게 두 가지다.
- --root: 엔트리 포인트
- --targetDir: 시각화 대상 디렉토리
이 두 데이터를 바탕으로 옵션을 설정할 수 있도록 해야 한다.
이 라이브러리의 기본값은 React + TS 환경에 맞춰 각각 src와 src/App.tsx로 설정할 수 있도록 한다.
함수를 하나 만들어 옵션을 적절히 파싱해야 한다.
이 함수는 옵션이 없다면 기본 값을 내보내는 역할을 함께 수행해야 한다.
이를 구현하기 위한 방법은 두 가지가 있다.
- minimist 라이브러리 활용 - minimist - npm (npmjs.com)
- 옵션이 적으니 직접 구현
옵션이 둘 밖에 없으니 2번을 선택하여 구현할 수 있도록 했다.
추후 업데이트 등을 통해 옵션을 여러 개 지정할 수 있게 된다면 minimist의 도입에 대해 고려하기로 결정했다.
export function config(argv) {
const options = {
root: 'src/App.tsx',
targetDir: 'src',
};
argv.forEach((arg) => {
if (arg == '--root') options.root = argv[argv.indexOf(arg) + 1];
if (arg == '--targetDir') options.targetDir = argv[argv.indexOf(arg) + 1];
});
return options;
}
위와 같이 함수를 구성하여 옵션을 입력할 수 있도록 구현했다.
--root 옵션 뒤에 오는 인자는 --root에 관한 설정이 될 것이므로 --root를 만나는 경우 다음 요소를 options 객체의 root로 설정하는 방식을 채택했다.
'개발 > 개발과정' 카테고리의 다른 글
[import-visualizer] 5. CDN의 한계 (0) | 2024.06.14 |
---|---|
[import-visualizer] 4. 시각화 (0) | 2024.06.14 |
[import-visualizer] 2. 경로별칭 (0) | 2024.06.13 |
[import-visualizer] 1. 프로젝트 시작 (요구사항 분석) (0) | 2024.06.13 |
React createElement로 리팩토링 하기 (권장 X) (0) | 2024.05.04 |