Problem/Motivation
ePub Generator can only produce a file to download. Users have to leave the site and open the file in an external reader, and every request regenerates the ePub — rendering the entity and assembling the ZIP — even for identical, repeated requests.
Steps to reproduce
- Enable ePub Generator and enable ePub output for a content type.
- Visit a node and use the "Download ePub" tab.
- Note that the only option is a file download, and that reloading regenerates the ePub from scratch each time.
Proposed resolution
Add an epub_generator_viewer submodule providing an in-browser reader built on epub.js, plus a response cache for generated ePubs.
The reader is a normal render array; epub.js fetches the book client-side via same-origin XHR from the existing download routes, so the session cookie applies and the current access checks ("generate epub" permission, entity view access, bundle gating) are reused unchanged. No new file-serving surface.
Caching is a kernel event subscriber rather than a service decorator, because the expensive work happens in the download controllers before the generator service is called. On RESPONSE it captures the BinaryFileResponse bytes into a dedicated swappable bin; on REQUEST, at a priority after routing and access enforcement, a hit replays the bytes, so a cached copy is never served more widely than a generated one. Downloads benefit alongside the viewer.
Keys vary by route, raw route parameters, query string, content language, and (configurable, default on) role fingerprint. Invalidation is tag-driven: entity tags, both settings config tags, the epub/full view display config tags, and a custom epub_generator_viewer:book:{bid} tag fired from node insert/update/delete so editing any child page flushes the assembled book. ETag/304 revalidation for the viewer's repeat fetches; size cap (default 8MB) protects database cache backends.
Known limitation, to be documented: per-role variance is supported, per-user variance is not — disable caching in that case.
Install epub.js and JSZip via pinned npm tarballs as package repositories typed drupal-library, which needs no extra plugin and resolves to libraries/epubjs/dist/epub.min.js and libraries/jszip/dist/jszip.min.js. asset-packagist is not recommended: it serves the unbuilt source tree with no dist/ directory, and pulls the full transitive npm dependency tree into the libraries directory.
Remaining tasks
- Review the submodule.
- Run the unit tests for
EpubCachePolicy. - Add kernel/functional test coverage for the cache subscriber (hit/miss, access enforcement on hit, book tag invalidation).
- Verify the reader against fixed-layout and RTL ePubs.
- Decide whether the generic uploaded-ePub field formatter belongs here long-term or in its own project.
User interface changes
- New "Read online" local task on nodes, next to "Download ePub" (weight 99). For nodes in a Book outline it opens the whole assembled book.
- New reader page with a toolbar: table of contents, title, progress, optional download link, fullscreen toggle. Keyboard navigation and position resume via localStorage.
- New "ePub viewer (epub.js)" field formatter for file fields; non-ePub files fall back to a plain file link.
- The generator settings page becomes tabbed: "Generator" and a new "Viewer" tab at
/admin/config/content/epub-generator/viewerwith reader defaults, cache controls, and a "Flush cached ePubs" button. - Status report warning when epub.js/JSZip are missing.
API changes
None to existing APIs. New in the submodule: routes epub_generator_viewer.node_view, .view, .settings; services epub_generator_viewer.cache_policy, .response_cache_subscriber, and the cache.epub_generator_viewer bin; theme hook epub_viewer; field formatter plugin epub_viewer; cache tag epub_generator_viewer:book:{bid}.
Data model changes
None. New config object epub_generator_viewer.settings (with schema) and field.formatter.settings.epub_viewer. No entity, field, or schema changes; the cache bin is a standard swappable bin.
Issue fork epub_generator-3611154
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #4
andileco commented