my javascript setup

Contrary to a lot of “hardcore” programmers out there, I enjoy coding in Javascript quite a lot. Here are some things I do with my setup to make things nicer.

The gist of it is that I use eslint and tsc as a compilation layer, where tsc does type checking (with mostly no annotation) and eslint prevents a lot of anti-patterns and some oversights. Together, they cover 90% of the issues I’d normally rely on the compiler of a regular language to figure out.

I end up with a language that is fun to use (I’d argue that async/await is the best language feature to come up in the last decade), and with most of the protections needed to not shoot myself in the foot.

eslint

eslint does statical analyzes in js. I have a global .eslintrc.js that is:

"use strict";

module.exports = {
  extends: "eslint:recommended",
  root: true,
  rules: {
    "array-bracket-spacing": ["error", "never"],
    "arrow-parens": ["error", "as-needed"],
    "camelcase": ["error"],
    "comma-spacing": ["error"],
    "comma-style": ["error"],
    "curly": ["error", "multi-line"],
    "guard-for-in": ["error"],
    "indent": ["error", 2],
    'func-call-spacing': 'error',
    "linebreak-style": ["error", "unix"],
    "max-len": ["error", { "code": 90, "tabWidth": 2 }],
    "no-multiple-empty-lines": ["error", {max: 1}],
    "no-tabs": ["error"],
    "keyword-spacing": "error",
    "key-spacing": ["error", {"beforeColon": false, "afterColon": true}],
    "no-with": ["error"],
    "new-cap": ["error", {"capIsNew": false}],
    'no-array-constructor': 'error',
    "no-caller": ["error"],
    "no-multi-spaces": ["error", { ignoreEOLComments: true }],
    "no-multi-str": ["error"],
    "no-invalid-this": ["error"],
    "no-trailing-spaces": ["error"],
    "no-new-wrappers": ["error"],
    "no-new-symbol": ["error"],
    "no-irregular-whitespace": ["error"],
    "no-unneeded-ternary": ["error", { "defaultAssignment": false }],
    "no-unused-vars": ["error", {
      "argsIgnorePattern": "^_",
      "varsIgnorePattern": "^_",
    }],
    "no-unused-expressions": ["error"],
    "prefer-promise-reject-errors": ["error"],
    "prefer-spread": ["error"],
    "rest-spread-spacing": ["error"],
    "prefer-rest-params": ["error"],
    "no-unexpected-multiline": ["error"],
    "no-unsafe-optional-chaining": "error",
    "no-use-before-define": ["error"],
    "no-constant-condition": ["error", { "checkLoops": false }],
    "no-constructor-return": ["error"],
    "no-implied-eval": ["error"],
    "no-throw-literal": ["error"],
    "no-var": ["error"],
    "no-shadow": ["error"],
    "padded-blocks": ["error", "never"],
    "prefer-const": ["error", { "destructuring": "all"}],
    "semi": ["error", "always"],
    "semi-spacing": ["error"],
    "require-await": ["error"],
    "space-before-blocks": ["error", "always"],
    "space-before-function-paren": ["error", {
      "anonymous": "never",
      "named": "never",
      "asyncArrow": "always",
    }],
    "space-in-parens": ["error", "never"],
    "space-infix-ops": ["error", {"int32Hint": true}],
  },
  ignorePatterns: [],
  env: {
    browser: true,
    es6: true
  },
  globals: {
  },
  parserOptions: {
    ecmaVersion: 2022,
    sourceType: 'module'
  }
};

It’s a mix of my own Javascript style (2 spaces tabWidth, 90 max width), with some basic protections for mistakes and some hard rules about spaces that I’m so used to that I can’t really code without.

I haven’t updated them in a long time, but I also haven’t followed up to see what’s new on eslint plugin land, so maybe there are new things that I’d try to enforce here.

tsc

I also use tsc, the TypeScript compiler, to go through the code, do its best at infering types, and complaining if anything weird is going on.

Usually I don’t add inline definitions and I limit definition files (d.ts) for generic libraries that I carry around.

The jsconfig.json for it is pretty generic:

{
  "compilerOptions": {
    "target": "es2022",
    "module": "esnext",
    "moduleResolution": "nodenext",
    "checkJs": true,
    "types": [
      "offscreencanvas", // not sure if still needed
    ]
  },
  "exclude": [
    "node_modules",
  ],
  "include": [
    "src",
  ]

I don’t want tsc to complaint about modern Javascript, so I keep it to esnext. I have another workflow to increase compatibility (usually using rollup/babel). Either way, I don’t want tsc to care about it much.

Running it

Both tools are integrated in my editor, so they show inline errors as I type. But I still keep a way to run this on the command line:

eslint src/
tsc --resolveJsonModule --noImplicitReturns --noEmit -p jsconfig.json

git pre-commit hook

On top of that, I make sure that I never commit any code that is not clear. To help with that, I have a simple pre-commit git hook that enforces that the code passes.

This is .git/hooks/pre-commit:

#!/bin/sh
set -euo pipefail

FILES="$(git diff --cached --name-only --diff-filter=ACM | egrep '\.js.?$')"
ESLINT="$(git rev-parse --show-toplevel)/node_modules/.bin/eslint"

if [[ "$FILES" = "" ]]; then
  exit 0
fi

printf "\033[32meslint\033[0m\n"
eslint --color $(echo $FILES | tr '\n' ' ')

printf "\033[32mtslint\033[0m\n"
tsc --resolveJsonModule --noImplicitReturns --noEmit -p jsconfig.json

This will run during git commit and stop the commit if the current edited files are not in order. To bypass the hook, use git commit --no-verify.