Packages and layers
Ten names, two rules, and one boundary that is enforced by the compiler rather than by agreement.
The two rules
@genomdev/core holds what any format needs. @genomdev/office-core holds what only Word, Excel and PowerPoint need. Everything else is a format package or a framework wrapper.
The boundary between the two kernels is decided by one question: would a non-office format want this? ZIP — yes, .epub and .odt are ZIPs. Streaming XML — yes. Cryptography — yes, PDF already uses it. OPC, DrawingML, EMU, twips, OLE2 — no.
| Package | Entry points | What it is |
|---|---|---|
@genomdev/genom | . /lazy /viewer /formats /node | The umbrella: every format, recognised without being told. Also the CLI. |
@genomdev/core | . /content /viewer /dom /dom/highlight | Bytes, ZIP, XML, detection, the registry, addressing, the content model, the drawing kit. |
@genomdev/office-core | . /ole /formula /media /view | OPC, DrawingML, Office Art, the compound file, the Excel formula language. |
@genomdev/docx | . /view | Word, both generations. |
@genomdev/xlsx | . /view | Excel, both generations. |
@genomdev/pptx | . /view | PowerPoint, both generations. |
@genomdev/pdf | . /view | PDF. |
@genomdev/react vue angular | . | Framework wrappers. They depend on no format. |
The universal half and the browser half
Every package is split in two, and the split is held by three things at once: a second tsconfig that compiles the universal half without the DOM library, so HTMLElement is not a name that exists in it; a lint rule that blocks imports pointing the wrong way; and the exports map, which means a server that imports @genomdev/docx never loads a line of the renderer.
This is why a Word parser can be loaded on a server at all. Before the split, a mount(document, container: HTMLElement) in the one package every parser depended on made the DOM a transitive dependency of reading text out of a file.
import { openDocx } from '@genomdev/docx'; // server, worker, edge, browser
import { DocxView } from '@genomdev/docx/view'; // browser only
import { extract } from '@genomdev/genom'; // anywhere
import { createViewer } from '@genomdev/genom/viewer'; // browser only
import { extractFile } from '@genomdev/genom/node'; // Node onlyWhich way dependencies point
Downwards, always, and never from a kernel to a format. That second half is the expensive one to get wrong: an extraction package that depends on all seven parsers means importing one function pulls a megabyte and a half of other people’s formats into the graph — and a viewer that loads formats on demand cannot be built on top of it at all.
So a format package depends on the kernels, and the kernels know nothing about formats. What makes that possible is that a format is one module, and the registry takes modules as values.
One copy of each kernel
Both kernels are declared as peer dependencies by everything that uses them. Two copies of the core would mean two format registries, two ContentDocument classes and an instanceof that stops working without a single error anywhere — the kind of failure that takes a day to find.
The umbrella is the deliberate exception: it depends on them normally, so npm i @genomdev/genom works in one command on every package manager, and the peers of the format packages resolve to the copy it brought.