Skip to content

๐Ÿ›น Reuse and manage build scripts, devDependencies and config files from your favourite presets, instead of copy and paste!

License

Notifications You must be signed in to change notification settings

alvis/presetter

Repository files navigation

Logo

Set up your build configurations from templatesโ€”quickly and accurately!

โ€ขโ€ƒ โ€ƒQuick Startโ€ƒ โ€ƒโ€ขโ€ƒ โ€ƒConceptโ€ƒ โ€ƒโ€ขโ€ƒ โ€ƒFAQโ€ƒ โ€ƒโ€ขโ€ƒ โ€ƒAboutโ€ƒ โ€ƒโ€ข

Managing shared build configurations across projects can be tedious. How often have you copied over settings for babel, eslint, vitest, typescript, or the scripts in package.json?
How many dependencies did you have to install before you could even start a new project?

And when itโ€™s time to update those configurations across multiple projects... ๐Ÿ˜ฉ

Presetter simplifies this process by setting up development tools from a template.
With just two packages - Presetter and your preferred preset โ€” all your essential development tools (e.g., TypeScript, ESLint, Vitest) and their configurations are set up automatically during initialization.

Before and After


Quick Start

1. Add Presetter and a preset to your project

Choose a preset, such as presetter-preset-esm, and add it along with Presetter to your devDependencies. Additionally, define a bootstrap script in your package.json to initialize the preset.

{
  "scripts": {
    "bootstrap": "presetter bootstrap"
  },
  "devDependencies": {
    "presetter": "<version>",
    "presetter-preset-esm": "<version>"
  }
}

2. Create a preset configuration file

Create a presetter.config.ts file in the same directory as your package.json to specify the preset:

Basic Example

// presetter.config.ts

// use a preset as is

// replace `presetter-preset-esm` with your desired preset
export { default } from 'presetter-preset-esm';

With Customization

// presetter.config.ts

// use a preset with customization

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

export default preset('<customization name>', {
  extends: [esm], // extend your chosen preset
  assets: {
    'eslint.config.ts': {
      default: [
        {
          rules: {
            // custom rules to override the preset
          },
        },
      ],
    },
  },
});

Note: Yes, presetter.config.ts itself functions as a preset!

3. Install dependencies

Run your package manager's installation command (e.g., npm install) to install all necessary dependencies.

After installation, all required configuration files will be automatically generated, enabling you to start development immediately.

4. Start developing

You're all set! Use the lifecycle scripts provided by the preset to begin your development workflow. For instance, try running:

npx run test

Demo


Concept

Presetter revolves around two main components: presetter (the utility) and a preset. You can even create your own custom preset.


presetter

Presetter handles two core tasks:

  1. Setting up your development environment:

    • Installs development dependencies defined by the preset without modifying your package.json.
    • Generates configuration files (e.g., .babelrc) in your project root based on the preset.
  2. Merging lifecycle scripts:

    • Combines lifecycle scripts from the preset with your local package.json.

Learn more about CLI usage.

Lifecycle Scripts

When you run presetter run <task> (or its alias run <task>), Presetter:

  1. Merges lifecycle scripts from the preset with your local package.json.
  2. Executes the task using @npmcli/run-script.

Pro Tip: Your local scripts always take priority over preset scripts, so you retain full control over customizations.

Example: Given the following preset and local package.json files:

Preset

{
  "scripts": {
    "build": "tsc",
    "prepare": "npm run lint && npm run build",
    "lint": "eslint *_/_.ts",
    "test": "vitest"
  }
}

Local package.json

{
  "scripts": {
    "build": "run build",
    "lint": "eslint --fix *_/_.ts",
    "coverage": "run test -- --coverage"
  }
}

Resulting package.json during execution

{
  "scripts": {
    "build": "tsc",
    "prepare": "npm run lint && npm run build",
    "lint": "eslint --fix *_/_.ts",
    "test": "vitest",
    "coverage": "vitest --coverage"
  }
}

preset

A preset is a reusable bundle of configurations and dependencies. For example, see presetter-preset-esm.

A preset typically includes:

  1. Development dependencies: Defined as peerDependencies and installed automatically by Presetter.
  2. Configuration files: Hardlinked or symlinked to your project root.
  3. Lifecycle scripts: Templates that integrate seamlessly with your local scripts.

Presets are highly customizable. Use the override field in presetter.config.ts to adjust configurations dynamically during installation and execution.
Check out an example preset for more details.


FAQ

How to Create a Preset?

Creating a preset is straightforward. You write a preset to configure your project, or you can either export a preset as a npm package to share with your team. Follow these steps to write an npm package that exports a default function with the required signature:

Creating a preset is straightforward. You can write a preset to configure your project or export it as an npm package to share with your team. Follow these steps to write a preset file that exports a default function with the required signature:

// either presetter.config.ts for configuring your project or the entry file (e.g. index.ts) for exporting as a npm package

import { preset } from 'presetter';

export default preset('preset-name', (context) => {
  return {
    extends: [
      // define any presets to extend here
    ],
    variables: {
      // define your variables here
    },
    scripts: {
      // define your scripts here
    },
    assets: {
      // define your assets here
    },
    override: {
      // define any overrides here
      variables: {
        // override variables here
      },
      scripts: {
        // override scripts here
      },
      assets: {
        // override assets here
      },
    },
  };
});

Alternatively, you can export a configuration object if the preset does not require dynamic generation. This approach is more performant for most presets:

// either presetter.config.ts for configuring your project or the entry file (e.g. index.ts) for exporting as an npm package

import { preset } from 'presetter';

export default preset('preset-name', {
  extends: [
    // define any presets to extend here
  ],
  variables: {
    // define your variables here
  },
  scripts: {
    // define your scripts here
  },
  assets: {
    // define your assets here
  },
  override: {
    // define any overrides here
  },
});

Example Preset

Here is an example of a simple preset:

import { preset } from 'presetter-types';

export default preset('my-preset', (context) => {
  return {
    variables: {
      root: '.',
      source: 'src',
      output: 'dist',
    },
    scripts: {
      build: 'tsc',
      test: 'vitest',
    },
    assets: {
      'tsconfig.json': {
        compilerOptions: {
          target: 'ES2020',
          module: 'commonjs',
          outDir: 'dist',
          rootDir: 'src',
        },
      },
      '.gitignore': ['node_modules', 'dist'],
    },
    override: {
      assets: {
        'tsconfig.json': (current, { variables }) => ({
          ...current,
          include: [`${variables.source}/**/*`],
        }),
      },
    },
  };
});

How to Configure Presetter for Monorepos?

To create a preset for a monorepo, define a preset that sets up the configurations for the monorepo. Individual projects within the monorepo can then extend this preset to meet their specific needs. Here is an example of a monorepo preset:

import { preset } from 'presetter';

export default preset('monorepo', (context) => {
  return context.root === import.meta.dirname ? {
    // configurations for the monorepo
    ...
  }: {
    // configurations for any child projects without a presetter.config.ts
    ...
  }
});

In individual projects, you can extend the monorepo preset and override configurations as needed. Presetter will always look for the nearest presetter.config.ts file in the parent directories. If it does not find one, it will use the configurations defined in the monorepo preset.

// /monorepo/path/to/project/presetter.config.ts
import { preset } from 'presetter';

import monorepo from '../path/to/root/presetter.config.ts';

export default preset('project', {
  extends: [monorepo], // extend the monorepo preset
  override: {
    // override configurations here
  },
});

How to Ignore Files?

To ignore files provided by a preset, you can override the relevant asset with null in the override field. For example, to ignore the .gitignore file provided by a preset, here is how you can override it:

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

import esm from 'presetter-preset-esm';

export default preset('project name', {
  extends: [esm],
  override: {
    assets: {
      '.gitignore': null,
    },
  },
});

How to Merge a Preset with Another Preset?

To merge a preset with another preset, you can extend the preset in the extends field of the preset configuration. For example, to merge the presetter-preset-esm preset with another preset, here is how you can extend it:

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

import esm from 'presetter-preset-esm';
import other from 'other-preset';

export default preset('project name', {
  extends: [esm, other],
  override: {
    // override the configuration here
  },
});

What is the difference between variables, scripts, assets and those in override?

The variables, scripts, and assets fields in the preset configuration object define the initial resolution. The override field, on the other hand, is used to customize or override the initial resolution.

  • Initial Resolution: The variables, scripts, and assets fields are used to set up the initial configuration.
  • Override: The override field is applied after the initial resolution, allowing you to customize the configuration provided by the preset. This is useful when you need to make adjustments based on the fully resolved configuration.

If you only need to provide additional configurations, you can define them directly in the preset configuration object. However, be aware that these configurations may be overridden by other presets if the user extends multiple presets. Using the override field ensures that your customizations are applied last and are not overridden by other presets.

How to Customize a Configuration Provided by a Preset?

There are two approaches to customize a configuration (either assets or scripts) provided by a preset:

  1. Generate the content via a function: If you provide a function as the value for a configuration file, the function will receive the current content of the file and the variables defined in the preset configuration. You can then return the updated content based on the current content and variables. The content returned by the function will be used as the final content of the configuration file without being merged with the current content.

  2. Provide additional object for merging: If you want to add additional configurations to the preset, you can provide the additions either in the assets or in the override.assets field. These additional configurations will be deep merged with the preset configuration.

For example, to add additional files to the .gitignore file provided by a preset, you can provide the additional files in the .gitignore of either the assets or override.assets field:

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

import esm from 'presetter-preset-esm';

export default preset('project name', {
  extends: [esm],
  assets: {
    '.gitignore': ['additional-file'],
  },
});

To add additional rules to the ESLint configuration provided by a preset, you can provide the additional rules like this:

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

import esm from 'presetter-preset-esm';

export default preset('project name', {
  extends: [esm],
  assets: {
    'eslint.config.ts': {
      default: [
        {
          rules: {
            'additional-rule': 'error',
          },
        },
      ],
    },
  },
});

Note that for ESLint configuration, if you want to add additional rules with file filters, it is recommended to use the override field to ensure that the additional rules are applied last. Otherwise, the additional rules may be overridden by other extended presets.


Demo Presets

Explore these example presets to see Presetter in action:

Preset Description
presetter-preset-essentials A foundational preset for modern ESM projects, bundling tools like ESLint and Vitest, with best-practice configurations.
presetter-preset-esm Builds on essentials, adding tools optimized for ESM projects.
presetter-preset-cjs Extends essentials with configurations tailored for CommonJS projects.
presetter-preset-hybrid Aimed at creating dual CommonJS/ESM packages with minimal hassle.
presetter-preset-react An opinionated preset optimized for React development.
presetter-preset-rollup An opinionated preset optimized for Rollup development.
presetter-preset-strict Extends presetter-preset-esm with additional strict rules for enhanced development workflows.
presetter-preset-web Extends presetter-preset-esm with bundling GraphQL, PostCSS, and TailwindCSS with no extras.

About

This project was born out of frustration with maintaining identical configurations across multiple projects. Every new project required copying numerous files and installing dozens of dependencies.

With Presetter, I consolidated 40 development dependencies into just 1 preset, simplifying project setup and maintenance. Let it save you time, too!

Philosophy

  • Presetter focuses solely on providing build tools for your project.
  • Presets are flexible yet reusable.
  • Updating a preset version is all you need to refresh your tools and configs.
  • Local changes are always preserved.

Contributing

Weโ€™d love your ideas and contributions! Submit issues or suggestions via GitHub Issues. See the Contribution Guide for more details.


License

Released under the MIT License. ยฉ 2020, Alvis Tang.

License

About

๐Ÿ›น Reuse and manage build scripts, devDependencies and config files from your favourite presets, instead of copy and paste!

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published