npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

epubit

v1.0.6

Published

Client-side EPUB 3 builder — create valid EPUB files directly in the browser

Readme

epubit — User Manual

A client-side JavaScript library for building valid EPUB 3 files directly in the browser. No server required.

GitHub License JavaScript HTML npm version Build Status Maintenance npm

🚀 Live Demo

Live Demo

Features

  • 📚 Build valid EPUB 3 files directly in the browser
  • 🚫 No server required
  • ⚡ Fast and lightweight
  • 🎨 Full client-side control
  • 📖 Support for standard EPUB 3 specification

Table of Contents

  1. Installation
  2. Quick Start
  3. Constructor
  4. Metadata
  5. Cover Image
  6. Table of Contents Page
  7. Chapters
  8. Stylesheets
  9. Images
  10. Fonts
  11. Sanitizer
  12. Generating the EPUB
  13. Method Chaining
  14. Full Example
  15. HTML Elements Reference
  16. EPUB Structure Reference
  17. Error Reference

1. Installation

Via <script> tag (browser)

Include JSZip first, then epubit.js. JSZip will also be auto-injected if missing, but including it yourself is more reliable.

<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/epubit/epubit.js"></script>

EBook is now available globally on window.

Via CommonJS / Node-compatible bundler

const EBook = require("./epubit.js");

Via ESM bundler (Webpack, Vite, Rollup)

The library uses a UMD wrapper, so it works with any bundler. Just import it:

import EBook from "./epubit.js";

Note: The library targets browser environments. It depends on DOMParser, FileReader, URL.createObjectURL, and document. It will not work in a pure Node.js environment without a DOM polyfill.


2. Quick Start

const book = new EBook({ title: "My First Book", author: "Jane Doe" });

book.addCSS("style.css", `
  body { font-family: Georgia, serif; line-height: 1.6; }
  h1   { font-size: 1.8em; }
`);

book.setCover(base64ImageString, "image/jpeg", "cover.jpg");
book.addTOCPage();

book.addChapter("Introduction", `
  <h1>Introduction</h1>
  <p>Welcome to my book.</p>
`, { css: ["style.css"] });

book.addChapter("Chapter One", `
  <h1>Chapter One</h1>
  <p>The story begins here.</p>
`, { css: ["style.css"] });

await book.download();

3. Constructor

const book = new EBook(options);

Creates a new EBook instance. All options are optional.

| Option | Type | Default | Description | |---|---|---|---| | title | string | "Untitled" | Book title | | author | string | "Unknown" | Author name | | language | string | "en" | BCP 47 language tag, e.g. "en", "fr", "ja" | | publisher | string | "" | Publisher name | | description | string | "" | Short book description | | date | string | today | Publication date in YYYY-MM-DD format | | uuid | string | auto | Custom UUID for the book identifier | | rights | string | "" | Copyright / rights statement | | sanitize | boolean | true | Auto-sanitize chapter HTML before writing |

const book = new EBook({
  title:       "Dune",
  author:      "Frank Herbert",
  language:    "en",
  publisher:   "Chilton Books",
  description: "A science fiction epic set on the desert planet Arrakis.",
  date:        "1965-08-01",
  rights:      "© 1965 Frank Herbert",
});

4. Metadata

setMeta(meta)this

Update any metadata field after construction. Accepts the same keys as the constructor.

book.setMeta({ title: "Dune Messiah", author: "Frank Herbert" });

Partial updates are fine — only the keys you pass are changed.

getMeta()object

Returns a shallow copy of the current metadata object.

const meta = book.getMeta();
console.log(meta.title);  // "Dune Messiah"

5. Cover Image

setCover(data, mimeType, filename, opts)this

Sets the cover image for the book.

| Parameter | Type | Default | Description | |---|---|---|---| | data | string | ArrayBuffer | Blob | — | Image data. Base64 string, ArrayBuffer, or Blob | | mimeType | string | "image/jpeg" | MIME type of the image | | filename | string | "cover.jpg" | Filename stored inside the EPUB | | opts.asPage | boolean | true | Insert a visible cover page as the first item in the spine | | opts.altText | string | "Cover" | Alt text for the cover <img> element |

// From a base64 string
book.setCover(base64str, "image/jpeg", "cover.jpg");

// From a file input
const file = document.querySelector("input[type=file]").files[0];
book.setCover(file, file.type, file.name);

// Cover image only — no visible page (for readers that use it as metadata only)
book.setCover(base64str, "image/png", "cover.png", { asPage: false });

// Custom alt text
book.setCover(base64str, "image/jpeg", "cover.jpg", {
  asPage: true,
  altText: "Cover art for Dune"
});

When asPage: true (the default), the cover is inserted as a full-bleed page — black background, centered image — as the very first page the reader opens.

removeCover()this

Removes the cover image and cover page.

book.removeCover();

6. Table of Contents Page

epubit.js generates two kinds of TOC automatically: an EPUB 3 nav.xhtml (used by modern readers for their built-in TOC sidebar) and an EPUB 2 toc.ncx (for legacy readers). These are structural and invisible as readable pages.

addTOCPage() adds a third, human-readable TOC as a real page inside the book — the kind you'd find printed on page 3 of a physical book. It has numbered entries and clickable links.

addTOCPage(opts)this

| Option | Type | Default | Description | |---|---|---|---| | opts.title | string | "Table of Contents" | Heading displayed at the top of the page | | opts.css | string[] | [] | Stylesheet filenames to link (must be added via addCSS) | | opts.inlineStyle | string | — | Raw CSS string injected as a <style> block, overriding the built-in default style |

// Default — uses built-in styling
book.addTOCPage();

// Custom heading
book.addTOCPage({ title: "Contents" });

// Linked to your own stylesheet
book.addTOCPage({ title: "Contents", css: ["style.css"] });

// Inline style override
book.addTOCPage({
  title: "Contents",
  inlineStyle: `
    body { font-family: sans-serif; background: #fafafa; }
    h1   { color: #333; }
    a    { color: #0066cc; }
  `
});

The TOC page is always placed after the cover (if any) and before chapter one. It lists every chapter in order with a number prefix and a clickable link.

Note: Call addTOCPage() before or after adding chapters — the page is built at generation time, so it always reflects the final chapter list.

removeTOCPage()this

Disables the TOC page if you change your mind.

book.removeTOCPage();

7. Chapters

addChapter(title, html, opts)this

Adds a chapter to the book.

| Parameter | Type | Default | Description | |---|---|---|---| | title | string | — | Chapter title, shown in the TOC | | html | string | — | Body HTML (inner content, not a full document) | | opts.id | string | auto | Explicit item ID. Auto-generated from title if omitted | | opts.order | number | append | Insertion position. Lower numbers appear first | | opts.css | string[] | [] | Stylesheet filenames to link | | opts.raw | boolean | false | Skip the sanitizer for this chapter only |

// Minimal
book.addChapter("Prologue", "<p>It began on a Tuesday.</p>");

// With stylesheet
book.addChapter("Chapter One", chapterHtml, { css: ["style.css"] });

// Explicit order — insert before other chapters
book.addChapter("Preface", prefaceHtml, { order: 0 });

// Custom ID
book.addChapter("About the Author", authorHtml, { id: "about-author" });

// Skip sanitizer (use with trusted HTML only)
book.addChapter("Technical Appendix", rawXhtml, { raw: true });

The HTML you pass is treated as the body content — headings, paragraphs, images, tables, lists, and links are all supported. You do not need to include <html>, <head>, or <body> tags.

updateChapter(id, title, html)this

Update a chapter's title and/or HTML content in place.

book.updateChapter("ch-1-chapter-one", "Chapter One (Revised)", newHtml);

// Update title only
book.updateChapter("ch-1-chapter-one", "Chapter One (Revised)");

// Update content only
book.updateChapter("ch-1-chapter-one", undefined, newHtml);

Throws an error if the ID is not found.

removeChapter(id)this

Removes a chapter by its ID.

book.removeChapter("ch-2-draft-notes");

reorderChapters(idArray)this

Re-sequence chapters by passing an array of IDs in the new desired order.

// Move chapter 3 to the front
book.reorderChapters(["ch-3-epilogue", "ch-1-intro", "ch-2-main"]);

IDs not mentioned keep their relative positions after the listed ones.

getChapters()object[]

Returns a shallow list of chapter metadata (does not include HTML content).

const chapters = book.getChapters();
// [{ id: "ch-1-introduction", title: "Introduction", order: 0 }, …]

8. Stylesheets

addCSS(filename, content)this

Adds a CSS file to the EPUB. Reference it in chapters via opts.css.

book.addCSS("style.css", `
  body {
    font-family: Georgia, "Times New Roman", serif;
    font-size: 1em;
    line-height: 1.7;
    margin: 0 auto;
    max-width: 36em;
    padding: 1em 1.5em;
  }
  h1, h2, h3 { font-weight: bold; margin-top: 2em; }
  p           { margin: 0 0 1em; text-indent: 1.5em; }
  p:first-child { text-indent: 0; }
  blockquote  { border-left: 3px solid #ccc; padding-left: 1em; color: #555; }
  table       { border-collapse: collapse; width: 100%; }
  th, td      { border: 1px solid #ccc; padding: .4em .8em; }
  img         { max-width: 100%; height: auto; }
  code        { font-family: monospace; background: #f4f4f4; padding: .1em .3em; }
`);

Then link it when adding chapters:

book.addChapter("Chapter One", html, { css: ["style.css"] });

Multiple stylesheets are supported. Pass all relevant filenames in the css array:

book.addCSS("base.css", baseStyles);
book.addCSS("chapter.css", chapterStyles);

book.addChapter("Chapter One", html, { css: ["base.css", "chapter.css"] });

removeCSS(filename)this

book.removeCSS("draft-style.css");

9. Images

addImage(filename, data, mimeType)this

Adds an image asset. Once added, reference it inside chapter HTML using a relative path: ../images/<filename>.

| Parameter | Type | Description | |---|---|---| | filename | string | e.g. "photo.jpg" — used as the reference path | | data | string | ArrayBuffer | Blob | Base64 string, ArrayBuffer, or Blob | | mimeType | string | Optional. Inferred from extension if omitted |

Supported formats: JPEG, PNG, GIF, WebP, AVIF, SVG.

// From a base64 string
book.addImage("sunset.jpg", base64string, "image/jpeg");

// From a Blob (e.g. fetched from the web)
const response = await fetch("https://example.com/photo.png");
const blob = await response.blob();
book.addImage("photo.png", blob);

// From a file input
const file = inputEl.files[0];
book.addImage(file.name, file);

Then reference it in chapter HTML:

<figure>
  <img src="../images/sunset.jpg" alt="A sunset over the ocean"/>
  <figcaption>Figure 1: Sunset at Cape Point</figcaption>
</figure>

removeImage(filename)this

book.removeImage("draft-diagram.png");

getImages()string[]

Returns a list of all added image filenames.

book.getImages(); // ["cover.jpg", "sunset.jpg", "map.png"]

10. Fonts

addFont(filename, data, mimeType)this

Embeds a font file. Reference it from a CSS @font-face rule inside a stylesheet you add via addCSS.

Supported formats: WOFF2 (recommended), WOFF, TTF, OTF.

// Add the font file
const fontResponse = await fetch("OpenSans-Regular.woff2");
const fontData = await fontResponse.arrayBuffer();
book.addFont("OpenSans-Regular.woff2", fontData, "font/woff2");

// Reference it in CSS
book.addCSS("style.css", `
  @font-face {
    font-family: "Open Sans";
    font-style: normal;
    font-weight: 400;
    src: url("../fonts/OpenSans-Regular.woff2") format("woff2");
  }
  body { font-family: "Open Sans", sans-serif; }
`);

Note: Font embedding increases EPUB file size. WOFF2 is the most compressed format — prefer it over TTF or OTF.


11. Sanitizer

The sanitizer runs automatically on every chapter's HTML (unless sanitize: false was set in the constructor or opts.raw: true was passed to addChapter). It converts HTML to EPUB-safe XHTML.

What it keeps

All common content elements are preserved:

  • Structure: <section>, <article>, <div>, <header>, <footer>, <aside>, <main>
  • Headings: <h1> through <h6>
  • Paragraphs & text blocks: <p>, <blockquote>, <pre>, <hr>, <br>
  • Inline text: <em>, <strong>, <b>, <i>, <u>, <s>, <span>, <mark>, <small>, <sup>, <sub>, <del>, <ins>, <abbr>, <cite>, <q>, <code>, <kbd>, <samp>, <var>, <time>
  • Links: <a href="..."> — relative, http://, https://, and internal #anchor links all work
  • Images: <img src="..." alt="...">, <figure>, <figcaption>, <picture>, <source>
  • Lists: <ul>, <ol>, <li>, <dl>, <dt>, <dd>
  • Tables: <table>, <thead>, <tbody>, <tfoot>, <tr>, <th>, <td>, <caption>, <colgroup>, <col> — including colspan and rowspan
  • Semantic / accessibility: <details>, <summary>, <ruby>, <rt>, <rp>, epub:type, role, aria-label, id, class, lang

What it strips

| Removed entirely (tag + content) | Tag stripped, children kept | |---|---| | <script> | Any tag not in the allowlist | | <style> | | | <iframe>, <frame>, <frameset> | | | <form>, <input>, <button> | | | <object>, <embed>, <applet> | | | <canvas>, <svg> | | | <link>, <meta>, <base> | | | <noscript> | |

URL validation

All href, src, srcset, and cite attributes are checked. The following are blocked:

  • javascript: — XSS vector
  • vbscript: — XSS vector
  • data: URIs that are not data:image/...

target="_blank" is stripped (not valid in EPUB).

Manual sanitization

You can sanitize HTML manually before passing it anywhere:

// Instance method
const clean = book.sanitize(rawHtml);

// Static utility — no instance needed
const clean = EBook.sanitize(rawHtml);

Disabling the sanitizer

// Disable globally for the entire book (only do this with fully trusted HTML)
const book = new EBook({ sanitize: false });

// Disable for one specific chapter
book.addChapter("Appendix", trustedXhtml, { raw: true });

12. Generating the EPUB

All generation methods are async and must be awaited.

download(filename?)Promise<void>

Generates the EPUB and triggers a browser download. The file dialog opens automatically.

await book.download();             // filename: "my-book.epub" (slugified from title)
await book.download("dune.epub");  // custom filename

generate()Promise<Blob>

Returns the EPUB as a Blob. Use this when you need to handle the file yourself.

const blob = await book.generate();

// Store it
const url = URL.createObjectURL(blob);

// Send it to a server
const form = new FormData();
form.append("file", blob, "book.epub");
await fetch("/upload", { method: "POST", body: form });

toBase64()Promise<string>

Returns the EPUB as a base64-encoded string. Useful for embedding or transmitting as JSON.

const b64 = await book.toBase64();
// "UEsDBBQACAgIAA..."

// Store in localStorage, send over API, etc.
localStorage.setItem("myBook", b64);

toObjectURL()Promise<string>

Returns a temporary object URL you can assign to a link or iframe. Remember to revoke it when done.

const url = await book.toObjectURL();

// Preview in an iframe
document.querySelector("iframe").src = url;

// Or as a download link
const link = document.createElement("a");
link.href     = url;
link.download = "book.epub";
link.textContent = "Download EPUB";
document.body.appendChild(link);

// Clean up when no longer needed
URL.revokeObjectURL(url);

13. Method Chaining

Every setter method returns this, so calls can be chained:

const book = new EBook({ title: "My Book", author: "Me" })
  .addCSS("style.css", cssContent)
  .setCover(coverData, "image/jpeg")
  .addTOCPage({ title: "Contents" })
  .addChapter("One",   ch1Html, { css: ["style.css"] })
  .addChapter("Two",   ch2Html, { css: ["style.css"] })
  .addChapter("Three", ch3Html, { css: ["style.css"] });

await book.download();

14. Full Example

// ── 1. Create the book ──────────────────────────────────────────────────────
const book = new EBook({
  title:       "The Midnight Garden",
  author:      "Elara Voss",
  language:    "en",
  publisher:   "Nightshade Press",
  description: "A story of secrets and seasons.",
  date:        "2024-06-01",
  rights:      "© 2024 Elara Voss. All rights reserved.",
});

// ── 2. Add a stylesheet ──────────────────────────────────────────────────────
book.addCSS("style.css", `
  body {
    font-family: Georgia, serif;
    font-size: 1em;
    line-height: 1.8;
    max-width: 36em;
    margin: 0 auto;
    padding: 2em 1.5em;
    color: #1a1a1a;
  }
  h1 { font-size: 1.6em; margin-top: 3em; text-align: center; }
  p  { margin: 0; text-indent: 1.5em; }
  p + p { margin-top: .8em; }
  blockquote {
    border-left: 3px solid #999;
    margin: 1.5em 0;
    padding: .5em 1em;
    color: #555;
    font-style: italic;
  }
  figure { text-align: center; margin: 2em 0; }
  figcaption { font-size: .85em; color: #777; margin-top: .5em; }
  img { max-width: 100%; }
  table { border-collapse: collapse; width: 100%; margin: 1.5em 0; }
  th, td { border: 1px solid #ddd; padding: .5em 1em; text-align: left; }
  th { background: #f5f5f5; }
`);

// ── 3. Set the cover ─────────────────────────────────────────────────────────
// Assuming you have a base64 string or a File object from a file input:
book.setCover(coverBase64, "image/jpeg", "cover.jpg", {
  asPage:  true,
  altText: "Cover of The Midnight Garden",
});

// ── 4. Add a TOC page ────────────────────────────────────────────────────────
book.addTOCPage({ title: "Contents", css: ["style.css"] });

// ── 5. Add chapters ──────────────────────────────────────────────────────────
book.addChapter("Prologue", `
  <h1>Prologue</h1>
  <p>The garden had no name, only a gate that opened at midnight.</p>
`, { css: ["style.css"] });

book.addChapter("Chapter One: The Gate", `
  <h1>Chapter One: The Gate</h1>
  <p>She found it on the first night of autumn.</p>
  <figure>
    <img src="../images/gate.jpg" alt="An old iron gate in moonlight"/>
    <figcaption>The gate at midnight</figcaption>
  </figure>
  <p>The iron was cold under her fingers, slick with dew.</p>
  <blockquote>
    <p>What you find beyond the gate is what you carried with you all along.</p>
  </blockquote>
`, { css: ["style.css"] });

book.addChapter("Chapter Two: The Seasons", `
  <h1>Chapter Two: The Seasons</h1>
  <p>The garden changed. Every visit brought a different sky.</p>
  <table>
    <thead>
      <tr><th>Season</th><th>What grew</th><th>What faded</th></tr>
    </thead>
    <tbody>
      <tr><td>Spring</td><td>White roses</td><td>Frost</td></tr>
      <tr><td>Summer</td><td>Wildflowers</td><td>Shadows</td></tr>
      <tr><td>Autumn</td><td>Red leaves</td><td>Warmth</td></tr>
      <tr><td>Winter</td><td>Silence</td><td>Everything</td></tr>
    </tbody>
  </table>
`, { css: ["style.css"] });

// ── 6. Add an image asset ─────────────────────────────────────────────────────
book.addImage("gate.jpg", gateImageBlob, "image/jpeg");

// ── 7. Generate and download ──────────────────────────────────────────────────
await book.download("the-midnight-garden.epub");

15. HTML Elements Reference

Quick reference for what you can use inside chapter HTML.

Text & Headings

<h1>Chapter Title</h1>
<h2>Section</h2>
<h3>Subsection</h3>
<p>A paragraph of text.</p>
<p>Text with <em>italics</em>, <strong>bold</strong>, <mark>highlighted</mark>, <code>code</code>.</p>
<p>Footnote reference<sup>1</sup>. Chemical formula H<sub>2</sub>O.</p>
<p><del>Removed text</del> and <ins>inserted text</ins>.</p>
<p><abbr title="HyperText Markup Language">HTML</abbr> is the language of the web.</p>
<hr/>
<br/>

Blockquote & Preformatted

<blockquote cite="https://example.com/source">
  <p>To be or not to be.</p>
</blockquote>

<pre><code>function hello() {
  console.log("Hello, world!");
}</code></pre>

Links

<!-- External link -->
<a href="https://example.com">Visit example.com</a>

<!-- Internal anchor within the same chapter -->
<a href="#section-2">Jump to Section 2</a>
<h2 id="section-2">Section 2</h2>

<!-- Link to another chapter -->
<a href="../text/ch-2-chapter-two.xhtml">Go to Chapter Two</a>

Tip: Chapter filenames follow the pattern ch-<n>-<slugified-title>.xhtml. Check auto-generated IDs using book.getChapters().

Images & Figures

<!-- Simple image -->
<img src="../images/photo.jpg" alt="Description of photo"/>

<!-- Image with caption -->
<figure>
  <img src="../images/diagram.png" alt="A diagram" width="400"/>
  <figcaption>Figure 1.1: System overview</figcaption>
</figure>

Lists

<!-- Unordered -->
<ul>
  <li>Apples</li>
  <li>Oranges</li>
</ul>

<!-- Ordered -->
<ol start="3">
  <li>Third item</li>
  <li>Fourth item</li>
</ol>

<!-- Definition list -->
<dl>
  <dt>Spice</dt>
  <dd>A substance that extends life and expands consciousness.</dd>
</dl>

Tables

<table>
  <caption>Table 1: Comparison of formats</caption>
  <thead>
    <tr>
      <th scope="col">Format</th>
      <th scope="col">Size</th>
      <th scope="col">Quality</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>JPEG</td>
      <td>Small</td>
      <td>Lossy</td>
    </tr>
    <tr>
      <td colspan="2">PNG / WebP</td>
      <td>Lossless</td>
    </tr>
  </tbody>
</table>

Semantic Structure

<section epub:type="chapter">
  <h1>Chapter Title</h1>
  <p>Content...</p>
</section>

<aside>
  <p>A sidebar note or callout box.</p>
</aside>

<details>
  <summary>Expand for more detail</summary>
  <p>Hidden content revealed on interaction (reader support varies).</p>
</details>

16. EPUB Structure Reference

The generated EPUB contains the following file tree:

book.epub
├── mimetype                        (uncompressed — required by spec)
├── META-INF/
│   └── container.xml               (points to OEBPS/content.opf)
└── OEBPS/
    ├── content.opf                 (EPUB 3 package document — manifest + spine)
    ├── toc.ncx                     (EPUB 2 navigation — for legacy readers)
    ├── nav.xhtml                   (EPUB 3 navigation document)
    ├── styles/
    │   └── style.css               (your stylesheets)
    ├── fonts/
    │   └── OpenSans.woff2          (your fonts)
    ├── images/
    │   ├── cover.jpg               (cover image)
    │   └── photo.jpg               (chapter images)
    └── text/
        ├── cover.xhtml             (cover page — if asPage: true)
        ├── toc-page.xhtml          (human-readable TOC — if addTOCPage() called)
        ├── ch-1-prologue.xhtml     (chapter files)
        └── ch-2-chapter-one.xhtml

The spine order is always: Cover page → TOC page → Chapters (in order).


17. Error Reference

| Error message | Cause | Fix | |---|---|---| | epubit.js: cannot generate — no chapters added. | generate() or download() called with no chapters | Add at least one chapter with addChapter() before generating | | epubit.js: chapter "id" not found | updateChapter() called with an ID that doesn't exist | Check IDs with getChapters() | | epubit.js: JSZip could not be loaded. | CDN unreachable and JSZip not included manually | Add <script src="jszip.min.js"> before epubit.js | | epubit.js: failed to load <url> | Script injection failed (network error, CSP, etc.) | Include JSZip manually rather than relying on auto-injection |


epubit.js produces valid EPUB 3.0 files with EPUB 2 (NCX) backward compatibility. Tested against Calibre, Apple Books, Kobo, and Google Play Books.