Skip to content

Proposal: 'configurator' field in package.json #215

Closed
@gaearon

Description

@gaearon

tl;dr

Add a field to package.json that tells ESLint, Flow, other tools, where to look for configs.
Useful for zero-conf opinionated packages like create-react-app and standard.

  // ESLint will look for react-scripts/config/.eslintrc
  // Flow will look for react-scripts/config/.flowconfig
  "configurator": "react-scripts"

As a community, we’ve mostly solved the problem of nested, extendable configuration. ESLint does this really well, Babel also does this.

However, I think that this project gaining 4k stars in 4 days proves that there is a market for non-configurable opinionated tools hiding dependencies from the user. I’d like to give props to standard for being one of the first tools demonstrating this is a viable model (it doesn’t matter whether I agree with its choices).

The problem I‘m starting to see is that many amazing tools, like ESLint and Flow, don’t currently play very well with this approach out of the box.

Problem

ESLint

ESLint is very flexible but if you want IDE integration, you need to tell it where your config is.
This is why our otherwise zeroconf package.json now has to look like this:

{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "react-scripts": "0.1.0"
  },
  "dependencies": {
    "react": "^15.2.1",
    "react-dom": "^15.2.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "./node_modules/react-scripts/config/eslint.js" // 😁
  }
}

Yes, one extra field is not a high price, but it is already a bit frustrating that we have to hardcode ESLint there. We would like to treat it as an implementation detail, to the extent that it is reasonable. (e.g. eslint-ignore comments are fine, but putting paths to configs into user’s package.json is annoying)

Also, it doesn’t even solve the problem completely because ESLint will look for plugins relative to the project root, so any transitive plugin dependencies won’t be discovered with npm 2 (or in some cases, admittedly not likely with our tool, with npm 3). You can find the discussion about this in eslint/eslint#3458.

Flow

Flow poses a similar problem for us, although in a more unfortunate way. We can’t even tell Flow to look at a different config. When you run flow init, it creates .flowconfig in the project root, and that’s it. This is reasonable if the user plans to change it (which seems necessary for lib integrations), but we can’t even influence the default generated config in any way. And unfortunately, for a number of reasons, the default generated config won’t work for create-react-app.

If you want Flow to work, you need to replace the config generated by flow init with this one:

[libs]
./node_modules/fbjs/flow/lib

[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable

module.name_mapper='^\(.*\)\.css$' -> 'react-scripts/config/flow/css'
module.name_mapper='^\(.*\)\.\(jpg\|png\|gif\|eot\|svg\|ttf\|woff\|woff2\|mp4\|webm\)$' -> 'react-scripts/config/flow/file'

suppress_type=$FlowIssue
suppress_type=$FlowFixMe

And we don’t want to do this automatically because:

  • Many people don’t currently use Flow.
  • Our philosophy is to not generate a ton of configs in the user folder.
  • We want the config to be compatible with the current version of react-scripts, so if we generate Flow config before the person actually starts using Flow, by the time they start using it, their checks might fail because something changed on our side.

Solution?

I don’t know if there is a nice solution to this, but here is my proposal. This is how I want package.json to look:

{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "react-scripts": "0.1.0"
  },
  "dependencies": {
    "react": "^15.2.1",
    "react-dom": "^15.2.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "eject": "react-scripts eject"
  },
  "configurator": "react-scripts" // 😀
}

ESLint would then use react-scripts/config as the base directory for its regular config/plugin search mechanism. So it would look for .eslintrc, package.json, etc, in that directory, and resolve plugin modules relative to it. Since ESLint wants to stay configurable, it will also look for overrides in the current folder (and use its regular override mechanism), but this would just add configurator as a first-class citizen for providing configuration.

Similarly, Flow would look into that project when running flow init, and if it finds .flowconfig in react-scripts/config, it would use that instead of its default config. This gives us a way to own the default configuration even if the user is on their own. In the future, Flow could support merging ${configurator}/config/.flowconfig with local .flowconfig—similar to how ESLint would work if it implemented this feature.

This would benefit an ecosystem by fostering development of opinionated presets of preconfigured tools. If you’re a tool, honor configurator in package.json and try reading configuration from ${configurator}/config in addition to your normal lookup paths. If you’re a preset author, you can fork react-scripts or any other configurator, make some changes to it, and keep it simple to adopt your preset: just swapping configurator is all end users need to do.

What do you think? Am I missing something?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions