Universal Plugin
The Universal Plugin (@pact-toolbox/unplugin
) is the core build tool integration for Pact Toolbox. It provides seamless Pact contract compilation, TypeScript type generation, and hot module replacement across 10+ different bundlers.
Features
- 🔌 Universal Compatibility - Works with Vite, Webpack, Next.js, Nuxt, Rollup, esbuild, RSpack, Rsbuild, Farm, and Jest
- 🔥 Hot Module Replacement - Instant feedback during development
- 📝 TypeScript Generation - Automatic type generation from Pact contracts
- 🚀 Framework Support - Generate React, Vue, Angular, and Svelte bindings
- 🛠️ Zero Config - Works out of the box with sensible defaults
- ⚡ Fast - Rust-powered parser for maximum performance
- 🔍 Source Maps - Full debugging support
Installation
# npm
npm install -D @pact-toolbox/unplugin
# pnpm
pnpm add -D @pact-toolbox/unplugin
# yarn
yarn add -D @pact-toolbox/unplugin
Quick Start
Vite
// vite.config.ts
import { defineConfig } from 'vite'
import pactPlugin from '@pact-toolbox/unplugin/vite'
export default defineConfig({
plugins: [
pactPlugin({
// Optional configuration
})
]
})
Webpack
// webpack.config.js
const PactPlugin = require('@pact-toolbox/unplugin/webpack')
module.exports = {
plugins: [
new PactPlugin({
// Optional configuration
})
]
}
Next.js
// next.config.js
const withPact = require('@pact-toolbox/unplugin/next')
module.exports = withPact({
// Your Next.js config
})
Nuxt
// nuxt.config.js
export default defineNuxtConfig({
modules: [
'@pact-toolbox/unplugin/nuxt'
],
pactToolbox: {
// Optional configuration
}
})
Rollup
// rollup.config.js
import pactPlugin from '@pact-toolbox/unplugin/rollup'
export default {
plugins: [
pactPlugin({
// Optional configuration
})
]
}
Configuration
Basic Options
interface PactPluginOptions {
// Glob patterns for Pact files to include
include?: string | string[]
// Default: ['**/*.pact']
// Glob patterns for files to exclude
exclude?: string | string[]
// Default: ['**/node_modules/**']
// Output directory for generated TypeScript files
outputDir?: string
// Default: '.pact-toolbox'
// Enable hot module replacement
hmr?: boolean
// Default: true in development
// Enable source maps
sourcemap?: boolean
// Default: true
// Transform options
transform?: TransformOptions
}
Transform Options
interface TransformOptions {
// Target framework for code generation
framework?: 'none' | 'react' | 'vue' | 'angular' | 'svelte'
// Default: 'none'
// TypeScript generation options
typescript?: {
// Generate .d.ts files
declarations?: boolean
// Default: true
// Emit JSDoc comments
jsdoc?: boolean
// Default: true
// Custom type mappings
typeMappings?: Record<string, string>
}
// Documentation generation
docs?: {
// Generate markdown documentation
markdown?: boolean
// Generate HTML documentation
html?: boolean
// Generate JSON schema
json?: boolean
}
// Test generation
tests?: {
// Generate test files
generate?: boolean
// Test framework
framework?: 'vitest' | 'jest' | 'mocha'
}
}
Usage Examples
Basic Setup
// vite.config.ts
import { defineConfig } from 'vite'
import pactPlugin from '@pact-toolbox/unplugin/vite'
export default defineConfig({
plugins: [
pactPlugin({
include: ['src/**/*.pact'],
outputDir: 'src/generated',
transform: {
framework: 'react',
typescript: {
declarations: true,
jsdoc: true
}
}
})
]
})
Importing Pact Contracts
Once configured, you can import Pact contracts directly in your TypeScript/JavaScript code:
// Import contract with generated types
import { todos } from './contracts/todos.pact'
// Use with full type safety
const result = await todos.createTodo({
id: 'todo-1',
title: 'Build awesome dApp',
completed: false
})
.sign()
.submitAndListen()
// TypeScript knows the return type
console.log(result.data) // Fully typed!
Framework-Specific Usage
React
// Configure for React
pactPlugin({
transform: {
framework: 'react'
}
})
// Use generated React hooks
import { useTodos } from './contracts/todos.pact'
function TodoList() {
const { data, loading, error } = useTodos.getAllTodos()
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
)
}
Vue
// Configure for Vue
pactPlugin({
transform: {
framework: 'vue'
}
})
// Use generated Vue composables
import { useTodos } from './contracts/todos.pact'
export default {
setup() {
const { data, loading, error } = useTodos.getAllTodos()
return { data, loading, error }
}
}
Advanced Configuration
Custom Type Mappings
pactPlugin({
transform: {
typescript: {
typeMappings: {
'decimal': 'string | number',
'time': 'Date | string',
'guard': 'KeysetGuard | CustomGuard'
}
}
}
})
Multiple Contract Directories
pactPlugin({
include: [
'src/contracts/**/*.pact',
'lib/shared-contracts/**/*.pact'
],
outputDir: 'src/types/contracts'
})
Development vs Production
const isDev = process.env.NODE_ENV === 'development'
export default defineConfig({
plugins: [
pactPlugin({
hmr: isDev,
sourcemap: isDev,
transform: {
typescript: {
declarations: true,
jsdoc: isDev // Only in development
},
docs: {
markdown: !isDev // Only in production
}
}
})
]
})
Hot Module Replacement
The plugin supports HMR out of the box. When you modify a Pact contract:
- The contract is automatically recompiled
- TypeScript types are regenerated
- Your application hot-reloads with the changes
- No manual restart required!
// This will auto-reload when todos.pact changes
import { todos } from './contracts/todos.pact'
// Your app stays in sync with contract changes
Error Handling
The plugin provides detailed error messages for common issues:
// Contract syntax error
❌ Error in contracts/invalid.pact:5:10
Expected ')', found ','
// Type generation error
❌ Failed to generate types for contracts/complex.pact
Unsupported type: custom-guard-type
// Import error
❌ Cannot find module './contracts/missing.pact'
Make sure the file exists and matches your include patterns
Performance Optimization
Caching
The plugin automatically caches:
- Parsed Pact ASTs
- Generated TypeScript code
- Transformation results
Parallel Processing
Multiple contracts are processed in parallel using Rust's threading capabilities.
Incremental Builds
Only changed files are reprocessed during watch mode.
Migration Guide
From Manual Contract Loading
// Before
const response = await fetch('/contracts/todos.pact')
const contractCode = await response.text()
const client = new PactClient()
await client.eval(contractCode)
// After
import { todos } from './contracts/todos.pact'
// Ready to use with types!
From @kadena/pactjs-cli
// Before - Manual type generation
pactjs contract-generate --file ./contracts/todos.pact
// After - Automatic with HMR
// Just configure the plugin and import!
Troubleshooting
Common Issues
Plugin not transforming .pact files
Make sure your bundler is configured to handle .pact
extensions:
// vite.config.ts
export default {
resolve: {
extensions: ['.ts', '.js', '.pact']
}
}
Types not generating
Check that:
- The
outputDir
is included in your TypeScript config
- The Pact files match your
include
patterns
- There are no syntax errors in your contracts
HMR not working
Ensure:
hmr: true
is set (default in development)
- Your bundler supports HMR
- You're running in development mode
Debug Mode
Enable debug logging to troubleshoot issues:
pactPlugin({
debug: true, // Enables detailed logging
logLevel: 'verbose' // 'error' | 'warn' | 'info' | 'verbose'
})
API Reference
Plugin Exports
unplugin/vite
- Vite plugin
unplugin/webpack
- Webpack plugin
unplugin/rollup
- Rollup plugin
unplugin/esbuild
- esbuild plugin
unplugin/next
- Next.js plugin
unplugin/nuxt
- Nuxt module
unplugin/rspack
- Rspack plugin
unplugin/rsbuild
- Rsbuild plugin
unplugin/farm
- Farm plugin
unplugin/jest
- Jest transformer
Type Exports
import type {
PactPluginOptions,
TransformOptions,
TransformResult,
PactContract
} from '@pact-toolbox/unplugin'
Best Practices
- Organize Contracts - Keep contracts in a dedicated directory
- Use TypeScript - Leverage generated types for safety
- Enable HMR - For faster development cycles
- Configure Output - Place generated files in a git-ignored directory
- Framework Integration - Use framework-specific transforms when applicable