Files
agent-skills/skills/tsdown/references/advanced-hooks.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

364 lines
6.9 KiB
Markdown

# Lifecycle Hooks
Extend the build process with lifecycle hooks.
## Overview
Hooks provide a way to inject custom logic at specific stages of the build lifecycle. Inspired by [unbuild](https://github.com/unjs/unbuild).
**Recommendation:** Use [plugins](advanced-plugins.md) for most extensions. Use hooks for simple custom tasks or Rolldown plugin injection.
## Usage Patterns
### Object Syntax
```ts
export default defineConfig({
entry: ['src/index.ts'],
hooks: {
'build:prepare': async (context) => {
console.log('Build starting...')
},
'build:done': async (context) => {
console.log('Build complete!')
},
},
})
```
### Function Syntax
```ts
export default defineConfig({
entry: ['src/index.ts'],
hooks(hooks) {
hooks.hook('build:prepare', () => {
console.log('Preparing build...')
})
hooks.hook('build:before', (context) => {
console.log(`Building format: ${context.format}`)
})
},
})
```
## Available Hooks
### `build:prepare`
Called before the build process starts.
**When:** Once per build session
**Context:**
```ts
{
options: ResolvedConfig,
hooks: Hookable
}
```
**Use cases:**
- Setup tasks
- Validation
- Environment preparation
**Example:**
```ts
hooks: {
'build:prepare': async (context) => {
console.log('Starting build for:', context.options.entry)
await cleanOldFiles()
},
}
```
### `build:before`
Called before each Rolldown build.
**When:** Once per format (ESM, CJS, etc.)
**Context:**
```ts
{
options: ResolvedConfig,
buildOptions: BuildOptions,
hooks: Hookable
}
```
**Use cases:**
- Modify build options per format
- Inject plugins dynamically
- Format-specific setup
**Example:**
```ts
hooks: {
'build:before': async (context) => {
console.log(`Building ${context.buildOptions.format} format...`)
// Add format-specific plugin
if (context.buildOptions.format === 'iife') {
context.buildOptions.plugins.push(browserPlugin())
}
},
}
```
### `build:done`
Called after the build completes.
**When:** Once per build session
**Context:**
```ts
{
options: ResolvedConfig,
chunks: RolldownChunk[],
hooks: Hookable
}
```
**Use cases:**
- Post-processing
- Asset copying
- Notifications
- Deployment
**Example:**
```ts
hooks: {
'build:done': async (context) => {
console.log(`Built ${context.chunks.length} chunks`)
// Copy additional files
await copyAssets()
// Send notification
notifyBuildComplete()
},
}
```
## Common Patterns
### Build Notifications
```ts
export default defineConfig({
hooks: {
'build:prepare': () => {
console.log('🚀 Starting build...')
},
'build:done': (context) => {
const size = context.chunks.reduce((sum, c) => sum + c.code.length, 0)
console.log(`✅ Build complete! Total size: ${size} bytes`)
},
},
})
```
### Conditional Plugin Injection
```ts
export default defineConfig({
hooks(hooks) {
hooks.hook('build:before', (context) => {
// Add minification only for production
if (process.env.NODE_ENV === 'production') {
context.buildOptions.plugins.push(minifyPlugin())
}
})
},
})
```
### Custom File Copy
```ts
import { copyFile } from 'fs/promises'
export default defineConfig({
hooks: {
'build:done': async (context) => {
// Copy README to dist
await copyFile('README.md', `${context.options.outDir}/README.md`)
},
},
})
```
### Build Metrics
```ts
export default defineConfig({
hooks: {
'build:prepare': (context) => {
context.startTime = Date.now()
},
'build:done': (context) => {
const duration = Date.now() - context.startTime
console.log(`Build took ${duration}ms`)
// Log chunk sizes
context.chunks.forEach((chunk) => {
console.log(`${chunk.fileName}: ${chunk.code.length} bytes`)
})
},
},
})
```
### Format-Specific Logic
```ts
export default defineConfig({
format: ['esm', 'cjs', 'iife'],
hooks: {
'build:before': (context) => {
const format = context.buildOptions.format
if (format === 'iife') {
// Browser-specific setup
context.buildOptions.globalName = 'MyLib'
} else if (format === 'cjs') {
// Node-specific setup
context.buildOptions.platform = 'node'
}
},
},
})
```
### Deployment Hook
```ts
export default defineConfig({
hooks: {
'build:done': async (context) => {
if (process.env.DEPLOY === 'true') {
console.log('Deploying to CDN...')
await deployToCDN(context.options.outDir)
}
},
},
})
```
## Advanced Usage
### Multiple Hooks
```ts
export default defineConfig({
hooks(hooks) {
// Register multiple hooks
hooks.hook('build:prepare', setupEnvironment)
hooks.hook('build:prepare', validateConfig)
hooks.hook('build:before', injectPlugins)
hooks.hook('build:before', logFormat)
hooks.hook('build:done', generateManifest)
hooks.hook('build:done', notifyComplete)
},
})
```
### Async Hooks
```ts
export default defineConfig({
hooks: {
'build:prepare': async (context) => {
await fetchRemoteConfig()
await initializeDatabase()
},
'build:done': async (context) => {
await uploadToS3(context.chunks)
await invalidateCDN()
},
},
})
```
### Error Handling
```ts
export default defineConfig({
hooks: {
'build:done': async (context) => {
try {
await riskyOperation()
} catch (error) {
console.error('Hook failed:', error)
// Don't throw - allow build to complete
}
},
},
})
```
## Hookable API
tsdown uses [hookable](https://github.com/unjs/hookable) for hooks. Additional methods:
```ts
export default defineConfig({
hooks(hooks) {
// Register hook
hooks.hook('build:done', handler)
// Register hook once
hooks.hookOnce('build:prepare', handler)
// Remove hook
hooks.removeHook('build:done', handler)
// Clear all hooks for event
hooks.removeHooks('build:done')
// Call hooks manually
await hooks.callHook('build:done', context)
},
})
```
## Tips
1. **Use plugins** for most extensions
2. **Hooks for simple tasks** like notifications or file copying
3. **Async hooks supported** for I/O operations
4. **Don't throw errors** unless you want to fail the build
5. **Context is mutable** in `build:before` for advanced use cases
6. **Multiple hooks allowed** for the same event
## Troubleshooting
### Hook Not Called
- Verify hook name is correct
- Check hook is registered in config
- Ensure async hooks are awaited
### Build Fails in Hook
- Add try/catch for error handling
- Don't throw unless intentional
- Log errors for debugging
### Context Undefined
- Check which hook you're using
- Verify context properties available for that hook
## Related
- [Plugins](advanced-plugins.md) - Plugin system
- [Rolldown Options](advanced-rolldown-options.md) - Build options
- [Watch Mode](option-watch-mode.md) - Development workflow