How Large Data Is Handled During Exports in the Design.md CLI

The Design.md CLI loads the entire DESIGN.md file into memory, builds an in-memory design system state, and writes the complete output in one operation, making it fast for typical files but memory-bound for extremely large documents.

The google-labs-code/design.md repository provides a CLI tool that converts DESIGN.md files into various export formats like Tailwind CSS and JSON. Understanding how large data is handled during exports is crucial for performance tuning and avoiding memory exhaustion when processing substantial design systems. The implementation follows a three-stage pipeline that materializes the complete dataset in RAM before generating output.

The Three-Stage Export Pipeline

The export command processes data through distinct ingestion, construction, and serialization phases. Each stage keeps the full dataset in memory, simplifying the architecture but creating implicit memory constraints.

Stage 1: Complete Input Ingestion

The CLI begins by reading the entire source file into a single string. In packages/cli/src/utils.ts, the readInput helper function uses readFileSync to load the complete DESIGN.md file (or buffers stdin) and returns it as a unified string (lines 41‑55).

This approach means the raw input occupies heap memory in its entirety before any processing begins. There is no streaming reader or chunked ingestion mechanism.

Stage 2: In-Memory Design System Construction

The source string passes to the core lint function, which constructs a DesignSystemState object. This state comprises multiple Map instances—such as state.colors, state.typography, and state.spacing—that hold the entire parsed design data.

Because the linter builds these maps from the complete source string, the CLI maintains two full representations of the data simultaneously: the raw input string and the parsed object structure.

Stage 3: Serialization and Single-Shot Output

Depending on the requested format, a dedicated emitter handler traverses the DesignSystemState and produces a result object. The CLI then serializes this object to a string and writes it in one operation:

  • Tailwind v4 CSS: The TailwindV4EmitterHandler (in packages/cli/src/linter/tailwind/v4/handler.ts) creates a plain object, and serializeToCss (in packages/cli/src/linter/tailwind/v4/serialize.ts) converts it to a single @theme { … } block (lines 30‑45). Output uses process.stdout.write (lines 82‑83).

  • Tailwind v3 JSON: The TailwindEmitterHandler builds a theme.extend object (lines 24‑35), written via console.log (lines 93‑94).

  • CSS Variables: The CssVarsEmitterHandler follows the same pattern for the css-vars format.

Memory Constraints and Performance Characteristics

Because the CLI does not stream input or output, the total memory footprint equals the sum of the raw input, parsed state, and serialized output strings. This design implicitly caps exportable design files to the available Node.js heap size.

In practice, the tool handles typical design files ranging from hundreds of kilobytes to several megabytes comfortably. However, extremely large DESIGN.md files will exhaust memory, as the architecture lacks chunked processing or back-pressure mechanisms.

Code Implementation Details

The export orchestration resides in packages/cli/src/commands/export.ts, which coordinates the pipeline:

  1. Calls readInput from utils.ts to load the full source
  2. Passes the string to lint to build the DesignSystemState
  3. Dispatches to the appropriate emitter handler based on the format flag
  4. Writes the complete serialized result to stdout

For Tailwind v4 specifically, the handler creates a plain-object representation of the theme, which serializeToCss then transforms into a CSS string. The v3 implementation similarly materializes the full theme.extend object before JSON serialization.

Practical Examples

Both of the following commands load the entire huge-design.md file into memory before producing output:


# Export to Tailwind v4 CSS

cat huge-design.md | design export - css-tailwind > huge-theme.css

# Export to JSON Tailwind v3

design export huge-design.md json-tailwind > huge-theme.json

In each case, the CLI reads the complete input, constructs the full design system state, and emits the result in a single write operation.

Summary

  • Complete materialization: The CLI reads the entire DESIGN.md file into memory using readFileSync before processing.
  • Dual memory representation: Both the raw source string and the parsed DesignSystemState (comprised of Map objects) reside in RAM simultaneously.
  • Single-shot output: Emitters serialize the complete in-memory state to a string and write it via process.stdout.write or console.log without streaming.
  • Memory-bound scalability: The architecture handles typical design files efficiently but cannot process extremely large files that exceed available heap memory.

Frequently Asked Questions

Does the Design.md CLI support streaming for large files?

No. According to the source code in packages/cli/src/utils.ts, the readInput function loads the entire file or stdin buffer into a single string using readFileSync. There is no chunked reader or stream-based processing, so the entire dataset must fit in memory.

What is the practical file size limit for exports?

The limit depends on your Node.js heap allocation. Since the CLI holds the raw input, the parsed DesignSystemState (with its Map objects for colors, typography, and spacing), and the serialized output string simultaneously, you need approximately 3-4x the source file size in available memory. Files up to several megabytes work comfortably, but gigabyte-scale files will crash with heap exhaustion.

How does the Tailwind v4 emitter handle data serialization?

The TailwindV4EmitterHandler in packages/cli/src/linter/tailwind/v4/handler.ts first converts the DesignSystemState into a plain JavaScript object. Then serializeToCss in packages/cli/src/linter/tailwind/v4/serialize.ts transforms this object into a single CSS @theme block. The complete CSS string is written to stdout in one operation using process.stdout.write.

Can I export to multiple formats without re-reading the file?

No. Each invocation of design export re-executes the full three-stage pipeline: reading the input, building the state, and serializing the output. There is no caching mechanism to persist the DesignSystemState between commands, so the file is read from disk on every export operation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →