プラグイン

プラグインはオブジェクトを返す関数です。より具体的には、オブジェクトにはSwagger UIの機能を拡張および変更する関数とコンポーネントを含めることができます。

注:セマンティックバージョニング

Swagger UIの内部APIは、パブリックコントラクトの一部ではないため、メジャーバージョンの変更なしに変更される可能性があります。

カスタムプラグインが内部コアAPIをラップ、拡張、オーバーライド、または使用する場合は、アプリケーションで使用するSwagger UIの特定のマイナーバージョンを指定することをお勧めします。パッチバージョン間では変更されないためです。

たとえば、NPM経由でSwagger UIをインストールする場合は、チルダを使用してこれを行うことができます。

{
  "dependencies": {
    "swagger-ui": "~3.11.0"
  }
}

形式

プラグインの戻り値には、次のキーを含めることができます。ここで、stateKeyは状態の名称です。

{
  statePlugins: {
    [stateKey]: {
      actions,
      reducers,
      selectors,
      wrapActions,
      wrapSelectors
    }
  },
  components: {},
  wrapComponents: {},
  rootInjects: {},
  afterLoad: (system) => {},
  fn: {},
}

システムはプラグインに提供されます

normal状態名前空間の下でdoStuffアクションを公開するプラグインNormalPluginがあると仮定しましょう。

const ExtendingPlugin = function(system) {
  return {
    statePlugins: {
      extending: {
        actions: {
          doExtendedThings: function(...args) {
            // you can do other things in here if you want
            return system.normalActions.doStuff(...args)
          }
        }
      }
    }
  }
}

ご覧のとおり、各プラグインには、構築中のsystemへの参照が渡されます。NormalPluginExtendingPluginの前にコンパイルされている限り、これは問題なく機能します。

プラグインシステムには依存関係管理が組み込まれていないため、別のプラグインに依存するプラグインを作成する場合は、依存するプラグインが依存されているプラグインのにロードされるようにするのはお客様の責任です。

インターフェース

アクション

const MyActionPlugin = () => {
  return {
    statePlugins: {
      example: {
        actions: {
          updateFavoriteColor: (str) => {
            return {
              type: "EXAMPLE_SET_FAV_COLOR",
              payload: str
            }
          }
        }
      }
    }
  }
}

アクションが定義されたら、システム参照を取得できる場所ならどこでも使用できます

// elsewhere
system.exampleActions.updateFavoriteColor("blue")

Actionインターフェースを使用すると、Swagger UIシステム内の状態内で新しいReduxアクションクリエーターを作成できます。

このアクションクリエーター関数は、コンテナコンポーネントにexampleActions.updateFavoriteColorとして公開されます。このアクションクリエーターが呼び出されると、戻り値(Flux Standard Actionである必要があります)は、次のセクションで定義するexampleリデューサーに渡されます。

Reduxのアクションの概念の詳細については、Redux Actionsドキュメントを参照してください。

リデューサー

リデューサーは状態(Immutable.jsマップ)とアクションを受け取り、新しい状態を返します。

リデューサーは、この場合EXAMPLE_SET_FAV_COLORのように、処理するアクションタイプの名前でシステムに提供する必要があります。

const MyReducerPlugin = function(system) {
  return {
    statePlugins: {
      example: {
        reducers: {
          "EXAMPLE_SET_FAV_COLOR": (state, action) => {
            // you're only working with the state under the namespace, in this case "example".
            // So you can do what you want, without worrying about /other/ namespaces
            return state.set("favColor", action.payload)
          }
        }
      }
    }
  }
}

セレクター

セレクターは、名前空間の状態にアクセスして、状態からデータを取得または派生させます。

ロジックを1か所に保持する簡単な方法であり、状態データをコンポーネントに直接渡すよりも推奨されます。

const MySelectorPlugin = function(system) {
  return {
    statePlugins: {
      example: {
        selectors: {
          myFavoriteColor: (state) => state.get("favColor")
        }
      }
    }
  }
}

Reselectライブラリを使用してセレクターをメモ化することもできます。Reselectはセレクター呼び出しを自動的にメモ化するため、使用頻度の高いセレクターには推奨されます。

import { createSelector } from "reselect"

const MySelectorPlugin = function(system) {
  return {
    statePlugins: {
      example: {
        selectors: {
          // this selector will be memoized after it is run once for a
          // value of `state`
          myFavoriteColor: createSelector((state) => state.get("favColor"))
        }
      }
    }
  }
}

セレクターが定義されたら、システム参照を取得できる場所ならどこでも使用できます

system.exampleSelectors.myFavoriteColor() // gets `favColor` in state for you

コンポーネント

システムに統合されるコンポーネントのマップを提供できます。

提供するコンポーネントのキー名に注意してください。これらの名前を使用して、他の場所でコンポーネントを参照する必要があります。

class HelloWorldClass extends React.Component {
  render() {
    return <h1>Hello World!</h1>
  }
}

const MyComponentPlugin = function(system) {
  return {
    components: {
      HelloWorldClass: HelloWorldClass
      // components can just be functions, these are called "stateless components"
      HelloWorldStateless: () => <h1>Hello World!</h1>,
    }
  }
}
// elsewhere
const HelloWorldStateless = system.getComponent("HelloWorldStateless")
const HelloWorldClass = system.getComponent("HelloWorldClass")

常にnullを返すステートレスコンポーネントを作成することで、不要なコンポーネントを「キャンセル」することもできます

const NeverShowInfoPlugin = function(system) {
  return {
    components: {
      info: () => null
    }
  }
}

システムにコンポーネントが存在しない場合に警告を表示しない場合は、config.failSilentlyを使用できます。

getComponent引数の順序に注意してください。下の例では、ブール値falseはコンテナの存在を指し、3番目の引数は、欠落しているコンポーネントの警告を抑制するために使用される構成オブジェクトです。

const thisVariableWillBeNull = getComponent("not_real", false, { failSilently: true })

ラップアクション

ラップアクションを使用すると、システム内のアクションの動作をオーバーライドできます。

これらは、(oriAction, system) => (...args) => resultというシグネチャを持つ関数ファクトリーです。

ラップアクションの最初の引数はoriActionであり、ラップされているアクションです。oriActionを呼び出すのはお客様の責任です。呼び出さないと、元のアクションは発生しません!

このメカニズムは、組み込みの動作を条件付きでオーバーライドしたり、アクションをリッスンしたりする場合に役立ちます。

// FYI: in an actual Swagger UI, `updateSpec` is already defined in the core code
// it's just here for clarity on what's behind the scenes
const MySpecPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        actions: {
          updateSpec: (str) => {
            return {
              type: "SPEC_UPDATE_SPEC",
              payload: str
            }
          }
        }
      }
    }
  }
}

// this plugin allows you to watch changes to the spec that is in memory
const MyWrapActionPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        wrapActions: {
          updateSpec: (oriAction, system) => (str) => {
            // here, you can hand the value to some function that exists outside of Swagger UI
            console.log("Here is my API definition", str)
            return oriAction(str) // don't forget! otherwise, Swagger UI won't update
          }
        }
      }
    }
  }
}

ラップセレクター

ラップセレクターを使用すると、システム内のセレクターの動作をオーバーライドできます。

これらは、(oriSelector, system) => (state, ...args) => resultというシグネチャを持つ関数ファクトリーです。

このインターフェースは、コンポーネントに流入するデータを制御する場合に役立ちます。API定義のバージョンに基づいてセレクターを無効にするために、コアコードでこれを使用します。

import { createSelector } from 'reselect'

// FYI: in an actual Swagger UI, the `url` spec selector is already defined
// it's just here for clarity on what's behind the scenes
const MySpecPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        selectors: {
          url: createSelector(
            state => state.get("url")
          )
        }
      }
    }
  }
}

const MyWrapSelectorsPlugin = function(system) {
  return {
    statePlugins: {
      spec: {
        wrapSelectors: {
          url: (oriSelector, system) => (state, ...args) => {
            console.log('someone asked for the spec url!!! it is', state.get('url'))
            // you can return other values here...
            // but let's just enable the default behavior
            return oriSelector(state, ...args)
          }
        }
      }
    }
  }
}

ラップコンポーネント

ラップコンポーネントを使用すると、システム内に登録されているコンポーネントをオーバーライドできます。

ラップコンポーネントは、(OriginalComponent, system) => props => ReactElementというシグネチャを持つ関数ファクトリーです。Reactコンポーネントクラスを提供する場合は、(OriginalComponent, system) => ReactClassも同様に機能します。

const MyWrapBuiltinComponentPlugin = function(system) {
  return {
    wrapComponents: {
      info: (Original, system) => (props) => {
        return <div>
          <h3>Hello world! I am above the Info component.</h3>
          <Original {...props} />
        </div>
      }
    }
  }
}

ラップされるコンポーネントのコードサンプルを含む別の例を次に示します

/////  Overriding a component from a plugin

// Here's our normal, unmodified component.
const MyNumberDisplayPlugin = function(system) {
  return {
    components: {
      NumberDisplay: ({ number }) => <span>{number}</span>
    }
  }
}

// Here's a component wrapper defined as a function.
const MyWrapComponentPlugin = function(system) {
  return {
    wrapComponents: {
      NumberDisplay: (Original, system) => (props) => {
        if(props.number > 10) {
          return <div>
            <h3>Warning! Big number ahead.</h3>
            <Original {...props} />
          </div>
        } else {
          return <Original {...props} />
        }
      }
    }
  }
}

// Alternatively, here's the same component wrapper defined as a class.
const MyWrapComponentPlugin = function(system) {
  return {
    wrapComponents: {
      NumberDisplay: (Original, system) => class WrappedNumberDisplay extends React.component {
        render() {
          if(props.number > 10) {
            return <div>
              <h3>Warning! Big number ahead.</h3>
              <Original {...props} />
            </div>
          } else {
            return <Original {...props} />
          }
        }
      }
    }
  }
}

複数のプラグインが同じコンポーネントをラップしている場合は、pluginsOptions.pluginLoadTypeパラメーターをchainに変更することを検討してください。

rootInjects

rootInjectsインターフェースを使用すると、システムの一番上に値を注入できます。

このインターフェースは、実行時にトップレベルのシステムオブジェクトにマージされるオブジェクトを受け取ります。

const MyRootInjectsPlugin = function(system) {
  return {
    rootInjects: {
      myConstant: 123,
      myMethod: (...params) => console.log(...params)
    }
  }
}

afterLoad

afterLoadプラグインメソッドを使用すると、プラグインが登録された後にシステムへの参照を取得できます。

このインターフェースは、バインドされたセレクターまたはアクションによって駆動されるメソッドをアタッチするために、コアコードで使用されます。また、プラグインがすでに準備できている必要があるロジックを実行するために使用することもできます。たとえば、リモートエンドポイントから初期データをフェッチし、プラグインが作成したアクションに渡すなどです。

thisにバインドされているプラグインコンテキストは文書化されていませんが、バインドされたアクションをトップレベルのメソッドとしてアタッチする方法の例を次に示します

const MyMethodProvidingPlugin = function() {
  return {
    afterLoad(system) {
      // at this point in time, your actions have been bound into the system
      // so you can do things with them
      this.rootInjects = this.rootInjects || {}
      this.rootInjects.myMethod = system.exampleActions.updateFavoriteColor
    },
    statePlugins: {
      example: {
        actions: {
          updateFavoriteColor: (str) => {
            return {
              type: "EXAMPLE_SET_FAV_COLOR",
              payload: str
            }
          }
        }
      }
    }
  }
}

fn

fnインターフェースを使用すると、他の場所で使用するためにヘルパー関数をシステムに追加できます。

import leftPad from "left-pad"

const MyFnPlugin = function(system) {
  return {
    fn: {
      leftPad: leftPad
    }
  }
}