From 9d48c4751a7c025e894a9eba5b181c2e25cd25f0 Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 28 Dec 2017 16:26:05 -0600 Subject: [PATCH 1/2] Refactor `afterLoad` interface to expose raw plugin context --- docs/customization/plugin-api.md | 11 ++-- src/core/plugins/auth/index.js | 3 +- src/core/system.js | 31 +++++++++--- test/core/system/system.js | 87 +++++++++++++++++++++++++++++++- 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/docs/customization/plugin-api.md b/docs/customization/plugin-api.md index ee08ba07026..54d62583910 100644 --- a/docs/customization/plugin-api.md +++ b/docs/customization/plugin-api.md @@ -19,7 +19,7 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam }, components: {}, wrapComponents: {}, - afterLoad: (system) => {} + afterLoad: (system) => {}, fn: {}, } ``` @@ -366,9 +366,11 @@ const MyWrapComponentPlugin = function(system) { ##### `afterLoad` -The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered with the system. +The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered. -This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the system. +This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the plugin context. + +The plugin context, which is bound to `this`, is undocumented, but below is an example of how to attach a bound action as a top-level method: ```javascript const MyMethodProvidingPlugin = function() { @@ -376,7 +378,8 @@ const MyMethodProvidingPlugin = function() { afterLoad(system) { // at this point in time, your actions have been bound into the system // so you can do things with them - system.myMethod = system.exampleActions.updateFavoriteColor + this.rootInjects = this.rootInjects || {} + this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor }, statePlugins: { example: { diff --git a/src/core/plugins/auth/index.js b/src/core/plugins/auth/index.js index 7e9413f4a53..3ac5e1ff1e5 100644 --- a/src/core/plugins/auth/index.js +++ b/src/core/plugins/auth/index.js @@ -6,7 +6,8 @@ import * as specWrapActionReplacements from "./spec-wrap-actions" export default function() { return { afterLoad(system) { - system.initOAuth = system.authActions.configureAuth + this.rootInjects = this.rootInjects || {} + this.rootInjects.initOAuth = system.authActions.configureAuth }, statePlugins: { auth: { diff --git a/src/core/system.js b/src/core/system.js index 87a258877c1..73c602a857f 100644 --- a/src/core/system.js +++ b/src/core/system.js @@ -68,13 +68,11 @@ export default class Store { if(rebuild) { this.buildSystem() } - - if(Array.isArray(plugins)) { - plugins.forEach(plugin => { - if(plugin.afterLoad) { - plugin.afterLoad(this.getSystem()) - } - }) + + const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem()) + + if(needAnotherRebuild) { + this.buildSystem() } } @@ -328,6 +326,25 @@ function combinePlugins(plugins, toolbox) { return {} } +function callAfterLoad(plugins, system, { hasLoaded } = {}) { + let calledSomething = hasLoaded + if(isObject(plugins) && !isArray(plugins)) { + if(typeof plugins.afterLoad === "function") { + calledSomething = true + plugins.afterLoad.call(this, system) + } + } + + if(isFunc(plugins)) + return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething }) + + if(isArray(plugins)) { + return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething })) + } + + return calledSomething +} + // Wraps deepExtend, to account for certain fields, being wrappers. // Ie: we need to convert some fields into arrays, and append to them. // Rather than overwrite diff --git a/test/core/system/system.js b/test/core/system/system.js index 205fc8a603b..fc5db9a1108 100644 --- a/test/core/system/system.js +++ b/test/core/system/system.js @@ -684,13 +684,13 @@ describe("bound system", function(){ }) describe("afterLoad", function() { - it("should call an plugin's `afterLoad` method after the plugin is loaded", function() { + it("should call a plugin's `afterLoad` method after the plugin is loaded", function() { // Given const system = new System({ plugins: [ { afterLoad(system) { - system.wow = system.dogeSelectors.wow + this.rootInjects.wow = system.dogeSelectors.wow }, statePlugins: { doge: { @@ -705,6 +705,89 @@ describe("bound system", function(){ ] }) + // When + var res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a preset plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [ + [MyPlugin] + ] + }) + + // When + var res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a function preset plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [ + () => { + return [MyPlugin] + } + ] + }) + + // When + var res = system.getSystem().wow() + expect(res).toEqual("so selective") + }) + it("should call a registered plugin's `afterLoad` method after the plugin is loaded", function() { + // Given + const MyPlugin = { + afterLoad(system) { + this.rootInjects.wow = system.dogeSelectors.wow + }, + statePlugins: { + doge: { + selectors: { + wow: () => (system) => { + return "so selective" + } + } + } + } + } + + const system = new System({ + plugins: [] + }) + + system.register([MyPlugin]) + // When var res = system.getSystem().wow() expect(res).toEqual("so selective") From 56caeec8c8c4b396eb8dd96deb99c2640e402fbe Mon Sep 17 00:00:00 2001 From: Kyle Shockey Date: Thu, 28 Dec 2017 16:44:20 -0600 Subject: [PATCH 2/2] Add documentation for `rootInjects` interface --- docs/customization/plugin-api.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/customization/plugin-api.md b/docs/customization/plugin-api.md index 54d62583910..0b3ec217eed 100644 --- a/docs/customization/plugin-api.md +++ b/docs/customization/plugin-api.md @@ -19,6 +19,7 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam }, components: {}, wrapComponents: {}, + rootInjects: {}, afterLoad: (system) => {}, fn: {}, } @@ -364,11 +365,28 @@ const MyWrapComponentPlugin = function(system) { } ``` +##### `rootInjects` + +The `rootInjects` interface allows you to inject values at the top level of the system. + +This interface takes an object, which will be merged in with the top-level system object at runtime. + +```js +const MyRootInjectsPlugin = function(system) { + return { + rootInjects: { + myConstant: 123, + myMethod: (...params) => console.log(...params) + } + } +} +``` + ##### `afterLoad` The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered. -This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the plugin context. +This interface is used in the core code to attach methods that are driven by bound selectors or actions. You can also use it to execute logic that requires your plugin to already be ready, for example fetching initial data from a remote endpoint and passing it to an action your plugin creates. The plugin context, which is bound to `this`, is undocumented, but below is an example of how to attach a bound action as a top-level method: