Repository: parcel-bundler/parcel
File: packages/core/core/src/PackagerRunner.js (JavaScript)

Code before:
          prevBuf = buf;
        }),
      ),
    );

    if (map != null) {
      let mapKey = getMapKey(cacheKey);
      await this.options.cache.setStream(mapKey, blobToStream(map));
    }

    let info = {size, hash: hash.digest('hex'), hashReferences};
    await this.options.cache.set(getInfoKey(cacheKey), JSON.stringify(info));
    return info;
  }
}

async function writeFileStream(
  fs: FileSystem,
  filePath: FilePath,
  stream: Readable,
  hashReferences: Array<string>,
  hashRefToNameHash: Map<string, string>,
  options: ?FileOptions,
): Promise<number> {
  // TODO: stream directly to filesystem instead of to string first
  //  - need to figure out how to replace hash references in a stream
  let bundleStr = '';
  await new Promise(resolve => {
    stream.on('data', buf => {
      bundleStr += buf.toString();
    });
    stream.on('end', () => {
      resolve();
    });
  });

  for (let hashRef of hashReferences) {
    let re = new RegExp(hashRef, 'g');
    bundleStr = bundleStr.replace(
      re,
      nullthrows(hashRefToNameHash.get(hashRef)),
    );
  }

  await fs.writeFile(filePath, bundleStr, options);

  return bundleStr.length;

  // return new Promise((resolve, reject) => {
  //   let fsStream = fs.createWriteStream(filePath, options);
  //   stream

Review comment (suggestion):
TODO: Skip stream replacement if we know there are no references to replace. We could probably start streaming them to dist immediately instead of waiting for all of the bundles to be processed to create the `hashRefToNameHash` mapping as well.

Code after:
      cacheKeys.content,
      blobToStream(contents).pipe(
        new TapStream(buf => {
          let str = boundaryStr + buf.toString();
          hashReferences = hashReferences.concat(
            str.match(HASH_REF_REGEX) ?? [],
          );
          size += buf.length;
          hash.update(buf);
          boundaryStr = str.slice(str.length - BOUNDARY_LENGTH);
        }),
      ),
    );

    if (map != null) {
      await this.options.cache.setStream(cacheKeys.map, blobToStream(map));
    }
    let info = {size, hash: hash.digest('hex'), hashReferences};
    await this.options.cache.set(cacheKeys.info, info);
    return info;
  }
}

function writeFileStream(
  fs: FileSystem,
  filePath: FilePath,
  stream: Readable,
  hashReferences: Array<string>,
  hashRefToNameHash: Map<string, string>,
  options: ?FileOptions,
): Promise<number> {
  return new Promise((resolve, reject) => {
    let initialStream = hashReferences.length
      ? stream.pipe(replaceStream(hashRefToNameHash))
      : stream;
    let fsStream = fs.createWriteStream(filePath, options);
    initialStream
      .pipe(fsStream)
      // $FlowFixMe
      .on('finish', () => resolve(fsStream.bytesWritten))
      .on('error', reject);
  });
}

function replaceStream(hashRefToNameHash) {
  let boundaryStr = '';
  return new Transform({
    transform(chunk, encoding, cb) {
      let str = boundaryStr + chunk.toString();
      let replaced = str.replace(HASH_REF_REGEX, match => {
        return hashRefToNameHash.get(match) || match;