File System Access API는 절대 경로를 주지 않습니다: 프로젝트 식별자를 다시 설계한 기록
로컬 폴더를 직접 읽는 개인 프로젝트 Workbench를 만들다가, 브라우저가 디렉터리 핸들의 실제 경로를 알려주지 않는다는 사실을 구현 단계에서 뒤늦게 확인하고 식별자 설계를 다시 짠 기록입니다.
- 제품 개발
- 트러블슈팅
- 시행착오
Workbench는 홈 디렉터리 아래 여러 개인 프로젝트를 한 화면에 모아 보여주는 개인용 앱입니다. ~/toy 같은 소스 폴더를 하나 마운트해 두면 그 안에서 .git이나 package.json이 있는 폴더를 프로젝트로 자동 인식하고, 홈 화면에 타임라인이나 격자로 늘어놓습니다.
서버가 사용자 데이터를 저장하지 않는 것 자체가 요구사항이라 브라우저의 File System Access API로 로컬 폴더를 직접 읽고 감시하는 구조로 잡았습니다.
시안에는 절대 경로가 그대로 있었습니다#
설계 초기에 그린 화면 시안에는 프로젝트 경로를 /Users/djlee/toy/plan-wall처럼 통째로 적어 넣었습니다. 숨김 처리한 프로젝트 목록도 이 절대 경로 문자열로 저장할 계획이었고, 설계 문서에도 그렇게 적었습니다.
그럴듯해 보였습니다. 사용자가 showDirectoryPicker()로 폴더를 직접 골랐으니, 그 폴더의 전체 경로 정도는 당연히 받아올 수 있을 거라 생각했습니다.
핸들은 마지막 폴더 이름만 줍니다#
구현 단계에서 실제로 확인해보니 아니었습니다. showDirectoryPicker()가 돌려주는 FileSystemDirectoryHandle에는 절대 경로를 담는 필드가 없습니다. MDN의 FileSystemHandle.name 문서에도 이 속성은 "핸들이 나타내는 항목의 이름을 반환한다"고만 적혀 있고, 값은 그냥 문자열 하나입니다.
plan-wall 폴더를 골라도 handle.name은 "plan-wall"이지 /Users/djlee/toy/plan-wall이 아닙니다. 하위 폴더로 내려가며 만드는 자식 핸들들도 마찬가지로 자기 이름만 알지, 전체 경로를 이어붙여 주지 않습니다.
그러니까 시안 단계에서 세운 "숨김 목록을 절대 경로로 저장한다"는 전제 자체가 브라우저 쪽에서부터 성립하지 않았던 겁니다. 뒤늦게 발견한 게 다행이었습니다. 화면만 먼저 만들고 저장 로직을 나중에 붙였다면 실제 데이터로 테스트하는 단계에서야 걸렸을 문제입니다.
고친 식별자: sourceId와 상대 경로를 붙인다#
실제 구현은 /Users/djlee/toy/workbench/src/fs.ts에 이렇게 정리했습니다.
export type Project = {
key: string; // stable id: sourceId + ':' + relPath
name: string;
sourceId: string;
sourceName: string;
relPath: string;
dir: FileSystemDirectoryHandle;
mtime: number;
lastFile: string;
recent: { path: string; mtime: number }[];
};sourceId는 사용자가 마운트한 소스 루트 하나하나에 붙이는 고유 값이고, relPath는 그 루트에서부터 내려간 상대 경로입니다. 트리를 순회하면서 relPath를 부모경로 + '/' + 폴더이름 형태로 계속 이어붙입니다.
그 결과 OS 절대 경로 없이도 같은 소스 안에서는 겹치지 않는 문자열이 만들어집니다. 숨김 목록도, 캐시 키도 전부 이 key(sourceId + ':' + relPath) 기준으로 다시 짰습니다.
화면에 보여주는 경로도 같은 재료로 조립합니다. OS의 실제 절대 경로는 애초에 알 방법이 없으니, sourceName(마운트한 루트 이름)과 relPath를 합쳐 toy/plan-wall처럼 표시합니다. 시안에 그렸던 /Users/djlee/... 전체 경로는 포기했습니다.
미결로 남긴 것#
여러 소스 루트를 마운트했을 때 같은 프로젝트 폴더를 두 번 다른 sourceId로 등록하는 경우까지는 아직 손대지 않았습니다. 지금은 그런 중복이 생기면 그냥 두 개의 별개 프로젝트로 보입니다.
실제로 그런 마운트 조합을 써보고 나서 다시 볼 문제로 남겨뒀습니다.
얻은 교훈#
로컬 파일을 다루는 웹앱을 설계할 때는 경로 문자열을 키나 표시값으로 미리 전제하면 안 됩니다. 브라우저가 애초에 그 정보를 주지 않으니까요.
시안 단계에서 실제 OS 경로를 화면에 그려 넣으면, 구현 단계에서 반드시 어긋나는 그림이 됩니다. File System Access API를 쓰는 다른 프로젝트를 설계할 일이 있으면 이번에는 시안을 그리기 전에 handle.name부터 먼저 콘솔에 찍어볼 생각입니다.
gif-moments는 영상 파일을 서버로 보내지 않고 브라우저 안에서만 GIF로 자릅니다.