6.9 KiB
6.9 KiB
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.
Recommendation: Use plugins for most extensions. Use hooks for simple custom tasks or Rolldown plugin injection.
Usage Patterns
Object Syntax
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
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:
{
options: ResolvedConfig,
hooks: Hookable
}
Use cases:
- Setup tasks
- Validation
- Environment preparation
Example:
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:
{
options: ResolvedConfig,
buildOptions: BuildOptions,
hooks: Hookable
}
Use cases:
- Modify build options per format
- Inject plugins dynamically
- Format-specific setup
Example:
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:
{
options: ResolvedConfig,
chunks: RolldownChunk[],
hooks: Hookable
}
Use cases:
- Post-processing
- Asset copying
- Notifications
- Deployment
Example:
hooks: {
'build:done': async (context) => {
console.log(`Built ${context.chunks.length} chunks`)
// Copy additional files
await copyAssets()
// Send notification
notifyBuildComplete()
},
}
Common Patterns
Build Notifications
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
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
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
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
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
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
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
export default defineConfig({
hooks: {
'build:prepare': async (context) => {
await fetchRemoteConfig();
await initializeDatabase();
},
'build:done': async (context) => {
await uploadToS3(context.chunks);
await invalidateCDN();
},
},
});
Error Handling
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 for hooks. Additional methods:
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
- Use plugins for most extensions
- Hooks for simple tasks like notifications or file copying
- Async hooks supported for I/O operations
- Don't throw errors unless you want to fail the build
- Context is mutable in
build:beforefor advanced use cases - 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 - Plugin system
- Rolldown Options - Build options
- Watch Mode - Development workflow