23

Using Husky, I've set up my package.json with a precommit hook so that my JavaScript code is formatted using Prettier before every commit:

{
  "name": "prettier-demo",
  "scripts": {
    "precommit": "prettier --write **/*.js && git add ."
  },
  "devDependencies": {
    "husky": "^0.14.3",
    "prettier": "^1.8.2"
  }
}

This works fine, but there are two drawbacks:

  1. If I have a large project with thousands of JavaScript files, I have to wait for Prettier to process all of them, even if only a few have changed; this can take very long and gets on my nerves quickly when it is done with every commit

  2. Sometimes I want to stage just a couple of files for committing, leaving other changes out of the commit; because I do a git add . after running Prettier, all my changes will always end up in the commit

How can I run Prettier before every commit only on the files that have been staged, ignoring unstaged or unchanged files?

Patrick Hund
  • 19,163
  • 11
  • 66
  • 95

5 Answers5

31

You can do that using lint-staged:

Linting makes more sense when running before committing your code. By doing that you can ensure no errors are going into repository and enforce code style. But running a lint process on a whole project is slow and linting results can be irrelevant. Ultimately you only want to lint files that will be committed.

This project contains a script that will run arbitrary npm and shell tasks with a list of staged files as an argument, filtered by a specified glob pattern.

Install lint-staged and husky, which is required for pre-commit hooks, with this command:

npm install --save-dev lint-staged husky

Change your package.json as follows:

{
  "scripts": {
    "precommit": "lint-staged"
  },
  "lint-staged": {
    "*.js": [
      "prettier --write",
      "git add"
    ]
  }
}
Community
  • 1
  • 1
str
  • 42,689
  • 17
  • 109
  • 127
  • @PatrickHund I agree, but I also like your hands-on attitude :) You can still undelete your answer if you want. – str Dec 02 '17 at 17:53
  • No, that's fine. Another dev on my team had actually set the precommit up with lint-staged, but it didn't work properly, because he put "git add ." in the config instead of "git add". So I wrote that bash script, could have just removed the dot and be done with it – Patrick Hund Dec 02 '17 at 17:55
  • I did this with prettier-eslint, same exact syntax as answer. ```"lint-staged": { "*.{js,less,json}": [ "prettier-eslint --write", "git add" ] }, ``` – Jazzy Aug 03 '18 at 22:55
  • I have also seen a 'precise-commits' repo, stating it only prettifies the parts of file staged i.e. staged hunks and not the entire file. What do you think about this? I haven't tried it yet. https://github.com/nrwl/precise-commits – Amir Eldor Nov 01 '18 at 08:04
  • @AmirEldor I think that is a bad idea as this could lead to inconsistent formatting within a single file. – str Nov 01 '18 at 08:12
  • @str, true, however, it might prevent accidental merge conflicts if two people prettify the same file as a whole in two branches (unless git is smart about similar lines from two branches). Also, git annotate info would be preserved rather than having the same person annotated on the whole file after they prettify it. – Amir Eldor Nov 05 '18 at 19:30
  • @AmirEldor Git is smart enough to handle that without merge conflicts. I would rather have messy annotations than messy code. But that is up to you ;) – str Nov 05 '18 at 19:52
  • Is there a way to run linters only against newly created (added) files? https://stackoverflow.com/questions/56540244/linting-only-newly-added-files – karthikaruna Jun 11 '19 at 09:22
19

I found that just running:

prettier --write $(git diff --name-only --diff-filter d | grep '\.js$' | xargs)

was quite enough for my needs, just made an alias and used that.

kigiri
  • 2,952
  • 21
  • 23
  • 3
    that actually returned some results that had .js in the path. this worked better for me: git diff --name-only | grep '\.ts$' – Nitsan Baleli Nov 02 '20 at 13:22
  • 2
    also I just learned that this will fail when a file is removed. Prettier will try to format the removed file. To make it work use `prettier --write $(git diff --name-only --diff-filter d | grep '\.ts$' | xargs)` – Seega Aug 12 '21 at 14:49
  • Thanks @NitsanBaleli I updated the message to include your grep match fix and filter deleted fixes ! – kigiri Aug 21 '21 at 09:43
  • 3
    If you're working on a TypeScript project, this will match .js, .ts, .jsx and .tsx: `prettier --write $(git diff --name-only --diff-filter d | grep -e '\.[tj]sx\?$' | xargs)` – odinho - Velmont May 30 '22 at 17:42
  • Does anyone know if there's a way to make ```prettier --write``` pass if there is no file/dir/glob arguments supplied to it? In this situation this is caused by there being no output from the git diff command – lmb Feb 22 '23 at 21:20
6

If you don't want to add the devDependency lint-staged you can also do the same with a Bash script:

#!/usr/bin/env bash
# chmod +x this and save in your PATH. Assumes `prettier` is in your `devDependencies` already
BASE=$(git merge-base master HEAD) # change master to whatever your trunk branch is
FILES=$(git diff --name-only $BASE HEAD | xargs)

npx prettier --list-different $FILES

# Want eslint too?
# npx eslint --ignore-path=.prettierignore $FILES
SgtPooki
  • 11,012
  • 5
  • 37
  • 46
Max Ogden
  • 910
  • 8
  • 13
6

I use this package pretty-quick

add add a script in my package.json

"pretty-quick": "pretty-quick" 

under scripts {}

Then in my pre-commit hook under .husky/pre-commit

I add

npm run pretty-quick
  • Sadly, `pretty-quick` doesn't receiving any updates for several years and dosen't work with prettier >= 3.x.x – eXception Aug 30 '23 at 12:26
3

The prettier docs has a section on this.

I use pretty-quick

npx husky-init
npm install --save-dev pretty-quick
npx husky set .husky/pre-commit "npx pretty-quick --staged"
Bar Horing
  • 5,337
  • 3
  • 30
  • 26