Configure Helix for Typescript + ESLint + Prettier/dprint
Tags: helix, typescript, eslint, prettier
Published: 2023-10-29 22:44:31

The new 23.10 version of Helix dropped the other day and I thought it was finally time that I got my favourite editor set up for web development with linting and auto-formatting of TS/TSX, JS/JSX, HTML, CSS, JSON and MD files.

I set out to do this a couple of times before but was never able to actually get it to work. Now that multi-LSP support is stable though, I can finally report that I got it up and running.

[This repo](https://github.com/morlinbrot/helix-webdev-config) has an example **Typescript + ESLint + Prettier/dprint** project with a corresponding Helix config. We are currently discussing if we should add support for web development to Helix's default config [over here](https://github.com/helix-editor/helix/discussions/8452).

**Currently, auto-formatting on save with `vscode-eslint-language-server` is not yet working so please let me know if you have a working setup for this!**

One thing that I struggled with during this endeavour was that  I was never sure if something didn't work because my Helix config wasn't right or because I used the wrong magic incantation of commands to set up Typescript and ESLint in my project correctly. Here's a full rundown of how to do that:

## Prerequisites
For completeness' sake and because it's never mentioned anywhere else, here are all the langservers you need to have installed.

### Language Servers
Install the [`typescript-language-server`](https://github.com/typescript-language-server/typescript-language-server). This is used by default by Helix.
```
npm install -g typescript-language-server typescript
```

The [`vscode-langservers-extracted`](https://github.com/hrsh7th/vscode-langservers-extracted) package provides language servers for Html, CSS, Json and ESLint.
```
npm i -g vscode-langservers-extracted
```

I'm also using [`emmet-ls`](https://github.com/aca/emmet-ls) for convenience:
```
npm install -g emmet-ls
```

### Formatters
#### dprint
We all know that a tool being written in Rust is enough reason for any true Rustacean to immediately switch over to it so in my personal setup I'm using [dprint](https://dprint.dev/install/) as my formatter:
```
curl -fsSL https://dprint.dev/install.sh | sh
```
In all seriousness though, I found dprint to be slightly faster than Prettier and liked its defaults better.

#### Prettier
If you want to go for the canonical Typescript + ESLint + Prettier setup, install [Prettier](https://prettier.io/docs/en/install.html) (globally or locally) and comment/uncomment a few lines in the `.helix/languages.toml` file.
```
npm i -g prettier
```

## Bonus: Just use Deno?
For my personal projects, I'm currently exploring simply using Deno for both linting and formatting as I like its defaults and the simplicity of using just one langserver.
```
curl -fsSL https://deno.land/x/install/install.sh | sh
```
Here's how the `languages.toml` looks like for that:
```
[language-server]
deno = { command = "deno", args = [ "lsp" ]}

[[language]]
name = "tsx"
language-servers = [ "deno", "emmet-ls" ]
auto-format = true
```
Simple and effective, right?

If I could just have it my way, I would use Deno even for non-Deno projects just for its CLI, it is just such a pleasure to use and honestly defines a standard for modern web development that other tools should take as an example (Shameless plug: Have you read [my article on Deno's full-stack Fresh framework yet?](https://dev.to/morlinbrot/simple-auth-with-denos-fresh-supabase-2e68)).


## Helix Setup
Here's the full [`languages.toml`](https://github.com/morlinbrot/helix-webdev-config/blob/main/.helix/languages.toml):
```toml
[language-server]
deno = { command = "deno", args = [ "lsp" ]}
emmet-ls = { command = "emmet-ls", args = [ "--stdio" ]}

[language-server.eslint]
command = "vscode-eslint-language-server"
args = ["--stdio"]

[language-server.eslint.config]
codeActionsOnSave = { mode = "all", "source.fixAll.eslint" = true }
format = { enable = true }
nodePath = ""
quiet = false
rulesCustomizations = []
run = "onType"
validate = "on"
experimental = {}
problems = { shortenToSingleLine = false }

[language-server.eslint.config.codeAction]
disableRuleComment = { enable = true, location = "separateLine" }
showDocumentation = { enable = false }

[language-server.vscode-json-language-server.config]
json = { validate = { enable = true }, format = { enable = true } }
provideFormatter = true

[language-server.vscode-css-language-server.config]
css = { validate = { enable = true } }
scss = { validate = { enable = true } }
less = { validate = { enable = true } }
provideFormatter = true


[[language]]
name = "typescript"
language-servers = [ "typescript-language-server", "eslint", "emmet-ls" ]
# formatter = { command = "prettier", args = [ "--parser", "typescript" ] }
formatter = { command = "dprint", args = [ "fmt", "--stdin", "typescript" ] }
auto-format = true

[[language]]
name = "tsx"
language-servers = [ "deno", "eslint", "emmet-ls" ]
# formatter = { command = "prettier", args = [ "--parser", "typescript" ] }
formatter = { command = "dprint", args = [ "fmt", "--stdin", "tsx" ] }
auto-format = true

[[language]]
name = "javascript"
language-servers = [ "typescript-language-server", "eslint", "emmet-ls" ]
# formatter = { command = "prettier", args = [ "--parser", "typescript" ] }
formatter = { command = "dprint", args = [ "fmt", "--stdin", "javascript" ] }
auto-format = true

[[language]]
name = "jsx"
language-servers = [ "typescript-language-server", "eslint", "emmet-ls" ]
# formatter = { command = "prettier", args = [ "--parser", "typescript" ] }
formatter = { command = "dprint", args = [ "fmt", "--stdin", "jsx" ] }
auto-format = true

[[language]]
name = "json"
# formatter = { command = "prettier", args = [ "--parser", "json" ] }
formatter = { command = "dprint", args = [ "fmt", "--stdin", "json" ] }
auto-format = true

[[language]]
name = "html"
language-servers = [ "vscode-html-language-server", "emmet-ls" ]
formatter = { command = 'prettier', args = ["--parser", "html"] }
auto-format = true

[[language]]
name = "css"
language-servers = [ "vscode-css-language-server", "emmet-ls" ]
formatter = { command = 'prettier', args = ["--parser", "css"] }
auto-format = true
```

## Project Setup
I'm using a [NextJS starter project](https://nextjs.org/docs/getting-started/installation) just to have something in hand and show off a correctly configured project. Here's what I did to create that.
```
npx create-next-app@latest helix-webdev-config
```

Add [`typescript-eslint`](https://typescript-eslint.io/getting-started) to add full Typescript support.
```
npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript
```

Exclude pure formatting lints from eslint. Formatting is a [different concern than linting](https://prettier.io/docs/en/comparison) and [should be handled separately](https://typescript-eslint.io/linting/troubleshooting/formatting).
```
npm i -D eslint-config-prettier
```

To quote directly from the above:

_Note that even if you use a formatter other than prettier, you can use eslint-config-prettier as it exclusively turns **off** all formatting rules._

Here's the full but minimal [`.eslintrc.js`](https://github.com/morlinbrot/helix-webdev-config/blob/main/.eslintrc.js) for your reference:
```
module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "next/core-web-vitals",
    "prettier", // This disables rules in presets that conflict with prettier (or other formatters).
  ],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    project: true, // Find the closest tsconfig.json for each source file.
    tsconfigRootDir: __dirname,
    ecmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["@typescript-eslint"],
  root: true,
};
```

Now, when you open a file and save it, you should see the formatter in action. To format the whole project, run:
```
dprint fmt
```
You should now have a working linting and auto-formatting setup for web development in Helix, happy hacking!

## Parting thoughts
I hope I could help someone in setting up their Helix editor properly and if so, I would appreciate a like and/or comment. I know that I struggled a few times with going through random people's configs and trying to find out what worked and what didn't and would've loved a comprehensive example like the one I hope I created.

Are you already using Helix as your daily driver? I know I am since I first came across it and it's still getting better and better every day.

I'm also interested in other people's experience with somewhat niche tools like dprint and Deno and if they're up to snuff for real-life production usage.

Since I'm using dprint for the first time, do you have any experience with it? Are you using it at work and does it hold up?

I just love the "Deno experience" - have you ever tried or advocated for using Deno for Typescript development? Have you built an app with Fresh?

Let me know in the comments!