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
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, mode, 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
},
}
})
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
}
}
import { definePlugin } from './plugin.ts'
export { definePlugin, pluginKey, pluginName } from './plugin.ts'
export * from './types.ts'
Registering the plugin:
import { defineConfig } from '@kubb/core'
import { definePlugin } from './index.ts'
export default defineConfig(() => {
return {
plugins: [
definePlugin({ name: 'custom-name' }),
],
}
})
Naming Convention
The naming convention for plugins is as follows:
- The name of the plugin follows the format
@kubb/plugin-name
orkubb-plugin-name
- Use
PluginFactoryOptions
to create your pluginOptions that will be used to create some TypeScript autocompletes and validations.
Simplified PluginFactoryOptions
type:
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, mode?: 'file' | 'directory', 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:
- Read out the input defined in
input.path
and check if that file exists or useinput.data
. - If
output.clean
is set remove all files inoutput.path
. - A new
PluginManager
instance is created. - The
PluginManager
instance will callbuildStart
of all plugins in parallel.- Plugin A is using
buildStart
to add files to theFileManager
.
typescriptasync 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, }, }) }
- Trigger
queueTask
.
- Plugin A is using
- The
PluginManager
instance will callbuildEnd
of all plugins in parallel. - Return all files that are generated.
Task
queueTask
is triggered when a new file is added to the FileManager
.
- Process the file to get back the generated code with imports, exports and source done by
FileManager.getSource(file)
. - The
PluginManager
instance will calltransform
of all plugins and combine the returned string by using thetransformReducer
. - The
PluginManager
instance will callwriteFile
of all plugins whenoutput.write
is set to true(by default) and the first one to NOT return null will be used as the returnedKubbFile.File
.