dominictarr/rc

Search for [appname]Config property in package.json file(s)

Closed this issue · 18 comments

It's common these days to allow an [appname]Config property in package.json as an alternative to creating .[appname]rc file. I guess that the same search pattern (./ ../ ../../ ../../../) would apply: so in every directory you would look for both the rc file and the property. Here's a description of the behavior in ESLint: stylelint/stylelint#442 (comment)

I was just wondering if you are interested in adding this feature to rc. rc already knows where to look for config and how to merge configs, so I thought it might be fitting. What do you think?

Is the purpose just to provide your own name for the default config?
On Oct 31, 2015 8:36 AM, "David Clark" notifications@github.com wrote:

It's common these days to allow an [appname]Config property in
package.json as an alternative to creating .[appname]rc file. I guess
that the same search pattern (./ ../ ../../ ../../../) would apply: so in
every directory you would look for both the rc file and the property.
Here's a description of the behavior in ESLint: stylelint/stylelint#442
(comment)
stylelint/stylelint#442 (comment)

I was just wondering if you are interested in adding this feature to rc.
rc already knows where to look for config and how to merge configs, so I
thought it might be fitting. What do you think?


Reply to this email directly or view it on GitHub
#64.

No, maybe I wasn't clear: A standalone config file would still be looked for at .[appname]rc, as now; but in addition rc would look in package.json files for an [appname]Config property.

Here are some projects that do something like this:

why is this useful? can you describe a situation this would be useful in?

@keithamus: Would you mind chiming in for the justification, because you had requested this feature in stylelint (stylelint/stylelint#442)?

My impression @dominictarr is that the reason some people like being able to use package.json is that they can reduce the clutter of dot-files at the root of their project. (I have never minded the "clutter" myself, but am raising this issue because of the noted request on my own project.)

Yup. Using a package.json over an rc file is helpful because it does clean up clutter in the dot files. There are also a few other reasons I'll go through:

  • We have one consistent, easily parseable file. JSON has no dependencies, while RC formats can use a variety of ini, yaml, toml, json, or custom formats. Each format has upsides and downsides, and some of these formats have had security problems in the past. JSON is standardised, and almost everyone knows how to write it.
  • Having one easily parseable file with all configuration makes it a lot easier to provision many projects at once. Rather than having to create new files each with custom contents for the many development tools we now have, we can provision one file.
  • Having one format reduces cognitive load. I expect the contributors of my projects to know and understand JSON, but the same cannot be said for all of the other formats.
  • Having one file to say "this is where all of my configuration is for this project" makes a lot of sense. Dotfiles loose a bit of that because they're likely intermingled with editor files (e.g. .editorconfig) or other artefacts.
  • This may sound tenuous but: having one file for all configuration allows for better use of git blame and git bisect - because I can track any configuration changes in one file, and easily see how configuration changes have affected builds.
  • When using npm scripts it makes a lot of sense to see the script you're running (e.g. "lint": "eslint .") and the config that command uses (e.g. "eslintConfig": {...}) in the same file.

As @davidtheclark pointed out, most of the currently popular tools already allow for a JSON config. I'll also add Browserify into that mix. Currently a suite of projects I'm working on only have two non-code files in the project root: .editorconfig and package.json, as I've mentioned, semantically this is "generic editor settings" and "project configuration". To give you an example of one such config:

{
  "name": "...",
  "version": "...",
  "//": "...etc",
  "main": "index.js",
  "files": [
    "*.js",
    "*.es6",
    "*.css"
  ],
  "browserify": {
    "transform": [ "babelify" ]
  },
  "babel": {
    "stage": 0,
    "loose": "all",
    "compact": false
  },
  "eslintConfig": {
    "extends": [ "strict", "strict-react" ]
  },
  "watch": {
    "test": [ "./*.js", "package.json" ]
  },
  "pre-commit": [ "lint" ],
  "config": {
    "lint_opts": "--ignore-path .gitignore --ext .es6",
    "compile_opts": ". -d . -x .es6",
  },
  "scripts": {
    "lint": "eslint $npm_package_config_lint_opts .",
    "prepublish": "babel $npm_package_config_compile_opts",
    "pretest": "npm run lint",
    "watch": "npm-watch",
    "//": "...etc"
  },
  "//": "...etc"
}

@keithamus you may be interested to know that you can force a rc file to use JSON. you can pass in JSON.parse as a "custom parser" (see docs) and then ini etc, won't be supported in your application.

For the record, I would like to say that I do not consider eslint to be a model that rc would like to emulate. eslint has a whole configuration language. CSS would be a fairer comparison for eslint than a humble configuration file.

Can you tell me more about your specific application, and what sort of thing it configures?
It's hard to make judgements from only abstract details and I'll gain a lot more insight if I understand how you actually use it.

@dominictarr To be honest, I'm not here because I use your lib (thats not a dig, I just haven't found a need for it, but I'd definitely use it if I were making a cli tool that took a config 😉). I'm here as a consumer of stylelint that wanted stylelint to be configured via package.json - for the reasons above.

I think saying eslint is a configuration language is not entirely accurate. It is really just a set of toggles for a set of rules. Granted, there's a load of rules, which makes eslint look like a bit of a behemoth to configure. However, stylelint is the same - in fact the configuration patterns for both is almost identical!

The way I'd like to configure stylelint (remember, I'm a user here, not the maintainer or even a contributor to stylelint), is the way I have configured eslint. Eslint allows shareable configs which means I can use a central package (in this case eslint-config-strict and eslint-config-strict-react) to do the bulk configuration of eslint, which is great because I use these configs across literally dozens of packages, and I just simply simply refer to it through the eslint config, as mentioned above:

  "eslintConfig": {
    "extends": [ "strict", "strict-react" ]
  },

Now, I could just have an eslint file that did the same:

{ "extends": ["strict", "strict-react"] }

But it's kind of a drag to have a whole file committed for one line of code, especially given my reasoning above. Currently, to set up stylelint, the single file is what I'd have to do:

{ "extends": ["my-stylelint-shareable-config"] } 

But, again, this is a drag. The .stylelintrc would be the only rc file in my projects, everything else uses package.json, and that is great for all of the reasons I mentioned above.

Ultimately, if I'm totally ruthless about it, as a user I don't care where the code lives that means I configure stylelint through my package.json, just so long as I can configure stylelint through my package.json. Having said that, it seems like the benefit of your project (for your consumers, of who I am not) is that it is a drop in, standard way for cli tools to be configured. Every one of those should take configuration from package.json (and any that I use that don't will certainly get an issue filed to fix that), it seems to make sense for your rc package to do that in a centralised, standardised manner.

@dominictarr : I thought of submitting this issue to rc instead of building the functionality into stylelint specifically because I figured there were only two real problems to solve when trying to use a package.json property for configuration, and both of those are problems that rc deals with:

  • Searching for package.json file(s) with [appname]Config in the same places that rc currently searches for .[appname]rc files.
  • Merging whatever [appname]Config values that are found with other configs that are found -- again in the same way that rc currently does for .[appname]rc files.

If you think that exceeds the scope of rc, I can't really argue with that. But if you're trying to decide whether or not it's useful to be able to put rc-style configuration directly into package.json, I think there's really no arguing: plenty of users do find it useful -- for reasons @keithamus has outlined and maybe others -- enough so that those JS libraries all decided to include that feature.

Can you tell me more about your specific application, and what sort of thing it configures?

For stylelint users create configuration objects that look something like this:

{
  "rules": {
    "block-opening-brace-space-after": [2, "always-single-line"],
    "block-opening-brace-space-before": [2, "always"],
    "color-no-invalid-hex": 2,
    "comment-empty-line-before": [2, "always"],
    "declaration-bang-space-after": [2, "never"],
    "declaration-bang-space-before": [2, "always"]
  }
}

Hope that helps clarify.

@davidtheclark @keithamus I still don't feel you really answered my question. I want to explain your needs in a concrete way. For example, suppose you where doing a wood working project, and asked my advice on how to waterproof the boards? Easy, I'd say, paint on some expoxy thinned down with acetone. Expect, turns out, I normally build boats, and you are just building a dog house. Sure, it's gonna be out in the rain, but it's not gonna be submerged. Regular house paint would probably be completetly fine just on it's own. But I don't know that, so I give you the wronge type of advice.

I don't use a linter. I'm not gonna try to talk you out of it, but if I could really imagine why you'd want to do that, then maybe I would -- consequently, you need to spell out in some detail how this would help, and how you'd use it. I set a very high bar for contributions to rc. That is what has kept it minimal and focused.
That is not to say that I have not accepted pull requests - I have. But those pull requests need to make a strong case for their inclusion.

I'm currently feeling that package.json config is a maybe. There may be some strong cases for it, but others maybe not. That doesn't mean it can't be merged, but it needs to be thought through.

For example, I can think of some cases where it would be much better to have a dot file - a text editor. You might still want some local config, but the project doesn't itself depend on purely local config. You shouldn't require that someone use your text editor to work on your project. (there are at least 2 text editors that do use rc: https://www.npmjs.com/package/slap and https://www.npmjs.com/package/hipster)

It seems that in-package config makes more sense when it's some sort of build or testing tool?

@dominictarr yes, absolutely. In-package config makes more sense when its a build or testing tool. Some build tools are, for example: Babel, Browserify. These both offer in-package config. Some testing tools (to which I include linting) are: mocha, eslint, jscs, stylelint. Most of these offer in-package config (mocha doesn't as it has a very small set of options which are typically tweaked via command line).

Stylelint (the upstream package for which this issue was filed for) does not (yet) support in-package config. It would like to - or at the very least I would like it to. It has two options to achieve this goal: write code to support this feature, or have an upstream package write code to support this feature. Stylelint's existing configuration loader, rc, seems like a good candidate to house this code considering it handles all of Stylelint's configuration loading (to my knowledge - once again, not a contributor, just a user).

I'm far from an expert on these matters, and I'm sure you have more realistic figures, but I would hazard to guess, of all of the dependants on rc, the majority fit into the category of "project related build or testing tool". I guess you could broaden that category to include project based tools like jsfmt, that don't quite fit the description of test or build tool.

As you suggest, dot files, in my opinion, are good for tools which the project does not depend on, but could still be useful for tooling which helps developers, such as .sublime-project, or .editorconfig files or .idea folders (IntelliJ). Other tools, which the project depends on to function, in my mind fit better in the canonical package.json - which is a single tome of everything the project needs to work (other than the source code).

Thanks @keithamus. I'll just reinforce that a linter like stylelint or a testing tool is often a mandatory part of a project's build process: contributors or CI cannot build unless they have the dev dependency installed and access to the relevant configuration. So if the distinction we're drawing is between user-specific-configuration and project-specific-configuration, linter config would often fall under the project- category. (That said, you might also have a user-configuration for your linter at e.g. ~/.stylelintrc, just to help you write code the way you like to write it even when a specific project doesn't itself enforce conventions.)

Hmm, so one thing that rc does, is handles a chain of configuration - you can have system config, user config, project config, and config that applies to a single process via env vars or cli args. I havn't really considered build tool specific config yet.

But, since the goal is to have everything you need for that project to build inside the package.json then you probably don't want to merge that with higher level configuration files... So, maybe rc is not the right configuration loader for this sort of config at all? maybe you could still use rc for incidential configuration that doesn't affect the actual build process - but stuff that is related to the build specifically could be loaded from the local package.json

ps. by the way, the npm website lists dependant packages, and if you click through all the pages of packages which depend on rc you'll see there are over 300... I did only a cursory survey but it looks like most arn't build tools.

That seems true to me. If I were working on a project that had a specific linter configuration that needed to pass before I could push, I wouldn't my own local linter config at ~/.linterrc to confuse me by adding extra apparent requirements.

With stylelint I was actually wondering whether there was much value in merging configs --- why not just look up the file tree and stop with the first one it finds (to avoid the confusion described above)? Same would go with package.json configs. What do you think @keithamus?

If stylelint weren't going to do a full file-tree search-and-merge, it wouldn't want to use rc --- would instead just do its own looking.

@jeddy3: Wonder if you have input about this search-and-merge business in stylelint (or build/test/lint tools generally).

Thanks for the discussion @dominictarr. I started https://github.com/davidtheclark/load-multiconfig to implement the configuration loading features that I was looking for that are a little outside of rc's domain.

@davidtheclark thanks.

Let the record show that although the suggestion looked reasonable, once we actually discussed the features in the context of the application in which they would be used it was realized by all that the change was out of scope of this module.

I had the same need and created a solution with https://github.com/GarthDB/config-attendant