diff --git a/website/public/biome.svg b/website/public/biome.svg new file mode 100644 index 0000000000..d9a20d3e69 --- /dev/null +++ b/website/public/biome.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/public/blog/why-another-javascript-linter/eslint-esmodule-errors.png b/website/public/blog/why-another-javascript-linter/eslint-esmodule-errors.png new file mode 100644 index 0000000000..f428fba6c6 Binary files /dev/null and b/website/public/blog/why-another-javascript-linter/eslint-esmodule-errors.png differ diff --git a/website/public/blog/why-another-javascript-linter/eslint-without-node-config.png b/website/public/blog/why-another-javascript-linter/eslint-without-node-config.png new file mode 100644 index 0000000000..f959dfa7e0 Binary files /dev/null and b/website/public/blog/why-another-javascript-linter/eslint-without-node-config.png differ diff --git a/website/public/blog/why-another-javascript-linter/index.ejs.html b/website/public/blog/why-another-javascript-linter/index.ejs.html new file mode 100644 index 0000000000..75091a60e7 --- /dev/null +++ b/website/public/blog/why-another-javascript-linter/index.ejs.html @@ -0,0 +1,521 @@ + + + + + + + + <%- await include("../../common-head.ejs.html") %> <%- await + include("../blog-head.ejs.html") %> + + + + +
<%- await include("../../common-nav.ejs.html") %>
+ +
+
+

<%= meta.title %>

+

<%= meta.description %>

+
+ +

+ Written by strager on + +

+ +

+ When people hear about quick-lint-js, they ask me one of two questions: +

+
    +
  1. What makes quick-lint-js so fast?
  2. +
  3. + Why did you make another JavaScript linter? Why not use ESLint? +
  4. +
+ +

+ ESLint is the de-facto standard JavaScript linter. It is highly + customizable and readily available. What's not to like about ESLint? +

+ +

Here is my list:

+
    +
  1. ESLint is editor-last
  2. +
  3. + ESLint is plugin & config hell +
  4. +
  5. ESLint is slow
  6. +
+ +

+ After a discussion of my ESLint grievances, I will evaluate new + JavaScript linters: +

+
    +
  1. Deno Lint
  2. +
  3. Biome
  4. +
  5. quick-lint-js
  6. +
+ +

Finally, I will share my predictions:

+
    +
  1. The future of JavaScript linters
  2. +
+ +
+
+

ESLint is editor-last

+ +

+ ESLint is command-line first, CI second, and editor last. + ESLint focuses on checking your code after you write it, not while + you write it. Programmers typically run ESLint as a batch job + pre-commit: +

+

@@@ picture of ESLint CLI

+ +

+ I want a JavaScript code assistant; I want to catch issues while I'm + typing. I miss the days when JetBrains Resharper had my back when I + hacked away in C#. +

+ +

@@@ picture of Resharper?

+ +

+ While you can run ESLint in your editor, the experience + isn't great. I teach JavaScript a lot, so I often make small + programs showcasing different JavaScript, Node.js, or browser + features. ESLint only really works inside projects, so it + fights you when you're writing one-off scripts. +

+ +

+ I use Vim. ESLint's Vim plugins are weak. The ALE plugin, for + example, starts a new Node.js process each time linting occurs. + ESLint takes over 150 milliseconds to just start up (on my + Zen 3 5950X CPU!); ESLint can't even keep up with typing at 80 WPM! + But let's talk about speed later. +

+ +

+ When typing, you often introduce syntax errors. You type + ( but don't write the closing ). ESLint + breaks. ESLint should at least give a helpful error message, but it + just tells me "unexpected token }": +

+

@@@ video

+ +

+ Apparently ESLint's designers want you to use ESLint only after + you've + tested your code. But at that point, ESLint is useless to me: it + only is for telling me that row_index is an evil + variable name, not that I have serious bugs in my code. +

+ +

+ I want a tool I can recommend to beginners. Unfortunately, the + ESLint Visual Studio Code extension does not bundle ESLint, so it + does not work out of the box. The extension doesn't tell you what's + wrong if you're missing ESLint or a config file. ESLint in VS Code + is bad for beginners. +

+ +

+ I want ESLint to work well in my editor. + Editor support should be a first-class feature. +

+
+ +
+

ESLint is plugin & config hell

+ +

+ Want to use ESLint to lint some JavaScript code? Great! Just run + ESLint: +

+

@@@ pic

+ +

+ Nope. ESLint requires you to create a configuration file, even if + you want a default well-established configuration. As I mentioned + earlier, this makes it annoying to use ESLint for one-off scripts I + make while teaching. +

+ +

+ Want to use ESLint with a Node.js project? Great! ESLint works out + of the box. Not! You need to convince ESLint that you're in + a Node.js project. By default, ESLint complains: +

+
+ ESLint reporting error: 'console' is not defined (no-undef) +
ESLint being confused by modern JavaScript
+
+ +

+ Want to use ESLint for ES modules? Great! But it doesn't work out of + the box; you gotta configure it:

+
+ ESLint reporting errors: Parsing error: 'import' and 'export' may appear only with 'sourceType: module' +
+ ESLint desperately trying to get your attention +
+
+ +

+ Want to use ESLint for JavaScript code from 2015, using advanced + features like arrow functions and async functions? Great! But it + doesn't work out of the box; you must configure the parser's + ecmaVersion. +

+ +

+ Want to use ESLint with a JSX/React project? Great! There's a plugin + for that. But you can't just install the React plugin; you also need + to configure ESLint's parser, and you should probably specify the + React version too. To make matters worse, the specifics for doing + these things have changed over the years, so old StackOverflow + answers don't work anymore. +

+ +

@@@ picture of minimal ESLint config for JSX

+ +

+ Want to use ESLint with a TypeScript project? Great! There's a + plugin for that. @@@ but stuff. +

+ +

+ Want to use ESLint for bug-finding only and leave formatting to + Prettier? Great! There's a plugin for that. But why do you need a + plugin? Shouldn't I be able to just turn off ESLint's formatting + rules? Well, now + most ESLint formatting rules are gone + (deprecated as of ESLint v8.53.0), but a few (such as no-extra-semi + and no-irregular-whitespace) remain as default-enabled rules. +

+ +

+ Configuring ESLint only needs to be done when you make major changes + to the project, or when you're just starting the project. There is + even an init tool to create an ESLint config for you. ESLint doesn't + make setup easy enough for me to recommend it to beginners, + and it's not easy enough for me to use for all my projects. +

+ +

+ I want my batteries included. No required config + file for the basics. No plugins for the basics. + Easy to beginners, please. +

+
+ +
+

ESLint is slow

+ +

+ Anyone who has used ESLint in a big project knows that ESLint's + performance sucks. But I work on smaller projects, so performance + shouldn't be too bad, right? +

+ +

+ On one 15k SLOC project, ESLint takes around 950 milliseconds + (16,000 lines per second). The --cache option helps, + bringing linting down to around 400 milliseconds if I change one + file. But even with lots of caching, that's only 38,000 lines per + second! C++ linters run at over 100,000 lines per second without + caching, and C++ is notoriously uglier to deal with than JavaScript. + And in bigger C++ projects, parallelization kicks in to improve + throughput more. +

+ +

+ Why does linting speed matter to me? Half a second isn't that bad. + But as I mentioned in the ESLint is editor-last section, I + need ESLint to keep up with my typing. ESLint being slow is + disorienting: +

+

@@@ video of lag

+ +

+ ESLint is distracting. "But strager, why are you linting while + typing? Just add debouncing so it doesn't flicker as much." No! + Unacceptable. The debouncing is less distracting, but it's still + distracting! In contrast, a fast linter like quick-lint-js is + not distracting to me: +

+

@@@ video of no lag

+ +

+ ESLint performance has been improving. + eslint_d and + the ESLint LSP server (hidden inside the Visual Studio Code extension) help with the latency. ESLint's core has gotten faster; I had to + adjust my "over 130× faster than ESLint" messaging to just "over 90× + faster". And Node.js, V8, and computer hardware are getting faster + too. +

+ +

+ Despite these improvements, + I want a 100× faster ESLint. +

+
+
+ +
+

New JavaScript linters

+ +

+ I don't like ESLint. What other options are there, and how do they + address the problems with ESLint? Let's look at three ESLint + alternatives: Deno Lint, + Biome, and + quick-lint-js. +

+ +

@@@ picture: Deno vs Biome vs qljs

+ +
+

Deno Lint

+ +

+ Deno Lint + is a part of the Deno project. Created in March 2020, it features + better performance than ESLint and integration with the rest of + Deno. +

+ +

+ Compatibility: Because Deno Lint is designed for Deno + projects, it does not work well for non-Deno projects. For example, + Deno Lint recognizes JSX syntax, but only in + .jsx files. But most React-based projects use + .js files, so Deno Lint explodes on JSX. Deno Lint + probably won't ever support frameworks such as Vue and Svelte + either. +

+ +

+ Editor integration: Deno has an LSP server that can run the + linter. Editor support is not prominently advertised, but it does + seem to be well-supported (including in Vim). The Visual Studio Code + extension does not bundle Deno, though, making it harder to use for + beginners. The LSP server also includes the TypeScript type checker, + which is handy (or annoying, depending on your perspective). @@@ try + it out. does it need a project to work? +

+ +

+ Plugin & configs: Deno Lint works out of the box with no + configuration. No plugins are necessary for JSX and TypeScript. + Nice! +

+ +

+ Speed: Deno Lint's command-line interface is decently fast. + Sadly, the LSP server is slow. The slowness is caused by intentional + debouncing. I could fork Deno and patch it out, of course. +

+
+ +
+

Biome

+ +

+ Biome is a linter and formatter + for JavaScript and other languages. Biome's history is a bit weird: + Rome was created in February 2020. It was rewritten from scratch in + September 2021 based on + RSLint + (which was created in September 2020). The Rome dev team forked Rome + and called it Biome in August 2023. +

+ +

+ Compatibility: Biome is designed to meet developers where + they are. As such, it supports JavaScript, JSX, and TypeScript + today, and will support Vue and Svelte in the future. +

+ +

+ Editor integration: Biome has an LSP server that works in Vim + with coc.nvim. In my experience, the LSP server is a bit flaky + because it talks to a separate Biome daemon. Biome's Visual Studio + Code extension requires Biome to be installed separately, making it + harder to use for beginners. +

+ +

+ Plugin & configs: Biome does not suffer from plugin hell + for basic features. Biome also works out of the box without configs. + However, Biome is opinionated by default; it disallows code such as + this.icons["zoomIn"] and sometimes + requires blocks inside + switch statements. Therefore, I need + to configure Biome to shut it up. +

+ +

+ Speed: Biome started fast when it was a light fork of RSLint. + As new lint rules have been added over time, + Biome's linting performance has 50× gotten worse. In my testing, Biome's LSP server is slower than ESLint's! I'm + sure some of this can be fixed, but it looks like linting + performance is not a key feature of Biome. +

+
+ +
+

+ quick-lint-js +

+ +

+ quick-lint-js is my own JavaScript linter. I + started writing it in March 2020 after being annoyed by ESLint's + poor editor integration and + Flow being too picky. +

+ +

+ Compatibility: Like Biome, quick-lint-js is designed to meet + developers where they are. It supports JavaScript and JSX today, + with experimental support for TypeScript. Vue and Svelte support + will come eventually. +

+ +

+ Editor integration: quick-lint-js was designed to work with + editors from the beginning. It has first-party support for various + editors, including different Vim and Emacs plugins. The Visual + Studio Code extension comes with the linter bundled, simplifying + installation. +

+ +

+ Plugin & configs: quick-lint-js is like Biome and Deno + Lint: Your code is correctly linted out of the box. quick-lint-js + focuses on correctness issues and avoids stylistic nitpicks, making + it usable in any project without configuration. However, + quick-lint-js's lack of configuration means that it cannot enforce + style rules in a team. +

+ +

+ Speed: As its name implies, quick-lint-js is fast. Low + latency is a goal because quick-lint-js lints as you type and does + not want to be distracting. Other linters struggle to hit 30 FPS, + and quick-lint-js easily passes 1000 FPS. quick-lint-js isn't + perfect, though; it currently cannot lint directories and does not + lint files in parallel. +

+ +

@@@ benchmark chart

+ +

When I created quick-lint-js, Deno Lint and Rome/Biome/RSLint didn't really + exist. If they had existed, I might have contributed to one of those + projects (probably Deno Lint). Today, it would be hard for me to + abandon quick-lint-js. Just thinking about it makes me sad, so let's + move on. 😆

+
+
+ +
+

The future of JavaScript linters

+ +

Biome started in February 2020. Deno Lint and quick-lint-js both + started in March 2020. RSLint (merged into Biome) started in September + 2020. Worldwide lockdowns sure gave people the time to start their + ESLint rewrites. 😅

+ +

But what about 2024 and beyond? + Will the industry-standard ESLint prevail, or will + Deno Lint, Biome, or quick-lint-js dethrone ESLint? Or will even newer linters, + like OXC's + oxlint from February 2023, take the crown?

+ +

The different linters of today have different strengths:

+ + +

I predict that some of these linters will converge. Biome might adopt + an 'easy mode', removing the need for quick-lint-js. Deno might replace + Deno Lint with a pre-configured ESLint. quick-lint-js might add plugins + and complicated configs and eventually replace ESLint. oxlint might + obsolete everything else!

+ +

It's hard to say what will happen exactly. But I suspect that the + linter landscape will change in the coming years. I hope that my + project (quick-lint-js) is useful today and will push other linters + forward.

+
+
+ + + + diff --git a/website/public/deno.svg b/website/public/deno.svg new file mode 100644 index 0000000000..73cc87c73a --- /dev/null +++ b/website/public/deno.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/website/public/index.mjs b/website/public/index.mjs index 5d65cd3c85..d532cdee7e 100644 --- a/website/public/index.mjs +++ b/website/public/index.mjs @@ -88,12 +88,14 @@ function getExtraIconAttributes(attributes) { let icons = { chocolatey: { path: "chocolatey.svg", alt: "Chocolatey" }, "arch-linux": { path: "arch-linux.svg", alt: "Arch Linux" }, + biome: { path: "biome.svg", alt: "Biome" }, "cli-and-lsp-server": { path: "gnome-terminal.svg", alt: "CLI and LSP server", }, codespaces: { path: "codespaces.png", alt: "GitHub Codespaces" }, debian: { path: "debian.svg", alt: "Debian" }, + deno: { path: "deno.svg", alt: "Deno" }, emacs: { path: "emacs.svg", alt: "Emacs", disableSpritesheet: true }, github: { path: "github.svg", alt: "GitHub" }, homebrew: { path: "homebrew.svg", alt: "Homebrew" }, diff --git a/website/public/license/biome.svg.html b/website/public/license/biome.svg.html new file mode 100644 index 0000000000..e03b2ac078 --- /dev/null +++ b/website/public/license/biome.svg.html @@ -0,0 +1,27 @@ +

MIT License

+ +

Copyright (c) 2023 Biome Developers and Contributors.

+ +

+ Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: +

+ +

+ The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. +

+ +

+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +

diff --git a/website/public/license/deno.svg.html b/website/public/license/deno.svg.html new file mode 100644 index 0000000000..6886959870 --- /dev/null +++ b/website/public/license/deno.svg.html @@ -0,0 +1,27 @@ +

MIT License

+ +

Copyright (c) 2018-2023 the Deno authors

+ +

+ Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: +

+ +

+ The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. +

+ +

+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +

diff --git a/website/public/license/index.ejs.html b/website/public/license/index.ejs.html index 8b8ae5b326..88d24ef8aa 100644 --- a/website/public/license/index.ejs.html +++ b/website/public/license/index.ejs.html @@ -94,10 +94,12 @@

Website

let path = await import("path"); let files = [ "../arch-linux.svg", + "../biome.svg", "../blue-folder.svg", "../chocolatey.svg", "../codespaces.png", "../debian.svg", + "../deno.svg", "../dusty.svg", "../emacs.svg", "../gnome-terminal.svg",