DEV Community

Cover image for Setting up ESLint to work with new or proposed JavaScript features such as private class fields.
George Griffiths
George Griffiths

Posted on • Updated on • Originally published at griffa.dev

Setting up ESLint to work with new or proposed JavaScript features such as private class fields.

Some members in my team this week wanted to make use of Private class fields in a NodeJS server. This proposal is currently shipped in Chrome, Edge, Firefox and NodeJS, with Safari notably absent. In this instance, we wanted to get them working for a backend server application, so support since Node 12, we're good to go, or so I thought, turns out linters aren't always here to save you time.

I summed my feelings on the whole process of figuring this out on Twitter.

Feel free if you want to skip ahead past the story, and to head right to Configuring ESLint.

For this article, i'll be using this code example of using Private class fields, the code used is irrelevant.

export class Animal {
    // this is a private class field!
    #noise = '';

    constructor(noise) {
        this.#noise = noise;
    }

    makeNoise() {
        console.log(this.#noise);
    }
}
Enter fullscreen mode Exit fullscreen mode

The first issue we hit when writing this new code, was of course, the linter started failing, so off to Google we went!

Struggling to finding a solution

A quick search for: eslint private class fields you will most likely end up in this Stack Overflow issue.
It will tell you that ESLint does not support experimental stage 3 features, which is indeed correct, and to:

npm install eslint babel-eslint --save-dev
Enter fullscreen mode Exit fullscreen mode

and to update your ESLint config file over to use:

  "parser": "babel-eslint",
Enter fullscreen mode Exit fullscreen mode

Sadly, it seems this is not an entire solution, it seems to make a couple of assumptions:

  • You have babel-core installed
  • You have a babel configuration file set up that knows how to transform code with a preset.
  • Its possible that when the answer was posted babel-eslint did indeed solve al the problems.

If you are in a NodeJS server module, a lot of these assumptions are probably not met.

If you are a developer that has never had to use Babel because you work on the backend or on a build-less frontend, all this stuff can get daunting very fast.

Additionally, it seems since this answer was posted, things have moved on and the recommended parser now lives at:

    "parser": "@babel/eslint-parser",
Enter fullscreen mode Exit fullscreen mode

The ESLint website does have some information about the Past, Present and Future of the babel-eslint over on its website.

Finding this information out was a bit of an adventure, and even on the official babel or ESLint website, it's super unclear that you need to set up a babel config, and then still, what to put in it. I'm pretty sure the only reason I managed to figure it out in the end was because i'm familiar with the mess that is configuring Webpack, Jest and Babel.

Configuring ESLint

Let's get our new syntax working!

First off, lets do the npm install dance:

npm i eslint @babel/core @babel/eslint-parser @babel/preset-env -D
Enter fullscreen mode Exit fullscreen mode

It's nice to set up a linter task in your package json so you can run npm run lint

  "scripts": {
    "lint": "eslint ./"
  },
Enter fullscreen mode Exit fullscreen mode

I'm opting to use @babel/preset-env because it has an easy way to enabled proposals that are shipped in Browsers/Node. Other presets/plugins are available.

Next we need to construct an .eslintrc file.
You can generate one using: ./node_modules/.bin/eslint --init or just copy this starter:

{
    "env": {
        "browser": true,
        "es2021": true,
        "node": true
    },
    "extends": "eslint:recommended",
    "parser": "@babel/eslint-parser",
    "parserOptions": {
        "ecmaVersion": 12,
        "sourceType": "module"
    },
    "rules": {
    }
}
Enter fullscreen mode Exit fullscreen mode

Now if you run npm run lint You will hit the following error:

/path/to/code/Animal.js
  0:0  error  Parsing error: No Babel config file detected for /path/to/code/Animal.js. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files

 1 problem (1 error, 0 warnings)
Enter fullscreen mode Exit fullscreen mode

It's telling you we need to configure babel for @babel/eslint-parser to work.

Lets set up a babel config file.

Create a file called .babelrc and populate it with:

{
  "presets": [
    ["@babel/preset-env"]
  ]
}
Enter fullscreen mode Exit fullscreen mode

You can read about @babel/preset-env on the Babel website.

Now if you run npm run lint again you will hit the final error:

/path/to/code/Animal.js
  2:4  error  Parsing error: /path/to/code/Animal.js: Support for the experimental syntax 'classPrivateProperties' isn't currently enabled (2:5):

  1 | export class Animal {
> 2 |     #noise = '';
    |     ^
  3 | 
  4 |     constructor(noise) {
  5 |         this.#noise = noise;

Add @babel/plugin-proposal-class-properties (https://git.io/vb4SL) to the 'plugins' section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-class-properties (https://git.io/vb4yQ) to the 'plugins' section to enable parsing

✖ 1 problem (1 error, 0 warnings)
Enter fullscreen mode Exit fullscreen mode

You could proceed to add plugins for each of the proposals as the instructions say, alternatively you can opt to say I want all shipped proposals.

To do this change your .babelrc over to:

{
    "presets": [
      ["@babel/preset-env",
      {
        "shippedProposals": true
      }]
    ]
  }
Enter fullscreen mode Exit fullscreen mode

From the Babel docs: "set the shippedProposals option to true. This will enable polyfills and transforms for proposal which have already been shipped in browsers for a while."

If you are using Jest

If you are using Jest, it will automatically pick up .babelrc files, this might be problematic, as it will very helpfully start to try to transpile things like async/await, potentially leading you down even more rabbit holes. With really helpful messages like:

ReferenceError: regeneratorRuntime is not defined
Enter fullscreen mode Exit fullscreen mode

By dumb luck, i've been through the pain of this message many times, and knew exactly what was wrong, Jest was trying to transform the perfectly valid code.

It's almost 2021, and this is a server app, I certainly do not want to transpile async/await especially not in unit tests!

One way to work around this is to use a non-standard name for your .babelrc file e.g. .babel-eslintrc. There may be better solutions, but I certainly don't want Jest unnecessarily transforming code.

In your .eslintrc file you can update babelOptions to use a custom configFile

"babelOptions": {
    "configFile": "./.babel-eslintrc"
 }
Enter fullscreen mode Exit fullscreen mode

And there we go, Jest is now happy again because it's not using the Babel configuration.

Summary

All in all this was a lot harder than I thought it would be, my guess is that many people don't hit this issue because they happen to already have Babel configured. But in the case of backend dev, getting along happily, just trying to make use of a shipped JavaScript feature in a server, you can get dragged into the hellscape of frontend development tooling, and no one has fun there.

I hope this was a good read, if you feel like reading more of my work, please follow me on Twitter @griffadev, or get me a coffee if you feel like it ☕.

Oldest comments (1)

Collapse
 
lcrespilho profile image
Leonardo Lourenço Crespilho

Hi George.
Thanks for the article. I felt contemplated by your article and have the same feelings about babel, webpack and eslint configurations. It'is simply a mess.

About the jest ReferenceError: regeneratorRuntime is not defined error, I think I can help in some way. For me, adding "transform: {}" property on jest configuration did the trick - i.e., I was able to follow your article without renaming .babelrc or creating "babelOptions" in .estlintrc.

Regards.