Introduction

We developed a rotation plugin called “mirador-rotation” compatible with the latest version of the IIIF viewer Mirador (Mirador 4) and published it on npm. This article covers the process from plugin development to publication, as well as integration methods for practical use.

Background

With the major update from Mirador 3 to Mirador 4, the following changes were made:

  • React 16 to React 18
  • Material-UI v4 to MUI v7
  • Numerous other dependency updates

As a result, existing plugins for Mirador 3 no longer work as-is.

Developing mirador-rotation-plugin

Repository

https://github.com/nakamura196/mirador-rotation-plugin

Main Features

  • Image rotation functionality
  • Integration into the Mirador 4 plugin menu

Publishing to npm

The developed plugin is published on npm:

npm install mirador-rotation

Updating mirador-integration

We built a Mirador 4-compatible integration environment based on the official mirador-integration repository.

Major Changes

ItemOldNew
Mirador3.x4.0.0
React16.14.018.x
Build toolWebpackParcel
UIMaterial-UI v4MUI v7

package.json

Configured with minimal dependencies:

{
  "dependencies": {
    "mirador": "^4.0.0",
    "mirador-rotation": "^4.0.0",
    "parcel": "^2.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

Making It Usable from External Pages

The Problem

Building the official mirador-integration as-is outputs ES module format. However, when trying to load it via a <script> tag from another HTML page, Mirador is not defined as a global variable.

script src="mirador.js">script>
script>
    Mirador.viewer({...}); // Mirador is not defined
script>

Solution

1. Creating a Library Entry Point

Explicitly export as a global variable in index.js:

import Mirador, * as MiradorAll from 'mirador';
import { miradorRotationPlugin } from 'mirador-rotation';

window.Mirador = { ...Mirador, ...MiradorAll };
window.miradorRotationPlugin = miradorRotationPlugin;

Important: * as MiradorAll retrieves all named exports, which are then flattened using the spread syntax. The reason for this is explained later.

2. Parcel Build Configuration

Specify global format output in package.json:

{
  "source": "index.js",
  "targets": {
    "global": {
      "outputFormat": "global",
      "distDir": "./dist"
    }
  },
  "scripts": {
    "build": "parcel build --target global"
  }
}

3. Normalizing File Names

Since Parcel generates file names with hashes by default, a rename script was created:

// scripts/rename-build.js
const fs = require('fs');
const path = require('path');

const distDir = path.join(__dirname, '..', 'dist');

fs.readdirSync(distDir).forEach(file => {
  if (file.startsWith('index') && file.endsWith('.js')) {
    fs.renameSync(
      path.join(distDir, file),
      path.join(distDir, 'mirador.js')
    );
  }
});

Usage

After building, dist/mirador.js can be loaded in any HTML:

div id="demo">div>
script src="mirador.js">script>
script>
    Mirador.viewer({
        id: 'demo',
        windows: [{
            rotationEnabled: true,
            manifestId: 'https://example.com/manifest.json',
        }],
    }, [...miradorRotationPlugin]);
script>

The API Flattening Issue

Discovery of the Problem

When using the UMD build of Mirador distributed via unpkg.com, the API could be called directly as follows:

Mirador.addCompanionWindow(windowId, { content: 'info' });

However, executing the same code after building with Parcel resulted in the following error:

TypeError: Mirador.addCompanionWindow is not a function

Investigating the Cause

Comparing the unpkg.com UMD build with the Parcel build revealed that the difference in export structure was the cause:

Build methodExport structureAPI call
unpkg.com (UMD)FlattenedMirador.addCompanionWindow()
Parcel (default export only)NestedMirador.actions.addCompanionWindow()

In the unpkg.com UMD build, all actions and selectors are flattened to the top level:

// End of UMD build (excerpt)
G.addCompanionWindow=Oj,
G.removeCompanionWindow=Aj,
G.updateCompanionWindow=yg,
// ... and many more

On the other hand, when retrieving only the default export with import Mirador from 'mirador':

// Default export structure
{
  viewer: function,
  actions: { addCompanionWindow, ... },
  selectors: { ... },
  // ...
}

Solution

Retrieve all named exports and flatten them using the spread syntax in index.js:

// Before (does not work)
import Mirador from 'mirador';
window.Mirador = Mirador;

// After (works)
import Mirador, * as MiradorAll from 'mirador';
window.Mirador = { ...Mirador, ...MiradorAll };

This results in the following flattening in the built file:

window.Mirador={...f.default,...f}

Note: mirador-rotation Versions

The mirador-rotation plugin has both 4.0.x and 4.1.x versions:

VersionMUITarget
4.0.xv5Mirador 4 alpha early version
4.1.xv7Mirador 4.0.0 official release

Since Mirador 4.0.0 official release uses MUI v7, please use mirador-rotation 4.1.x.

Summary

This article covered developing and publishing a plugin for Mirador 4, and building it as a library that can be used from external pages.

Key points:

  • ES module format does not define global variables
  • import Mirador, * as MiradorAll retrieves both default and named exports
  • { ...Mirador, ...MiradorAll } flattens and exports them
  • Parcel’s outputFormat: "global" builds in global format

Repositories