From ceaa8b4f96c2d3b7ed51fd5dd66729b877c836a7 Mon Sep 17 00:00:00 2001 From: Christian Lent Date: Tue, 7 Jan 2020 15:28:21 -0500 Subject: [PATCH] Add redux-bundler support for Electrode subapps (#1484) * Integrate redux-bundler into subapp-pbundle * Use preact for jsx, render, and hydrate --- packages/subapp-pbundle/lib/index.js | 2 + packages/subapp-pbundle/lib/redux-bundler.js | 20 +++++ packages/subapp-pbundle/package.json | 2 + packages/subapp-pbundle/src/index.js | 2 + packages/subapp-pbundle/src/redux-bundler.jsx | 73 +++++++++++++++++++ packages/subapp-pbundle/src/shared.js | 56 ++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 packages/subapp-pbundle/lib/redux-bundler.js create mode 100644 packages/subapp-pbundle/src/redux-bundler.jsx create mode 100644 packages/subapp-pbundle/src/shared.js diff --git a/packages/subapp-pbundle/lib/index.js b/packages/subapp-pbundle/lib/index.js index ab3268068..de3a4f877 100644 --- a/packages/subapp-pbundle/lib/index.js +++ b/packages/subapp-pbundle/lib/index.js @@ -3,6 +3,7 @@ const subappWeb = require("subapp-web"); const preact = require("preact"); const FrameworkLib = require("./framework-lib"); +const { reduxBundlerLoadSubApp } = require("./redux-bundler"); const { default: AppContext } = require("../browser/app-context"); const { h, Component, render } = preact; @@ -16,5 +17,6 @@ module.exports = { preact, h, Component, + reduxBundlerLoadSubApp, render }; diff --git a/packages/subapp-pbundle/lib/redux-bundler.js b/packages/subapp-pbundle/lib/redux-bundler.js new file mode 100644 index 000000000..506f065ba --- /dev/null +++ b/packages/subapp-pbundle/lib/redux-bundler.js @@ -0,0 +1,20 @@ +"use strict"; + +const { registerSubApp } = require("subapp-util"); + +const shared = require("../dist/shared"); + +module.exports = { + reduxBundlerLoadSubApp: subapp => { + const extras = { + __redux: true + }; + + if (!subapp.reduxCreateStore) { + extras._genReduxCreateStore = "subapp"; + extras.reduxCreateStore = shared.getReduxCreateStore(subapp); + } + + return registerSubApp(Object.assign(extras, subapp)); + } +}; diff --git a/packages/subapp-pbundle/package.json b/packages/subapp-pbundle/package.json index 925ec5eca..a79921934 100644 --- a/packages/subapp-pbundle/package.json +++ b/packages/subapp-pbundle/package.json @@ -42,6 +42,8 @@ "jsdom": "^15.2.1", "preact": "^10.1.1", "preact-render-to-string": "^5.1.3", + "redux-bundler": "^26.0.0", + "redux-bundler-preact": "^2.0.1", "run-verify": "^1.2.2" }, "peerDependencies": { diff --git a/packages/subapp-pbundle/src/index.js b/packages/subapp-pbundle/src/index.js index 26645cf45..d865abb95 100644 --- a/packages/subapp-pbundle/src/index.js +++ b/packages/subapp-pbundle/src/index.js @@ -13,3 +13,5 @@ export { h, Component, render } from "preact"; export { default as AppContext } from "./app-context"; export { FrameworkLib }; + +export { reduxBundlerLoadSubApp } from "./redux-bundler"; diff --git a/packages/subapp-pbundle/src/redux-bundler.jsx b/packages/subapp-pbundle/src/redux-bundler.jsx new file mode 100644 index 000000000..3cab2e3e3 --- /dev/null +++ b/packages/subapp-pbundle/src/redux-bundler.jsx @@ -0,0 +1,73 @@ +import { h, render, hydrate } from "preact"; +import { loadSubApp } from "subapp-web"; +import { Provider } from 'redux-bundler-preact'; +import { setStoreContainer, getReduxCreateStore } from "./shared"; + +setStoreContainer(typeof window === 'undefined' ? global : window); + +// +// client side function to start a subapp with redux-bundler support +// +export function reduxRenderStart(options) { + const store = options._store || options.reduxCreateStore(options.initialState); + const { Component } = options; + + if (options.serverSideRendering) { + hydrate( + + + , + options.element + ); + } else { + render( + + + , + options.element + ); + } + + return store; +} + +let store; + +// +// Load a subapp with redux-bundler support +// info - the subapp's information +// +export function reduxBundlerLoadSubApp(info) { + const renderStart = function(instance, element) { + const initialState = instance._prepared || instance.initialState; + const reduxCreateStore = instance.reduxCreateStore || this.info.reduxCreateStore; + const Component = this.info.StartComponent || this.info.Component; + + const store = reduxRenderStart({ + _store: instance._store, + initialState, + reduxCreateStore, + Component, + serverSideRendering: instance.serverSideRendering, + element + }); + + instance._store = store; + return store; + }; + + const extras = { + __redux: true + }; + + if (!info.reduxCreateStore) { + extras._genReduxCreateStore = "subapp"; + } + + return loadSubApp( + Object.assign(extras, info, { + reduxCreateStore: getReduxCreateStore(info) + }), + renderStart + ); +} diff --git a/packages/subapp-pbundle/src/shared.js b/packages/subapp-pbundle/src/shared.js new file mode 100644 index 000000000..d155124bd --- /dev/null +++ b/packages/subapp-pbundle/src/shared.js @@ -0,0 +1,56 @@ +const composeBundles = require("redux-bundler").composeBundles; + +// +// - stores can be shared between subapps with reduxShareStore flag +// - if it's true, then a common global store is used +// - if it's a string, then it's use to name a store for sharing. +// - otherwise subapp gets its own private store +// - state sharing is made possible through named reducers +// - each subapp must provide named reducers for states +// - other subapps can then provide the same reducer under the same name +// - all the reducers are then merged into a single object and then combined +// - initial state handling follow these rules +// - the first subapp that loads and initializes wins and all subapps load +// after will use the initial state from it +// - a top level initializer can be specified to do this +// + +let shared = {}; + +function setStoreContainer(container) { + shared = container; +} + +function clearSharedStore() { + delete shared.store; +} + +function getSharedStore() { + return shared.store; +} + +function setSharedStore(store) { + shared.store = store; +} + +function getReduxCreateStore(info) { + const bundles = info.reduxBundles || []; + return function reduxCreateStore(initialState) { + let store = getSharedStore(); + if (store) { + store.integrateBundles.apply(this, bundles); + } else { + store = composeBundles.apply(this, bundles)(initialState); + setSharedStore(store); + } + return store; + } +} + +module.exports = { + setStoreContainer, + getReduxCreateStore, + getSharedStore, + setSharedStore, + clearSharedStore +};