The JSP UI was customizable; we had options to configure custom homepage, footer, header and css.
It is possible to fork the repo and build your own version of the SPA, but a more modular approach would be preferable.
Could we have a Plugin architecture, where we have modules (ESM for instance) that are dynamically loaded, possibly even from a remote source (like CDN)?
For instance; we could then have a Footer or HomePage plugin build from its own project (with vite,react,etc.) and have it loaded via some configuration in the SPA.
Note that once we have the Plugin architecture in place we could use it for more that just the obvious 'branding' customizations.
As a test, I created a project for our DANS footer. The Github repo is here: https://github.com/PaulBoon/dataverse-dans-footer-plugin .
I just copied our html code and made it into jsx.
If you build it and copy that js file into a plugin dir in the root of the SPA dataverse-frontend project, you can load it from there inside the SPA's Footer.tsx file.
I also added that file in the release assets: https://github.com/PaulBoon/dataverse-dans-footer-plugin/releases/download/v0.1.0/dansfooterplugin.js
Somehow I cannot get remote loading to work, possibly CORS, or otherwise unsave for the import.
If you drop it in a plugin folder, it will work.
Change the Footer.tsx of the dataverse-frontend project to this:
import { useTranslation } from 'react-i18next'
import { Container, Row, Col } from '@iqss/dataverse-design-system'
import dataverseProjectLogo from '@/assets/dataverse-project-logo.svg'
import { DataverseInfoRepository } from '../../../info/domain/repositories/DataverseInfoRepository'
import { useDataverseVersion } from './useDataverseVersion'
import styles from './Footer.module.scss'
import React, { useState, useEffect } from "react";
type PluginComponent = React.ComponentType<{ message: string }>;
interface FooterProps {
dataverseInfoRepository: DataverseInfoRepository
}
export function Footer({ dataverseInfoRepository }: FooterProps) {
const { t } = useTranslation('footer')
const { dataverseVersion } = useDataverseVersion(dataverseInfoRepository)
const currentYear = new Date().getFullYear().toString()
const [Plugin, setPlugin] = useState<PluginComponent | null>(null);
useEffect(() => {
(async () => {
const module = await import("/plugins/dansfooterplugin.js");
//const module = await import("https://github.com/PaulBoon/dataverse-dans-footer-plugin/releases/download/v0.1.0/dansfooterplugin.js");
//const module = await import("https://paulboon.github.io/dataverse-dans-footer-plugin/dansfooterplugin.js");
setPlugin(() => module.default);
})();
}, []);
return (
<footer className={styles.container}>
<Container>
{Plugin ? <Plugin message="Hello from host app!" /> : <p>Loading plugin...</p>}
<Row>
<Col sm={8}>
<em className={styles.copyright}>
{t('copyright', { year: currentYear })}
<a
href="https://support.dataverse.harvard.edu/harvard-dataverse-privacy-policy"
rel="noreferrer"
target="_blank">
{t('privacyPolicy')}
</a>
</em>
</Col>
<Col sm={4}>
<div className={styles['powered-by-container']}>
<span className={styles['powered-by-text']}>{t('poweredBy')}</span>
<a
href="https://dataverse.org/"
title="The Dataverse Project"
target="_blank"
rel="noreferrer">
<img
src={dataverseProjectLogo}
width="118"
height="40"
alt="The Dataverse Project logo"
/>
</a>
<span className={styles.version}>{dataverseVersion}</span>
</div>
</Col>
</Row>
</Container>
</footer>
)
}
There are some problems with layout and other style stuff so it needs futher CSS tweaking.
The main issue is the actual path to the file used in the import call, which it is now hardcoded, that should be made configurable, or the app should scan this location for plugin files.
Interesting. So remote loading doesn't work but local loading does?
@Philip Durbin ๐ I could not get that remote loading working, but even when it was only with local loading this plugin modularity would be nice. It is similar to having a Java app that picks up jar files in a plugin directory. You have to use some interface (plugin contract) and the app must have this loading mechanism. The main thing is that it provides extensibility/customization without rebuilding or changing the application (core) code.
@Philip Durbin ๐ Sorry, but it seems to only work in dev mode, so I am having trouble to get it imported at run time from my plugins directory instead of loading it from a compiled in assets dir.
Need to put some more time in this, hopefully with some help from people with React/Vite knowledge.
Ok, I have it working by placing that plugin directory inside the 'public' folder, then it also ends up in the 'dist'. When deployed you can replace the js file in that plugin dir with another implementation of that interface/type and it should work. The only thing now is that, when building it, you would need some default implementation that does nothing.
Last updated: Nov 01 2025 at 14:11 UTC