디렉터리를 여는 방법

토마스 슈타이너
토마스 슈타이너

디렉터리를 처리하는 것은 일상적으로 감당할 수 있는 작업이 아니지만, 디렉터리의 모든 이미지를 처리하려는 경우와 같은 사용 사례가 발생하는 경우가 있습니다. File System Access API를 사용하면 이제 사용자가 브라우저에서 디렉터리를 열고 쓰기 액세스 권한이 필요한지 결정할 수 있습니다.

현대적인 방식

File System Access API의 showDirectoryPicker() 메서드 사용

디렉터리를 열려면 showDirectoryPicker()를 호출합니다. 그러면 선택한 디렉터리가 포함된 프로미스가 반환됩니다. 쓰기 액세스 권한이 필요한 경우 메서드에 { mode: 'readwrite' }를 전달할 수 있습니다.

브라우저 지원

  • 86
  • 86
  • x
  • x

소스

기존의 방식

<input type="file" webkitdirectory> 요소 사용

페이지의 <input type="file" webkitdirectory> 요소를 사용하면 사용자가 요소를 클릭하여 디렉터리를 열 수 있습니다. 이 방법은 자바스크립트를 사용하여 페이지에 보이지 않게 요소를 삽입하고 프로그래매틱 방식으로 클릭하는 것입니다.

브라우저 지원

  • 7
  • 13
  • 50
  • 11.1

소스

점진적 개선

아래 방법은 지원되는 경우 File System Access API를 사용하고 그 외의 경우에는 기존 접근 방식으로 대체합니다. 두 경우 모두 함수가 디렉터리를 반환하지만 File System Access API가 지원되는 경우 각 파일 객체에는 directoryHandle 속성에 저장된 FileSystemDirectoryHandlehandle 속성에 저장된 FileSystemFileHandle도 있으므로 핸들을 디스크에 직렬화할 수 있습니다.

const openDirectory = async (mode = "read") => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showDirectoryPicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();
  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let directoryStructure = undefined;

    // Recursive function that walks the directory structure.
    const getFiles = async (dirHandle, path = dirHandle.name) => {
      const dirs = [];
      const files = [];
      for await (const entry of dirHandle.values()) {
        const nestedPath = `${path}/${entry.name}`;
        if (entry.kind === "file") {
          files.push(
            entry.getFile().then((file) => {
              file.directoryHandle = dirHandle;
              file.handle = entry;
              return Object.defineProperty(file, "webkitRelativePath", {
                configurable: true,
                enumerable: true,
                get: () => nestedPath,
              });
            })
          );
        } else if (entry.kind === "directory") {
          dirs.push(getFiles(entry, nestedPath));
        }
      }
      return [
        ...(await Promise.all(dirs)).flat(),
        ...(await Promise.all(files)),
      ];
    };

    try {
      // Open the directory.
      const handle = await showDirectoryPicker({
        mode,
      });
      // Get the directory structure.
      directoryStructure = getFiles(handle, undefined);
    } catch (err) {
      if (err.name !== "AbortError") {
        console.error(err.name, err.message);
      }
    }
    return directoryStructure;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.webkitdirectory = true;

    input.addEventListener('change', () => {
      let files = Array.from(input.files);
      resolve(files);
    });
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

추가 자료

데모

HTML

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
    />
    <title>How to open a directory</title>
  </head>
  <body>
    <h1>How to open a directory</h1>
    <button type="button">Open directory</button>
    <pre></pre>
  </body>
</html>

CSS


        :root {
  color-scheme: dark light;
}

html {
  box-sizing: border-box;
}

*,
*:before,
*:after {
  box-sizing: inherit;
}

body {
  margin: 1rem;
  font-family: system-ui, sans-serif;
}
        

JS


        const button = document.querySelector('button');
const pre = document.querySelector('pre');

const openDirectory = async (mode = "read") => {
  // Feature detection. The API needs to be supported
  // and the app not run in an iframe.
  const supportsFileSystemAccess =
    "showDirectoryPicker" in window &&
    (() => {
      try {
        return window.self === window.top;
      } catch {
        return false;
      }
    })();
  // If the File System Access API is supported…
  if (supportsFileSystemAccess) {
    let directoryStructure = undefined;

    const getFiles = async (dirHandle, path = dirHandle.name) => {
      const dirs = [];
      const files = [];
      for await (const entry of dirHandle.values()) {
        const nestedPath = `${path}/${entry.name}`;
        if (entry.kind === "file") {
          files.push(
            entry.getFile().then((file) => {
              file.directoryHandle = dirHandle;
              file.handle = entry;
              return Object.defineProperty(file, "webkitRelativePath", {
                configurable: true,
                enumerable: true,
                get: () => nestedPath,
              });
            })
          );
        } else if (entry.kind === "directory") {
          dirs.push(getFiles(entry, nestedPath));
        }
      }
      return [
        ...(await Promise.all(dirs)).flat(),
        ...(await Promise.all(files)),
      ];
    };

    try {
      const handle = await showDirectoryPicker({
        mode,
      });
      directoryStructure = getFiles(handle, undefined);
    } catch (err) {
      if (err.name !== "AbortError") {
        console.error(err.name, err.message);
      }
    }
    return directoryStructure;
  }
  // Fallback if the File System Access API is not supported.
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.webkitdirectory = true;

    input.addEventListener('change', () => {
      let files = Array.from(input.files);
      resolve(files);
    });
    if ('showPicker' in HTMLInputElement.prototype) {
      input.showPicker();
    } else {
      input.click();
    }
  });
};

button.addEventListener('click', async () => {
  const filesInDirectory = await openDirectory();
  if (!filesInDirectory) {
    return;
  }
  Array.from(filesInDirectory).forEach((file) => (pre.textContent += `${file.name}\n`));
});