Skip to content

Commit

Permalink
Switch from ESLint, Prettier and Stylelint to Biome (#140)
Browse files Browse the repository at this point in the history
* Install biome, run initial migration

* Update with inspired rules

* Update biome.json

* Add missing rules

* Add performance rules

* Move biome.json to root

* Remove old files

* Integrate with VCS, ignore shadcn/ui

* Update README and settings.json

* Simplify biome config

* Update biome.json

* Replace excess rules with "all"

* Add nursery rules

* Update biome.json

* Misc fixes

* Update biome.json

* Update package.json

* Update README.md

* Update README.md

* Update website config

* Remove ESLint Inspector from Website

* Run Ultracite on website

* Update biome.json

* Run biome unsafe fixes on website

* Misc fixes

* Update README.md

* Update package.json

* Update pnpm-lock.yaml

* Update README.md

* Update package.json

* Clean website deps

* Bump deps

* Update biome.json
  • Loading branch information
haydenbleasel authored Jul 19, 2024
1 parent 0f2af8f commit c92a9d2
Show file tree
Hide file tree
Showing 81 changed files with 564 additions and 29,975 deletions.
18 changes: 2 additions & 16 deletions .github/workflows/push.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ on:
# what the action will do
jobs:
release:
# The operating system it will run on
runs-on: ubuntu-latest
# This check needs to be in place to prevent a publish loop with auto and github actions

if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')"
# The list of steps that the action will go through

steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
Expand All @@ -23,19 +22,6 @@ jobs:
- name: Prepare repository
run: git fetch --unshallow --tags

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Cache node modules
uses: actions/cache@v4
with:
path: node_modules
key: pnpm-deps-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
pnpm-deps-${{ hashFiles('pnpm-lock.yaml') }}
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
16 changes: 4 additions & 12 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"emmet.showExpandedAbbreviation": "never",
"editor.codeActionsOnSave": {
"source.fixAll.esbenp.prettier-vscode": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.useFlatConfig": true,
"eslint.options": {
"overrideConfigFile": "eslint.config.mjs"
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
}
162 changes: 37 additions & 125 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,50 @@
# Ultracite

Ultracite is a robust linting preset for modern TypeScript apps. It's comprised of configuration files for [ESLint](https://eslint.org/), [Prettier](https://prettier.io/) and [Stylelint](https://stylelint.io/). It is incredibly opinionated and strict, enforcing the maximum amount of type safety and code quality through ESLint rules and TypeScript compiler options. It is designed for [Next.js](https://nextjs.org/) apps, but can be used with any TypeScript project, such as [React Native](https://reactnative.dev/) or [Node.js](https://nodejs.org/).
Ultracite is a robust linting configuration for modern TypeScript apps, built on [Biome](https://biomejs.dev/). It is incredibly opinionated and strict, enforcing the maximum amount of type safety and code quality. Once Ultracite is set up, it will automatically lint, fix and format your code on save.

<img src="https://img.shields.io/github/actions/workflow/status/haydenbleasel/ultracite/push.yaml" alt="" />

<img src="https://img.shields.io/npm/dy/ultracite" alt="" />

<img src="https://img.shields.io/npm/v/ultracite" alt="" />

<img src="https://img.shields.io/github/license/haydenbleasel/ultracite" alt="" />

## Features

### ESLint

Ultracite uses [ESLint](https://eslint.org/) to enforce code quality and type safety. It includes a wide range of rules to ensure your code is consistent and error-free. Ultracite combines with pre-defined rulesets for ESLint, as well as the following plugins: [Import](https://www.npmjs.com/package/eslint-plugin-import), [jsx-a11y](https://www.npmjs.com/package/eslint-plugin-jsx-a11y), [React](https://www.npmjs.com/package/eslint-plugin-react), [React Hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks), [jest](https://www.npmjs.com/package/eslint-plugin-jest), [promise](https://www.npmjs.com/package/eslint-plugin-promise), [n](https://www.npmjs.com/package/eslint-plugin-n), [Typescript](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin), [Prettier](https://www.npmjs.com/package/eslint-plugin-prettier), [Next.js](https://nextjs.org/docs/basic-features/eslint#eslint-plugin), [Cypress](https://www.npmjs.com/package/eslint-plugin-cypress), [HTML](https://www.npmjs.com/package/eslint-plugin-html), [SonarJS](https://www.npmjs.com/package/eslint-plugin-sonarjs), [Compat](https://www.npmjs.com/package/eslint-plugin-compat), [Unicorn](https://www.npmjs.com/package/eslint-plugin-unicorn), [GitHub](https://www.npmjs.com/package/eslint-plugin-github) and [TanStack Query](https://www.npmjs.com/package/@tanstack/eslint-plugin-query).

### Prettier

Ultracite uses [Prettier](https://prettier.io/) to format your code. It's configured to work with ESLint, so you can use both tools together without conflicts. Ultracite includes a pre-defined Prettier configuration that ensures your code is formatted consistently, as well as the [Tailwind](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) plugin for automatic class sorting.

### Stylelint

Ultracite uses [Stylelint](https://stylelint.io/) to enforce CSS code quality. Ultracite combines with pre-defined rules for Stylelint, as well as the [Stylelint-Prettier](https://www.npmjs.com/package/stylelint-prettier) plugin to ensure Stylelint and Prettier work together without conflicts.
<div>
<img src="https://img.shields.io/github/actions/workflow/status/haydenbleasel/ultracite/push.yaml" alt="" />
<img src="https://img.shields.io/npm/dy/ultracite" alt="" />
<img src="https://img.shields.io/npm/v/ultracite" alt="" />
<img src="https://img.shields.io/github/license/haydenbleasel/ultracite" alt="" />
</div>

## Installation

Run the command below to install Ultracite with peer dependencies:
Run the command below to install Ultracite:

```sh
pnpm add -D ultracite eslint@8 prettier stylelint typescript jest
pnpm add -D --save-exact ultracite @biomejs/biome
```

If you're running [VS Code](https://code.visualstudio.com/), ensure you have the following extensions installed:

```sh
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension biomejs.biome
code --install-extension bradlc.vscode-tailwindcss
code --install-extension stylelint.vscode-stylelint
```

## Setup

Create an `eslint.config.mjs` with the following contents:

```js
export { default } from 'ultracite';
```
Create an `biome.json` with the following contents:

Create a `prettier.config.mjs` with the following contents:

```js
export { default } from 'ultracite/prettier';
```

Create a `stylelint.config.mjs` with the following contents:

```js
export { default } from 'ultracite/stylelint';
```json
{ "extends": ["ultracite"] }
```

Ultracite is designed to be used with [VS Code](https://code.visualstudio.com/), and includes a `.vscode/settings.json` file that enables full formatting on save. Create a `.vscode/settings.json` file with the following contents:
Ultracite is designed to be used with [VS Code](https://code.visualstudio.com/). Create a `.vscode/settings.json` file with the following contents to enable full formatting and fixing on save:

```json
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"emmet.showExpandedAbbreviation": "never",
"editor.codeActionsOnSave": {
"source.fixAll.esbenp.prettier-vscode": "explicit",
"source.fixAll.eslint": "explicit",
"source.fixAll.stylelint": "explicit"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"eslint.useFlatConfig": true,
"eslint.options": {
"overrideConfigFile": "eslint.config.mjs"
},
"eslint.runtime": "node"
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
}
```

Expand All @@ -92,88 +54,38 @@ Lastly, ensure your `tsconfig.json` (if it exists) includes your new ESLint conf
{
"compilerOptions": {
"strictNullChecks": true
},
"include": ["eslint.config.mjs"]
}
}
```

## Usage

Once Ultracite is set up, it will automatically format your code on save.

## Configuration

You can opt-out of certain rules by modifying your `eslint.config.mjs` file. For example, here's a common exception I use to avoid linting [shadcn/ui](https://ui.shadcn.com/) components:
While Ultracite is designed to be zero-config, you can modify anything you'd like in your `biome.json` file. For example, to enable the `noAutofocus` rule, you can do the following:

```js
import ultracite from 'ultracite';

for (const config of ultracite) {
if (config.ignores) {
config.ignores.push('./components/ui/**/*');
```json
{
"extends": ["ultracite"],
"linter": {
"rules": {
"a11y": {
"noAutofocus": "off"
}
}
}
}

export { default } from 'ultracite';
```

Ultracite also lints the browser compatibility of your code. You can specify which polyfills exist in your project by modifying your `eslint.config.mjs` file. For example, here's how you can add polyfills for Next.js:

```ts
import ultracite from 'ultracite';

for (const config of ultracite) {
config.settings ||= {};
config.settings.polyfills ||= [];

config.settings.polyfills.push(
// These are from Next.js - https://nextjs.org/docs/architecture/supported-browsers#polyfills
'fetch',
'URL',
'Object.assign',
You can also disable rules on a per-line basis by adding a comment to the end of the line:

// This one is running on the server
'URLSearchParams'
);
}

export { default } from 'ultracite';
```tsx
// biome-ignore lint/security/noDangerouslySetInnerHtml: I do what I want mate.
<div dangerouslySetInnerHTML={{ ... }} />
```

## Debugging

If you're having issues with Ultracite, you can open the ESLint Output panel in VS Code to see what's going on. Optimally, it should look something like this:

```
[Info - 10:42:49 PM] ESLint server is starting.
[Info - 10:42:49 PM] ESLint server running in node v18.15.0
[Info - 10:42:49 PM] ESLint server is running.
[Info - 10:42:50 PM] ESLint library loaded from: /Users/haydenbleasel/GitHub/ultracite/node_modules/.pnpm/eslint@8.51.0/node_modules/eslint/lib/unsupported-api.js
```
## Notes

If you see any errors, it could be related to peer dependencies or changes in dependency versions. Feel free to report these as issues.
Ultracite was previously built on [ESLint](https://eslint.org/), [Prettier](https://prettier.io/) and [Stylelint](https://stylelint.io/). If you'd like to use stack, you can install it with the following command:

## Usage in Monorepos

If you're using Ultracite in a monorepo, you will need to do two things:

1. Place the files above in every package and application, as well as the root.
2. Add the following to your `.vscode/settings.json` file:

```json
{
"eslint.workingDirectories": [
{
"mode": "auto"
}
]
}
```sh
pnpm add -D ultracite@3 eslint@8 prettier stylelint typescript jest
```

This will ensure ESLint works correctly in all packages and applications by automatically detecting the working directory based on the nearest `package.json` and `eslint.config.mjs`, thus limiting the scope of ESLint to the current package or application and improving performance.

## Roadmap

- https://github.com/ota-meshi/eslint-plugin-yml
- https://github.com/mdx-js/eslint-mdx/tree/master/packages/eslint-plugin-mdx
- https://github.com/eslint/eslint-plugin-markdown
102 changes: 102 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"formatter": {
"enabled": true,
"formatWithErrors": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"attributePosition": "auto"
},
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"all": true,
"a11y": {
"noAutofocus": "off"
},
"nursery": {
"all": true,
"useImportRestrictions": "off",
"noUndeclaredDependencies": "off",
"noReactSpecificProps": "off",
"useImportExtensions": "off"
},
"style": {
"noDefaultExport": "off",
"noImplicitBoolean": "off",
"useFilenamingConvention": {
"level": "error",
"options": {
"requireAscii": true,
"filenameCases": ["kebab-case"]
}
},
"useSingleVarDeclarator": "off"
}
}
},
"javascript": {
"formatter": {
"arrowParentheses": "always",
"attributePosition": "auto",
"bracketSameLine": false,
"bracketSpacing": true,
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"jsxQuoteStyle": "double",
"lineEnding": "lf",
"lineWidth": 80,
"quoteProperties": "asNeeded",
"quoteStyle": "single",
"semicolons": "always",
"trailingCommas": "es5"
}
},
"css": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"quoteStyle": "double"
},
"linter": {
"enabled": true
},
"parser": {
"allowWrongLineComments": false,
"cssModules": false
}
},
"json": {
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf",
"lineWidth": 80,
"trailingCommas": "none"
},
"linter": {
"enabled": true
},
"parser": {
"allowComments": false,
"allowTrailingCommas": false
}
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true,
"defaultBranch": "main"
},
"files": {
"ignore": ["**/components/ui/**"]
}
}
1 change: 0 additions & 1 deletion eslint.config.mjs

This file was deleted.

Loading

0 comments on commit c92a9d2

Please sign in to comment.