From 4d99089afb1f027999a862dae338688805093cd8 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Fri, 8 Jul 2016 14:26:23 -0700 Subject: [PATCH] improve decoration with persistent state with `react-proxy` --- app/hyperterm.js | 9 +++-- app/index.js | 14 ++++---- app/package.json | 1 + app/plugins.js | 94 +++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 96 insertions(+), 22 deletions(-) diff --git a/app/hyperterm.js b/app/hyperterm.js index 9bb92646..9335e804 100644 --- a/app/hyperterm.js +++ b/app/hyperterm.js @@ -1,10 +1,15 @@ -import Tabs from './tabs'; -import Term from './term'; +import Tabs_ from './tabs'; +import Term_ from './term'; import RPC from './rpc'; import Mousetrap from 'mousetrap'; import classes from 'classnames'; import shallowCompare from 'react-addons-shallow-compare'; import React, { Component } from 'react'; +import decorate from './plugins'; + +// make subcomponents reload siwth plugin changes +const Tabs = decorate(Tabs_); +const Term = decorate(Term_); export default class HyperTerm extends Component { constructor (props) { diff --git a/app/index.js b/app/index.js index bee1cff7..5d0ab9e0 100644 --- a/app/index.js +++ b/app/index.js @@ -1,16 +1,14 @@ import { render } from 'react-dom'; -import HyperTerm from './hyperterm'; +import HyperTerm_ from './hyperterm'; import React from 'react'; import Config from './config'; -import Plugins from './plugins'; +import decorate from './plugins'; + +// make the component reload with plugin changes +const HyperTerm = decorate(HyperTerm_); require('./css/hyperterm.css'); require('./css/tabs.css'); -const app = - - - -; - +const app = ; render(app, document.getElementById('mount')); diff --git a/app/package.json b/app/package.json index c1d07b7f..14d41e81 100644 --- a/app/package.json +++ b/app/package.json @@ -11,6 +11,7 @@ "react": "15.1.0", "react-addons-shallow-compare": "15.1.0", "react-dom": "15.1.0", + "react-proxy": "1.1.8", "semver-compare": "1.0.0" }, "devDependencies": { diff --git a/app/plugins.js b/app/plugins.js index 91955736..253091a0 100644 --- a/app/plugins.js +++ b/app/plugins.js @@ -1,18 +1,88 @@ -import React from 'react'; +import { ipcRenderer, remote } from 'electron'; +import notify from './notify'; +import { createProxy } from 'react-proxy'; -export default class Plugins extends React.Component { +// remote interface to `../plugins` +let plugins = remote.require('./plugins'); - componentDidMount () { - - } - - render () { - const child = React.Children.only(this.props.children); - return React.cloneElement(child, this.props); - } - - componentWillUnmount () { +// `require`d modules +let modules; +// the fs locations where usr plugins are stored +const { path, localPath } = plugins.getBasePaths(); + +// where we store the decorated components +let proxies = {}; + +const clearCache = () => { + // clear require cache + for (const entry in window.require.cache) { + if (entry.indexOf(path) === 0 || entry.indexOf(localPath) === 0) { + // `require` is webpacks', `window.require`, electron's + delete window.require.cache[entry]; + } + } +}; + +const loadModules = () => { + console.log('(re)loading renderer plugins'); + const paths = plugins.getPaths(); + modules = paths.plugins.concat(paths.localPlugins).map((path) => { + // window.require allows us to ensure this doens't get + // in the way of our build + try { + return window.require(path); + } catch (err) { + const name = remote.require('path').basename(path); + console.error(err.stack); + notify('Plugin load error', `"${name}" failed to load in the renderer process. Check Developer Tools for details.`); + } + }); +}; + +const updateProxy = (name) => { + const [Component, proxy] = proxies[name]; + let decorated = Component; + modules.forEach((mod) => { + const decorator = mod[`decorate${name}`]; + if (decorator) { + console.log('decorating', name); + decorated = decorator(Component, __webpack_require__); + } + }); + if (decorated !== Component) { + proxy.update(decorated); + } +}; + +const updateProxies = () => { + for (const name in proxies) { + updateProxy(name); + } +}; + +// load modules for initial decoration +loadModules(); + +// we want to refresh our modules cache every time +// plugins reload. +// the re-painting happens by the top-level `Config` component +// that reacts to configuration changes and plugin changes +ipcRenderer.on('plugins change', () => { + clearCache(); + loadModules(); + updateProxies(); +}); + +export default function decorate (Component, props = null) { + const name = Component.name; + + if (!proxies[name]) { + const proxy = createProxy(Component); + proxies[name] = [Component, proxy]; + updateProxy(name); } + const [, proxy] = proxies[name]; + return proxy.get(); }