Skip to content

Getting started

Install @prose-reader/core, the streamer and the archive reader, then open an EPUB with jszip and render it in your page with createReader() and mount().

Terminal window
npm install @prose-reader/core @prose-reader/streamer @prose-reader/archive-reader rxjs

You don’t absolutely need @prose-reader/streamer and @prose-reader/archive-reader installed to run the reader, but they are very useful to standardize and feed books to the engine (archive-reader turns your source into an archive, streamer serves it). rxjs is a peer-dependency of prose-reader and needs to be installed alongside, at 7.5.5 or any later 7.x (^7.5.5). 7.5.5 is the first rxjs whose exports include its type declarations, which TypeScript needs to resolve them with bundler, node16 or nodenext module resolution.

On your html file you need to define a container for the reader. We will be using a full screen reader but you can have any dimensions.

index.html

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body,
html,
#app {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

The next step is to create a streamer and load our book into the reader. There are a lot of comments on purpose to help you understand the basic flow but in the end the code is rather simple.

We are gonna use an epub as example so you should also install jszip

Terminal window
npm install jszip

index.ts

import { createReader } from "@prose-reader/core"
import { Streamer } from "@prose-reader/streamer"
import { createArchiveFromJszip } from "@prose-reader/archive-reader/archives/createArchiveFromJszip"
import { from } from "rxjs"
import { loadAsync } from "jszip"
async function createStreamer() {
/**
* We create a streamer to load and serve our archive.
*
* A streamer is a convenient way to fetch manifest and resources from an archive.
* This will be used to feed information to the reader.
*
* For simplicity we use a web reader but we have a ServiceWorkerStreamer class
* that can and should be used whenever possible. This will offload the work from
* the main thread.
*/
const streamer = new Streamer({
getArchive: async () => {
/**
* First you need to fetch your book.
* It can be from anywhere, local, remote, etc.
*/
const epubResponse = await fetch("http://localhost:3000/accessible_epub_3.epub")
const epubBlob = await epubResponse.blob()
/**
* Because epubs are zip archives, we are gonna use jszip to manage them.
* You could use a different library but we provide helpers to deal with
* jszip formats as a convenience.
*/
const epubJszip = await loadAsync(epubBlob)
/**
* We are gonna use prose streamer to manage the epub.
* The streamer works with archives, so we need to create one from the epub.
*
* An archive is a higher level interface that is agnostic to the underlying
* format so the streamer can manage different formats transparently.
*
* We do provide several helpers to create archives from different formats.
* Epubs being zip archives, we have a helper that creates an archive from a
* jszip object.
*/
const archive = await createArchiveFromJszip(epubJszip)
return archive
},
/**
* You can configure the streamer to let it know how long it should
* keep an archive in memory for example. When the archive is no longer available
* the getArchive callback will be called again.
*/
cleanArchiveAfter: 5 * 60 * 1000,
})
return streamer
}
async function run() {
const streamer = await createStreamer()
/**
* A streamer can serve several archives, each under its own key, which
* `getArchive` receives. We only serve one book here, so we leave the key
* out and the streamer uses its default one.
*/
const manifestResponse = await streamer.fetchManifest()
const manifest = await manifestResponse.json()
const reader = createReader({
/**
* A manifest provides a list of resources and their URIs.
*
* A reader is tied to a single book: create it once you have the
* manifest and create a new reader if you need to open another book
* (after destroying the previous one with `reader.destroy()`).
*/
manifest,
/**
* We are loading the epub locally in memory here so the items cannot be
* fetched directly. This would be possible and encouraged if using a service worker
* but in our situation we have to hook into the reader to tell it to get its resources
* from our local streamer directly.
*
* This may seem boilerplaty but the separation between the reader and the streamer is what
* makes prose very flexible and powerful.
*
* You can pretty much load anything in any way you want.
*
* We tried to create streamers that simplify the friction as much as possible.
*/
getResource: (item) => {
return from(streamer.fetchResource({ resourcePath: item.href }))
},
})
/**
* Finally we can mount the reader into the DOM. This is a one-shot
* operation: the reader starts rendering the book into the container.
*
* The container must belong to the reader's document. By default that is
* the ambient `document`; to render inside a foreign document (eg: an
* iframe's `contentDocument`) pass it as `ownerDocument` to `createReader`.
*/
reader.mount(document.getElementById(`app`)!)
}
run()

You should now see the book being displayed.

Explore the next sections to see how to add functionalities such as navigating, gestures, etc.