Skip to content
Snippets Groups Projects
Commit c02a7195 authored by Mathias Maaß's avatar Mathias Maaß
Browse files

Refactor plugin system for performance

parent 8b56e610
No related branches found
No related tags found
No related merge requests found
import React from 'react';
const PluginContext = React.createContext();
export default PluginContext;
import React, { useContext, useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { ReactReduxContext } from 'react-redux';
import PluginContext from './PluginContext';
import {
filterValidPlugins,
addPluginReducersToStore,
connectPluginsToStore,
createTargetToPluginMapping,
} from './pluginPreprocessing';
/** */
export default function PluginProvider(props) {
const { store } = useContext(ReactReduxContext);
const { plugins, createRootReducer, children } = props;
const [pluginMap, setPluginMap] = useState({});
useEffect(() => {
const validPlugins = filterValidPlugins(plugins);
const connectedPlugins = connectPluginsToStore(validPlugins);
createRootReducer && addPluginReducersToStore(store, createRootReducer, validPlugins);
setPluginMap(createTargetToPluginMapping(connectedPlugins));
}, []);
return (
<PluginContext.Provider value={pluginMap}>
{ children }
</PluginContext.Provider>
);
}
PluginProvider.propTypes = {
children: PropTypes.node,
createRootReducer: PropTypes.func,
plugins: PropTypes.array, // eslint-disable-line react/forbid-prop-types
};
PluginProvider.defaultProps = {
children: null,
createRootReducer: null,
plugins: [],
};
import update from 'lodash/update';
import { connect } from 'react-redux';
import { validatePlugin } from './pluginValidation';
/**
* Returns a mapping from targets to plugins and modes
*
* @param {Array} plugins
* @return {Object} - looks like:
*
* {
* 'WorkspacePanel': {
* wrap: [plugin3, ...],
* add: [plugin4, ...],
* },
* ...
* }
*/
export function createTargetToPluginMapping(plugins) {
return plugins.reduce((map, plugin) => (
update(map, [plugin.target, plugin.mode], x => [...x || [], plugin])
), {});
}
let pluginMap = {};
/** */
export function filterValidPlugins(plugins) {
const { validPlugins, invalidPlugins } = splitPluginsByValidation(plugins);
logInvalidPlugins(invalidPlugins);
return validPlugins;
export function getPlugins(targetName) {
return pluginMap[targetName];
}
/** */
export function connectPluginsToStore(plugins) {
return plugins.map(plugin => (
{ ...plugin, component: connectPluginComponent(plugin) }
));
export function storePlugins(plugins = []) {
const validPlugins = filterValidPlugins(plugins);
pluginMap = createTargetToPluginMapping(validPlugins);
}
/** */
export function addPluginReducersToStore(store, createRootReducer, plugins) {
const pluginReducers = getReducersFromPlugins(plugins);
store.replaceReducer(createRootReducer(pluginReducers));
function filterValidPlugins(plugins) {
const { validPlugins, invalidPlugins } = splitPluginsByValidation(plugins);
logInvalidPlugins(invalidPlugins);
return validPlugins;
}
/** */
......@@ -60,12 +40,22 @@ function logInvalidPlugins(plugins) {
));
}
/** Connect plugin component to state */
function connectPluginComponent(plugin) {
return connect(plugin.mapStateToProps, plugin.mapDispatchToProps)(plugin.component);
}
/** */
function getReducersFromPlugins(plugins) {
return plugins && plugins.reduce((acc, plugin) => ({ ...acc, ...plugin.reducers }), {});
/**
* Returns a mapping from targets to plugins and modes
*
* @param {Array} plugins
* @return {Object} - looks like:
*
* {
* 'WorkspacePanel': {
* wrap: [plugin3, ...],
* add: [plugin4, ...],
* },
* ...
* }
*/
export function createTargetToPluginMapping(plugins) {
return plugins.reduce((map, plugin) => (
update(map, [plugin.target, plugin.mode], x => [...x || [], plugin])
), {});
}
import React, { useContext } from 'react';
import React from 'react';
import { connect } from 'react-redux';
import curry from 'lodash/curry';
import isEmpty from 'lodash/isEmpty';
import PluginContext from './PluginContext';
import { getPlugins } from './pluginStore';
/** withPlugins should be the innermost HOC */
function _withPlugins(targetName, TargetComponent) { // eslint-disable-line no-underscore-dangle
/** */
function PluginHoc(props) {
const pluginMap = useContext(PluginContext);
const plugins = pluginMap[targetName];
function _withPlugins(targetName, TargetComponent) { // eslint-disable-line no-underscore-dangle
let PluginHoc;
const plugins = getPlugins(targetName);
if (isEmpty(plugins)) {
return <TargetComponent {...props} />;
return TargetComponent;
}
if (!isEmpty(plugins.wrap)) {
const PluginComponent = plugins.wrap[0].component;
return <PluginComponent targetProps={props} TargetComponent={TargetComponent} />;
PluginHoc = createWrapPluginHoc(plugins, TargetComponent);
}
if (!isEmpty(plugins.add)) {
const PluginComponents = plugins.add.map(plugin => plugin.component);
return <TargetComponent {...props} PluginComponents={PluginComponents} />;
}
PluginHoc = createAddPluginHoc(plugins, TargetComponent);
}
PluginHoc.displayName = `WithPlugins(${targetName})`;
return PluginHoc;
}
/** */
function createWrapPluginHoc(plugins, TargetComponent) {
const PluginComponent = connectPluginComponent(plugins[0]);
return props => <PluginComponent targetProps={props} TargetComponent={TargetComponent} />;
}
/** */
function createAddPluginHoc(plugins, TargetComponent) {
const PluginComponents = plugins.map(connectPluginComponent);
return props => <TargetComponent {...props} PluginComponents={PluginComponents} />;
}
/** */
function connectPluginComponent(plugin) {
return connect(plugin.mapStateToProps, plugin.mapDispatchToProps)(plugin.component);
}
/** withPlugins('MyComponent')(MyComponent) */
export const withPlugins = curry(_withPlugins);
......@@ -2,12 +2,11 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import deepmerge from 'deepmerge';
import PluginProvider from '../extend/PluginProvider';
import App from '../containers/App';
import { storePlugins } from '../extend/pluginStore';
import createStore from '../state/createStore';
import createRootReducer from '../state/reducers/rootReducer';
import * as actions from '../state/actions';
import settings from '../config/settings';
import App from '../containers/App';
/**
* Default Mirador instantiation
......@@ -16,7 +15,9 @@ class MiradorViewer {
/**
*/
constructor(config, plugins) {
this.store = createStore();
storePlugins(plugins);
const pluginReducers = getReducersFromPlugins(plugins);
this.store = createStore(pluginReducers);
this.config = config;
this.processConfig();
const viewer = {
......@@ -26,9 +27,7 @@ class MiradorViewer {
ReactDOM.render(
<Provider store={this.store}>
<PluginProvider plugins={plugins} createRootReducer={createRootReducer}>
<App />
</PluginProvider>
</Provider>,
document.getElementById(config.id),
);
......@@ -74,4 +73,9 @@ class MiradorViewer {
}
}
/** Return reducers from plugins */
function getReducersFromPlugins(plugins) {
return plugins && plugins.reduce((acc, plugin) => ({ ...acc, ...plugin.reducers }), {});
}
export default MiradorViewer;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment