Mathias Osterkamp

Specialist – focus development Microsoft technology stack

SPFX 2019 eslint and prettier

How to use a state of the art formatter

For our current projects we used tslint and suffered for some problems. First main concern was, that VSCode was not able to give full feedback about code problems. Later on execution of gulp build all errors where thrown. Furthermore we got some conflicts with prettier solution and it was a hell to understand the current used rules from tslint. Also the tslint performance was very awful. We know it is deprecated for a while source and now its time to get rid of it.

We decided to move to current state of art eslint and there was a good starting point from Sergei Sergeev. source. From my point of view we cannot come alone with the eslint rule set, we need a good code formatter solution, too. Luckily there is a good solution with prettier, it works with a combined rule set. Here i will introduce a full demo solution for SPFX 2019 with react.

You can find the source code here.

ESLint and prettier motivation

I found a really god explanation from Joel Reis, why it makes sense to use these tools together

When building apps, it’s important to have a good setup of automated and manual tools that ensures the best standards and code quality. Each project must have a linting tool to fulfill these needs. Both tools are configurable and they work well together, each one having a different linting responsibility between programming and stylistic errors, making it easy to catch errors.

ESLint is one of the most used linting tools and there is a reason for it. Highly configurable, it has a huge adoption from the community having hundreds of open-source configurations and plugins. It allows the configuration of several options like coding rules, environments, parser options, extend configurations, and use plugins.

On one hand, ESLint is responsible for checking against programming errors, on the other hand, we have Prettier an opinionated code formatter capable of finding any stylistic errors. It comes with some code style standards and is also easy to configure. It’s easy to integrate with ESLint and has Code Editor extensions that can format the code on save!

“Joel Reis” source

Prerequisite

For SPFX 2019 we have normally to use Node version 8, this is not more possible with eslint and we have to upgrade to Node version 10. The good news is, we had very good experiences to using this newer version and no major issues about that. So just check your node version.

node -v
v10.22.0

Packages

We need packages for eslint and prettier in our package.json under devDependencies section, we also need a fresh typescript version:

Package.json

    "@typescript-eslint/eslint-plugin": "^4.22.0",
    "@typescript-eslint/parser": "^4.22.0",
    "eslint": "^7.24.0",
    "eslint-config-prettier": "^8.2.0",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-react": "^7.23.2",
    "gulp": "~3.9.1",
    "gulp-eslint": "^6.0.0",
    "prettier": "2.2.1",
    "typescript": "3.6.5"

Or with command line

npm i --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-react gulp gulp-eslint prettier typescript@3.6.5

ESLint config

Our main configuration goes to .eslintrc.json. We register on extensions 4 rule sets, you will find several specific details about your rules:

{
  "root": true,
  "env": {
    "browser": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": ["react", "@typescript-eslint", "prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-use-before-define": "off",
    "@typescript-eslint/no-use-before-define": ["error"],
    "no-multiple-empty-lines": "error",
    "no-multi-spaces": "error",
    "no-var": "error",
    "@typescript-eslint/ban-ts-comment": "off",
    "semi": ["error", "always"]
  },
  "ignorePatterns": [
    "*.js",
    "**/coverage",
    "**/dist",
    "**/etc",
    "**/lib",
    "**/sharepoint",
    "**/lib-amd",
    "**/lib-commonjs",
    "**/node_modules",
    "**/temp",
    "**/*.scss.ts"
  ],
  "settings": {
    "react": {
      "version": "detect"
    }
  }
}

extends

plugins

Just register our functions

rules

Here you can override rules by your own project specific settings. For bigger projects it maybe makes sense to create an own rule set.

ignorePatterns

We like to exclude everything what is not our source code.

settings

We need for react a specific setting, to detect our react version.

Integration

Now we like to replace our default tslint build process with custom eslint task in gulpfile.js. We create a new prebuild Task for eslint and disable tslint. Futhermore we need to register the current typescript version.

"use strict";
const build = require("@microsoft/sp-build-web");
const eslint = require("gulp-eslint");
const typeScriptConfig = require("@microsoft/gulp-core-build-typescript/lib/TypeScriptConfiguration");
const buildtypescript = require("@microsoft/gulp-core-build-typescript");

typeScriptConfig.TypeScriptConfiguration.setTypescriptCompiler(
  require("typescript")
);
buildtypescript.tslint.enabled = false;

const eslintSubTask = build.subTask(
  "eslint",
  function (gulp, buildOptions, done) {
    return (
      gulp
        .src(["src/**/*.{ts,tsx}"])
        // eslint() attaches the lint output to the "eslint" property
        // of the file object so it can be used by other modules.
        .pipe(eslint())
        // eslint.format() outputs the lint results to the console.
        // Alternatively use eslint.formatEach() (see Docs).
        .pipe(eslint.format())
        // To have the process exit with an error code (1) on
        // lint error, return the stream and pipe to failAfterError last.
        .pipe(eslint.failAfterError())
    );
  }
);

build.addSuppression(
  `Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`
);
build.rig.addPreBuildTask(build.task("eslint-task", eslintSubTask));

build.initialize(require("gulp"));

Prettier

We need a basic configuration file .prettierrc.yaml to set our prettier. You can find here the most important options.

{
  "singleQuote": true, # use quotes instead of double quotes
  "jsxSingleQuote": true, # single quotes for jsx
  "jsxBracketSameLine": true, # closing bracked in same line
  "arrowParens": "always", # use bracked in arrow functions
  "printWidth": 120, # preferred width (not same as eslint max-length)
  "tabWidth": 2, # number of spaces
  "useTabs": false, # spaces instead of tabs
  "semi": true, # semicolons at ends of statement
  "trailingComma": "none", # removes trailing comma inside objects
  "htmlWhitespaceSensitivity": "strict", # whitespace inside html is significant
  "endOfLine": "auto", # correct end of line
}

And we need to ignore some files .prettierignore:

# Ignore artifacts:
sharepoint
lib
node_modules
temp
dist

Plugins for VSCode

VSCode extensions are helping us a lot to make the integration with eslint and prettier. You will find these two extensions here:

To work i recommend to update your settings.json inside .vscode folder:

  "editor.defaultFormatter": "esbenp.prettier-vscode", #enable prettier as default formatter
  "prettier.configPath": "./.prettierrc.yaml", #set config file
  "editor.formatOnSave": true #set format on save

Run

Now you also can format your complete project by running just one line

PS C:\daten\git\spfx-2019-prettier> npm run format

> spfx-2019-prettier@0.0.1 format C:\daten\git\spfx-2019-prettier
> npx prettier --write . --config .prettierrc.yaml

.eslintrc.json 105ms
.prettierrc.yaml 85ms
.vscode\extensions.json 9ms
.vscode\launch.json 47ms
.vscode\settings.json 13ms
.yo-rc.json 21ms
config\config.json 27ms
config\copy-assets.json 12ms
config\deploy-azure-storage.json 15ms

Furthermore you can also use the eslint fix option.

PS C:\daten\git\spfx-2019-prettier> npm run fix

> spfx-2019-prettier@0.0.1 fix C:\daten\git\spfx-2019-prettier
> npx eslint src/ --fix --ext .ts --ext .tsx


C:\daten\git\spfx-2019-prettier\src\webparts\helloWorld\components\HelloWorld.tsx
  6:75  error  Don't use `{}` as a type. `{}` actually means "any non-nullish value".
- If you want a type meaning "any object", you probably want `Record<string, unknown>` instead.
- If you want a type meaning "any value", you probably want `unknown` instead.
- If you want a type meaning "empty object", you probably want `Record<string, never>` instead  @typescript-eslint/ban-types

✖ 1 problem (1 error, 0 warnings)

npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! spfx-2019-prettier@0.0.1 fix: `npx eslint src/ --fix --ext .ts --ext .tsx`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the spfx-2019-prettier@0.0.1 fix script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:

You should fix your errors:

export default class HelloWorld extends React.Component<IHelloWorldProps, unknown> {

And you will run a pretty fast build with everything together:

PS C:\daten\git\spfx-2019-prettier> npm run package

> spfx-2019-prettier@0.0.1 package C:\daten\git\spfx-2019-prettier
> gulp clean && gulp bundle && gulp package-solution

Build target: DEBUG
[20:56:49] Using gulpfile C:\daten\git\spfx-2019-prettier\gulpfile.js
[20:56:49] Starting gulp
[20:56:49] Starting 'clean'...
[20:56:49] Starting subtask 'clean'...
[20:56:49] Finished subtask 'clean' after 25 ms
[20:56:49] Finished 'clean' after 32 ms
[20:56:50] ==================[ Finished ]==================
[20:56:50] Project spfx-2019-prettier version: 0.0.1
[20:56:50] Build tools version: 3.2.7
[20:56:50] Node version: v10.22.0
[20:56:50] Total duration: 6.41 s
Build target: DEBUG
[20:56:58] Using gulpfile C:\daten\git\spfx-2019-prettier\gulpfile.js
[20:56:58] Starting gulp
[20:56:58] Starting 'bundle'...
[20:56:58] Starting subtask 'configure-sp-build-rig'...
[20:56:58] Finished subtask 'configure-sp-build-rig' after 25 ms
[20:56:58] Starting subtask 'pre-copy'...
[20:56:58] Finished subtask 'pre-copy' after 42 ms
[20:56:58] Starting subtask 'eslint'...
[20:57:04] Finished subtask 'eslint' after 5.61 s

From now on you have a good boilerplate to use eslint and prettier together on your spfx 2019 projects.