Skip to content


Switch branches/tags

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time
  digital reading simplified

Introduction is a lightweight JavaScript-based reading system that allows publishers to distribute responsive content that can be read in a book-like form in modern browsers on practically all devices. can be combined with content in a single file, either EPUB or HTML, and travel with it, allowing future browser users to view the publication as the publisher intended. can also be used to serve up a library of EPUB titles, allowing publishers to easily web-serve their existing EPUB files.

If you prefer to have a single HTML file that can be distributed to a user such that they can double-click on the file to view it in the browser, Solo will provide this. Finally, when viewing any publication in the browser, the user is able to drag-and-drop another (non-DRM) EPUB into the browser page to begin reading the new publication. builds on the efforts of others, notably Matteo Spinelli's iScroll4, as well as jQuery and RequireJs, while the Manifesto provides an example of the use of enquire.js for JavaScript-based Media Queries.

More information, along with examples, can be found on the site.

Flavours comes in four flavours:

  1. readkit.solo: a self-contained HTML file is produced by combining with an EPUB. This provides for a single file which can be distributed to users, and which can also be easily opened for reading by double-clicking.

  2. readkit.library: a number of readkit.solo files that they can be web-served as a collection, with the user able to choose any from the library for reading.

  3. readkit.epub: a valid EPUB build, with included as part of the EPUB such that it can travel along with it. In this case, the EPUB can be opened at any time and web-served to allow a browser-based reading experience of the EPUB content.

  4. readkit.reader: a version of which does not itself contain any EPUB content, but which can either be web-served or double-clicked to allow the user to drag-and-drop an EPUB file of their choosing onto the page for reading using Note that all other flavours of also provide this functionality.

Before we begin


Although not necessary to make use of, a knowledge of certain technologies is useful in order to understand how works and how it can be tweaked or extended:

Getting started


Perhaps the easiest way to use is to simply double-click on the index.html file in the folder to open it in a browser, and then drag and drop an EPUB onto the page. Providing your EPUB is of a reasonable size, it should then display in the browser.


Embedding inside an EPUB file can be as easy as dropping the folder into the OEBPS (or equivalent) folder of your EPUB. You may want to try this before running a complete build; to be able to read the publication, you'll then need to serve it with a web-server. I tend to fire up an instance of the node http-server and navigate to http://localhost:8000/OEBPS/ Note that, in this case, the EPUB directory is the web server content root.

Note that it is assumed that, in this case, the root directory of the EPUB is reached from the folder using ../.. — if this is not the case, amend the epub_directory parameter in accordingly.

Building uses Grunt as its build tool. In order to compile EPUBs with, you'll need to:

  1. Ensure that you have Python, Ruby and Compass installed on your system

  2. Install node and npm

  3. Install grunt: npm install -g grunt-cli

  4. Install lxml python module: pip install lxml

  5. Clone the repository

  6. cd to the directory

  7. Install the node modules for the application: npm install

  8. Copy your unzipped EPUBs to the readkit.epub directory, one EPUB per folder

  9. At a command line, navigate to the top-level directory and simply issue the command:


This will create a dist directory, inside of which you'll find the various flavours of

If you want to create a development version, with js and css unminified and unconcatenated, then simply run:

    grunt dev

Grunt performs the following tasks:

  • compiling the SASS code into CSS
  • minifying the CSS and JavaScript
  • assembling only the files that actually needs to be in the output files
  • checking the JavaScript files for consistency
  • zipping the files up into an EPUB

Note that the actual EPUB content is not optimised; this is intentional in order to preserve the sanity of future production staff who have to work with the EPUB content files to produce a new version. Although not ideal, it often happens that production staff only have recourse to the published files when producing the next version of content, say because the original source files have been lost, are not available, or are obsolete.


A successful build will result in the following outputs in the dist folder.

  1. readkit.solo: a self-contained HTML file produced by combining with the content of each EPUB. This provides for a single file which can be distributed to users, but which can also be easily opened for reading by double-clicking.

  2. readkit.epub: the compiled EPUBs with included, each named with the identifier and title in order to provide a unique but recognisable name. Also in this directory is the unzipped content of each EPUB, containting This content can be web-served to allow a browser-based reading experience of the EPUB content, e.g. http://localhost:8000/OEBPS/ Note that the root directory for the web server should be the root directory of the EPUB (i.e. the directory containing the META-INF directory).

  3. readkit.library: the readkit.solo HTML files such that they can be web-served as a collection, with the user able to choose any from the library for reading. In this case, the root directory for the web server should be dist/readkit.library, with the library being served as http://localhost:8000/library/library.html

  4. readkit.reader: a version of which does not itself contain any EPUB content, but which can either be web-served or double-clicked to allow the user to drag-and-drop an EPUB file of their choosing onto the page for reading using Note that all other flavours of also provide this functionality.



If you build normally or drop the folder into the OEBPS (or equivalent) folder, you won't need to make changes, however if you decide to place the folder elsewhere, then you can specify this path in

    // Normally the folder will live directly in the OEBPS folder,
    // so we'll be serving something like http://localhost:8000/OEBPS/
    // The Epub directory is therefore ../../, but if we want to 
    // live in a different place, we can specify another directory.
    epub_directory: "../../"

This path is used by to navigate from the folder up to where the EPUB's META-INF/container.xml lives.

Other settings in this file control aspects of behaviour, such as intervals and durations that may need to be tweaked for larger publications (or slower reading devices).


As operates by compiling all of the EPUB XHTML files into a single HTML page (and thereby ignoring their HEAD elements and any scripts defined therein), if your publication needs access to JavaScript files/libraries other than those that uses, these can be specified in, for example:

var client = {
    // The paths for our EPUB assets
    paths: {
        client_js: '../../../js'
    // The required modules for our EPUB assets.
    // Note that we don't need to specify the following as 
    // has them baked in:
    // * jQuery2
    // * Modernizr
    // * Detectizr
    required: [
        /* E.G.
    shims: {
        // We need to describe the dependencies of any non-AMD modules here
        // so that require.js loads them in the correct order.
        /* E.G.
        'client_js/queries': ['client_js/libs/enquire.min'],
        'client_js/script': ['client_js/queries']

In order to fully understand the above syntax you'll need to be familiar with RequireJs. Note that makes the following JavaScript libraries globally available, so that your content can take advantage of them:

Technical notes

Design considerations

Fonts use web-fonts, specifically WOFF, to allow the user to switch between a standard serif (Lora) and sans-serif (Source Sans Pro) face if they don't want to use the face natively embedded in the EPUB. WOFF has good cross-browser support these days in recent browser versions, and is compressed. These two fonts have been optimised using the Font Squirrel generator, so together occupy only 310KB of space. Note that, to achieve this we've had to reduce to weights that we use, as well as removing non-latin glyphs. Before doing this, these fonts occupied 650KB (Lora) and 1.4MB (Source Sans Pro) for a total payload of more than 2MB.

Also, we've used to wonderful Fontello service to minimise the font icons we use, meaning that our Fontelloed WOFF occupies only 6KB. Notice that, however, for the actual EPUB content, you should provide both WOFFs (for optimal use in browsers) and Truetypes (for use in dedicated eBook reading devices). The Manifesto uses Lato, which is around 450KB in size.


It goes without saying that, without JavaScript support, will not function (although the EPUB containing will still work fine conventionally in reading systems that don't support JavaScript). uses JavaScript to perform the retrieval and layout of the files in the EPUB, as well as linking up the navigation controls. itself uses only CSS for Media Query support for differing device sizes, and not JavaScript. However, if you look into the Manifesto EPUB you will find use of a JavaScript library, enquire.js, used to provide JavaScript-based Media Query support for features such as detection of desktop browsers (e.g. for support of full-screen mode, which is not needed in mobile browsers or dedicated reading devices).


A simple testing framework is available: http://localhost:8000/OEBPS/


Single-page app follows a model whereby it loads all EPUB files into a single web page. It's smart enough to rewrite internal URLs and anchors (preventing collisions), and any third-party JavaScript files specifically required by the EPUB can also be configured to be loaded.

The reason adopts this behaviour is to allow a simple means of loading the entire EPUB, such that it can then be read without further recourse to the server. With the use of application cache to store all of the EPUB assets client-side and thereby allow further reading offline, this approach also makes sense.

However, the main limitation this imposes is one of file size; this approach works well for normal-length text-based EPUBs but if your EPUB contains lots of media (particularly video), then you may find that it takes a long time for the publication to load (providing the browser doesn't time-out). Similarly, for large text-based publications such as Moby Dick, your experience will differ depending on the capabilities of your deivce. Moby Dick is not so much large as extensive, containing around 150 files, each of which has to be loaded before the publication can be displayed. For instance, my testing has shown that it loads generally well in desktop browsers, will load in my Kindle HD tablet (although taking about two minutes to do so), and I've yet to get it to load in my aging iPhone 3GS. The solution to this is simple: split your publication up into a series of smaller publications, and consider using the library configuration to serve them.


The EPUB standard is verbose and complex (canonical fragment identifiers, anyone?) and does not attempt to do much more than provide a mechanism for prising open an EPUB and wrapping it with a simple but functional navigation system. That's not to say that it couldn't be extended to support specific (and ocassionally esoteric) EPUB functionality in the way that Readium does, however the high majority of users should find that what provides is ample. This is somewhat in the spirit of EPUB Zero, if somewhat less heretical in being able to cope with the existing EPUB file structure and metadata layout.

Cross-browser support has been designed to work with modern browsers, including Internet Explorer 10. However, it is not guaranteed to work seamlessly in all modern browsers, and known issues include:

Data-URIs Solo makes use of Data URIs and blobs to allow all of the content to be included in a single file. Unfortuantely Internet Explorer 10 treats blobs as cross-origin and denies access when the content is opened as a file URL (i.e. double-clicked). A ticket is currently open with Microsoft regarding this behaviour.


Given that we are targeting modern browsers (and that was designed primarily for use on mobile devices), we have made a conscious choice to go with jQuery2, meaning that older browsers are not catered for.

Non-standard browsers

Browsers such as Opera Mini and the native Android browser are unlikely to support the functionaliy needed to allow to work.


Because of the security risks of using web-workers for file URLs, drag-and-drop will work when using file urls though it will be noticeably slower than when web-served (in this case the zip.js library falls back to using a single process to unzip EPUB assets).



The grunt-readkit-dom-munger plugin is a patched version of grunt-dom-munger, created in order to support requirements:

  1. node_modules/grunt-dom-munger/tasks/dom_munger.js

    In the function processFile change:

     updatedContents = $.html();  


     if (options.xmlMode) {
       updatedContents = $.xml();
     } else {
       updatedContents = $.html();  

    and change



     var writeto;
     if ( {
       writeto = grunt.config.get(['readkit_dom_munger','data',]);
       writeto = writeto && writeto.length && vals.length ? writeto.concat(vals) : writeto && writeto.length ? writeto : vals;
     } else {
       writeto = vals;
     grunt.verbose.writeln(('writeto: ' + writeto).cyan);
  2. node_modules/grunt-readkit-dom-munger/npm-shrinkwrap.json

    We use npm-shrinkwrap.json to specify a hard dependency for our own version of htmlparser2 (used by cheerio: node_modules/grunt-readkit-dom-munger/node_modules/cheerio/node_modules/htmlparser2/lib/Parser.js), in which we've commented out the meta reference in voidElements, and added a few of the tags found in the .opf file:

     var voidElements = {
     //  Add extra tags found in the opf file.
         item: true,
         itemref: true,
         reference: true,
     //  Comment out the link and meta reference in voidElements, otherwise we end up with broken meta tags in the opf file 
     //  (specifically, meta tags that have both an opening and a closing tag lose their closing tag).
     //  meta: true,

About Digital reading simplified






No releases published


No packages published