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 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 update from 'lodash/update';
import { connect } from 'react-redux';
import { validatePlugin } from './pluginValidation'; import { validatePlugin } from './pluginValidation';
/**
* Returns a mapping from targets to plugins and modes let pluginMap = {};
*
* @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])
), {});
}
/** */ /** */
export function filterValidPlugins(plugins) { export function getPlugins(targetName) {
const { validPlugins, invalidPlugins } = splitPluginsByValidation(plugins); return pluginMap[targetName];
logInvalidPlugins(invalidPlugins);
return validPlugins;
} }
/** */ /** */
export function connectPluginsToStore(plugins) { export function storePlugins(plugins = []) {
return plugins.map(plugin => ( const validPlugins = filterValidPlugins(plugins);
{ ...plugin, component: connectPluginComponent(plugin) } pluginMap = createTargetToPluginMapping(validPlugins);
));
} }
/** */ /** */
export function addPluginReducersToStore(store, createRootReducer, plugins) { function filterValidPlugins(plugins) {
const pluginReducers = getReducersFromPlugins(plugins); const { validPlugins, invalidPlugins } = splitPluginsByValidation(plugins);
store.replaceReducer(createRootReducer(pluginReducers)); logInvalidPlugins(invalidPlugins);
return validPlugins;
} }
/** */ /** */
...@@ -60,12 +40,22 @@ function logInvalidPlugins(plugins) { ...@@ -60,12 +40,22 @@ function logInvalidPlugins(plugins) {
)); ));
} }
/** Connect plugin component to state */ /**
function connectPluginComponent(plugin) { * Returns a mapping from targets to plugins and modes
return connect(plugin.mapStateToProps, plugin.mapDispatchToProps)(plugin.component); *
} * @param {Array} plugins
* @return {Object} - looks like:
/** */ *
function getReducersFromPlugins(plugins) { * {
return plugins && plugins.reduce((acc, plugin) => ({ ...acc, ...plugin.reducers }), {}); * '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 curry from 'lodash/curry';
import isEmpty from 'lodash/isEmpty'; 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) { function _withPlugins(targetName, TargetComponent) { // eslint-disable-line no-underscore-dangle
const pluginMap = useContext(PluginContext); let PluginHoc;
const plugins = pluginMap[targetName]; const plugins = getPlugins(targetName);
if (isEmpty(plugins)) { if (isEmpty(plugins)) {
return <TargetComponent {...props} />; return TargetComponent;
} }
if (!isEmpty(plugins.wrap)) { if (!isEmpty(plugins.wrap)) {
const PluginComponent = plugins.wrap[0].component; PluginHoc = createWrapPluginHoc(plugins, TargetComponent);
return <PluginComponent targetProps={props} TargetComponent={TargetComponent} />;
} }
if (!isEmpty(plugins.add)) { if (!isEmpty(plugins.add)) {
const PluginComponents = plugins.add.map(plugin => plugin.component); PluginHoc = createAddPluginHoc(plugins, TargetComponent);
return <TargetComponent {...props} PluginComponents={PluginComponents} />;
}
} }
PluginHoc.displayName = `WithPlugins(${targetName})`; PluginHoc.displayName = `WithPlugins(${targetName})`;
return PluginHoc; 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) */ /** withPlugins('MyComponent')(MyComponent) */
export const withPlugins = curry(_withPlugins); export const withPlugins = curry(_withPlugins);
...@@ -2,12 +2,11 @@ import React from 'react'; ...@@ -2,12 +2,11 @@ import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import deepmerge from 'deepmerge'; import deepmerge from 'deepmerge';
import PluginProvider from '../extend/PluginProvider'; import { storePlugins } from '../extend/pluginStore';
import App from '../containers/App';
import createStore from '../state/createStore'; import createStore from '../state/createStore';
import createRootReducer from '../state/reducers/rootReducer';
import * as actions from '../state/actions'; import * as actions from '../state/actions';
import settings from '../config/settings'; import settings from '../config/settings';
import App from '../containers/App';
/** /**
* Default Mirador instantiation * Default Mirador instantiation
...@@ -16,7 +15,9 @@ class MiradorViewer { ...@@ -16,7 +15,9 @@ class MiradorViewer {
/** /**
*/ */
constructor(config, plugins) { constructor(config, plugins) {
this.store = createStore(); storePlugins(plugins);
const pluginReducers = getReducersFromPlugins(plugins);
this.store = createStore(pluginReducers);
this.config = config; this.config = config;
this.processConfig(); this.processConfig();
const viewer = { const viewer = {
...@@ -26,9 +27,7 @@ class MiradorViewer { ...@@ -26,9 +27,7 @@ class MiradorViewer {
ReactDOM.render( ReactDOM.render(
<Provider store={this.store}> <Provider store={this.store}>
<PluginProvider plugins={plugins} createRootReducer={createRootReducer}>
<App /> <App />
</PluginProvider>
</Provider>, </Provider>,
document.getElementById(config.id), document.getElementById(config.id),
); );
...@@ -74,4 +73,9 @@ class MiradorViewer { ...@@ -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; export default MiradorViewer;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment