Code Monkey home page Code Monkey logo

presetter's Introduction

Logo

Setup build settings from a template, quick and right!

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

Sharing configurations for building tools across projects is painful. How many time you've copied configs for babel, eslint, jest, typescript or the life cycle scripts in package.json? How many dev dependencies you have to install before you can kick start a project?

What's more, what if you want to update configs for all projects? ๐Ÿคฆโ€โ™‚๏ธ

Presetter is a utility for setting up building tools for your project from a template. This means with just only two dev packages, namely this package and your favorite template preset, all essential development packages, such as typescript, eslint and jest, together with their configuration files provided by the preset, are automatically setup for you upon the project's initialization.

Before and After

Quick Start

  1. Bootstrap your project with a preset (e.g. presetter-preset-esm)
npx presetter use <preset package name>

That's. One command and you're set.

  1. Develop and run life cycle scripts provided by the preset

At this point, all development packages specified in the preset are installed, and now you can try to run some example life cycle scripts provided by the preset (e.g. try npx run test).

Demo

Concept

The concept comprises two part: presetter (this package) and a preset, which you can easily create one for your own requirement.

presetter

Presetter is a utility for two tasks:

  1. setting up a development environment for a project by
    • installing development dependencies specified in the preset without polluting package.json
    • hardlinking or symlinking configuration files (e.g. .babelrc) from the preset module to the project root
  2. merging life-cycle scripts from the template and the local version in package.json

SEE HERE FOR THE CLI USAGE

Life-Cycle Scripts

When you run presetter run <task> (or its alias run <task>), presetter will perform the following:

  1. Combine the local scripts and those provided by the preset
  2. Backup package.json as ~package.json
  3. Place the combined script into a temporary package.json
  4. Run the task via npm run <task> as usual
  5. Restore the original package.json after running the task

PROTIPS: Local scripts always have a higher priority than the template scripts. So you can always customize the life cycle script by putting your own version into package.json.

Upon running a life-cycle script involving the run <task> command in package.json, presetter will automatically resolve the task according to the template, so that you can always use the conventional npm run <task> as usual.

For example, with the following template and local package.json, presetter will generate a package.json with the content below before running the script.

Template

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

Local package.json

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

Output

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

preset

A preset is a collection of configuration to be shared. An example can be found in presetter-preset-esm which is also used for developing presetter and other demo presets below.

A preset contains three parts:

  1. A set of development packages declared as peerDependencies in the package.json: For a project adopting a preset, during its installation these packages will be installed by presetter automatically without making changes to the project's package.json.
  2. A set of configuration files: These configuration files are to be hardlinked (or symlinked if hardlink is not possible) to the adopting project's root.
  3. A set of life cycle script template: These scripts provide the base where the presetter run command will use for merging.

For 1, the set of development packages to be installed is exported via package.json. For 2 & 3, the configuration is exported via the default function in the preset package (example).

Config Extension

To overwrite part of the preset configuration (e.g. add a rule to the eslint config file template), you can specify the new configuration under the config parameter in the configuration file (.presetterrc or .presetterrc.json).

During installation and life cycle script execution, the content of this parameter will be passed to the configurator function provided by the preset package. With this parameter, the preset can dynamically export customized config files and life cycle scripts. You can checkout the example preset to see how it work.

Known Limitations

Missing dependent development packages after npm install <package>

In npm v5 & v6, any subsequent npm install <package> command will cause the installed development packages to be removed after installation. This is due to a side effect of the introduction of package-lock.json in npm v5, where the npm dedupe process begins to remove any packages not recorded in package-lock.json after package installation.

Since the development packages are only declared as peer dependencies in the preset package, it's not recorded in package-lock.json and therefore the problem.

Currently, there are two solutions

  1. Run presetter bootstrap manually after each additional package installation. This will make sure any missing dependencies will be installed again.
  2. Use yarn to install additional packages as it won't remove any packages during the process.

This problem, fortunately, should soon has now become a history when npm v7 was released. The auto peer dependencies installation feature will now resolve this issue for good.

FAQ

Life cycle scripts are broken

It may be the case when a life cycle script crashed, resulting in package.json not be restored to its original version. To fix the issue, you can simply replace the temporary package.json by its original at ~package.json.

How to create a preset?

It's actually rather simple. You just need to prepare an ordinary npm package with a default export with signature (args: PresetContext) => PresetAsset | Promise<PresetAsset>, where

/** input for a preset configurator */
export interface PresetContext {
  /** information about the targeted project */
  target: {
    /** the package name defined in the targeted project's package.json */
    name: string;
    /** the root folder containing the targeted project's .presetterrc.json */
    root: string;
    /** normalized package.json from the targeted project's package.json */
    package: PackageJson;
  };
  /** content of .presetterrc */
  custom: PresetterConfig;
}

/** expected return from the configuration function from the preset */
export interface PresetAsset {
  /** list of presets to extend from */
  extends?: string[];
  /** mapping of files to be generated to its configuration template files (key: file path relative to the target project's root, value: template path) */
  template?: TemplateMap | TemplateMapGenerator;
  /** list of templates that should not be created as hardlinks or symlinks */
  noSymlinks?: string[] | Generator<string[]>;
  /** path to the scripts template */
  scripts?: string;
  /** variables to be substituted in templates */
  variable?: Record<string, string>;
  /** supplementary configuration applied to .presetterrc for enriching other presets */
  supplementaryConfig?: ConfigMap | ConfigMapGenerator;
}

/** an auxiliary type for representing a file path */
type Path = string;
/** an auxiliary type for representing a template (either path to the template file or its content) */
export type Template = string | Record<string, unknown>;
/** an auxiliary type for representing a dynamic template generator */
export type TemplateGenerator = Generator<Template>;
/** an auxiliary type for representing a collection of template (key: output path, value: template definition) */
export type TemplateMap = Record<string, Path | Template | TemplateGenerator>;
/** an auxiliary type for representing a dynamic template map generator */
export type TemplateMapGenerator = Generator<TemplateMap>;
/** an auxiliary type for representing a config */
export type Config = string[] | Record<string, unknown>;
/** an auxiliary type for representing a dynamic config generator */
export type ConfigGenerator = Generator<Config>;
/** an auxiliary type for representing a config map */
export type ConfigMap = Record<string, Path | Config | ConfigGenerator>;
/** an auxiliary type for representing a dynamic config map generator */
export type ConfigMapGenerator = Generator<ConfigMap>;

This function is a manifest generator which will be used to inform presetter what and how use the template files. For bundling other dev tools, you only need to declare them in peerDependencies in the package.json of the preset package. Presetter will pick them up and automatically install them on your target project.

Demo Presets

There are many ways to create a preset. Checkout our example presets to learn more:

Preset Description
presetter-preset-cjs An extension of presetter-preset-esm but loaded with tools to help you to develop an commonjs project with ease.
presetter-preset-esm A starter preset with lots of useful dev tools (e.g. eslint, jest etc.) bundled and configuration following the best practices for a modern ESM project.
presetter-preset-hybrid Another extension of presetter-preset-esm aiming to help you to create a dual CommonJS/ESM package without all the pains.
presetter-preset-react Want to control the output path of the generated files? or which template to use based on the context? presetter-preset-react is an example showing you how to generate your manifest programmatically.
presetter-preset-rollup An advanced preset showing you how to generate a content based on consolidated configs.
presetter-preset-strict Want to build a preset on top of an existing one? Check this out, it extends presetter-preset-esm with extra rules.
presetter-preset-web Just want a preset with tools bundled? This one has only GraphQL, PostCSS and TailwindCSS bundled, with nothing extra.
@alvis/preset-gatsby How to make a preset without publishing it? Check out my personal preset. For my case, I can just use presetter use https://github.com/alvis/preset-gatsby to setup my dev environment for a Gatsby project.

About

This project originated from my personal pain on maintaining a number of projects with fairly similar structure, having exactly the same build and test procedures, same .babelrc, tsconfig.json etc. Every time when I setup a new project, I have to copy many identical config files such as .babelrc, .eslintrc, .jestrc, .lintstagedrc, .npmignore, tsconfig.json to name a few, together with the following 40 ๐Ÿ˜ฑ development dependencies!!!

  1. @babel/cli
  2. @babel/core
  3. @babel/node
  4. @babel/plugin-proposal-class-properties
  5. @babel/plugin-proposal-decorators
  6. @babel/plugin-proposal-nullish-coalescing-operator
  7. @babel/plugin-proposal-object-rest-spread
  8. @babel/plugin-proposal-optional-chaining
  9. @babel/preset-env
  10. @babel/preset-typescript
  11. @types/jest
  12. @types/node
  13. @typescript-eslint/eslint-plugin
  14. @typescript-eslint/parser
  15. babel-plugin-transform-typescript-metadata
  16. conventional-changelog-metahub
  17. cross-env
  18. eslint
  19. eslint-config-prettier
  20. eslint-plugin-eslint-comments
  21. eslint-plugin-header
  22. eslint-plugin-import
  23. eslint-plugin-jsdoc
  24. eslint-plugin-no-secrets
  25. eslint-plugin-sonarjs
  26. husky
  27. jest
  28. leasot
  29. lint-staged
  30. npm-run-all
  31. presetter
  32. prettier
  33. shx
  34. standard-version
  35. ts-jest
  36. ts-node
  37. ts-node-dev
  38. tsc-alias
  39. tsconfig-paths
  40. typescript

So, I imagine, if it is possible to reduce all these 40 packages into 1? I tried to look for a solution but no luck. Therefore, I make this tool and make it available to everyone who has a similar problem as me.

Philosophy

Every design has a design philosophy and here are those for presetter:

  • Presetter should do one and only one job, which is providing building tools for the adopting project.
  • A preset should be made flexible enough to adapt to different project need while maintaining the reusability.
  • For the adopting project, updating only the preset version should be the only thing you need to do for updating the build dev dependencies and configuration files.
  • Any changes to the local config should be preserved, even during a preset update.

Related Projects

Let me know if you find any similar projects. It would be nice to be included here.

Contributing

Any new ideas? or got a bug? We definitely would love to have your contribution!

If you have any suggestion or issue, feel free to let the community know via issues.

Further, read the contribution guide for the detail of the code structure and useful commands etc.

License

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

license

presetter's People

Contributors

alvis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

danbamikiya

presetter's Issues

Cannot set a preset in a fresh project using `presetter use`

Here are some user reports as I think there are a few bugs that crept into v3. No rush, just playing with it.

I tried:

npx presetter use presetter-preset-strict

in an empty directory. This failed because

Error: failed to find target's package.json

No big deal but it might help to add a note in the README that you first need to initialize the package.

Then I tried

 npx presetter use presetter-preset-strict

But this crashes for me too:

Error: Missing preset defined in .presetterrc
    at getPresetterRC (/home/faassen/.npm/_npx/19815/lib/node_modules/presetter/source/preset.ts:66:9)
    at getContext (/home/faassen/.npm/_npx/19815/lib/node_modules/presetter/source/preset.ts:295:18)
    at setupPreset (/home/faassen/.npm/_npx/19815/lib/node_modules/presetter/source/preset.ts:198:19)

This seems to be a bug?

(Incidentally the error message is Error: Missing preset defined in .presetterrc but .presetterrc.json is the file I find inside presetter; maybe both work?)

Not being entirely sure, I also tried:

 npx presetter use presetter-preset-strict

(which one is it? The docs mention you can use preset-essentials but the package listing has the presetter- prefix)

And

npx presetter use preset-essentials

But give me the same error. So it looks like a correct .presetterrc is required.

So then I added:

{
  "preset": "presetter-preset-strict"
}

And did (weirdly enough, okay):

npx presetter use preset-essentials

But that doesn't work either as I get this:

HttpErrorGeneral: 404 Not Found - GET https://registry.npmjs.org/preset-essentials - Not found

So it is presetter-preset-essentials then, unlike what the README at first suggested. This does work:

npx presetter use presetter-preset-essentials

Unfortunately all my symlinks are broken. For some reason it's pointing to:

../../.npm/_npx/20288

And while ../../.npm/_npx exists, 20288 does not.

Trying it again with a new package, this time with a .presetterrc.json like this:

{
  "preset":  []
}

Nope, I still end up with broken symlinks.

Node version: v14.17.6
NPM version: 6.14.15

Allow removing config in override

I found a possible edge case bug in the rollup preset where the json plugin needs to be loaded first, but besides that I cannot force it to the top or otherwise re-order the plugins in a .presetterrc.

Please advise.

Kudos.

questions about creating a preset

I discovered presetter yesterday and today we played with it. I'm excited about a tool that can turn boilerplate into a reusable package! I have a few questions and comments; my apologies if issues are not the right channel for it.

I looked at the sample preset. Unfortunately it contains quite a bit of code, which makes it somewhat difficult to comprehend what is essential for a preset and what is incidental. I think I understand from the documentation that the essential contract is quite minimal, but it's hard to see. I think a very minimal preset that just contains one config file, a single development dependency and a single script would be very helpful.

The example preset offers a particular system for managing configuration files as yaml files and then converting them into a json format, with the option for overrides. I think it would be interesting to consider extracting this and create a reusable package that presets can build on. That would be another way to make the example preset less intimidating as well.

My first reaction is to wonder why the files are maintained in the yaml format while they are generated as JSON. It makes it harder for me to recognize familiar configuration files. Is this because with yaml you can address overrides in a convenient manner? It won't work for all possible configuration files as not everything is expressed as JSON; I see for npmignore there's a special system for a list-based config.

I spent a bit of time looking for a way to interpolate template variables, but I didn't find any. While most config files don't need a variables, in some cases it would be very useful to be able to refer to the repository or package name within a particular config file. Perhaps the system could even automatically extract this from package.json and make it available.

In the example preset I don't see any examples of configuration that is stored in a directory. husky is an example of such a configuration system. Does presetter support this? Another use case is to store github actions configuration in .github in a preset.

Finally I have a question about the way scripts are integrated. We noticed that when running a script it would modify the package.json and then after run replace it again with the original version. I saw that you mention in the FAQ what to do if that fails and leaves the package.json changed by accident. I was trying to understand why modifying the scripts section is necessary in the first place. Why can't presetter run work without this modification? Is this because it needs those scripts to exist to allow executing them exactly as if they were really in the source code? It'd be nice if there were a way to issue commands programmatically in the same context as scripts.

Thanks again for producing this very interesting tool! I'm going to experiment with it some more.

A preset that has a (peer) dependency on a package in a private repo

I have a preset that has a peer dependency on a package that's published on a private github repo. When I use presetter use it takes a long time and then finally crashes complaining it cannot find this dependency on registry.npmjs.org which makes sense as it isn't there.

I tried to put this configuration in a .npmrc in the module that wants to use the preset, but it didn't work. (I'd prefer to have this .npmrc to be part of the preset anyway).

I tried passing through this configuration through the npm_package_repository_url environment variable but it wasn't successful.

Perhaps there's a better way to do this I'm not aware of, or perhaps presetter needs support for this in some way.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.