skip to Main Content

I’d like to set up a React hook (or functional component) to read data in from gzip-compressed file, using the pako library to extract its data, and then render it in the browser.

Edit: The compressed binary file is local to the project. It is not hosted on a web server elsewhere.

A simple test I had in mind looked similar to this:

import { memo } from 'react';

import compressedFile from "./data.json.gz";

import './App.css';

const pako = require('pako');

const App = memo(({}) => {

  try {
    const compressedBytes = new Uint8Array(compressedFile);
    const uncompressedStr = pako.inflate(compressedBytes, { to: 'string' });

    return (
      <div>
        Data: <pre>{uncompressedStr}</pre>
      </div>
    );
  } catch (err) {
    return (
      <div>
        Error: <pre>{err}</pre>
      </div>
    );
  }
});

export default App

However, this did not work due to the following error:

Uncaught SyntaxError: Invalid or unexpected token (at data.json.gz?import:1:1)

The file data.json.gz is in the same directory as App and is just a gzip-compressed test file, e.g.:

$ echo -e '{"a":123,"b":456}' | gzip -c > data.json.gz

Can import be used to read in binary data? Is there another way to read in a binary file either directly or indirectly into a byte array I can process with pako?

Please note that I am looking for an answer specifically about working directly with a binary file, and not a base-64 or other re-encoded file that is a string.

2

Answers


  1. As far as I know, the module system expects a file structure that can be parsed into a format that JavaScript can understand and execute. Binary files do not fall into that category by default.

    I can think of two ways to deal with this issue:

    Configuring file-loader from Webpack.

    Or simply by using fetch:

    const response = await fetch("/data.json.gz");
    const compressedBytes = await response.arrayBuffer();
    
    Login or Signup to reply.
  2. You can use untar-js and pako to extract and read a tarball in the browser.

    Here is an example with the Maven binary:

    Note: See the extractFiles function near the bottom.

    const { StrictMode, useEffect, useState } = React;
    
    const TarballExplorer = ({ path }) => {
      const [files, setFiles] = useState([]);
    
      useEffect(() => {
        extractFiles(path).then((extractedFiles) => {
          setFiles(sortByFilePaths(extractedFiles.map(mapFile)));
        });
      }, [path]);
    
      return (
        <div>
          <h1>Viewing files in {path.split('/').pop()}</h1>
          <ul>
            {files.map(file => (
              <li key={file.name}>{file.name} ({filesize(file.size)})</li>
            ))}
          </ul>
        </div>
      );
    }
    
    ReactDOM
      .createRoot(document.getElementById("root"))
      .render(
        <StrictMode>
          <TarballExplorer
            path="https://dlcdn.apache.org/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.tar.gz"
          />
        </StrictMode>
    );
    
    // Simplify a file object to store in state
    function mapFile(file) {
      const { name, size } = file;
      return { name, size };
    }
    
    // Fetch a tarball, decompress it, and return the file names and sizes
    function extractFiles(path) {
      return new Promise((resolve) => {
        fetch(path)
          .then(response => response.arrayBuffer())
          .then(buffer => {
            // Decompress the tarball using pako (output is a Uint8Array)
            const decompressedData = pako.ungzip(buffer);
    
            // Use js-untar from the window object to unpack the ArrayBuffer
            untar(decompressedData.buffer).then((extractedFiles) => {
              resolve(extractedFiles)
            });
          });
      })
    }
    
    // Sort files by their names (aka paths)
    function sortByFilePaths(files) {
      return files.sort((a, b) => {
        const aParts = a.name.split('/');
        const bParts = b.name.split('/');
    
        // Compare directories or files until there's a difference
        for (let i = 0, len = Math.min(aParts.length, bParts.length); i < len; i++) {
          const comparison = aParts[i].localeCompare(bParts[i]);
          if (comparison !== 0) return comparison;
        }
    
        // If all directories match, prioritize directories over files
        return Math.sign(bParts.length - aParts.length);
      });
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/filesize/9.0.11/filesize.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pako.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/InvokIT/[email protected]/build/dist/untar.js"></script>
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search