Skip to content

Migrate from ESLint/Prettier/Stylelint to Biome or OXC #478

@fabiankaegy

Description

@fabiankaegy

Summary

10up-toolkit currently relies on three separate tools for code quality: ESLint (JavaScript linting), Prettier (formatting), and Stylelint (CSS linting). This requires managing multiple configurations, dependencies, and plugin ecosystems.

Modern alternatives like Biome offer a unified, significantly faster experience. This issue explores what it would take to migrate 10up-toolkit's linting and formatting infrastructure to Biome or similar modern tooling.

Current State

What 10up-toolkit Uses Today

@10up/eslint-config     → ESLint + eslint-config-airbnb + plugins
@10up/stylelint-config  → Stylelint + various plugins
Prettier                → Integrated via eslint-plugin-prettier

Dependencies involved:

  • eslint + ~15-20 plugins (React, JSX-a11y, import, WordPress, Jest, etc.)
  • prettier + eslint-plugin-prettier + eslint-config-prettier
  • stylelint + ~5-10 plugins (SCSS, order, etc.)

Total: 100+ transitive dependencies, complex version management

Pain Points

  1. Slow: ESLint with TypeScript type-checking can take 10-15+ seconds on large projects
  2. Complex configuration: Three separate config files, often conflicting
  3. Dependency hell: Plugin version conflicts, peer dependency warnings
  4. CI overhead: Linting often the slowest step in CI pipelines
  5. Maintenance burden: Keeping plugins updated across all three tools

Biome: The Leading Alternative

Biome is an all-in-one toolchain written in Rust that combines linting, formatting, and more into a single binary.

Key Features (as of Biome v2.0, June 2025)

Feature Details
Speed 10-25x faster than ESLint + Prettier
Unified Linter + Formatter + Import sorter in one tool
Languages JavaScript, TypeScript, JSX, JSON, CSS, GraphQL
Rules 425+ rules from ESLint, typescript-eslint, and other sources
Prettier compat 97% formatting compatibility with Prettier
Type-aware v2.0 introduced type-aware linting WITHOUT requiring TypeScript compiler
Zero deps Single binary, no node_modules bloat
Domains Auto-configures rules based on package.json dependencies (React, Next.js, etc.)

Biome v2.0 Highlights

Biome v2—codename: Biotype—the first JavaScript and TypeScript linter
that provides type-aware linting rules that doesn't rely on the
TypeScript compiler!
  • Type inference detects ~85% of floating promise cases at a fraction of the performance cost
  • Linter plugins (limited scope, but growing)
  • Domains auto-enable React/Next.js rules based on dependencies
  • Biome Assist for import organizing, key sorting, attribute sorting

Performance Comparison

Tool Time (example project)
ESLint + Prettier 10-15 seconds
Biome < 1 second
Oxlint < 0.5 seconds

Alternative: Oxlint

Oxlint is another Rust-based linter from the Oxc project.

Oxlint v1.0 (August 2025)

Feature Details
Speed 50-100x faster than ESLint (even faster than Biome)
Rules 520+ ESLint rules supported
Zero config Works out of the box
Limitations Linting only (no formatting), no type-aware rules yet

When to Choose Which

Use Case Recommendation
All-in-one replacement Biome
Maximum speed, keep Prettier Oxlint + Prettier
Need type-aware rules Biome v2
Heavy plugin ecosystem needs Stay with ESLint (for now)

Migration Analysis

What Can Be Migrated

Current Tool Biome Replacement Coverage
ESLint core rules ✅ Biome linter ~90%
eslint-plugin-react ✅ Built-in JSX rules ~90%
eslint-plugin-jsx-a11y ✅ Built-in a11y rules ~80%
eslint-plugin-import ✅ Import organizer ~85%
typescript-eslint ✅ Type-aware rules (v2) ~85%
Prettier (JS/TS/JSON) ✅ Biome formatter 97%
Prettier (CSS) ✅ Biome formatter 97%
Stylelint ⚠️ Partial CSS linting ~60%

What Cannot Be Migrated (Yet)

Tool/Plugin Status
@wordpress/eslint-plugin ❌ No direct equivalent - WordPress-specific rules
eslint-plugin-jest ⚠️ Partial - some rules available
SCSS linting ❌ Biome doesn't support SCSS syntax
HTML/Markdown ❌ Not yet supported
Vue/Svelte/Astro ⚠️ Partial support
Custom ESLint plugins ❌ Plugin system is limited

WordPress-Specific Considerations

The @wordpress/eslint-plugin provides several WordPress-specific rules:

// Rules we'd lose or need alternatives for:
'@wordpress/no-unsafe-wp-apis'
'@wordpress/dependency-group'
'@wordpress/no-unused-vars-before-return'
'@wordpress/i18n-*' rules
// etc.

Options:

  1. Run Biome + ESLint (just for WordPress rules) in parallel
  2. Contribute WordPress rules to Biome as plugins
  3. Accept some rule loss initially

WordPress Globals

Biome needs manual configuration for WordPress globals:

{
  "javascript": {
    "globals": ["wp", "jQuery", "Backbone", "JSON", "_"]
  }
}

The biome migrate eslint command won't automatically pick these up from @wordpress/eslint-plugin.

Proposed Migration Path

Phase 1: Formatter Migration (Low Risk)

Replace Prettier with Biome formatter only:

// 10up-toolkit.config.js
module.exports = {
  formatter: 'biome', // Instead of 'prettier'
};

Benefits:

  • Immediate speed improvement for formatting
  • No rule changes, just formatting
  • Easy rollback if issues

Changes needed:

  • Add @biomejs/biome as dependency
  • Update lint-style and format commands
  • Provide biome.json with Prettier-compatible settings

Phase 2: JavaScript/TypeScript Linting

Replace ESLint for JS/TS with Biome linter:

module.exports = {
  linter: {
    js: 'biome',     // Instead of 'eslint'
    css: 'stylelint' // Keep Stylelint for now
  }
};

Migration steps:

  1. Run biome migrate eslint --write to convert config
  2. Add WordPress globals manually
  3. Identify and document any lost rules
  4. Provide @10up/biome-config preset

Phase 3: CSS Linting

Evaluate Biome CSS linting vs keeping Stylelint:

Consideration Biome Stylelint
Speed ✅ Much faster ❌ Slower
SCSS support ❌ None ✅ Full
Rule coverage ⚠️ Basic ✅ Comprehensive
Property order ✅ Via assist ✅ Via plugin

Recommendation: Keep Stylelint for SCSS projects, use Biome for CSS-only projects.

Phase 4: Full Migration

For projects that can fully migrate:

{
  "linter": "biome",
  "formatter": "biome"
}

Single biome.json replaces:

  • .eslintrc.js
  • .prettierrc
  • .stylelintrc.js

Configuration Design

New 10up-toolkit Config Options

// 10up-toolkit.config.js
module.exports = {
  // Linting/formatting tool selection
  codeQuality: {
    // 'legacy' = ESLint + Prettier + Stylelint (current default)
    // 'biome' = Biome for JS/TS/CSS, Stylelint for SCSS
    // 'biome-full' = Biome only (no SCSS support)
    preset: 'biome',

    // Or granular control:
    linter: {
      js: 'biome',      // 'eslint' | 'biome' | 'oxlint'
      css: 'stylelint', // 'stylelint' | 'biome'
    },
    formatter: 'biome', // 'prettier' | 'biome'
  }
};

Biome Preset for 10up

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "extends": ["@10up/biome-config"],
  "javascript": {
    "globals": ["wp", "jQuery", "ajaxurl", "wpApiSettings"]
  },
  "linter": {
    "rules": {
      "recommended": true,
      "domains": {
        "react": "all"
      }
    }
  },
  "formatter": {
    "indentStyle": "tab",
    "indentWidth": 4,
    "lineWidth": 100
  }
}

CLI Changes

Current Commands

10up-toolkit lint-js [files]
10up-toolkit lint-style [files]
10up-toolkit format [files]

Proposed Commands

# Smart routing based on config
10up-toolkit lint [files]        # Runs appropriate linter(s)
10up-toolkit format [files]      # Runs appropriate formatter

# Or explicit tool selection
10up-toolkit lint --tool=biome [files]
10up-toolkit lint --tool=eslint [files]

# Check mode (CI-friendly)
10up-toolkit check [files]       # Lint + format check, no writes

Package Changes

New Packages

@10up/biome-config    # Biome configuration preset

Modified Packages

10up-toolkit          # Add Biome support, keep ESLint as fallback
@10up/eslint-config   # Maintain for legacy/WordPress-specific needs
@10up/stylelint-config # Maintain for SCSS projects

Deprecated (Eventually)

@10up/eslint-config   # Once Biome covers all use cases

Risks and Mitigations

Risk Mitigation
WordPress rule coverage gaps Run Biome + minimal ESLint config for WP rules
SCSS not supported Keep Stylelint for SCSS, or use CSS-only
Team unfamiliarity Documentation, gradual rollout
Different formatting output Run both tools in CI initially, compare
Plugin ecosystem Biome plugins are maturing; wait or contribute

Timeline Consideration

Milestone Estimated Timeframe
Biome formatter only Ready now
JS/TS linting (non-WordPress) Ready now
WordPress-aware config Needs config work
Full SCSS support Waiting on Biome
Complete ESLint replacement 2026+

Questions to Consider

  1. Should we default new projects to Biome while keeping ESLint for existing projects?
  2. How do we handle the WordPress-specific ESLint rules gap?
  3. Should we support running Biome + ESLint in parallel for WordPress rules?
  4. What's our SCSS story - require CSS, or maintain Stylelint?
  5. Should we contribute WordPress-specific rules to Biome's plugin system?

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions