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
    No fields configured for 🧪 Under Discussion.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions