bud.hooks
bud.hooks allows for modifying values with callbacks. This is useful for things like logging, or for modifying a value before it is used.
bud.hooks.on
Callbacks and raw values are registered with bud.hooks.on
bud.hooks.on
takes two parameters:
- The
name
of the hook. - Either a raw value or a callback which is passed the current value
Example defined with a raw value
bud.hooks.on('build.externals', {
$: 'jquery',
})
Example defined with a callback
You should always assume the value passed to the callback may be undefined and handle it appropriately.
bud.hooks.on('build.externals', (externals = {}) => ({
...externals,
$: 'jquery',
})
bud.hooks.filter
Filters are registered with bud.hooks.filter
.
const initialValue = `foo`
const result = bud.hooks.filter('my.filter.key', initialValue)
bud.hooks.filter
takes two parameters:
- The
name
of the hook. - An initial value which is passed to the first registered function. If there is nothing hooked to this filter, the value will be returned as-is.
bud.hooks.async
Some hooks are asynchronous. An example of this are values for build.resolve.modules
:
bud.hooks.async('build.resolve.modules', async (paths = []) => [
...paths,
bud.path(`modules`),
])
Callbacks for asynchronous hooks should be registered as asynchronous functions.
bud.hooks.action
Actions are registered with bud.hooks.action
. Every action is passed the bud object and it will never be undefined.
You don't need to return anything.
bud.hooks.action('my.action.key', async bud => {
bud.log('action log') // do something
})
Multiple actions can be defined for the same key in one call:
bud.hooks.action(
`my.action.key`,
async bud => bud.log(`action log 1`),
async bud => bud.log(`action log 2`),
async bud => bud.log(`action log 3`),
)
All actions are asynchronous. The order of execution is not guaranteed. This matters whether you are specifying a single action or multiple actions.
bud.hooks.fire
Actions are fired with bud.hooks.fire
.
await bud.hooks.fire('my.action.key')
Putting it together
bud.hooks.filter(`my.hook`, 1)
// => 1
bud.hooks.on(`my.hook`, (value = 0) => value + 1)
bud.hooks.filter(`my.hook`, 1)
// => 2
bud.hooks.on(`my.hook`, 5)
bud.hooks.filter(`my.hook`, 1)
// => 5 (since the last registered value is not a callback)
bud.hooks.action(`my.action`, async bud => bud.log(`action log`))
await bud.hooks.fire(`my.action`)
// [stdout] action log
What to do when a hook value is a function
Consider this example, where we want to get a function that has been registered to a hook. We only want to run the last registered function and we only want to call it one time:
const doThing = () => {
console.log(`thing`)
}
bud.hooks.on(`my.hook`, doThing)
const doThing2 = () => {
console.log(`thing2`)
}
bud.hooks.on(`my.hook`, doThing2)
const myFunction = bud.hooks.filter(`my.hook`)
myFunction()
This is not what we want. That will log both thing
and thing2
and then cause a TypeError
(because myFunction
is undefined
).
But it makes sense -- every registered hook will always be called when
the filter runs, and the functions we defined don't return anything. So the end result is undefined
and every
console.log
call happened as each hook function was called.
The way around it is to wrap our functions in a callback:
const doThing = () => {
console.log(`thing`)
}
bud.hooks.on(`my.hook`, () => doThing)
const doThing2 = () => {
console.log(`thing2`)
}
bud.hooks.on(`my.hook`, () => doThing2)
const myFunction = bud.hooks.filter(`my.hook`)
myFunction()
Now it will only log thing2
from doThing2
. doThing
will remain unexecuted.
Something to remember if you ever need to pass a function around in a hook.