Skip to main content

Command Palette

Search for a command to run...

Writing a Zero-Dependency CSV and XLSX Reader/Writer

Published
5 min read
Writing a Zero-Dependency CSV and XLSX Reader/Writer
T

A Web Developer based in Belgium, specialized in PHP & JS development

Some projects start with a grand plan. Others start because you just need to read a file without thinking too much about it.

That was the case for spread-compat. I needed a common interface for CSV, XLSX, and sometimes legacy XLS files, without having to rewrite the same code depending on the underlying library. Sometimes I needed a "simple" parser, sometimes, I needed something that supports more complex files. The goal was simple: switch adapters, keep the rest of the code the same, and let the package deal with the details. That is still very much the idea behind spread-compat today: one facade over several backends, including native PHP for CSV, OpenSpout, League CSV, PhpSpreadsheet, and SimpleXLSX, with the package favoring the fastest available adapter by format.​

The first idea

At first, the main motivation was convenience.

CSV is everywhere, and it is often the fastest and easiest format to process. But in real applications, users also expect XLSX support because that is what they open in Excel. And every now and then, you still run into old XLS files as well. spread-compat was my way to smooth over those differences and expose a single API for reading and writing tabular data. The repository describes that goal very directly: importing and exporting CSV data is common, but users are often more comfortable with Excel files, so changing format should ideally be just a matter of changing an adapter.​

At that stage, I also realized something else: for many use cases, a simple CSV or XLSX reader can be much faster than a full-featured spreadsheet library.

The part that looks simple

This was the point where the project became more interesting.

Reading CSV or XLSX files sounds simple until you start listing edge cases. CSV alone has plenty of traps: separators, enclosures, escapes, line endings, encoding issues, multiline fields, streaming, malformed files, and small RFC details that are easy to overlook until a real file breaks your parser. The spread-compat repository itself shows that the package had to support configuration around separators, generators, streams, output handling, and multiple adapters, which is already a sign that “simple” file handling quickly grows into something more subtle.​

That is also why mature libraries such as League CSV, OpenSpout, and PhpSpreadsheet exist in the first place. They are not big by accident. spread-compat supports those libraries precisely because each of them solves a different part of the problem space: speed, compatibility, streaming, or richer spreadsheet features.​

Why baresheet happened

After working on the “native” adapters in spread-compat, I reached a point where it made sense to split that work into its own package.

I wanted something focused: a small library dedicated to the hard parts of simple spreadsheet reading and writing, while still reusing the experience and code paths I had already explored in spread-compat. That eventually became baresheet.

In a way, baresheet came from a contradiction I kept running into: I wanted something lightweight, but I also wanted it to behave correctly in the messy situations that real files bring. So the work was no longer just about making a fast reader. It became about handling RFC details properly, improving performance without cutting corners, thinking about security, benchmarking the results, and iterating until the package felt solid enough to stand on its own.

Today, that feels like the real value of baresheet. It is not just “native code extracted from another package”. It is the result of many rounds of refinement around the deceptively small problem of reading and writing tabular files well.

How the two fit together

I see the relationship between the two packages as fairly natural.

spread-compat is the compatibility layer. It gives you one interface over several libraries and lets you choose the right adapter for the job. It is especially useful when your application needs to support more than one format or when you want flexibility without coupling your code to a single spreadsheet package. The project supports multiple backends and exposes a facade for reading, writing, and browser output, all while keeping the “one worksheet” scope intentionally narrow so CSV and XLSX can be treated in a similar way.​

baresheet is the focused engine that grew out of the fast-path use case. When you just want efficient handling of common CSV/XLSX scenarios, a dedicated library can be a better foundation than generic adapters embedded inside a broader compatibility package. That separation also makes the design cleaner: one package is about abstraction, the other is about doing the low-level work well.

A few things I find interesting

A few lessons became clearer while building both packages.

  • “Simple” formats are rarely simple. CSV looks trivial right up until you care about correctness on weird files (hello BOM).

  • Lightweight code does not mean less engineering. In practice, it often means more attention to edge cases, benchmarks, and failure modes.

  • Older formats never really disappear. Legacy XLS support is the kind of requirement that sounds outdated until a client sends you one.

  • Small-scope tools can become better by being honest about scope. spread-compat explicitly limits itself to one worksheet, which is a good example of choosing constraints instead of pretending to cover everything.​

If I had to describe the evolution in one line, I would say this: spread-compat started as a practical abstraction layer, and baresheet grew out of the realization that the fast, simple path deserved to become a real library of its own.

At the moment, baresheet is published as version 0.2 and already feels much better than what used to live as the “native” adapters inside spread-compat.

Have a look if you are interested :-)

https://github.com/lekoala/baresheet

https://github.com/lekoala/spread-compat