Prettier 1.10: One Year of Prettier 🎂
Happy Prettier-versary! It's pretty incredible that Prettier was only released a year ago and has already seen such a massive adoption and great number of contributors. For this special release, we're going to do a small retrospective around the project.
It's also an exciting release in itself because Prettier now has partial support for .vue
files and the internals have been refactored such that there's a proper plugin API in order to support different languages!
Retrospective
Ever since gofmt came out in 2013, I (@vjeux) became obsessed with the idea of automatic formatting. I started seeing problems that would be solved by an automatic formatter all the time: arguing about coding style, having to format the code manually, difficulty to write codemod tools…
When last December there were not one but two people independently working on a JavaScript pretty printer, it was a sign! Pieter Vanderwerff built one in Reason based on Flow infrastructure and James Long in JavaScript. I played the role of a cheerleader by setting up the same test suite for the two projects as well as running their pretty printer on existing codebases and extracting lists of things that were not printed well.
Sadly, after the holidays were over, both of them had to go back to work on their real job :( I decided to step in and convince my manager to work on it full time. James had open sourced his project (Prettier) and I was more familiar with JavaScript so I decided that this was the one I would work on!
Here's a list of some of the things that worked well during this project.
Spend time on release notes
Release notes have an interesting property that they are very often shared, even if the content is terrible. Everyone seems to want to know that version 1.5.8 of some software has been released. This is a very good opportunity to also sell your project.
For Prettier releases, I spent a lot of time explaining the rationale behind all the changes and talked about some things that would likely have been written as a separate blog post (like this!). Plus, you're likely going to be lazy and find all the excuses in the world not to write that blog post, but you really want to publish that new version, so it's a good forcing function.
Clear decision-making process
Style rules are one of the most controversial subjects and at the same time a lot of decisions needed to be made. I tried to design a decision-making process that would let us make progress.
People find all sort of emotional arguments in order to convince you that their opinion on style is the best. We needed to have something purely rational so that even though it's not what people would prefer, they couldn’t argue against the methodology.
The best I could find was to count how many times each variant was used in the Facebook codebase. It’s easy for me to run and provide relative numbers (style A is used 5x more than style B) to convince people. If such a big codebase that has been written by thousands of people over the years has a winner, it may not be the best but is likely not going to be hugely controversial.
Not everything could be solved this way, some things didn't have a clear winner or it was not obvious how to come up with an algorithm to print it well. In those cases, it was important to have an escalation process with a single person making the decision at the end (me). This way we could make progress without needing to find a consensus which would have likely been very hard.
Open source as guinea pigs
A total pretty printer is one of those projects where you need it to be close to perfect to bring it to a big codebase like Facebook. The first impression is extremely important and it's very hard to get something that will be correct (it won't introduce invalid JavaScript or code that would behave differently) for all the edge cases.
My initial plan was to work on it alone in a cave hidden from the outside world for 6 months until I got everything just right and then get people to adopt it. What I didn't expect is that people in open source didn't have that same quality bar and started using it way earlier than that. The risks of using it on a side project are vastly different than on millions of lines of code at Facebook.
It ended up being very beneficial for the project to get feedback along the way until it was finally ready to be used at Facebook.
Open source as contributors
For the first 6 months of the project, I was working full time on it and ended up writing only half of the commits. This is pretty mind-blowing in itself! Not only was the throughput two times better on the project itself, but people ended up working on things that I wouldn't have spent time on like integrations with all the editors on earth, a fuzzer to find bugs, TypeScript support, infrastructure to support parsing multiple languages in the same file, and more.
Even though we don't use all those things at Facebook, many of them ended up being useful. CSS-in-JS support made it easy to get GraphQL fragments in template literals formatted. The large number of users was also a great way to surface obscure bugs that we would eventually hit and many different people chimed in to fix them.
The best thing is that I stopped working on the project full time 6 months ago and the project kept going under the leadership of @azz. I'd like to thank all the people that helped in various ways, this is so exciting to make history together!
Open source has been doing wonders for the project, delivering a ton of value for Facebook (almost all our JavaScript files are now pretty printed) and for the industry at large as seen by the huge adoption.
Tooling: Jest Snapshot Tests & Playground
All those contributions were possible because it was easy to report errors, contribute and review code. The two tools that were most impactful in this project were jest snapshot tests and the playground.
Snapshot tests are a wonder for a pretty printer. Adding new tests is dead easy, just create a file in the test folder with the code you want to format and run jest, voila! Anytime you change something, you can see how a bunch of examples would be printed differently, then it's up to you to decide if it's better or not. For a reviewer, it's also really nice, you can look at the before/after of all the things it changed. I've come to pay more attention to the snapshots than the actual implementation.
The playground is a really nice way to get to a repro or play with Prettier without having to install the development environment, being on the right branch... This has proven to be extremely valuable to get people to provide really good bug reports that are actionable.
Highlights
#3563) by @vjeux
Support for Vue Single File Components (There's a lot of demand for Vue SFC (#2097). We've introduced partial support for them: All the HTML is printed as is, but now the <script>
and <style>
tags are printed using Prettier.
To use it, just run prettier
on your *.vue
files!
#3536) by @azz
Prettier Plugin API (As Prettier for JavaScript has become stable, we've recently had contributors wanting to add new languages to Prettier, notably we had pull requests to add Python support and PHP support. We want to keep the core prettier
package portable and maintainable, but we also want to give people the opportunity to run Prettier on more languages. To that end we've introduced a plugin API! Prettier plugins can contribute parsers and/or printers to the Prettier formatter. They are treated as first-class citizens and can even contribute to the embedding support (e.g. formatting your language inside Markdown).
Using plugins is as simple as installing them via npm
/yarn
, and running Prettier as you usually would, there's no additional configuration to worry about!
There are three official plugins under development:
All three of these plugins are still under active development and aren't ready to throw production code at, but keep an eye on them as they're progressing!
If you're interested in helping build those plugins, check out their repository issues list or run it against your own code and start raising bugs! Similarly, if you're interested in building a new language in Prettier and need some help getting it off the ground, create an issue in the prettier repo and let us help you out.
Prettier's in-built languages have been refactored to be expressed in terms of the plugin API, this way we can guarantee going forward that the core API is generic.
Check the docs for more information.
Please note that as this is a new, significantly large feature, we're releasing this under a beta label in the documentation. While we don't anticipate anything significant, this means that we may make breaking changes in the next release.
Other Changes
TypeScript
#3580) by @azz
Support numeric separators (Numeric separators are a stage 3 ECMAScript proposal. Support has been added for them in TypeScript 2.7, and Prettier will now preserve them.
// Before
SyntaxError: ',' expected. (1:10)
> 1 | var a = 1_000_000_000;
| ^
2 | var b = 0b1101_0101_1001;
3 | var c = 0xAE_FE_2F;
// After
var a = 1_000_000_000;
var b = 0b1101_0101_1001;
var c = 0xAE_FE_2F;
Flow
#3449) by @duailibe
Print flow type annotation comments as comments (Flow type annotation comments are a nice way to get type checking without having to transpile your files. Before this release, Prettier, using the flow parser, would not print the type annotation as comments, making it impossible to use that feature. Now, Prettier will correctly detect if the type annotation was a comment and print accordingly for /*: ... */
type comments. Work is in progress for /*:: ... */
comments.
// Input
let foo /*: string */ = "a";
// Before
let foo: string = "a";
// After
let foo /*: string */ = "a";
#3444) by @duailibe
Print comment after arrow function params (If you are using the babylon parser, there are different kind of issues with flow comments where comments are moved around. One of them was fixed during this release. We're now getting closer to be able to support that feature.
// Before
const run = (cmd /*: string */ /*: Promise<void> */) => {};
// After
const run = (cmd /*: string */) /*: Promise<void> */ => {};
#3616) by @duailibe
Print parens in FunctionTypeAnnotation when arrowParens is "always" (In the last release we added a new arrowParens
option. One place were it was overlooked is in flow function type annotations. Now when arrowParens
is set to always
we will put parenthesis around single arguments.
// --arrow-parens always
// Before
type SomeType = {
something: number => number
};
// After
type SomeType = {
something: (number) => number
};
JSX
#3607) by @vjeux
Inline do expressions inside of JSX ("Do expressions" are a stage 1 ECMAScript proposal, and they're particularly useful in JSX, however the extra level of indentation isn't required, so we've inlined them into the expression container.
// Before
{
do {
// ...
}
}
// After
{do {
// ...
}}
#3641) by @duailibe
Prevent adding softline after arrow attribute with comments (This fixes a minor issue where an additional line break would appear in JSX attributes.
// --print-width 13 (for demonstration)
// Before
<span
attr={
// comment
() =>
true
}
/>;
// After
<span
attr={
// comment
() =>
true
}
/>;
()
(#3640) by @duailibe
Don't wrap JSX elements as attribute in The JSX spec has a hidden feature that allows passing an element as an attribute to another element and Prettier was adding parens around, causing a syntax error. This was fixed in this release. Note while some parsers are parsing this feature, none of the jsx transforms allow it. The first one that will support it will be Babel 7 which is currently in beta.
// Before
<Foo
content=(
<div>
<div />
</div>
)
/>
// After
<Foo
content=<div>
<div />
</div>
/>
SCSS
#3649) by @vjeux
Print comments inside of selector as is (Unfortunately the CSS parser we're using for selectors does not support comments. As such there was a case where comments where selectors could be moved onto lines with comments. This has now been fixed.
/* Before */
// Foo
.foo,
// Bar .bar {
display: block;
}
/* After */
// Foo
.foo,
// Bar
.bar {
display: block;
}
GraphQL
#3605) by @vjeux
Update GraphQL parser (The GraphQL specification is evolving and prettier now supports the three new features:
The ability to annotate types with string descriptions
"""
Type description
"""
type Foo {
"some description"
someProperty: String!
"""
some really
long description
"""
someOtherProperty: [String!]!
}
Omitting {}
when there is no content.
extend enum Site @onEnum
Being able to extend all the types
extend input InputType {
other: Float = 1.23e4
}
Markdown
#3611) by @ikatyang
Replace newlines with hardlines in multiparser (When embedding JavaScript inside a Markdown blockquote, the blockquote's >
character was removed. This has now been corrected.
<!-- before -->
> ````md
> <!-- prettier-ignore -->
> ```js
ugly ( code ) ;
```
> ````
<!-- after -->
> ````md
> <!-- prettier-ignore -->
> ```js
> ugly ( code ) ;
> ```
> ````
#3481) by @j-f1
Conform the quotes in Markdown link titles to the singleQuote option (Previously all link titles were quoted in "
, now it will respect the singleQuote
option. We'll use ()
-style quotes if the title has both '
and "
in it.
<!-- --single-quote -->
<!-- Before -->
[ref]: https://prettier.io "Prettier"
[other-ref]: https://example.com "Shakespeare's \"Romeo and Juliet\" is a famous play"
<!-- after -->
[ref]: https://prettier.io 'Prettier'
[other-ref]: https://example.com (Shakespeare's "Romeo and Juliet" is a famous play)
#3643) by @duailibe
Print imageReference with no alt in Markdown (We've fixed an edge case with image references with no alt text, which cause Prettier to crash.
<!-- before -->
TypeError: doc is null
<!-- after -->
![][logo]
[logo]: https://github.com/prettier/prettier-logo/blob/master/images/prettier-logo-light.png?raw=true
tabWidth
for list indentation (#3694) by @ikatyang
Respect We used to enforce 2-space for list indentation, now we take tadWidth
in to account.
<!-- --tab-width 4 -->
<!-- before -->
* one
* two
<!-- after -->
* one
* two
API
options
field to getSupportInfo()
(#3433) by @ikatyang
Add prettier.getSupportInfo().options
will now contain an array of the options that Prettier supports.
.editorconfig
up to the VCS directory (#3559) by @josephfrazier
Only search for It can be quite confusing to diagnose an issue where Prettier is formatting code differently between two people with the same repository. Because Prettier supports .editorconfig
files, and we searched all the way up to ~/.editorconfig
, this had the unintended side-effect of changing the way some people's code is formatted. We now only look up to the root directory of your project.
CLI
#3515) by @azz
Make all CLI logging go through a logger (The --loglevel
option wasn't respected in a handful of cases, now it is respected for all CLI logging.
# Before
$ prettier --loglevel silent --write test.js
test.js 91ms
# After
$ prettier --loglevel silent --write test.js
#3618) by @duailibe
Output files as-is if ignored (We've fixed up the behavior of the CLI when it comes to ignored files. Previously when a file was ignored by .prettierignore
, the CLI would output nothing. This affected editor integrations. The behaviour is now:
- With
--write
, don't read or write to the ignored file. - Without
--write
, read the ignored file and output as-is.
Editor Integrations
getSupportInfo()
(#3454) by @ardevelop
Add PostCSS extensions to Some editor integrations use Prettier's getSupportInfo
function to dynamically support all of Prettier's languages. This adds support for *.pcss
files.
getSupportInfo()
(#3496) by @thorn0
Add "JSON with comments" to Similarly, "JSON with comments" will also be detected.