Skip to main content

Configuration Guide

Learn how to customize Presetter to fit your exact needs through the presetter.config.ts file.

Configuration File Structure

The presetter.config.ts file is the heart of your Presetter setup:

// presetter.config.ts
import { preset } from 'presetter';
import esm from '@presetter/preset-esm';

export default preset('my-project', {
extends: [esm], // Inherit from other presets
variables: { // Define context variables
source: 'src',
output: 'dist'
},
scripts: { // Define npm scripts
start: 'node dist/index.js'
},
assets: { // Define configuration files
'tsconfig.json': {
// TypeScript configuration
}
},
override: { // Override inherited configurations
// Applied after everything else
}
});

Tip: presetter already ships with the type definitions from @presetter/types. You only need @presetter/types if you're authoring a preset package.

Basic Configuration Patterns

1. Simple Preset Usage

Use a preset as-is without any customization:

// presetter.config.ts
export { default } from '@presetter/preset-esm';

2. Light Customization

Extend a preset with minor modifications:

import esm from '@presetter/preset-esm';
import { preset } from 'presetter';

export default preset('my-app', {
extends: [esm],
variables: {
source: 'lib', // Change from default 'src'
target: 'ES2022' // Update TypeScript target
},
scripts: {
start: 'node dist/app.js' // Add custom start script
}
});

3. Multiple Preset Composition

Combine multiple presets for complex setups:

import base from '@presetter/preset-essentials';
import react from '@presetter/preset-react';
import { preset } from 'presetter';

export default preset('full-stack-react', {
extends: [base, react],
// Each preset contributes its configurations
});

Variables Configuration

Variables provide dynamic values to your configurations:

Common Variables

variables: {
// Directories
source: 'src', // Source code directory
output: 'dist', // Build output directory
test: 'tests', // Test directory

// Build settings
target: 'ES2022', // TypeScript/Babel target
module: 'ESNext', // Module system

// Project info
name: 'my-awesome-app', // Project name
description: 'An awesome app'
}

Dynamic Variables

Use functions for context-aware variables:

variables: {
// Read from package.json
name: (context) => context.package.name,

// Environment-based
nodeVersion: process.version,

// Computed values
testPattern: (context) => `${context.variables.source}/**/*.test.ts`
}

Variable Usage in Assets

Reference variables in configuration files:

assets: {
'tsconfig.json': {
compilerOptions: {
outDir: '{output}', // Becomes: "dist"
rootDir: '{source}', // Becomes: "src"
target: '{target}' // Becomes: "ES2022"
},
include: ['{source}/**/*']
}
}

Scripts Configuration

Scripts define the npm/pnpm/yarn commands available in your project:

Basic Scripts

scripts: {
// Build commands
build: 'tsc',
'build:watch': 'tsc --watch',

// Testing
test: 'vitest',
'test:watch': 'vitest --watch',
'test:ui': 'vitest --ui',
coverage: 'vitest --coverage',

// Linting
lint: 'eslint {source}/**/*.ts',
'lint:fix': 'eslint {source}/**/*.ts --fix',

// Formatting
format: 'prettier --write {source}/**/*.ts',
'format:check': 'prettier --check {source}/**/*.ts'
}

Script Composition

Combine multiple commands:

scripts: {
// Sequential execution
ci: 'npm run lint && npm run test && npm run build',

// Parallel execution (with npm-run-all)
'dev:all': 'run-p dev:server dev:client',

// Conditional scripts
precommit: 'lint-staged',
prepublishOnly: 'npm run ci'
}

Script Merging

Local package.json scripts take priority over preset scripts:

// Preset scripts
scripts: {
build: 'tsc',
test: 'vitest'
}

// Your package.json
{
"scripts": {
"build": "vite build", // Overrides preset
"dev": "vite dev" // Adds to preset
}
}

// Result: build uses Vite, test uses Vitest, dev available

Assets Configuration

Assets are configuration files generated in your project:

JSON Configuration Files

assets: {
'tsconfig.json': {
compilerOptions: {
target: '{target}',
module: '{module}',
outDir: '{output}',
rootDir: '{source}',
strict: true,
esModuleInterop: true,
skipLibCheck: true
},
include: ['{source}/**/*'],
exclude: ['node_modules', '{output}']
}
}

Array-Based Files

Great for .gitignore, .eslintignore, etc.:

assets: {
'.gitignore': [
'node_modules',
'{output}',
'*.log',
'.env.local',
'coverage',
'.nyc_output'
],

'.eslintignore': [
'{output}',
'node_modules',
'*.d.ts'
]
}

Template-Based Files

Generate complex configuration files:

assets: {
'vite.config.ts': `
import { defineConfig } from 'vite';

export default defineConfig({
root: '{source}',
build: {
outDir: '../{output}',
target: '{target}'
},
test: {
globals: true,
environment: 'jsdom'
}
});
`.trim()
}

Function-Generated Assets

For complex, dynamic configurations:

assets: {
'eslint.config.ts': (current, { variables, context }) => {
const config = {
...current,
languageOptions: {
parserOptions: {
ecmaVersion: variables.target === 'ES2022' ? 2022 : 2020
}
}
};

// Add React rules if React is detected
if (context.package.dependencies?.react) {
config.plugins = {
...config.plugins,
react: require('eslint-plugin-react')
};
}

return config;
}
}

Override System

The override field allows you to customize inherited configurations:

Variable Overrides

override: {
variables: {
// Override inherited variables
source: 'lib', // Change from 'src'
target: 'ES2023', // Update target

// Add new variables
customPath: 'custom/path'
}
}

Script Overrides

override: {
scripts: {
// Replace inherited script
build: 'rollup -c',

// Add new scripts
deploy: 'npm run build && gh-pages -d dist'
}
}

Asset Overrides

Deep Merging

override: {
assets: {
'tsconfig.json': {
compilerOptions: {
// Merged with existing compilerOptions
strict: false, // Override inherited setting
noImplicitAny: true // Add new setting
}
}
}
}

Function-Based Overrides

override: {
assets: {
'eslint.config.ts': (current, { variables }) => ({
...current,
rules: {
...current.rules,
// Add custom rules
'no-console': 'warn',
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_' }
]
}
})
}
}

Removing Assets

Set assets to null to prevent their generation:

override: {
assets: {
'.prettierrc': null, // Don't generate Prettier config
'vitest.config.ts': null // Don't generate Vitest config
}
}

Advanced Configuration Patterns

Context-Aware Configuration

Make configurations responsive to project context:

export default preset('adaptive', (context) => {
const isMonorepo = context.package.workspaces !== undefined;
const hasReact = context.package.dependencies?.react;
const isLibrary = context.package.main !== undefined;

return {
variables: {
target: isLibrary ? 'ES2018' : 'ES2022',
format: isLibrary ? 'cjs' : 'esm'
},

assets: {
'tsconfig.json': {
compilerOptions: {
composite: isMonorepo,
jsx: hasReact ? 'react-jsx' : undefined,
declaration: isLibrary
}
}
}
};
});

Environment-Based Configuration

export default preset('env-aware', {
variables: {
isDev: process.env.NODE_ENV === 'development',
isProd: process.env.NODE_ENV === 'production'
},

override: {
assets: {
'vite.config.ts': (current, { variables }) => ({
...current,
build: {
...current.build,
minify: variables.isProd,
sourcemap: variables.isDev
}
})
}
}
});

Conditional Asset Generation

assets: {
// Only generate Storybook config if Storybook is installed
'.storybook/main.ts': (current, { context }) => {
if (!context.package.devDependencies?.['@storybook/react']) {
return null; // Don't generate this file
}

return {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-essentials']
};
}
}

Best Practices

1. Start Simple

Begin with a basic preset and add customizations incrementally:

// Start here
export { default } from '@presetter/preset-esm';

// Then customize
import esm from '@presetter/preset-esm';
export default preset('my-project', {
extends: [esm],
variables: { source: 'lib' }
});

2. Use Variables for Consistency

Define paths and settings once:

variables: {
source: 'src',
output: 'dist',
test: 'test'
},

assets: {
'tsconfig.json': {
compilerOptions: {
rootDir: '{source}',
outDir: '{output}'
}
},
'vitest.config.ts': {
test: {
include: ['{test}/**/*.test.ts']
}
}
}

3. Prefer Override for Customizations

Use the override field for modifications:

// Good: Override ensures changes are applied last
override: {
assets: {
'eslint.config.ts': (current) => ({
...current,
rules: { ...current.rules, 'no-console': 'warn' }
})
}
}

// Avoid: Direct asset modification may be overridden
assets: {
'eslint.config.ts': { rules: { 'no-console': 'warn' } }
}

4. Document Your Configuration

Add comments to explain custom configurations:

export default preset('my-project', {
extends: [esm],

variables: {
// Use 'lib' instead of 'src' to match legacy structure
source: 'lib'
},

override: {
assets: {
'tsconfig.json': {
compilerOptions: {
// Allow importing JSON files for configuration
resolveJsonModule: true
}
}
}
}
});

Troubleshooting Configuration

Debug Configuration Resolution

Use the Presetter CLI to inspect resolved configurations:

# View resolved variables
npx presetter list variables

# View resolved scripts
npx presetter list scripts

# View generated assets
npx presetter list assets

Common Issues

Variables not being substituted:

// Wrong: Missing braces
'rootDir': 'source'

// Correct: Use braces for variable substitution
'rootDir': '{source}'

Assets not being generated:

// Check if asset is set to null in override
override: {
assets: {
'tsconfig.json': null // This prevents generation
}
}

Script conflicts: Local package.json scripts always take priority over preset scripts.

Next Steps