feat(wrapComponents): new chain configuration option (#7236)

This commit provides a backward compatible mechanism to chain wrap 
an individual component multiple times

`Chain` mode: allow chaining of plugins on a given component
`Legacy` mode: last plugin to wrap a given component will supercede others

* chore: Add unit test for wrapComponent wrapping

* doc: Add documentation about the new pluginsOptions configuration

* doc: Add a sidenote on plugin-api page

Co-authored-by: Tim Lai <timothy.lai@gmail.com>
This commit is contained in:
Damien
2021-05-21 00:41:11 +02:00
committed by GitHub
parent 60ac6d24ba
commit 516e666f1c
5 changed files with 99 additions and 11 deletions

View File

@@ -388,6 +388,10 @@ const MyWrapComponentPlugin = function(system) {
} }
``` ```
**Note:**
If you have multiple plugins wrapping the same component, you may want to change the [`pluginsOptions.pluginLoadType`](/docs/usage/configuration.md#Plugins-options) parameter to `chain`.
#### `rootInjects` #### `rootInjects`
The `rootInjects` interface allows you to inject values at the top level of the system. The `rootInjects` interface allows you to inject values at the top level of the system.

View File

@@ -39,9 +39,16 @@ Read more about the plugin system in the [Customization documentation](/docs/cus
Parameter name | Docker variable | Description Parameter name | Docker variable | Description
--- | --- | ----- --- | --- | -----
<a name="layout"></a>`layout` | _Unavailable_ | `String="BaseLayout"`. The name of a component available via the plugin system to use as the top-level layout for Swagger UI. <a name="layout"></a>`layout` | _Unavailable_ | `String="BaseLayout"`. The name of a component available via the plugin system to use as the top-level layout for Swagger UI.
<a name="pluginsOptions"></a>`pluginsOptions` | _Unavailable_ | `Object`. A Javascript object to configure plugin integration and behaviors (see below).
<a name="plugins"></a>`plugins` | _Unavailable_ | `Array=[]`. An array of plugin functions to use in Swagger UI. <a name="plugins"></a>`plugins` | _Unavailable_ | `Array=[]`. An array of plugin functions to use in Swagger UI.
<a name="presets"></a>`presets` | _Unavailable_ | `Array=[SwaggerUI.presets.ApisPreset]`. An array of presets to use in Swagger UI. Usually, you'll want to include `ApisPreset` if you use this option. <a name="presets"></a>`presets` | _Unavailable_ | `Array=[SwaggerUI.presets.ApisPreset]`. An array of presets to use in Swagger UI. Usually, you'll want to include `ApisPreset` if you use this option.
##### Plugins options
Parameter name | Docker variable | Description
--- | --- | -----
<a name="pluginLoadType"></a>`pluginLoadType` | _Unavailable_ | `String=["legacy", "chain"]`. Control behavior of plugins when targeting the same component with wrapComponent.<br/>- `legacy` (default) : last plugin takes precedence over the others<br/>- `chain` : chain wrapComponents when targeting the same core component, allowing multiple plugins to wrap the same component
##### Display ##### Display
Parameter name | Docker variable | Description Parameter name | Docker variable | Description

View File

@@ -93,6 +93,13 @@ export default function SwaggerUI(opts) {
plugins: [ plugins: [
], ],
pluginsOptions: {
// Behavior during plugin registration. Can be :
// - legacy (default) : the current behavior for backward compatibility last plugin takes precedence over the others
// - chain : chain wrapComponents when targeting the same core component
pluginLoadType: "legacy"
},
// Initial state // Initial state
initialState: { }, initialState: { },
@@ -118,6 +125,7 @@ export default function SwaggerUI(opts) {
configs: constructorConfig.configs configs: constructorConfig.configs
}, },
plugins: constructorConfig.presets, plugins: constructorConfig.presets,
pluginsOptions: constructorConfig.pluginsOptions,
state: deepExtend({ state: deepExtend({
layout: { layout: {
layout: constructorConfig.layout, layout: constructorConfig.layout,

View File

@@ -35,6 +35,7 @@ export default class Store {
deepExtend(this, { deepExtend(this, {
state: {}, state: {},
plugins: [], plugins: [],
pluginsOptions: {},
system: { system: {
configs: {}, configs: {},
fn: {}, fn: {},
@@ -63,7 +64,7 @@ export default class Store {
} }
register(plugins, rebuild=true) { register(plugins, rebuild=true) {
var pluginSystem = combinePlugins(plugins, this.getSystem()) var pluginSystem = combinePlugins(plugins, this.getSystem(), this.pluginsOptions)
systemExtend(this.system, pluginSystem) systemExtend(this.system, pluginSystem)
if(rebuild) { if(rebuild) {
this.buildSystem() this.buildSystem()
@@ -310,19 +311,21 @@ export default class Store {
} }
function combinePlugins(plugins, toolbox) { function combinePlugins(plugins, toolbox, pluginOptions) {
if(isObject(plugins) && !isArray(plugins)) { if(isObject(plugins) && !isArray(plugins)) {
return assignDeep({}, plugins) return assignDeep({}, plugins)
} }
if(isFunc(plugins)) { if(isFunc(plugins)) {
return combinePlugins(plugins(toolbox), toolbox) return combinePlugins(plugins(toolbox), toolbox, pluginOptions)
} }
if(isArray(plugins)) { if(isArray(plugins)) {
const dest = pluginOptions.pluginLoadType === "chain" ? toolbox.getComponents() : {}
return plugins return plugins
.map(plugin => combinePlugins(plugin, toolbox)) .map(plugin => combinePlugins(plugin, toolbox, pluginOptions))
.reduce(systemExtend, {}) .reduce(systemExtend, dest)
} }
return {} return {}

View File

@@ -191,6 +191,72 @@ describe("wrapComponents", () => {
expect(children.eq(1).text()).toEqual("WOW much data") expect(children.eq(1).text()).toEqual("WOW much data")
}) })
it("should wrap correctly when registering multiple plugins targeting the same component", function () {
// Given
const mySystem = new System({
pluginsOptions: {
pluginLoadType: "chain"
},
plugins: [
() => {
return {
components: {
wow: () => <div>Original component</div>
}
}
}
]
})
mySystem.register([
() => {
return {
wrapComponents: {
wow: (OriginalComponent, system) => (props) => {
return <container1>
<OriginalComponent {...props}></OriginalComponent>
<div>Injected after</div>
</container1>
}
}
}
},
() => {
return {
wrapComponents: {
wow: (OriginalComponent, system) => (props) => {
return <container2>
<div>Injected before</div>
<OriginalComponent {...props}></OriginalComponent>
</container2>
}
}
}
}
])
// Then
let Component = mySystem.getSystem().getComponents("wow")
const wrapper = render(<Component name="Normal" />)
const container2 = wrapper.children().first()
expect(container2[0].name).toEqual("container2")
const children2 = container2.children()
expect(children2.length).toEqual(2)
expect(children2[0].name).toEqual("div")
expect(children2.eq(0).text()).toEqual("Injected before")
expect(children2[1].name).toEqual("container1")
const children1 = children2.children()
expect(children1.length).toEqual(2)
expect(children1.eq(0).text()).toEqual("Original component")
expect(children1[0].name).toEqual("div")
expect(children1.eq(1).text()).toEqual("Injected after")
})
it("should wrap correctly when building a system twice", function () { it("should wrap correctly when building a system twice", function () {
// Given // Given