A better Prettier CLI and config file
See original GitHub issueI would like to present a completely new take on Prettier’s CLI. Let’s restart from scratch. This isn’t a complete spec or even fully thought through yet. But I’ve been having this on my mind for a long time and wanted to get something out there for discussion.
Goal: Make it easy to do the right thing, and hard to do the wrong thing. Simplify everything! No magic! More explicitness! But still easy to use.
This is a bad thing:
{
"scripts": {
"prettier": "prettier --single-quote --write \"src/**/*.{js,css}\" \"docs/**/*.md\""
}
}
Why?
- Editors won’t know which options to use, and which files to work on.
- How do you run that with
--check
for CI? - What about git hooks or lint-staged?
- What if you want to run
prettier
on a couple of files from the command line for a one-off task? - It is annoying to write those globs.
Globs and --foo
-style options for formatting are anti-patterns. I suggest we get rid of them!
Instead, imagine having this:
{
"scripts": {
"prettier": "prettier format",
"prettier:check": "prettier check"
}
}
Nice, huh?
prettier
A subcommand is required. Print help.
prettier format [PATH]...
Format files and overwrite them on disk. Like today’s --write
, more or less.
Passing no paths is a shortcut for prettier format .
.
If a PATH points to a file, that file is formatted. If the file is ignored a note about that is printed. (We could have a --force
flag to override.) If the file type isn’t something that Prettier handles, an error is printed.
If a PATH points to a directory, Prettier will automatically find all files to work deeply and format them. Which files exactly? More on that later.
Regarding the output, I think we can take inspiration from black. Let’s be minimal and print stuff like “✨All files formatted with Prettier!” and “✨Formatted 5 files.”
Who will use this?
- People who for whatever reason don’t run Prettier in their editor.
- After adding Prettier to a project, or changing an option.
- After a CI failure, you can run
prettier format
to fix everything. - After a big find-and-replace refactor in
src/components
you can runprettier format src/components
to fix formatting (without having to run on the entire project). - git hooks.
- lint-staged (it passes absolute paths to tools).
Note: I think we should ditch built-in support for globs altogether. They’re not needed! If you need globs for a one-off command, then it’s up to you to get a good shell for yourself and use its globs.
prettier check [PATH]...
Like prettier format
, but only checks if files are already formatted or not. Like today’s --check
.
Who will use this?
- CI.
- Git hooks and lint-staged could alternatively use this instead of
prettier format
.
prettier stdio FILE
Run Prettier over stdin and stdout.
The FILE argument is required, so that Prettier knows what file type the content is, and if the file should be ignored or not.
If FILE exists, prettier stdio some/file.js
is a shortcut for prettier stdio some/file.js < some/file.js
.
If FILE is ignored, or isn’t supported by Prettier we should use exit codes and stuff to let the caller know.
Who will use this?
- vim-prettier, and other editors that shell out to format.
- People who occasionally use Prettier as a unix tool.
prettier stdio tmp/index.min.js | grep foo
.
prettier.json
Prettier advocates for few options, but our configuration is super complicated! I think we should take a step back and re-think here.
Let’s have one config file to rule them all – prettier.json. A prettier.json should be required for Prettier to run – this makes Prettier opt-in. For every file Prettier formats, there must be a prettier.json next to it or up the file tree. Only the first found prettier.json is used – there’s no cascade.
Making Prettier opt-in would make it super clear that a project is using Prettier. For example, it would help editor integrations. I hate it when my editor changes an entire file on save when I just wanted to make a quick edit in somebody else’s non-Prettier project.
A --force
flag could be used for one-off formatting of files with no prettier.json. That would use the default options. --config ~/foo/prettier.json
could use a specified one.
prettier.json should specify:
- Options. Global ones, as well as options per language. Not per file. Per language. This allows having double quotes in HTML, but single quotes in JS – even inside
<script>
tags in HTML. Some options can only be set per language and not globally – such as which parser to use. - File extension mappings. You should be able to map
.foo
to be handled as JSON, for example. When runningprettier format .
, Prettier looks for all files with known file extensions. - What files to ignore. An array of gitignore-style patterns. And the possibility to link to ignore files, such as .gitignore and .eslintignore. Or .prettierignore or .whateverignore if you want! Explicit and no magic. Ignore
*.html
to opt out of HTML formatting, for example. - Anything you’d ever need to configure, so you don’t need CLI flags. prettier.json should be the source of truth so that other tools know what to do.
We should continue supporting "@company/prettier-config"
and similar to load the config from an npm package.
prettier init
could be an interactive command to help create a prettier.json. For most people, it will just contain {}
(an empty object), though. For example, it could ask which languages your are/aren’t interested in formatting (based on what files we find on disk) and help set up ignore patterns. And we could detect .gitignore and .eslintignore and ask if we should link to them.
Not sure if we need overrides
.
Some options could take a "editorconfig"
as a value. If so, we’ll look for .editorconfig to figure out what value to use. No magic!
What about YAML or TOML or whatever instead of JSON? Keep it simple, I’d say! prettier.json should ideally be so short and simple that we don’t need a fancy language to write it.
{
"useTabs": true,
"javascript": {
"singleQuote": true,
"parser": "babel-flow"
},
"fileExtensions": {
".foo": "javascript"
},
"ignore": [
"*.html",
"/legacy/",
{ "include": "./.gitignore" },
"!*.config.js"
]
}
Open questions
--range-start
,--range-end
and--cursor-offset
. Not sure how they fit in yet.--insert-pragma
and--require-pragma
. I’ve never used these myself so I’m not sure.--file-info
and--support-info
. Not sure these are going to be needed anymore.- Many other little command line options…
Issue Analytics
- State:
- Created 4 years ago
- Reactions:60
- Comments:26 (18 by maintainers)
Wow!
config
→plugins
works indeed and this trick is also compatible with Yarn PnP / v2! Terrific! I just tweaked my personal shared Prettier config, updated the config package in https://github.com/kachkaev/njt/pull/29 and Prettier picked the right plugins! I did not have to add them as the project dependencies, having@kachkaev/prettier-config
was enough! 🕺Perhaps, we could improve docs to help Yarn PnP / v2 Berry folks with their shared config? Lack of
node_modules
in such setups makes plugin autodiscovery unavailable, so settingconfig
→plugins
becomes only working solution.+1
I’ll mention that in a large monorepo, we generally prefer for each project to have its own local tooling config files. In most cases the config will be a single line that
$extends
from a boilerplate"@company/foo-config"
, with occasional extra options for certain projects. This makes it easier to move projects around between folders/monorepos without worrying about coordinating changes in a centralized config file. It also reduces the overhead for updates, since modifying a centralized file in a large monorepo typically requires extra approvals/signoffs that can slow people down.At least, this was certainly the case for files like
tsconfig.json
,eslintrc.js
,jest.config.js
, etc. With TypeScript/ESLint we also had to worry about different projects using different versions of the tool/plugins, essentially requiring the tool to be invoked separately in each folder. The Prettier config /might/ avoid this concern, since migrating to a new Prettier release should require no human effort. So maybe Prettier’s CLI can always be invoked once for an entire monorepo. I haven’t used it enough to have a good sense of this yet.Just something to think about in your design discussion.