Skip to content

chore: Experimenting with Oxlint & Biome#19145

Closed
arnolicious wants to merge 3 commits into
mainfrom
chore/oxlint-migration
Closed

chore: Experimenting with Oxlint & Biome#19145
arnolicious wants to merge 3 commits into
mainfrom
chore/oxlint-migration

Conversation

@arnolicious

Copy link
Copy Markdown
Collaborator

What is this?

We (@danieldietzler & me) wanted to try out the newly released 🦀-based linter Oxlint so see how feasible a migration from eslint to Oxlint would be, and what performance enhancement could be gained.

I also explored how BiomeJS would fare.

Also since the main pain-point is web, I'm only taking a look at that, but the server could ofc also benefit in the same way (with probably less migration problems).

Migration

Both Oxlint and Biome offer a migration tool to migrate from eslint to their respective tool. That is needed, since both tools (re-)implement their own rules, rules written for eslint cannot simply be used.

Oxlint

For the oxlint migration, it helpfully outputs which rules are not supported (yet) which gives a great overview:

Oxlint migration output
unsupported rule: svelte/comment-directive
unsupported rule: svelte/system
special parser detected: svelte-eslint-parser
special parser detected: svelte-eslint-parser
unsupported rule: svelte/comment-directive
unsupported rule: svelte/infinite-reactive-loop
unsupported rule: svelte/no-at-debug-tags
unsupported rule: svelte/no-at-html-tags
unsupported rule: svelte/no-dom-manipulating
unsupported rule: svelte/no-dupe-else-if-blocks
unsupported rule: svelte/no-dupe-on-directives
unsupported rule: svelte/no-dupe-style-properties
unsupported rule: svelte/no-dupe-use-directives
unsupported rule: svelte/no-export-load-in-svelte-module-in-kit-pages
unsupported rule: svelte/no-immutable-reactive-statements
unsupported rule: svelte/no-inner-declarations
unsupported rule: svelte/no-inspect
unsupported rule: svelte/no-not-function-handler
unsupported rule: svelte/no-object-in-text-mustaches
unsupported rule: svelte/no-raw-special-elements
unsupported rule: svelte/no-reactive-functions
unsupported rule: svelte/no-reactive-literals
unsupported rule: svelte/no-reactive-reassign
unsupported rule: svelte/no-shorthand-style-property-overrides
unsupported rule: svelte/no-store-async
unsupported rule: svelte/no-svelte-internal
unsupported rule: svelte/no-unknown-style-directive-property
unsupported rule: svelte/no-unnecessary-state-wrap
unsupported rule: svelte/no-unused-props
unsupported rule: svelte/no-unused-svelte-ignore
unsupported rule: svelte/no-useless-children-snippet
unsupported rule: svelte/no-useless-mustaches
unsupported rule: svelte/prefer-writable-derived
unsupported rule: svelte/require-each-key
unsupported rule: svelte/require-event-dispatcher-types
unsupported rule: svelte/require-store-reactive-access
unsupported rule: svelte/system
unsupported rule: svelte/valid-each-key
unsupported rule: svelte/valid-prop-names-in-kit-pages
unsupported rule: unicorn/expiring-todo-comments
unsupported rule: unicorn/import-style
unsupported rule: unicorn/no-array-callback-reference
unsupported rule: unicorn/no-for-loop
unsupported rule: unicorn/no-named-default
unsupported rule: unicorn/no-unnecessary-array-splice-count
unsupported rule: unicorn/no-unnecessary-polyfills
unsupported rule: unicorn/prefer-at
unsupported rule: unicorn/prefer-default-parameters
unsupported rule: unicorn/prefer-export-from
unsupported rule: unicorn/prefer-keyboard-event-key
unsupported rule: unicorn/prefer-module
unsupported rule: unicorn/prefer-single-call
unsupported rule: unicorn/prefer-switch
unsupported rule: unicorn/prefer-ternary
unsupported rule: unicorn/prefer-top-level-await
unsupported rule: unicorn/prevent-abbreviations
unsupported rule: unicorn/relative-url-style
unsupported rule: unicorn/template-indent
unsupported rule: constructor-super
unsupported rule, but in development: getter-return
unsupported rule: no-dupe-args
unsupported rule: no-misleading-character-class
unsupported rule: no-octal
unsupported rule, but in development: no-undef
unsupported rule, but in development: no-unreachable
ignore allow list is currently not supported: !\*\*/.env.example
unsupported rule, but in development: getter-return
unsupported rule, but in development: no-undef
unsupported rule, but in development: no-unreachable
unsupported rule: prefer-const
unsupported rule: svelte/button-has-type
unsupported rule: @typescript-eslint/await-thenable
unsupported rule: @typescript-eslint/no-floating-promises
unsupported rule: @typescript-eslint/no-misused-promises
unsupported rule: @typescript-eslint/require-await
unsupported rule: object-shorthand
special parser detected: svelte-eslint-parser

To summarize:

Biome

For Biome unfortunately things are much more unclear. The migration does not give out any feedback on which rules are unsupported and thus not migrated. So this would be tedious manual work, to find all the rules that are not supported.

What we do know, is that it also does not support type-aware linting (yet) (biomejs/biome#3187)

And after running a first lint, I found biome flagged a lot of unused variable errors in svelte files, for variables that were only used in the svelte markup

Speed

Now even with a significant amount of rules not working in both new tools, looking at those juicy timing numbers is still very fun, so here are some very un-scientific numbers of just a few one-shot runs I did on my PC (which has 16 threads)

Current state

Single threaded:
npm run lint -> 516s (8min 16s)

Parallel:
npm run lint:p -> 174s (3min 14s)

Oxlint

npm run lint:ox -> 45ms

Biome

npx @biomejs/biome lint -> 87ms

Further remarks on Oxlint

Oxlint also has a very interesting solution for rules that have not yet been implemented: eslint-plugin-oxlint

This is an eslint plugin, that disabled all eslint rules, that are now covered by Oxlint. Thus you can then run as your lint command:

npx oxlint && npx eslint

However, I either didn't really manage to include this plugin correctly, or it made no significant change to the runtime of eslint. Ofc the main rules causing the slowness are the type-aware promise rules, which would fallback to eslint in this configuration. But even with those disabled and supposedly all the Oxlint rules disabled with the plugin, the eslint run time was still at least 50s which I find odd.

Conclusion

From my experimenting, Oxlint looks like a safer bet, even though biome has been around for longer. With Oxlint we know exactly which rules are still missing which is a huge plus imo.

The main blocker for now would be the svelte lint support.
Given how atrociously slow the type-aware rules currently are, and how there has already been discussions to possibly remove them (in some circumstances), I feel like those aren't that big of a blocker.

Notes

Both tools do also offer a formatter, which might be worth it to migrate to, if we ever migrate the linter, just for some extra 🦀

@github-actions

github-actions Bot commented Jun 12, 2025

Copy link
Copy Markdown
Contributor

Label error. Requires exactly 1 of: changelog:.*. Found: 🖥️web. A maintainer will add the required label.

@arendjr

arendjr commented Jun 13, 2025

Copy link
Copy Markdown

Hi, Biome contributor popping in here :)

I just wanted to let you know that we are launching Biome 2 shortly and will be introducing type-aware linting with rules such as noFloatingPromises and useExhaustiveSwitchCases. Others are already on the way.

The point about the migration not listing unsupported rules is a great point of feedback and something we intend to address! In the meantime, you can use our page with rule sources to look up rules that you feel are important to you.

Cheers!

@arnolicious arnolicious changed the title chore: Experimenting with oxlint chore: Experimenting with Oxlint & Biome Jun 13, 2025
@arnolicious

Copy link
Copy Markdown
Collaborator Author

Thanks a lot for letting us know @arendjr !

I have now upgraded to the biome v2 Beta, and it does look pretty good to me!

Biome v2

Migration

A small hiccup is that either during the migration or initialization, the overrides blocks in the biome config get duplicated, but that's no big deal.

What's different?

We got the rules that arend mentioned, noFloatingPromises, which worked in my simple example, where I removed the await on a promise in template-settings.svelte.

Timing

With those 2 new rules (and all the other migrated ones), the lint takes around 120ms 🎉

Drawbacks

  • There is still the unused variable issue in svelte files, which is also acknowledge in the biome docs.
  • Another issue I found is that biome doesn't understand svelte store references with the $ accessor. These get flagged as undeclared variables.

Oxlint

However while testing those biome rules, I realized Oxlint hadn't migrated the no-undef, and it turns out oxide has exactly the same problem with svelte stores and svelte runes.

And the same can be said about the unused-vars rule: In Oxlint, there are no reports at all of unused vars in svelte files, even when I add an actually unused var. I believe this is because Oxlint automatically ignores vue and svelte files for this rule.

Another conclusion

So after this additional round of testing, biome v2 is looking more promising in my opinion, since both tools seem to struggle similarly with svelte, however biome:

  • gets false positives in svelte files instead of false negatives
  • supports the no-floating-promises rule

@benmccann

Copy link
Copy Markdown
Collaborator

Perhaps we should mix and match? I.e. use eslint for Svelte and biome for non-Svelte

@arnolicious

Copy link
Copy Markdown
Collaborator Author

Unfortunately the svelte files are the main bottleneck, so switching the non-svelte files to biome would not really be worth it imo.

For comparison, running the current eslint-parallel config takes around 166s, and (very crudely) stripping all svelte plugins and configs from the eslint config, results in barely 5s.

So as long as I didn't make some blunder, it does seem like the main problem are the svelte files

@benmccann

Copy link
Copy Markdown
Collaborator

I guess my comment was a bit unclear. I mean could we have the eslint run the eslint-plugin-svelte rules? Those are quite fast. It's applying the type-checked rules from estlint-typescript to Svelte files that is slow

@arnolicious

Copy link
Copy Markdown
Collaborator Author

Oh right, yeah that's a good idea, that should be fairly straight-forward!

@zackpollard

Copy link
Copy Markdown
Member

Hey, I am going to close this for now due to it being stale for quite a long time. I'm also not sure if this is a direction we actually want to go. It might be worth trying Bens suggestion, let us know if that gets you anywhere. If you do get time to fix this please feel free to make a new PR or comment to have this one re-opened 🙂

@meesfrensel meesfrensel deleted the chore/oxlint-migration branch April 2, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants