Skip to content

Plugin system

Under construction

Kubb provides a lightweight yet powerful plugin system to implement most of its functionality and allows the implementation of your plugin.

Plugins written by developers can generate code, resolve paths, resolve names and call other plugins.

Our plugin system is based on the following packages:

Setup

Plugins provide a function similar to (options?: PluginOptions) => KubbUserPluginWithLifeCycle as an entry point.

Plugin example

typescript
import path from 'node:path'

import { camelCase } from '@kubb/core/transformers'

import type { PluginOptions } from './types.ts'

export const pluginName = 'kubb-custom-plugin' satisfies PluginOptions['name']
export const pluginKey: PluginOptions['key'] = [pluginName] satisfies PluginOptions['key']

export const definePlugin = createPlugin<PluginOptions>((options) => {
  const {
    name,
  } = options

  return {
    name: pluginName,
    options,
    pre: [],
    resolvePath(baseName, directory, options) {
      const root = path.resolve(this.config.root, this.config.output.path)

      return path.resolve(root, output.path, baseName)
    },
    resolveName(name, type) {
      return camelCase(name)
    },
    async buildStart() {
      // run something when the build is started
    },
    async writeFile(source, writePath) {
      if (!writePath.endsWith('.ts') || !source) {
        return
      }

      return this.fileManager.write(source, writePath, { sanity: false })
    },
    async buildEnd() {
      // run something when the build is ended
    },
  }
})
typescript
export type Options = {
  name: string
}

export type PluginOptions = PluginFactoryOptions<'kubb-custom-plugin', Options, Options>

// register your plugin to the `@kubb/core` typescript system
declare module '@kubb/core' {
  export interface _Register {
    ['kubb-custom-plugin']: PluginOptions
  }
}
typescript
import { definePlugin } from './plugin.ts'

export { definePlugin, pluginKey, pluginName } from './plugin.ts'
export * from './types.ts'

export default definePlugin

Registering the plugin:

typescript
import { defineConfig } from '@kubb/core'
import createPlugin from './index.ts'

export default defineConfig(() => {
  return {
    plugins: [
      createPlugin({ name: 'custom-name' }),
    ],
  }
})

Naming Convention

The naming convention for plugins is as follows:

  • The name of the plugin follows the format @kubb/plugin-name or kubb-plugin-name
  • Use PluginFactoryOptions to create your pluginOptions that will be used to create some TypeScript autocompletes and validations.

Simplified PluginFactoryOptions type:

typescript
export type PluginFactoryOptions<Name, Options, ResolveOptions, API, ResolvePathOptions, AppMeta>

Template Repository

plugin-template is a minimal Kubb plugin template repository that you can use as a basis for developing your Kubb plugin.

Configure

To get started with creating plugins you need to set some options for the PluginManager.
More info about the lifecycle: PluginManager Lifecycle
More info about the PluginContext or this: Plugin Core

TIP

When using type PluginOptions with PluginFactoryOptions you will already receive better types, name will be what you have defined as the first parameter to PluginFactoryOptions instead of string.

  • Type: KubbUserPluginWithLifeCycle

name

Name of your plugin

  • Type: string

options

Specify some options here that you want to expose for other plugins or for internal use.

  • Type: object

pre

Which plugin(s) should be executed before the current one.

  • Type: string[]

post

Which plugin(s) should be executed after the current one.

  • Type: string[]

api

Add some extra functionality to your plugin, here you can even use functions which is not possible with options.

  • Type: (this: Omit<PluginContext, "addFile">) => object

resolvePath

This will be called when pluginManager.resolvePath is called, see Pluginmanager and resolving a path.

  • Type: (this: PluginContext, baseName: string, directory?: string | undefined, options?: object) => KubbFile.OptionalPath

resolveName

This will be called when pluginManager.resolveName is called, see Pluginmanager and resolving a name.

  • Type: (this: PluginContext, name: string, type?: "function" | "type" | "file" | undefined) => string)

buildStart

This will be called when the build starts, see Lifecycle.

  • Type: (this: PluginContext, kubbConfig: KubbConfig) => PossiblePromise<void>

buildEnd

This will be called when the build is done, see Lifecycle.

  • Type: (this: PluginContext) => PossiblePromise<void>

writeFile

This will be called when a new file is ready to be written to the fileSystem, see Lifecycle.

  • Type: (this: Omit<PluginContext, "addFile">, source: string | undefined, path: string) => PossiblePromise<string | void>

transform

This will be called just before we call writeFile, see Lifecycle. Here you can override the source/content of a file.

  • Type: (this: Omit<PluginContext, "addFile">, source: string, path: string) => PossiblePromise<TransformResult>

Lifecycle

The moment that build of @kubb/core is called or you trigger the CLI the following things will happen:

  1. Read out the input defined in input.path and check if that file exists or use input.data.
  2. If output.clean is set remove all files in output.path.
  3. A new PluginManager instance is created.
  4. The PluginManager instance will call buildStart of all plugins in parallel.
    1. Plugin A is using buildStart to add files to the FileManager.
    typescript
    async buildStart() {
      // creating a file `test.ts` with as source `export const hello = 'world'`
      await this.addFile({
        path: './src/gen/test.ts',
        baseName: 'test.ts',
        source: "export const hello = 'world'",
        imports: [],
        meta: {
          pluginKey: this.plugin.key,
        },
      })
    }
    1. Trigger queueTask.
  5. The PluginManager instance will call buildEnd of all plugins in parallel.
  6. Return all files that are generated.

Task

queueTask is triggered when a new file is added to the FileManager.

  1. Process the file to get back the generated code with imports, exports and source done by FileManager.getSource(file).
  2. The PluginManager instance will call transform of all plugins and combine the returned string by using the transformReducer.
  3. The PluginManager instance will call writeFile of all plugins when output.write is set to true(by default) and the first one to NOT return null will be used as the returned KubbFile.File.

Released under the MIT License.