{"_id":"@eddocu/hmml","name":"@eddocu/hmml","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@eddocu/hmml","version":"0.1.0","description":"HyperMedia Markup Language - a tiny binary format where an image is HTML, SVG and a 3D space. Markup as text + images as raw bytes (no base64) in one file; ~2 KB reader. The native target for AI-generated media.","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"sideEffects":false,"scripts":{"build":"tsup","prepublishOnly":"npm run build","typecheck":"tsc --noEmit","demo":"tsx examples/roundtrip.ts","pg:build":"npm run build && node playground/build.mjs","pg:bundle":"npm run pg:build && node playground/bundle.mjs","playground":"npm run pg:build && node e2e/server.mjs","bench":"npm run build && node bench/perf.mjs","size":"node bench/size.mjs","site:build":"npm run build && node docs/build.mjs","site:deploy":"npm run site:build && npx wrangler pages deploy site --project-name=hmml","test":"vitest run","test:browser":"npm run build && playwright test","test:all":"npm run test && npm run test:browser"},"keywords":["hmml","binary","html","image","container","webp","format","hypermedia"],"author":{"name":"Argun"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/yeargun/hmml.git"},"homepage":"https://hmml.eddocu.com","bugs":{"url":"https://github.com/yeargun/hmml/issues"},"publishConfig":{"access":"public"},"devDependencies":{"@playwright/test":"1.57.0","terser":"^5.31.0","@types/node":"^20.12.7","highlight.js":"^11.9.0","marked":"^12.0.2","marked-highlight":"^2.1.1","tsup":"^8.0.2","tsx":"^4.7.1","typescript":"^5.4.5","vitest":"^1.5.0"},"_id":"@eddocu/hmml@0.1.0","gitHead":"57d5dd531bc897431209c6a4a649498fba77cd66","_nodeVersion":"20.19.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-9R0ptvCrYsGf9SnlB9ZVZ8RMJIzZXR8SKxpJHL2esxM5YF8OFBkPr4athoE9dS9hy8mRS64fYZDVZpRZXn9z7g==","shasum":"8bb141212250c597e28322c330727fa7f0f9501e","tarball":"https://registry.npmjs.org/@eddocu/hmml/-/hmml-0.1.0.tgz","fileCount":9,"unpackedSize":63819,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBS/MO6jq1iGK7QpuwOJvD14gdL8CpZB5EoejayTUN/sAiBI4n8NEXTh0xSnMAH1KrXacP5TwKINjvrvrHJjG1h38Q=="}]},"_npmUser":{"name":"yeargun","email":"yeargun24@gmail.com"},"directories":{},"maintainers":[{"name":"yeargun","email":"yeargun24@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hmml_0.1.0_1780777964553_0.96586881920496"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-06T20:32:44.336Z","0.1.0":"2026-06-06T20:32:44.716Z","modified":"2026-06-06T20:32:44.936Z"},"maintainers":[{"name":"yeargun","email":"yeargun24@gmail.com"}],"description":"HyperMedia Markup Language - a tiny binary format where an image is HTML, SVG and a 3D space. Markup as text + images as raw bytes (no base64) in one file; ~2 KB reader. The native target for AI-generated media.","homepage":"https://hmml.eddocu.com","keywords":["hmml","binary","html","image","container","webp","format","hypermedia"],"repository":{"type":"git","url":"git+https://github.com/yeargun/hmml.git"},"author":{"name":"Argun"},"bugs":{"url":"https://github.com/yeargun/hmml/issues"},"license":"MIT","readme":"<div align=\"center\">\n\n# HMML\n\n### HyperMedia Markup Language\n\n**A tiny open format for one file that is HTML *and* its images - together, in bytes.**\n\n`image + html  →  one binary document`\n\nIt's bits & bytes and a contract. Markup stays text; images stay raw (no base64).\nSmaller than a self‑contained HTML file, and it renders with the full power of a browser.\n\n<sub>~2 KB gzipped reader · sub‑millisecond decode · zero dependencies · works in the browser, Node, Deno, Bun & workers</sub>\n\n[Spec](./SPEC.md) · [Playground](./playground) · [Quick start](#quick-start) · [Why](#why-hmml)\n\n</div>\n\n---\n\n## The idea\n\nA web page is already *hypermedia* - text, layout, and media in one experience. But the\nmoment you want it as **one portable file**, you hit a wall: you base64 every image into\nthe HTML and pay a ~33% size tax, or you ship a folder of loose assets.\n\nHMML is the third option. One binary document:\n\n```\n┌─────────────────────────────────────────────┐\n│  HMML document                                │\n│                                               │\n│   MARK   <html>…</html>      ← text, gzipped  │\n│   RSRC   image/webp  ▒▒▒▒▒   ← raw bytes      │\n│   RSRC   image/png   ▒▒▒▒▒   ← raw bytes      │\n│   META   { title, … }                         │\n│                                               │\n│   markup points at images with  hmml:<id>     │\n└─────────────────────────────────────────────┘\n```\n\nThe markup references each image by a tiny token (`<img src=\"hmml:r0\">`), and the bytes\nlive next to it - uncompressed‑duplicated, exactly as the camera/encoder produced them.\nBecause the renderer is a real browser, your layout has **no limits**: `matrix3d`,\nfilters, blend modes, SVG, `<canvas>`, CSS grid - all of it, for free.\n\n## Why HMML\n\n- **Smaller than base64.** Raw image bytes drop the ~33% base64 tax; markup is gzipped.\n  Measured **~25% smaller** than the equivalent single‑file HTML on image‑heavy docs,\n  and **35–40% smaller** on markup‑heavy ones.\n- **Tiny & fast clients.** The reader is **~2 KB gzipped** and decodes at **~800 MB/s**.\n- **Zero dependencies.** Pure `Uint8Array`; compression uses the platform\n  `CompressionStream`. Runs unchanged in browsers, Node 18+, Deno, Bun and Web Workers.\n- **A real contract, not a blob.** A PNG‑style signature + self‑describing chunks +\n  optional CRC32. Forward‑compatible: unknown chunks are skipped, so v2 won't break v1.\n- **Render however you like.** Resolve images to `blob:` object URLs (cheap to paint),\n  `data:` URIs (self‑contained export), or keep `hmml:` refs and serve them yourself.\n\n## Numbers\n\nMeasured on this repo (`npm run size`, `npm run bench`; Node 20, single‑threaded).\n\n**Bundle size** - minified, by what you import:\n\n| You import | gzip | brotli |\n| --- | ---: | ---: |\n| `decode` (the reader) | **2.0 KB** | 1.8 KB |\n| `encode` + `extract` (the writer) | **2.0 KB** | 1.8 KB |\n| `pack` + `unpack` (typical app) | **3.2 KB** | 2.9 KB |\n| everything | **3.8 KB** | 3.4 KB |\n\n**Throughput** - a 494 KB document (one ~492 KB image + markup):\n\n| Operation | per call | throughput |\n| --- | ---: | ---: |\n| decode (gzip) | 0.61 ms | **829 MB/s** |\n| encode (gzip) | 1.17 ms | 434 MB/s |\n| decode (store, no compression) | 0.06 ms | **8.0 GB/s** |\n| encode (store) | 0.36 ms | 1.4 GB/s |\n\nAnd it was **25.2% smaller** than the self‑contained base64 HTML of the same content.\n\n## Quick start\n\n```sh\nnpm install @eddocu/hmml\n```\n\nThe easy path - `pack` / `unpack`:\n\n```ts\nimport { pack, unpack } from \"@eddocu/hmml\";\n\n// Pass HTML that has data: URIs - they're auto-extracted into raw resources.\n// (gzip by default → smallest file, no config.)\nconst bytes: Uint8Array = await pack(`\n  <section style=\"transform: matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1)\">\n    <img src=\"data:image/webp;base64,UklGR…\">\n  </section>\n`, { meta: { title: \"Card\" } });\n\n// …store/send `bytes` (a .hmml file)…\n\nconst doc = await unpack(bytes);\nel.innerHTML = doc.toHTML();          // images inlined as data URIs\n// or, cheaper to paint in the browser:\nconst { html, revoke } = doc.createObjectUrls();\nel.innerHTML = html;                  // images as blob: URLs - call revoke() on teardown\n```\n\nBuilding a document explicitly (no auto‑extract):\n\n```ts\nawait pack({\n  html: `<img src=\"hmml:hero\">`,\n  resources: [{ id: \"hero\", mime: \"image/png\", data: pngBytes }],\n});\n```\n\nUse it from a plain `<script>` - exposes `window.HMML`. Until it's on npm, self-host\nthe global build (`dist/index.global.js`, ~4 KB gzip) or inline it; the\n[landing page](https://hmml.pages.dev) ships a decode-only reader inlined this way.\n\n```html\n<script src=\"/hmml.global.js\"></script>\n<script>\n  const doc = await HMML.unpack(new Uint8Array(await file.arrayBuffer()));\n  document.body.innerHTML = doc.toHTML();\n</script>\n```\n\n## How it works (the contract)\n\nA document is a signature + a stream of self‑describing chunks:\n\n```\n89 'H' 'M' 'M' 'L' 0D 0A 1A 0A   signature (PNG-style corruption guard)\nmajor · minor · codec            1 byte each\n─ chunk ─ … ─ chunk ─            TYPE(4) FLAGS(1) LEN(u32 LE) PAYLOAD [CRC32?]\n   MARK   markup (gzip-able)\n   RSRC   id + mime + raw bytes\n   META   JSON metadata\n   ENDF   end marker\n```\n\nLittle‑endian, UTF‑8, one codec per file (recorded in the header so the reader\nauto‑resolves it). Full byte‑level details, including a hex worked example, are in\n**[SPEC.md](./SPEC.md)**.\n\n## How it compares\n\n| | binary | images stored | one file | renders as | to *read* it |\n| --- | :---: | --- | :---: | --- | --- |\n| **HMML** | ✅ | **raw** | ✅ | HTML, any browser | **~2 KB lib** |\n| Single‑file HTML (`data:` URIs) | ❌ text | base64 (+33%) | ✅ | HTML | nothing |\n| MHTML `.mht` | ❌ MIME text | base64 | ✅ | HTML | a parser |\n| Safari `.webarchive` | ✅ bplist | raw | ✅ | HTML (Safari only) | Apple‑only |\n| Web Bundle `.wbn` | ✅ CBOR | raw | ✅ | HTTP exchanges | CBOR + flagged browser |\n| ZIP (EPUB‑style) | ✅ | raw | ✅ | depends | a zip lib |\n\nHMML's niche: a **small, embeddable, smaller‑than‑base64, dependency‑free** binary\ndocument you fully control - ideal for storing a rich snippet/card compactly and\nrehydrating it in the browser. (For archiving whole pages, MHTML or a ZIP are fine;\nthis is a different job.)\n\n## Playground\n\nReal files and pages to poke - see [`playground/`](./playground).\n\n```sh\nnpm run playground   # build + generate + serve a gallery at http://127.0.0.1:5188\nnpm run pg:bundle    # zip a self-contained, file://-openable bundle (no server/npm)\n```\n\n- **Viewer** - drag a `.hmml` in and watch it render (works from `file://`).\n- **Create** - pick images, build & download your own `.hmml`.\n- Every sample also ships as a double‑clickable standalone `.html`.\n\n## Compression codecs\n\nDefault for `pack` is **gzip**; for the low‑level `encode` it's `store` (no deps at all).\nBuilt‑in ids are auto‑resolved on decode.\n\n```ts\nimport { encode, gzipCodec, storeCodec, deflateRawCodec } from \"@eddocu/hmml\";\nawait encode(input, { codec: gzipCodec });            // smallest\nawait encode(input, { codec: storeCodec, crc: true }); // no compression + integrity\n```\n\nCustom codec (e.g. [`fflate`](https://github.com/101arrowz/fflate) for old runtimes):\n\n```ts\nimport { deflateSync, inflateSync } from \"fflate\";\nconst fflateCodec = { id: 16, deflate: deflateSync, inflate: inflateSync };\nawait encode(input, { codec: fflateCodec });\nawait decode(file, { codec: fflateCodec }); // custom id → pass it back\n```\n\n## API\n\n| Export | What it does |\n| --- | --- |\n| `pack(html \\| input, opts?)` | One‑call encode (auto‑extracts `data:` URIs; gzip default) |\n| `unpack(bytes, opts?)` | One‑call decode → `HmmlDocument` |\n| `encode` / `decode` | Low‑level encode/decode |\n| `extract` / `inlineDataUris` / `inlineObjectUrls` | Markup ⇄ resources helpers |\n| `storeCodec` / `gzipCodec` / `deflateRawCodec` / `deflateCodec` | Built‑in codecs |\n| `sniffMime` / `extensionFor` / `toBase64` / `fromBase64` / `crc32` | Utilities |\n\n`HmmlDocument`: `{ html, resources: Map, meta, codecId, toHTML(), createObjectUrls() }`.\n\n## Development\n\n```sh\nnpm install\nnpm run demo          # node round-trip + size comparison\nnpm test              # vitest unit tests\nnpm run test:browser  # Playwright: decode & render in real Chromium\nnpm run bench         # encode/decode throughput\nnpm run size          # bundle sizes by import shape\nnpm run build         # dual ESM/CJS + IIFE global + .d.ts (minified) via tsup\n```\n\n## Status\n\n**v0 / draft.** The format (major version `1`) is implemented and tested end‑to‑end, but\nthe spec may still evolve before a `1.0` tag. Feedback and breakage reports welcome.\n\n## License\n\nMIT © Argun\n","readmeFilename":"README.md","_rev":"1-39ec92d3faac74834b5d6d773926d44d"}