async function toBase64(string, options) {
  let bytes = new TextEncoder().encode(string);

  if (options.gzip) {
    if (typeof CompressionStream !== 'undefined') {
      const cs = new CompressionStream('gzip');
      const writer = cs.writable.getWriter();
      writer.write(bytes);
      writer.close();
      const compAb = await new Response(cs.readable).arrayBuffer();
      bytes = new Uint8Array(compAb);
    } else {
      /** @type {import('pako')=} */
      const pako = window.pako;
      bytes = pako.gzip(string);
    }
  }

  let binaryString = '';
  // This is ~25% faster than building the string one character at a time.
  // https://jsbench.me/2gkoxazvjl
  const chunkSize = 5000;
  for (let i = 0; i < bytes.length; i += chunkSize) {
    binaryString += String.fromCharCode(...bytes.subarray(i, i + chunkSize));
  }
  return btoa_(binaryString);
}
Takes an UTF-8 string and returns a base64 encoded string.
If gzip is true, the UTF-8 bytes are gzipped before base64'd, using
CompressionStream (currently only in Chrome), falling back to pako
(which is only used to encode in our Node tests).
@param {string} string
@param {{gzip: boolean}} options
@return {Promise<string>}