Core Concepts
Understand the fundamental concepts that make Presetter powerful and flexible.
The Big Picture
Presetter solves a common problem: configuration fatigue. Instead of copying build configs across projects or maintaining dozens of development dependencies, Presetter lets you define configurations once and reuse them everywhere.
Core Components
1. Presets
A preset is a reusable bundle of:
- 📦 Dependencies: Tools like TypeScript, ESLint, Vitest
- 📄 Configuration files: tsconfig.json, eslint.config.ts, etc.
- 📜 Scripts: build, test, lint commands
- 🎨 Assets: Templates with variable substitution
// A preset exports a configuration object
import { preset } from 'presetter';
export default preset('my-preset', {
variables: { /* context data */ },
scripts: { /* npm scripts */ },
assets: { /* config files */ },
override: { /* customizations */ }
});
2. Assets
Assets are configuration files that get generated in your project. They support:
- Static content: Direct JSON/text content
- Variable substitution: Dynamic values from context
- Function generation: Programmatic config creation
- Deep merging: Combining multiple config sources
// Example asset definitions
assets: {
// Static JSON content
'tsconfig.json': {
compilerOptions: {
target: 'ES2020',
module: 'ESNext'
}
},
// Array content (for .gitignore, etc.)
'.gitignore': [
'node_modules',
'dist',
'*.log'
],
// Function-generated content
'vite.config.ts': (current, { variables }) => ({
...current,
root: variables.source
})
}
3. Variables
Variables provide context to your configurations. They're available throughout the preset system:
variables: {
source: 'src', // Source directory
output: 'dist', // Output directory
target: 'ES2020', // TypeScript target
node: process.version // Dynamic values
}
Use variables in assets:
'tsconfig.json': {
compilerOptions: {
outDir: '{output}', // Becomes: "dist"
rootDir: '{source}' // Becomes: "src"
}
}
4. Scripts
Scripts define npm/pnpm/yarn commands available in your project:
scripts: {
build: 'tsc',
test: 'vitest',
lint: 'eslint {source}/**/*.ts',
'test:watch': 'vitest --watch'
}
Presetter merges preset scripts with your local package.json scripts, with local scripts taking priority.
5. Context
The context object provides information about your project environment:
interface Context {
root: string; // Project root directory
package: PackageJson; // Your package.json content
custom: Record<string, unknown>; // Custom data
}
Use context for dynamic presets:
import { preset } from 'presetter';
export default preset('smart-preset', (context) => {
const isReactProject = context.package.dependencies?.react;
return {
assets: {
'eslint.config.ts': isReactProject
? reactEslintConfig
: standardEslintConfig
}
};
});
Key Principles
🔄 Configuration Inheritance
Presets can extend other presets, creating inheritance chains:
import { preset } from 'presetter';
// Base preset
const base = preset('base', {
scripts: { build: 'tsc' },
assets: { 'tsconfig.json': { /* ... */ } }
});
// Extended preset
const react = preset('react', {
extends: [base],
scripts: { start: 'react-scripts start' },
assets: { 'tsconfig.json': { /* React-specific additions */ } }
});
🎯 Override System
The override field lets you customize inherited configurations:
import { preset } from 'presetter';
export default preset('my-project', {
extends: [basePreset],
override: {
variables: {
source: 'lib' // Override default 'src'
},
assets: {
'eslint.config.ts': (current) => ({
...current,
rules: {
...current.rules,
'no-console': 'error' // Add custom rule
}
})
}
}
});
🚀 Automatic Dependency Management
Presetter automatically installs preset dependencies as peer dependencies, keeping your package.json clean:
// Your package.json stays minimal
{
"devDependencies": {
"presetter": "^9.0.0",
"@presetter/preset-esm": "^9.0.0"
}
}
// But you get all these tools automatically:
// typescript, eslint, vitest, prettier, etc.
🔗 File Linking Strategy
Configuration files are hard-linked (or symlinked) to your preset, not copied:
- ✅ Always up-to-date: Changes to preset configs are immediately available
- ✅ No drift: No risk of local configs getting out of sync
- ✅ Easy updates: Just update the preset version
Advanced Concepts
Conditional Configuration
Make configurations responsive to project context:
import { preset } from 'presetter';
export default preset('adaptive', (context) => {
const isMonorepo = context.package.workspaces !== undefined;
const hasReact = context.package.dependencies?.react;
return {
assets: {
'tsconfig.json': {
compilerOptions: {
composite: isMonorepo, // Enable for monorepos
jsx: hasReact ? 'react-jsx' : undefined
}
}
}
};
});
Asset Templating
Use advanced templating for complex configurations:
assets: {
'vite.config.ts': `
import { defineConfig } from 'vite';
export default defineConfig({
root: '{source}',
build: {
outDir: '../{output}'
},
test: {
globals: true
}
});
`.trim()
}
Multi-Preset Composition
Combine multiple specialized presets:
import { preset } from 'presetter';
import typescript from '@presetter/preset-typescript';
import testing from '@presetter/preset-testing';
import linting from '@presetter/preset-linting';
export default preset('full-stack', {
extends: [typescript, testing, linting],
// Each preset contributes its part
});
Next Steps
Now that you understand the concepts:
- 🛠️ Configure your setup: Configuration Guide
- 📚 Explore presets: Preset Ecosystem
- 🎨 Create custom presets: Tutorials
- 🔍 See comparisons: Presetter vs Others