JSZip으로 여러 파일 한번에 zip으로 압축해서 다운받기

어드민이나, 홈페이지에서 다운로드 버튼을 클릭하면 대량의 파일(CSV, 이미지 파일, PDF 등)을 다운로드 받아야되는 경우가 있다.

기존의 어드민에서는 다운받아야하는 파일의 개수가 1,2개 단위로 한정적이여서 아래와 같이 반복문 방식으로 react-saver 패키지의 saveAs() 메소드를 파일의 개수만큼 호출하는 방식으로 구성하였다.

const saveFiles = (files: File[]) => {
	files.map((file: File) => {
    	saveAs(file.url,`${file.name}.{file.type}`); // saveAs 메소드는 'file-saver.js'에서 import함
    })
}

어드민에서 한번에 700~1000개 내외의 다른 포맷의 파일을 다운로드 받아야하는데 위의 방식대로 하나씩 다운로드 받는 경우, 다운로드 받을 때마다 어드민이 파일 저장 팝업이 뜨고 저장 버튼을 700~1000번을 클릭해야하는 비효율이 발생하게되므로 다운로드 버튼을 클릭 시, 모든 파일을 하나의 zip파일로 저장하는 방식으로 변경하였다.

 

npm 패키지 중 JSZip이라는 Zip파일을 생성하고 다운로드 받을 수 있게 구현되어있는 패키지가 있다.

JSZip의 공식 문서를 확인해보면 아래처럼 예제 코드 자체는 단순하다.

 

 

위의 예제 코드를 살펴보면 Zip 파일 타입의 객체를 생성한 후, file 메소드를 사용하여 파일들을 정의한 객체안에 저장하고, 마지막에 generateAsync 메소드를 통해 해당 객체를 zip파일로 생성하고 file-saver 패키지의 saveAs메소드를 사용해서 zip파일을 다운로드 받는 방식으로 구성되어있다.

 

예저 코드를 참고해서 코드를 아래처럼 작성하였다.

const saveFileAsZip = (files: File[]) => {
    const zip = new JSZip();
    files.map((file: File) => {
    	zip.file(`${file.name}.${file.format}`,file.url, {base64: true})
    })
    zip.generateAsync({type:"blob"})
    .then((content) => {
    	saveAs(content, "test.zip");
    });
}

스택 오버 플로우나 다른 기술블로그들을 확인해봐도 위의 코드처럼 작성했더니 zip파일에 제대로 정보가 담긴다는 코멘트들이 있었지만, 파일 dataURL을 file 메소드가 base64타입으로 인식하지 못하는 이슈로 인해 아래와 같이 base64: true 옵션을 제거하고, 직접 fetch를 통해 파일를 대용량 바이러니 파일(blob) 형식으로 불러와서 zip 파일 안에 저장하는 방법으로 코드를 수정하였다.

 

const saveFileAsZip = (files: File[]) => {
    const zip = new JSZip();
    files.map(async (file: File) => {
      const fetchedFile = await fetch(image.path)
        .then((res: Response) => {
          if (res.status === 200) return res.blob();
        })
        .catch((err: any) => console.log(err));
      if (fetchedFile) {
        zip.file(`${file.name}.${file.type}`, fetchedFile, {
          binary: true,
        });
      }
    });
    zip.generateAsync({ type: 'blob' }).then((blob) => {
        saveAs(blob, `test.zip`);
    });
  };

코드를 수정후, base64 에러는 말생하지 않았지만, zip 파일에 아무것도 들어가지 않은 상태에서 zip파일이 생성되고 다운로드 되는 이슈가 발생하였다. 이슈가 발생된 것의 원인으로 예상되는 부분은 files.map을 통해 file을 하나씩 dataURL을 fetch를 통해 파일을 불러와서 blob형식으로 넣는 부분이었다.

 

해당 부분의 코드 이후에 zip.generateAsync메소드를 호출하는 부분에 Promise.all을 통해 fetch 후 파일을 blob로 저장하는 것이 완료된 후 generateAsync 메소드가 작동하게 수정하였다.

const saveFilesAsZip = (files: File[]) => {
    const zip = new JSZip();
    const remoteZips = files.map(async (file: File) => {
      const fetchedFile = await fetch(image.path)
        .then((res: Response) => {
          if (res.status === 200) return res.blob();
        })
        .catch((err: any) => console.log(err));
      if (fetchedFile) {
        zip.file(`${file.name}.${file.type}`, fetchedFile, {
          binary: true,
        });
      }
    });
    Promise.all(remoteZips).then(() => {
      zip.generateAsync({ type: 'blob' }).then((blob) => {
        saveAs(blob, `test.zip`);
      });
    });
  };

위와 같이 Promise.all을 통해 위의 파일 blob 데이터를 다 받아온 후 generateAsync 메소드가 동작하도록 구성하여 정상적으로 zip 파일 안에 파일들이 들어가 있는 것을 확인할 수 있었다.

 

참고)

https://www.npmjs.com/package/file-saver

 

file-saver

An HTML5 saveAs() FileSaver implementation. Latest version: 2.0.5, last published: a year ago. Start using file-saver in your project by running `npm i file-saver`. There are 2851 other projects in the npm registry using file-saver.

www.npmjs.com

https://www.npmjs.com/package/jszip

 

jszip

Create, read and edit .zip files with JavaScript http://stuartk.com/jszip. Latest version: 3.7.1, last published: 6 months ago. Start using jszip in your project by running `npm i jszip`. There are 2851 other projects in the npm registry using jszip.

www.npmjs.com