Hooks

Use build lifecycle hooks to extend Packem's functionality

Hooks

Hooks allow you to tap into Packem's build lifecycle and execute custom logic at specific points during the build process. This enables advanced customization and integration with external tools.

Hook Merging

When using presets, hooks from both the preset and your configuration are automatically merged. This means:

  • Hooks from presets are preserved - Preset hooks (like Solid preset's rollup:options hook) are automatically included
  • User config hooks take precedence - If both preset and user config define the same hook, your hook will be used
  • Multiple hooks can coexist - Different hooks from preset and user config are all registered
// Preset defines: hooks: { 'rollup:options': presetHook }
// Your config defines: hooks: { 'build:before': myHook }

// Result: Both hooks are registered
// - 'rollup:options' uses presetHook
// - 'build:before' uses myHook

Hooks are merged automatically when using presets. You don't need to manually combine them.

Available Hooks

Build Lifecycle

export default defineConfig({
  hooks: {
    'build:before': async () => {
      console.log('Build starting...')
    },

    'build:after': async () => {
      console.log('Build completed!')
    },

    'build:error': async (error) => {
      console.error('Build failed:', error)
    }
  }
})

File System Hooks

export default defineConfig({
  hooks: {
    'file:change': async (file, event) => {
      console.log(`File ${event}: ${file}`)
    },

    'file:add': async (file) => {
      console.log(`File added: ${file}`)
    },

    'file:delete': async (file) => {
      console.log(`File deleted: ${file}`)
    }
  }
})

Bundle Hooks

export default defineConfig({
  hooks: {
    'bundle:start': async (options) => {
      console.log('Bundle generation started')
    },

    'bundle:end': async (bundle) => {
      console.log('Bundle generated:', Object.keys(bundle))
    },

    'chunk:generated': async (chunk) => {
      console.log(`Chunk generated: ${chunk.fileName}`)
    }
  }
})

Common Use Cases

Code Generation

export default defineConfig({
  hooks: {
    'build:before': async () => {
      // Generate types from schema
      const { generateTypes } = await import('./scripts/generate-types')
      await generateTypes()
    }
  }
})

Asset Processing

export default defineConfig({
  hooks: {
    'build:after': async () => {
      // Optimize images
      const { optimizeImages } = await import('./scripts/optimize-images')
      await optimizeImages('dist/assets')
    }
  }
})

Version Management

export default defineConfig({
  hooks: {
    'build:before': async () => {
      // Update version in files
      const { writeFile } = await import('fs/promises')
      const version = process.env.npm_package_version

      await writeFile(
        'src/version.ts',
        `export const VERSION = '${version}'`
      )
    }
  }
})

Bundle Analysis

export default defineConfig({
  hooks: {
    'bundle:end': async (bundle) => {
      // Analyze bundle size
      const sizes = Object.entries(bundle).map(([name, chunk]) => ({
        name,
        size: chunk.code?.length || 0
      }))

      console.table(sizes)
    }
  }
})

Advanced Patterns

Conditional Hooks

export default defineConfig({
  hooks: {
    'build:after': async () => {
      if (process.env.NODE_ENV === 'production') {
        // Production-only tasks
        await deployToS3()
      }

      if (process.env.ANALYZE_BUNDLE) {
        // Optional bundle analysis
        await generateBundleReport()
      }
    }
  }
})

Async Hook Chains

export default defineConfig({
  hooks: {
    'build:before': [
      async () => {
        console.log('Step 1: Clean dist')
        await cleanDist()
      },

      async () => {
        console.log('Step 2: Generate types')
        await generateTypes()
      },

      async () => {
        console.log('Step 3: Copy assets')
        await copyAssets()
      }
    ]
  }
})

Error Handling

export default defineConfig({
  hooks: {
    'build:before': async () => {
      try {
        await riskyOperation()
      } catch (error) {
        console.warn('Optional operation failed:', error.message)
        // Continue build despite error
      }
    },

    'build:error': async (error) => {
      // Send error to monitoring service
      await sendErrorReport(error)

      // Clean up resources
      await cleanup()
    }
  }
})

File System Integration

Watch Mode Hooks

export default defineConfig({
  hooks: {
    'watch:start': async () => {
      console.log('Watch mode started')
    },

    'watch:restart': async () => {
      console.log('Restarting due to config change')
    },

    'watch:close': async () => {
      console.log('Watch mode stopped')
      await cleanup()
    }
  }
})

File Change Reactions

export default defineConfig({
  hooks: {
    'file:change': async (file, event) => {
      // React to specific file changes
      if (file.endsWith('.graphql')) {
        await regenerateGraphQLTypes()
      }

      if (file.includes('i18n/')) {
        await updateTranslations()
      }

      if (file === 'package.json') {
        await updateDependencies()
      }
    }
  }
})

Testing Integration

Test Hooks

export default defineConfig({
  hooks: {
    'build:after': async () => {
      if (process.env.RUN_TESTS) {
        const { exec } = await import('child_process')
        const { promisify } = await import('util')
        const execAsync = promisify(exec)

        try {
          await execAsync('npm test')
          console.log('Tests passed!')
        } catch (error) {
          console.error('Tests failed:', error.message)
          process.exit(1)
        }
      }
    }
  }
})

Coverage Reports

export default defineConfig({
  hooks: {
    'build:after': async () => {
      if (process.env.GENERATE_COVERAGE) {
        await generateCoverageReport()
        await uploadCoverageToService()
      }
    }
  }
})

Deployment Hooks

Automatic Deployment

export default defineConfig({
  hooks: {
    'build:after': async () => {
      if (process.env.AUTO_DEPLOY === 'true') {
        console.log('Deploying to production...')

        // Upload to CDN
        await uploadToCDN('dist')

        // Update service
        await updateService()

        // Send notification
        await sendDeploymentNotification()
      }
    }
  }
})

Environment-Specific Deployments

export default defineConfig({
  hooks: {
    'build:after': async () => {
      const env = process.env.DEPLOY_ENV

      switch (env) {
        case 'staging':
          await deployToStaging()
          break
        case 'production':
          await deployToProduction()
          break
        default:
          console.log('No deployment configured')
      }
    }
  }
})

Performance Monitoring

Build Metrics

export default defineConfig({
  hooks: {
    'build:before': async () => {
      // Record build start time
      global.buildStartTime = Date.now()
    },

    'build:after': async () => {
      // Calculate build duration
      const duration = Date.now() - global.buildStartTime
      console.log(`Build completed in ${duration}ms`)

      // Send metrics to monitoring service
      await sendMetrics({
        buildDuration: duration,
        timestamp: Date.now()
      })
    }
  }
})

Bundle Size Tracking

export default defineConfig({
  hooks: {
    'bundle:end': async (bundle) => {
      const bundleSizes = Object.entries(bundle).reduce((acc, [name, chunk]) => {
        acc[name] = chunk.code?.length || 0
        return acc
      }, {})

      // Track bundle size changes
      await trackBundleSizes(bundleSizes)

      // Alert if bundle size increased significantly
      await checkBundleSizeThresholds(bundleSizes)
    }
  }
})

Custom Hook System

Hook Registration

class HookManager {
  private hooks = new Map<string, Function[]>()

  register(name: string, fn: Function) {
    if (!this.hooks.has(name)) {
      this.hooks.set(name, [])
    }
    this.hooks.get(name)!.push(fn)
  }

  async execute(name: string, ...args: any[]) {
    const hooks = this.hooks.get(name) || []

    for (const hook of hooks) {
      await hook(...args)
    }
  }
}

const hookManager = new HookManager()

export default defineConfig({
  hooks: {
    'build:before': async () => {
      await hookManager.execute('custom:before-build')
    }
  }
})

Plugin Hook Integration

function customPlugin() {
  return {
    name: 'custom-plugin',

    buildStart() {
      // Register custom hooks
      this.emitFile({
        type: 'asset',
        fileName: 'custom-hook.js',
        source: 'console.log("Custom hook executed")'
      })
    }
  }
}

export default defineConfig({
  rollup: {
    plugins: [customPlugin()]
  },

  hooks: {
    'build:after': async () => {
      // Execute plugin-specific hooks
      await executePluginHooks()
    }
  }
})

Debugging Hooks

Hook Logging

export default defineConfig({
  hooks: {
    'build:before': async () => {
      console.log('🚀 Starting build process')
    },

    'file:change': async (file, event) => {
      console.log(`📝 File ${event}: ${file}`)
    },

    'bundle:end': async (bundle) => {
      console.log('📦 Bundle generated:', Object.keys(bundle))
    },

    'build:after': async () => {
      console.log('✅ Build completed successfully')
    },

    'build:error': async (error) => {
      console.error('❌ Build failed:', error.message)
    }
  }
})

Hook Performance

function withTiming(hookName: string, fn: Function) {
  return async (...args: any[]) => {
    const start = Date.now()
    await fn(...args)
    const duration = Date.now() - start
    console.log(`Hook ${hookName} took ${duration}ms`)
  }
}

export default defineConfig({
  hooks: {
    'build:before': withTiming('build:before', async () => {
      await heavyOperation()
    })
  }
})

Troubleshooting

Hook Errors

Hook errors can interrupt the build process. Use proper error handling.

export default defineConfig({
  hooks: {
    'build:before': async () => {
      try {
        await riskyOperation()
      } catch (error) {
        console.error('Hook error:', error)
        // Decide whether to continue or fail
        if (error.critical) {
          throw error // Fail the build
        }
        // Otherwise continue
      }
    }
  }
})

Async Hook Issues

export default defineConfig({
  hooks: {
    'build:before': async () => {
      // Ensure all async operations complete
      await Promise.all([
        operation1(),
        operation2(),
        operation3()
      ])
    }
  }
})

Hook Order Dependencies

export default defineConfig({
  hooks: {
    'build:before': [
      // Order matters - dependencies first
      async () => await setupDependencies(),
      async () => await configureEnvironment(),
      async () => await startServices()
    ]
  }
})

  • Plugins - Rollup plugins
  • Watch - File watching
  • Build - Build configuration
  • Scripts - Custom scripts
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues