-
Notifications
You must be signed in to change notification settings - Fork 4
Creating a new plugin app
The first step in creating a new plugin for the DAaaS frontend is to create a new Git repository. You will also need the same tooling as for the parent app specified here
Once you have this then you can run create react app
to make a new app and push to you new repo.
To make a new react app with Typescript support run:
npx create-react-app my-app --typescript
For an example of the code for a plugin see https://github.com/ral-facilities/daaas-frontend-demo-plugin, the modifications made to that app to make it a plugin are described below.
To modify a react app to work as a plugin then the webpack config needs to be modified to change the target to be a library. To do this we need to install react-scripts-rewired
; this allows us to modify the webpack config without doing a full blown eject on the react app.
npm install --save-dev react-scripts-rewired
Then add a webpack.config.extend.js
to the root of the codebase with the code:
module.exports = (webpackConfig, env, { paths }) => {
webpackConfig.externals = {
'react': 'React', // Case matters here
'react-dom': 'ReactDOM', // Case matters here
}
if (env == "production") {
webpackConfig.output.library = "demo_plugin"
webpackConfig.output.libraryTarget = "window"
webpackConfig.output.filename = '[name].js'
webpackConfig.output.chunkFilename = '[name].chunk.js'
delete webpackConfig.optimization.splitChunks
webpackConfig.optimization.runtimeChunk = false
}
return webpackConfig
}
where demo_plugin
should be changed for the name of your plugin. Note the webpack externals - this will mean that the plugin uses external versions of React and ReactDOM. Add the following to your index.html
:
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
inside the body tag. If you need a dev version then you can change these to react.development.js
and react-dom.developement.js
temporarily.
Also add a webpackDevServer.config.extend.js
file with the contents:
module.exports = (webpackDevServerConfig, env, { paths }) => {
return webpackDevServerConfig
}
React scripts rewired has slightly older requirements for libraries and some may need to be downgraded; this will very much depend on the version of react-scripts-rewired at the time of making a plugin. Therefore, to work out what to downgrade run npm run build
and see what fails due to a library version.
For example, when run for the demo plugin then the following had to be downgraded:
npm install --save-dev [email protected] [email protected] [email protected]
The parent app runs Single-SPA and so expects certain hooks to be able to load a plugin (i.e. a bootstrap
, mount
and unmount
method).
If the plugin uses React then you can install single-spa-react
:
npm install single-spa-react @types/single-spa-react
and modify index.tsx
to have the required hooks:
{ other imports }
import singleSpaReact from 'single-spa-react';
...
function domElementGetter(): HTMLElement {
// Make sure there is a div for us to render into
let el = document.getElementById('demo_plugin');
if (!el) {
el = document.createElement('div');
el.id = 'demo_plugin';
document.body.appendChild(el);
}
return el;
}
const reactLifecycles = singleSpaReact({
React,
ReactDOM,
rootComponent: App,
domElementGetter,
});
/* eslint-disable @typescript-eslint/no-explicit-any */
// Single-SPA bootstrap methods have no idea what type of inputs may be
// pushed down from the parent app
export function bootstrap(props: any): Promise<void> {
return reactLifecycles.bootstrap(props);
}
export function mount(props: any): Promise<void> {
return reactLifecycles.mount(props);
}
export function unmount(props: any): Promise<void> {
return reactLifecycles.unmount(props);
}
/* eslint-enable @typescript-eslint/no-explicit-any */
where demo_plugin
should be swapped for the name of your plugin. Normally, as part of the library we would also want to remove the ReactDOM.render(...)
line in index.tsx
but we still want to be able to develop the plugin locally and in isolation, therefore we should only render to the screen if in development mode:
if (process.env.NODE_ENV === `development`) {
ReactDOM.render(<App />, document.getElementById('demo_plugin'));
}
This also means updating the line with <div id="root"></div>
in index.html
to
<div id="demo_plugin"></div>
where again demo_plugin
is the name of your plugin. This simulates how it will be mounted by the parent app (i.e. in to a div with the corresponding ID).
Additionally, you will want to implement the componentDidCatch
lifecycle method in your root component (App
in this case). This ensures that if the plugin throws an unhandled error, this method is called and the plugin is not unmounted due to single-spa
not being able to deal with the error. Additionally, you can use this method to log the error and use it to display a fallback UI (in the example this is done by setting a state variable hasError
). An example implementation of this is shown below:
public componentDidCatch(error: Error | null): void {
this.setState({ hasError: true });
log.error(`demo_plugin failed with error: ${error}`);
}
-
Architecture
-
Dev environment
-
Developing a plugin
-
Deployment
- Deploying SciGateway
- SciGateway Settings
- Deploying plugins
-
Releasing
-
Plugins
-
Continuous Integration
-
UX
-
Feedback