UNPKG

@loaders.gl/zip

Version:

Zip Archive Loader

114 lines (98 loc) 3.38 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type {WriterWithEncoder, WriterOptions} from '@loaders.gl/loader-utils'; import JSZip, {JSZipFileOptions, JSZipGeneratorOptions} from 'jszip'; // @ts-ignore TS2304: Cannot find name '__VERSION__'. const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest'; export type ZipWriterOptions = WriterOptions & { zip?: { onUpdate?: (metadata: {percent: number}) => void; /** When enabled, parent directory entries are created for nested file keys. */ createFolders?: boolean; }; /** Passthrough options to jszip. Note that jszip maybe replaced in future versions of loaders.gl */ jszip?: JSZipGeneratorOptions & JSZipFileOptions; }; /** * Zip exporter */ export const ZipWriter = { name: 'Zip Archive', id: 'zip', module: 'zip', version: VERSION, extensions: ['zip'], category: 'archive', mimeTypes: ['application/zip'], options: { zip: { onUpdate: () => {}, createFolders: false }, jszip: {} }, encode: encodeZipAsync } as const satisfies WriterWithEncoder<Record<string, ArrayBuffer>, never, ZipWriterOptions>; async function encodeZipAsync( fileMap: Record<string, ArrayBuffer>, options: ZipWriterOptions = {} ): Promise<ArrayBuffer> { const jsZip = new JSZip(); const directoryEntries = new Set<string>(); const zipOptions = {...ZipWriter.options.zip, ...options?.zip}; const jszipOptions = {...ZipWriter.options?.jszip, ...options.jszip}; const jszipFileOptions: JSZipFileOptions = { createFolders: false, ...jszipOptions }; // add files to the zip for (const subFileName in fileMap) { const subFileData = fileMap[subFileName]; const isDirectoryEntry = subFileName.endsWith('/'); if (isDirectoryEntry || zipOptions.createFolders) { addParentDirectoryEntries(jsZip, subFileName, options, directoryEntries); } // jszip supports both arraybuffer and string data (the main loaders.gl types) // https://stuk.github.io/jszip/documentation/api_zipobject/async.html if (isDirectoryEntry) { jsZip.file(subFileName, null, {...jszipFileOptions, dir: true}); } else { jsZip.file(subFileName, subFileData, jszipFileOptions); } } const jszipGeneratorOptions: JSZipGeneratorOptions = jszipOptions; try { return await jsZip.generateAsync( {...jszipGeneratorOptions, type: 'arraybuffer'}, // generate an arraybuffer zipOptions.onUpdate ); } catch (error) { options.core?.log?.error(`Unable to encode zip archive: ${error}`); throw error; } } function addParentDirectoryEntries( jsZip: JSZip, subFileName: string, options: ZipWriterOptions, directoryEntries: Set<string> ): void { const subPathParts = subFileName.split('/').filter((part) => part.length > 0); const subPathPartCount = subFileName.endsWith('/') ? subPathParts.length : subPathParts.length - 1; let parentDirectoryPath = ''; for (let index = 0; index < subPathPartCount; index++) { parentDirectoryPath = `${parentDirectoryPath}${subPathParts[index]}/`; if (directoryEntries.has(parentDirectoryPath)) { continue; } jsZip.file(parentDirectoryPath, null, { createFolders: false, ...options?.jszip, dir: true }); directoryEntries.add(parentDirectoryPath); } }