concepts
hooks

Bamboo Integration Hooks

Leveraging hooks in Bamboo to create custom functionality.

Bamboo hooks can be used to add new functionality or modify existing behavior during certian parts of the compiler lifecycle.

Hooks are mostly callbacks, registered through plugins — a plugin is a name and a hooks object.

They used to be registrable two ways: through plugins, or through a bare hooks key on the config, which was treated as a nameless plugin appended after the rest. That gave one mechanism two spellings and an ordering rule you had to know, and left every diagnostic about a hook with no name to print for the config's own set. Your own hooks are a plugin like any other now, so ordering is just the order of the array.

See the Reference below for the full list of hooks and their signatures.

Examples

Prefixing token names

This is especially useful when migrating from other css-in-js libraries, like Stitches.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'tokens:created': ({ configure }) => {
          configure({
            formatTokenName: (path) => '$' + path.join('-'),
          })
        },
      },
    },
  ],
})

Customizing the hash function

When using the hash: true config property, you can customize the function used to hash the classnames.

export default defineConfig({
  // ...
  hash: true,
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'utility:created': ({ configure }) => {
          configure({
            toHash: (paths, toHash) => {
              const stringConds = paths.join(':')
              const splitConds = stringConds.split('_')
              const hashConds = splitConds.map(toHash)
              return hashConds.join('_')
            },
          })
        },
      },
    },
  ],
})

Modifying the config

Here's an example of how to leveraging the provided utils functions in the config:resolved hook to remove the float pattern from the resolved config.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'config:resolved': ({ config, utils }) => {
          return utils.omit(config, ['patterns.float'])
        },
      },
    },
  ],
})

Modifying presets

You can use the preset:resolved hook to modify presets after they are resolved. This is useful for customizing or filtering out parts of a preset.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'preset:resolved': ({ utils, preset, name }) => {
          if (name === '@bamboocss/preset-bamboo') {
            return utils.omit(preset, ['theme.tokens.colors', 'theme.semanticTokens.colors'])
          }
          return preset
        },
      },
    },
  ],
})

Configuring JSX extraction

Use the matchTag / matchTagProp functions to customize the way Bamboo extracts your JSX.

This can be especially useful when working with libraries that have properties that look like CSS properties but are not and should be ignored.

Let's see a Radix UI example where the Select.Content component has a position property that should be ignored:

// Here, the `position` property will be extracted because `position` is a valid CSS property, but we don't want that
<Select.Content position="popper" sideOffset={5}>
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'parser:before': ({ configure }) => {
          configure({
            // ignore the Select.Content entirely
            matchTag: (tag) => tag !== 'Select.Content',
            // ...or specifically ignore the `position` property
            matchTagProp: (tag, prop) => tag === 'Select.Content' && prop !== 'position',
          })
        },
      },
    },
  ],
})

Transforming the generated css

cssgen:done runs just before the CSS is written to disk by the CLI or emitted by the Vite plugin. It receives the artifact being written and the CSS as a string, and whatever you return replaces it — return nothing to leave it alone.

Rewriting asset URLs so the stylesheet can be served from a CDN:

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'my-app',
      hooks: {
        'cssgen:done': ({ artifact, content }) => {
          if (artifact === 'styles.css') {
            return content.replaceAll('url(/assets/', 'url(https://cdn.example.com/assets/')
          }
        },
      },
    },
  ],
})
⚠️

Do not use this to strip unused tokens or keyframes. prune.tokens and prune.keyframes already do it, are on by default, and are safe in a way a scan of the finished CSS cannot be — they keep the declarations token() and token.value() resolve to at runtime, which nothing in the stylesheet refers to. preflight.prune covers the reset.

Sharing hooks

A plugin is already shareable — it is a plain object with a name and a hooks object, so exporting one from a package is all there is to it.

💡

Plugins differ from presets as they can't be extended, and they are called in sequence in the order they are defined in the plugins array.

import { defineConfig } from '@bamboocss/dev'
 
export default defineConfig({
  // ...
  plugins: [
    {
      name: 'token-format',
      hooks: {
        'tokens:created': ({ configure }) => {
          configure({
            formatTokenName: (path) => '$' + path.join('-'),
          })
        },
      },
    },
  ],
})

Reference

export interface BambooHooks {
  /**
   * Called when the config is resolved, after all the presets are loaded and merged.
   * This is the first hook called, you can use it to tweak the config before the context is created.
   */
  'config:resolved': (args: ConfigResolvedHookArgs) => MaybeAsyncReturn<void | ConfigResolvedHookArgs['config']>
  /**
   * Called when each preset is resolved, allowing modification of individual presets.
   * This hook is called for each preset during the resolution process, before they are merged together.
   */
  'preset:resolved': (args: PresetResolvedHookArgs) => MaybeAsyncReturn<void | PresetResolvedHookArgs['preset']>
  /**
   * Called when the token engine has been created
   */
  'tokens:created': (args: TokenCreatedHookArgs) => MaybeAsyncReturn
  /**
   * Called when the classname engine has been created
   */
  'utility:created': (args: UtilityCreatedHookArgs) => MaybeAsyncReturn
  /**
   * Called when the Bamboo context has been created and the API is ready to be used.
   */
  'context:created': (args: ContextCreatedHookArgs) => void
  /**
   * Called when the config file or one of its dependencies (imports) has changed.
   */
  'config:change': (args: ConfigChangeHookArgs) => MaybeAsyncReturn
  /**
   * Called after reading the file content but before parsing it.
   * You can use this hook to transform the file content to a tsx-friendly syntax so that Bamboo's parser can parse it.
   * You can also use this hook to parse the file's content on your side using a custom parser, in this case you don't have to return anything.
   */
  'parser:before': (args: ParserResultBeforeHookArgs) => string | void
  /**
   * Called after the file styles are extracted and processed into the resulting ParserResult object.
   * You can also use this hook to add your own extraction results from your custom parser to the ParserResult object.
   */
  'parser:after': (args: ParserResultAfterHookArgs) => void
  /**
   * Called right before writing the codegen files to disk.
   * You can use this hook to tweak the codegen files before they are written to disk.
   */
  'codegen:prepare': (args: CodegenPrepareHookArgs) => MaybeAsyncReturn<void | Artifact[]>
  /**
   * Called after the codegen is completed
   */
  'codegen:done': (args: CodegenDoneHookArgs) => MaybeAsyncReturn
  /**
   * Called right before adding the design-system CSS (global, static, preflight, tokens, keyframes) to the final CSS
   * Called right before writing/injecting the final CSS (styles.css) that contains the design-system CSS and the parser CSS
   * You can use it to tweak the CSS content before it's written to disk by the CLI or emitted by the Vite plugin.
   */
  'cssgen:done': (args: CssgenDoneHookArgs) => string | void
  /**
   * Called when CSS needs to be optimized. Use this hook to replace the default PostCSS-based optimizer
   * with a custom one (e.g. LightningCSS).
   * Return the optimized CSS string, or void to fall through to the default PostCSS optimizer.
   */
  'css:optimize': (args: CssOptimizeHookArgs) => string | void
}