Skip to content

Commit ccc3b10

Browse files
authored
Merge pull request #4059 from shockey/master
Refactor `afterLoad` interface to expose raw plugin context
2 parents 1646b27 + 56caeec commit ccc3b10

File tree

4 files changed

+136
-14
lines changed

4 files changed

+136
-14
lines changed

docs/customization/plugin-api.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ A plugin return value may contain any of these keys, where `myStateKey` is a nam
1919
},
2020
components: {},
2121
wrapComponents: {},
22-
afterLoad: (system) => {}
22+
rootInjects: {},
23+
afterLoad: (system) => {},
2324
fn: {},
2425
}
2526
```
@@ -364,19 +365,39 @@ const MyWrapComponentPlugin = function(system) {
364365
}
365366
```
366367

368+
##### `rootInjects`
369+
370+
The `rootInjects` interface allows you to inject values at the top level of the system.
371+
372+
This interface takes an object, which will be merged in with the top-level system object at runtime.
373+
374+
```js
375+
const MyRootInjectsPlugin = function(system) {
376+
return {
377+
rootInjects: {
378+
myConstant: 123,
379+
myMethod: (...params) => console.log(...params)
380+
}
381+
}
382+
}
383+
```
384+
367385
##### `afterLoad`
368386

369-
The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered with the system.
387+
The `afterLoad` plugin method allows you to get a reference to the system after your plugin has been registered.
388+
389+
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.
370390

371-
This interface is used in the core code to attach methods that are driven by bound selectors or actions directly to the system.
391+
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:
372392

373393
```javascript
374394
const MyMethodProvidingPlugin = function() {
375395
return {
376396
afterLoad(system) {
377397
// at this point in time, your actions have been bound into the system
378398
// so you can do things with them
379-
system.myMethod = system.exampleActions.updateFavoriteColor
399+
this.rootInjects = this.rootInjects || {}
400+
this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor
380401
},
381402
statePlugins: {
382403
example: {

src/core/plugins/auth/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import * as specWrapActionReplacements from "./spec-wrap-actions"
66
export default function() {
77
return {
88
afterLoad(system) {
9-
system.initOAuth = system.authActions.configureAuth
9+
this.rootInjects = this.rootInjects || {}
10+
this.rootInjects.initOAuth = system.authActions.configureAuth
1011
},
1112
statePlugins: {
1213
auth: {

src/core/system.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,11 @@ export default class Store {
6868
if(rebuild) {
6969
this.buildSystem()
7070
}
71-
72-
if(Array.isArray(plugins)) {
73-
plugins.forEach(plugin => {
74-
if(plugin.afterLoad) {
75-
plugin.afterLoad(this.getSystem())
76-
}
77-
})
71+
72+
const needAnotherRebuild = callAfterLoad.call(this.system, plugins, this.getSystem())
73+
74+
if(needAnotherRebuild) {
75+
this.buildSystem()
7876
}
7977
}
8078

@@ -328,6 +326,25 @@ function combinePlugins(plugins, toolbox) {
328326
return {}
329327
}
330328

329+
function callAfterLoad(plugins, system, { hasLoaded } = {}) {
330+
let calledSomething = hasLoaded
331+
if(isObject(plugins) && !isArray(plugins)) {
332+
if(typeof plugins.afterLoad === "function") {
333+
calledSomething = true
334+
plugins.afterLoad.call(this, system)
335+
}
336+
}
337+
338+
if(isFunc(plugins))
339+
return callAfterLoad.call(this, plugins(system), system, { hasLoaded: calledSomething })
340+
341+
if(isArray(plugins)) {
342+
return plugins.map(plugin => callAfterLoad.call(this, plugin, system, { hasLoaded: calledSomething }))
343+
}
344+
345+
return calledSomething
346+
}
347+
331348
// Wraps deepExtend, to account for certain fields, being wrappers.
332349
// Ie: we need to convert some fields into arrays, and append to them.
333350
// Rather than overwrite

test/core/system/system.js

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -684,13 +684,13 @@ describe("bound system", function(){
684684
})
685685

686686
describe("afterLoad", function() {
687-
it("should call an plugin's `afterLoad` method after the plugin is loaded", function() {
687+
it("should call a plugin's `afterLoad` method after the plugin is loaded", function() {
688688
// Given
689689
const system = new System({
690690
plugins: [
691691
{
692692
afterLoad(system) {
693-
system.wow = system.dogeSelectors.wow
693+
this.rootInjects.wow = system.dogeSelectors.wow
694694
},
695695
statePlugins: {
696696
doge: {
@@ -705,6 +705,89 @@ describe("bound system", function(){
705705
]
706706
})
707707

708+
// When
709+
var res = system.getSystem().wow()
710+
expect(res).toEqual("so selective")
711+
})
712+
it("should call a preset plugin's `afterLoad` method after the plugin is loaded", function() {
713+
// Given
714+
const MyPlugin = {
715+
afterLoad(system) {
716+
this.rootInjects.wow = system.dogeSelectors.wow
717+
},
718+
statePlugins: {
719+
doge: {
720+
selectors: {
721+
wow: () => (system) => {
722+
return "so selective"
723+
}
724+
}
725+
}
726+
}
727+
}
728+
729+
const system = new System({
730+
plugins: [
731+
[MyPlugin]
732+
]
733+
})
734+
735+
// When
736+
var res = system.getSystem().wow()
737+
expect(res).toEqual("so selective")
738+
})
739+
it("should call a function preset plugin's `afterLoad` method after the plugin is loaded", function() {
740+
// Given
741+
const MyPlugin = {
742+
afterLoad(system) {
743+
this.rootInjects.wow = system.dogeSelectors.wow
744+
},
745+
statePlugins: {
746+
doge: {
747+
selectors: {
748+
wow: () => (system) => {
749+
return "so selective"
750+
}
751+
}
752+
}
753+
}
754+
}
755+
756+
const system = new System({
757+
plugins: [
758+
() => {
759+
return [MyPlugin]
760+
}
761+
]
762+
})
763+
764+
// When
765+
var res = system.getSystem().wow()
766+
expect(res).toEqual("so selective")
767+
})
768+
it("should call a registered plugin's `afterLoad` method after the plugin is loaded", function() {
769+
// Given
770+
const MyPlugin = {
771+
afterLoad(system) {
772+
this.rootInjects.wow = system.dogeSelectors.wow
773+
},
774+
statePlugins: {
775+
doge: {
776+
selectors: {
777+
wow: () => (system) => {
778+
return "so selective"
779+
}
780+
}
781+
}
782+
}
783+
}
784+
785+
const system = new System({
786+
plugins: []
787+
})
788+
789+
system.register([MyPlugin])
790+
708791
// When
709792
var res = system.getSystem().wow()
710793
expect(res).toEqual("so selective")

0 commit comments

Comments
 (0)