text: class Crc32 {
      constructor() {
          this.crc = -1
      }
  
      append(data) {
          var crc = this.crc | 0;
          var table = this.table
          for (var offset = 0, len = data.length | 0; offset < len; offset++) {
              crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF]
          }
          this.crc = crc
      }
  
      get() {
          return ~this.crc
      }
  }
  
  Crc32.prototype.table = (() => {
      var i;
      var j;
      var t;
      var table = []
      for (i = 0; i < 256; i++) {
          t = i
          for (j = 0; j < 8; j++) {
              t = (t & 1) ?
                  (t >>> 1) ^ 0xEDB88320 :
                  t >>> 1
          }
          table[i] = t
      }
      return table
  })()
  
  const getDataHelper = byteLength => {
      var uint8 = new Uint8Array(byteLength)
      return {
          array: uint8,
          view: new DataView(uint8.buffer)
      }
  }
  
  const pump = zipObj => zipObj.reader.read().then(chunk => {
      if (chunk.done) return zipObj.writeFooter()
      const outputData = chunk.value
      zipObj.crc.append(outputData)
      zipObj.uncompressedLength += outputData.length
      zipObj.compressedLength += outputData.length
      zipObj.ctrl.enqueue(outputData)
  })
  
  /**
   * [createWriter description]
   * @param  {Object} underlyingSource [description]
   * @return {Boolean}                  [description]
   */
  function createWriter(underlyingSource) {
      const files = Object.create(null)
      const filenames = []
      const encoder = new TextEncoder()
      let offset = 0
      let activeZipIndex = 0
      let ctrl
      let activeZipObject, closed
      let zip64 = false
      let filesSize = 0
  
      function next() {
          activeZipIndex++
          activeZipObject = files[filenames[activeZipIndex]]
          if (activeZipObject) processNextChunk()
          else if (closed) closeZip()
      }
  
      var zipWriter = {
          enqueue(fileLike) {
              if (closed) throw new TypeError('Cannot enqueue a chunk into a readable stream that is closed or has been requested to be closed')
  
              let name = fileLike.name.trim()
              const date = new Date(typeof fileLike.lastModified === 'undefined' ? Date.now() : fileLike.lastModified)
  
              if (fileLike.directory && !name.endsWith('/')) name += '/'
              if (files[name]) throw new Error('File already exists.')
              filesSize += fileLike.size
              zip64 = (filesSize >= 0xffffffff)
  
              const nameBuf = encoder.encode(name)
              filenames.push(name)
  
  
              const zipObject = files[name] = {
                  level: 0,
                  ctrl,
                  directory: !!fileLike.directory,
                  nameBuf,
                  comment: encoder.encode(fileLike.comment || ''),
                  compressedLength: 0,
                  uncompressedLength: 0,
                  extraArray: null,
  
                  writeHeader() {
                      var header = getDataHelper(26)
                      var data = getDataHelper(30 + nameBuf.length)
  
                      zipObject.header = header
                      zipObject.offset = offset
                      if (zipObject.level !== 0 && !zipObject.directory) {
                          header.view.setUint16(4, 0x0800)
                      }
                      header.view.setUint32(0, 0x14000808)
  
                      if (zip64) //Zip64 Min ver.
                          header.view.setUint16(0, 45, true)
  
                      header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true)
                      header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true)
                      header.view.setUint16(22, nameBuf.length, true)
                      data.view.setUint32(0, 0x504b0304)
                      data.array.set(header.array, 4)
                      data.array.set(nameBuf, 30)
                      offset += data.array.length
                      ctrl.enqueue(data.array)
                  },
  
                  writeFooter() {
                      if (zipObject.compressedLength && zipObject.compressedLength >= 0xffffffff) {
                          zipObject.header.view.setUint16(0, 45)
                          zip64 = true
                      }
  
                      var footer = getDataHelper(zip64 ? 24 : 16)
                      footer.view.setUint32(0, 0x504b0708)
  
                      if (zipObject.crc) {
                          zipObject.header.view.setUint32(10, zipObject.crc.get(), true)
                          footer.view.setUint32(4, zipObject.crc.get(), true)
                      }
  
                      if (zip64) {
                          let zip64Extra = getDataHelper(28)
                          zipObject.header.view.setUint32(14, 0xffffffff, true)
                          zipObject.header.view.setUint32(18, 0xffffffff, true)
                          footer.view.setBigUint64(8, BigInt(zipObject.compressedLength), true)
                          footer.view.setBigInt64(16, BigInt(zipObject.uncompressedLength), true)
                          zip64Extra.view.setUint16(0, 0x0001, true)
                          zip64Extra.view.setUint16(2, 24, true)
                          zip64Extra.view.setBigUint64(4, BigInt(zipObject.uncompressedLength), true)
                          zip64Extra.view.setBigUint64(12, BigInt(zipObject.compressedLength), true)
                          zip64Extra.view.setBigUint64(20, BigInt(files[name].offset), true)
                          zipObject.extraArray = zip64Extra.array
                      } else {
                          zipObject.header.view.setUint32(14, zipObject.compressedLength, true)
                          zipObject.header.view.setUint32(18, zipObject.uncompressedLength, true)
                          
                          footer.view.setUint32(8, zipObject.compressedLength, true)
                          footer.view.setUint32(12, zipObject.uncompressedLength, true)
  
                      }
  
                      ctrl.enqueue(footer.array)
                      offset += zipObject.compressedLength + footer.array.length
                      next()
                  },
                  fileLike
              }
  
              if (!activeZipObject) {
                  activeZipObject = zipObject
                  processNextChunk()
              }
          },
          close() {
              if (closed) throw new TypeError('Cannot close a readable stream that has already been requested to be closed')
              if (!activeZipObject) closeZip()
              closed = true
          }
      }
  
      function closeZip() {
          var length = 0
          var index = 0
          var indexFilename, file, cdOffset, totalEntries = filenames.length
          var zip64 = false
          for (indexFilename = 0; indexFilename < totalEntries; indexFilename++) {
              file = files[filenames[indexFilename]]
              length += 46 + file.nameBuf.length + file.comment.length
              if (file.extraArray) {
                  length += file.extraArray.length
                  zip64 = true
              }
          }
          cdOffset = offset
          if (cdOffset + length >= 0xffffffff || totalEntries >= 0xffff)
              zip64 = true
  
          const data = getDataHelper(length + (zip64 ? 56 + 20 : 0) + 22)
          for (indexFilename = 0; indexFilename < totalEntries; indexFilename++) {
              file = files[filenames[indexFilename]]
              data.view.setUint32(index, 0x504b0102)
              data.view.setUint16(index + 4, 0x1400)
              data.array.set(file.header.array, index + 6)
              if (file.extraArray) {
                  data.view.setUint16(index + 30, file.extraArray.length, true)
              }
              data.view.setUint16(index + 32, file.comment.length, true)
              if (file.directory) {
                  data.view.setUint8(index + 38, 0x10)
              }
              if (file.offset >= 0xffffffff)
                  data.view.setUint32(index + 42, 0xffffffff, true)
              else
                  data.view.setUint32(index + 42, file.offset, true)
  
              data.array.set(file.nameBuf, index + 46)
              var extraLength = 0
              if (file.extraArray) {
                  extraLength = file.extraArray.length
                  data.array.set(file.extraArray, index + 46 + file.nameBuf.length)
              }
              data.array.set(file.comment, index + 46 + file.nameBuf.length + extraLength)
              index += 46 + file.nameBuf.length + file.comment.length + extraLength
          }
          if (zip64) {
              // Zip64 End of Central Directory record
              // 0: Signature
              data.view.setUint32(index, 0x504b0606);
              // 4: Size of zip64 EOCD
              data.view.setBigUint64(index + 4, BigInt(44), true);
              // 12: Version made By
              data.view.setUint16(index + 12, 45, true);
              // 14: version needed to extract
              data.view.setUint16(index + 14, 45, true);
              // 16: number of this disk
              // 20: number of the disk with the start of CD
              // 24: total number of entries in the central directory on this disk
              data.view.setBigUint64(index + 24, BigInt(totalEntries), true);
              // 32: total number of entries in the central directory
              data.view.setBigUint64(index + 32, BigInt(totalEntries), true);
              // 40: size of the central directory
              data.view.setBigUint64(index + 40, BigInt(length), true);
              // 48: Offset of start of central directory
              data.view.setBigUint64(index + 48, BigInt(cdOffset), true);
              index += 56
  
              // Zip64 End of Central Directory locator
              // 0: Signature
              data.view.setUint32(index, 0x504b0607);
              // 4: number of the disk with the zip64 EOCD
              // 8: Offset of the zip64 EOCD
              data.view.setBigUint64(index + 8, BigInt(cdOffset + length), true);
              // 16: total number of disks
              data.view.setUint32(index + 16, 1, true);
              index += 20
  
              // EOCD must set these values to 0xffff and 0xffffffff when using ZIP64 format
              totalEntries = 0xffff;
              cdOffset = 0xffffffff;
          }
          data.view.setUint32(index, 0x504b0506)
          data.view.setUint16(index + 8, totalEntries, true)
          data.view.setUint16(index + 10, totalEntries, true)
          data.view.setUint32(index + 12, length, true)
          data.view.setUint32(index + 16, cdOffset, true)
          ctrl.enqueue(data.array)
          ctrl.close()
      }
  
      function processNextChunk() {
          if (!activeZipObject) return
          if (activeZipObject.directory) return activeZipObject.writeFooter(activeZipObject.writeHeader())
          if (activeZipObject.reader) return pump(activeZipObject)
          if (activeZipObject.fileLike.stream) {
              activeZipObject.crc = new Crc32()
              activeZipObject.reader = activeZipObject.fileLike.stream().getReader()
              activeZipObject.writeHeader()
          } else next()
      }
      return new ReadableStream({
          start: c => {
              ctrl = c
              underlyingSource.start && Promise.resolve(underlyingSource.start(zipWriter))
          },
          pull() {
              return processNextChunk() || (
                  underlyingSource.pull &&
                  Promise.resolve(underlyingSource.pull(zipWriter))
              )
          }
      })
  }
  
  window.ZIP = createWriter
  