references/antfu-eslint-config.md
---
name: antfu-eslint-config
description: Configuring @antfu/eslint-config for framework support, formatters, and rule overrides. Use when adding React/Vue/Svelte/Astro support, customizing rules, or setting up VS Code integration.
---
# @antfu/eslint-config
Handles both linting and formatting (no Prettier needed). Auto-detects TypeScript and Vue.
**Style**: Single quotes, no semicolons, sorted imports, dangling commas.
## Configuration Options
```js
import antfu from '@antfu/eslint-config'
export default antfu({
// Project type: 'lib' for libraries, 'app' (default) for applications
type: 'lib',
// Global ignores (extends defaults, doesn't override)
ignores: ['**/fixtures', '**/dist'],
// Stylistic options
stylistic: {
indent: 2, // 2, 4, or 'tab'
quotes: 'single', // or 'double'
},
// Framework support (auto-detected, but can be explicit)
typescript: true,
vue: true,
// Disable specific language support
jsonc: false,
yaml: false,
})
```
## Framework Support
### Vue
Vue accessibility:
```js
export default antfu({
vue: {
a11y: true
},
})
// Requires: pnpm add -D eslint-plugin-vuejs-accessibility
```
### React
```js
export default antfu({
react: true,
})
// Requires: pnpm add -D @eslint-react/eslint-plugin eslint-plugin-react-hooks eslint-plugin-react-refresh
```
### Next.js
```js
export default antfu({
nextjs: true,
})
// Requires: pnpm add -D @next/eslint-plugin-next
```
### Svelte
```js
export default antfu({
svelte: true,
})
// Requires: pnpm add -D eslint-plugin-svelte
```
### Astro
```js
export default antfu({
astro: true,
})
// Requires: pnpm add -D eslint-plugin-astro
```
### Solid
```js
export default antfu({
solid: true,
})
// Requires: pnpm add -D eslint-plugin-solid
```
### UnoCSS
```js
export default antfu({
unocss: true,
})
// Requires: pnpm add -D @unocss/eslint-plugin
```
## Formatters (CSS, HTML, Markdown)
For files ESLint doesn't handle natively:
```js
export default antfu({
formatters: {
css: true, // Format CSS, LESS, SCSS (uses Prettier)
html: true, // Format HTML (uses Prettier)
markdown: 'prettier' // or 'dprint'
}
})
// Requires: pnpm add -D eslint-plugin-format
```
## Rule Overrides
### Global overrides
```js
export default antfu(
{
// First argument: antfu config options
},
// Additional arguments: ESLint flat configs
{
rules: {
'style/semi': ['error', 'never'],
},
}
)
```
### Per-integration overrides
```js
export default antfu({
vue: {
overrides: {
'vue/operator-linebreak': ['error', 'before'],
},
},
typescript: {
overrides: {
'ts/consistent-type-definitions': ['error', 'interface'],
},
},
})
```
### File-specific overrides
```js
export default antfu(
{ vue: true, typescript: true },
{
files: ['**/*.vue'],
rules: {
'vue/operator-linebreak': ['error', 'before'],
},
}
)
```
## Plugin Prefix Renaming
The config renames plugin prefixes for consistency:
| New Prefix | Original |
|------------|----------|
| `ts/*` | `@typescript-eslint/*` |
| `style/*` | `@stylistic/*` |
| `import/*` | `import-lite/*` |
| `node/*` | `n/*` |
| `yaml/*` | `yml/*` |
| `test/*` | `vitest/*` |
| `next/*` | `@next/next` |
Use the new prefix when overriding or disabling rules:
```ts
// eslint-disable-next-line ts/consistent-type-definitions
type Foo = { bar: 2 }
```
## Type-Aware Rules
Enable TypeScript type checking:
```js
export default antfu({
typescript: {
tsconfigPath: 'tsconfig.json',
},
})
```
## Config Composer API
Chain methods for flexible composition:
```js
export default antfu()
.prepend(/* configs before main */)
.override('antfu/stylistic/rules', {
rules: {
'style/generator-star-spacing': ['error', { after: true, before: false }],
}
})
.renamePlugins({
'old-prefix': 'new-prefix',
})
```
## Less Opinionated Mode
Disable Anthony's most opinionated rules:
```js
export default antfu({
lessOpinionated: true
})
```
## Lint-Staged Setup
```json
{
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
}
}
```
```bash
pnpm add -D lint-staged simple-git-hooks
npx simple-git-hooks
```
## VS Code Settings
Add to `.vscode/settings.json`:
```jsonc
{
"prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "never"
},
"eslint.rules.customizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true }
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
"html",
"markdown",
"json",
"jsonc",
"yaml",
"toml",
"xml",
"astro",
"svelte",
"css",
"less",
"scss"
]
}
```
<!--
Source references:
- https://github.com/antfu/eslint-config
- https://raw.githubusercontent.com/antfu/eslint-config/refs/heads/main/README.md
-->
references/app-development.md
---
name: app-development
description: Vue/Nuxt/UnoCSS application conventions. Use when building web apps, choosing between Vite and Nuxt, or writing Vue components.
---
# App Development
## Framework Selection
| Use Case | Choice |
|----------|--------|
| SPA, client-only, library playgrounds | Vite + Vue |
| SSR, SSG, SEO-critical, file-based routing, API routes | Nuxt |
## Nuxt Conventions
### Disable Auto-imports (new projects)
Prefer explicit imports over auto-imports so every symbol is traceable. For new Nuxt projects, turn off both app-side (Nuxt) and server-side (Nitro) auto-imports in `nuxt.config.ts`:
```ts
// nuxt.config.ts
export default defineNuxtConfig({
imports: {
autoImport: false, // disable composable/util auto-imports
},
components: {
dirs: [], // disable component auto-imports
},
nitro: {
imports: false, // disable server-side (Nitro) auto-imports
},
})
```
Framework helpers stay available through the `#imports` alias — import them explicitly:
```ts
import { computed, ref } from '#imports'
```
| Option | Effect |
|--------|--------|
| `imports.autoImport: false` | Stops auto-importing `~/composables` and `~/utils` (and framework APIs like `ref`) |
| `components.dirs: []` | Stops auto-importing components from `~/components` |
| `nitro.imports: false` | Stops auto-importing in the server (`server/utils`, etc.) |
> Standalone Nitro projects already default to `imports: false` — leave server auto-imports off rather than enabling them.
### Path Aliases
Nuxt's built-in aliases (`~/`, `@/`, `#imports`) are already configured, so they're fine to use. Don't add custom path aliases for new code — prefer relative imports otherwise.
## Vue Conventions
| Convention | Preference |
|------------|------------|
| Script syntax | Always `<script setup lang="ts">` |
| State | Prefer `shallowRef()` over `ref()` |
| Objects | Use `ref()`, avoid `reactive()` |
| Styling | UnoCSS |
| Utilities | VueUse |
### Props and Emits
```vue
<script setup lang="ts">
interface Props {
title: string
count?: number
}
interface Emits {
(e: 'update', value: number): void
(e: 'close'): void
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
})
const emit = defineEmits<Emits>()
</script>
```
### Storybook for Components
Set up Storybook for component development. Expressing each state as a story keeps components side-effect-free and their states predictable.
Run the story tests in CI. Prefer the Vitest addon (`@storybook/addon-vitest`) so stories run as part of the existing `vitest` run (see [setting-up](setting-up.md)).
references/library-development.md
---
name: library-development
description: Building and publishing TypeScript libraries with tsdown. Use when creating npm packages, configuring library bundling, or setting up package.json exports.
---
# Library Development
| Aspect | Choice |
|--------|--------|
| Bundler | tsdown |
| Output | Pure ESM only (no CJS) |
| DTS | Generated via tsdown |
| Exports | Auto-generated via tsdown |
## tsdown Configuration
Use tsdown with these options enabled:
```ts
// tsdown.config.ts
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
exports: true,
})
```
| Option | Value | Purpose |
|--------|-------|---------|
| `format` | `['esm']` | Pure ESM, no CommonJS |
| `dts` | `true` | Generate `.d.ts` files |
| `exports` | `true` | Auto-update `exports` field in `package.json` |
### Multiple Entry Points
```ts
export default defineConfig({
entry: [
'src/index.ts',
'src/utils.ts',
],
format: ['esm'],
dts: true,
exports: true,
})
```
The `exports: true` option auto-generates the `exports` field in `package.json` when running `tsdown`.
---
## API Stability
For published libraries, lock the public API surface so accidental breaking changes appear as a diff in code review.
| Tool | Purpose |
|------|---------|
| [`tsnapi`](https://github.com/antfu/tsnapi) | Snapshots runtime exports + type declarations into committed `.snapshot.js` / `.snapshot.d.ts` files via Vitest |
| [`tsdown-stale-guard`](https://github.com/antfu-collective/tsdown-stale-guard) | Records build input/output hashes so tests fail fast when run against a stale build |
Install both as dev dependencies. Wire `tsdown-stale-guard` as a tsdown plugin so every build records its hash:
```ts
// tsdown.config.ts
import { defineConfig } from 'tsdown'
import { StaleGuardRecorder } from 'tsdown-stale-guard'
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
exports: true,
plugins: [
StaleGuardRecorder(),
],
})
```
Snapshot the public API from a Vitest test — prefer the Vitest helpers over the tsdown/Rolldown plugin so build config stays focused on building. Each file gates itself on a fresh build via a per-file `beforeAll(guardStaleBuild)` so the committed `dist/` can never drift from source:
```ts
// test/api.test.ts (single package)
import { beforeAll } from 'vitest'
import { snapshotApiPerEntry } from 'tsnapi/vitest'
import { guardStaleBuild } from 'tsdown-stale-guard'
beforeAll(async () => {
await guardStaleBuild()
})
await snapshotApiPerEntry(new URL('..', import.meta.url).pathname)
```
For monorepos, wrap `describePackagesApiSnapshots()` in a `describe` so the per-package suites share a single stale-build gate:
```ts
// test/api.test.ts (monorepo, run from root)
import { beforeAll, describe } from 'vitest'
import { describePackagesApiSnapshots } from 'tsnapi/vitest'
import { guardStaleBuild } from 'tsdown-stale-guard'
describe('packages api', async () => {
beforeAll(async () => {
await guardStaleBuild()
})
await describePackagesApiSnapshots()
})
```
### Updating snapshots
After an intentional API change, rebuild and update both snapshots in one go:
```bash
nr build # regenerates dist/ and the stale-guard hash
nr test -u # updates the .snapshot.js / .snapshot.d.ts files
```
---
## package.json
Required fields for pure ESM library:
```json
{
"type": "module",
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": ["dist"],
"scripts": {
"build": "tsdown",
"prepack": "pnpm build",
"test": "vitest",
"release": "bumpp -r"
}
}
```
The `exports` field is managed by tsdown when `exports: true`.
### prepack Script
For each public package, add `"prepack": "pnpm build"` to `scripts`. This ensures the package is automatically built before publishing (e.g., when running `npm publish` or `pnpm publish`). This prevents accidentally publishing stale or missing build artifacts.
references/monorepo.md
---
name: monorepo
description: Monorepo setup with pnpm workspaces, centralized aliases, and Turborepo. Use when creating or managing multi-package repositories.
---
# Monorepo Setup
## pnpm Workspaces
Use pnpm workspaces for monorepo management:
```yaml
# pnpm-workspace.yaml
packages:
- 'packages/*'
```
## Scripts Convention
Have scripts in each package, and use `-r` (recursive) flag at root,
Enable ESLint cache for faster linting in monorepos.
```json
// root package.json
{
"scripts": {
"build": "pnpm run -r build",
"test": "vitest",
"lint": "eslint . --cache --concurrency=auto"
}
}
```
In each package's `package.json`, add the scripts.
```json
// packages/*/package.json
{
"scripts": {
"build": "tsdown",
"prepack": "pnpm build"
}
}
```
## ESLint Cache
```json
{
"scripts": {
"lint": "eslint . --cache --concurrency=auto"
}
}
```
## Turborepo (Optional)
For monorepos with many packages or long build times, use Turborepo for task orchestration and caching.
See the dedicated Turborepo skill for detailed configuration.
## Centralized Alias
Path aliases should be explicit and centralized — this is the deliberate "already set up" case. Outside of these configured aliases, prefer relative imports; don't sprinkle ad-hoc aliases across the codebase.
For better DX across Vite, Nuxt, Vitest configs, create a centralized `alias.ts` at project root:
```ts
// alias.ts
import fs from 'node:fs'
import { fileURLToPath } from 'node:url'
import { join, relative } from 'pathe'
const root = fileURLToPath(new URL('.', import.meta.url))
const r = (path: string) => fileURLToPath(new URL(`./packages/${path}`, import.meta.url))
export const alias = {
'@myorg/core': r('core/src/index.ts'),
'@myorg/utils': r('utils/src/index.ts'),
'@myorg/ui': r('ui/src/index.ts'),
// Add more aliases as needed
}
// Auto-update tsconfig.alias.json paths
const raw = fs.readFileSync(join(root, 'tsconfig.alias.json'), 'utf-8').trim()
const tsconfig = JSON.parse(raw)
tsconfig.compilerOptions.paths = Object.fromEntries(
Object.entries(alias).map(([key, value]) => [key, [`./${relative(root, value)}`]]),
)
const newRaw = JSON.stringify(tsconfig, null, 2)
if (newRaw !== raw)
fs.writeFileSync(join(root, 'tsconfig.alias.json'), `${newRaw}\n`, 'utf-8')
```
Then update the `tsconfig.json` to use the alias file:
```json
{
"extends": [
"./tsconfig.alias.json"
]
}
```
### Using Alias in Configs
Reference the centralized alias in all config files:
```ts
// vite.config.ts
import { alias } from './alias'
export default defineConfig({
resolve: { alias },
})
```
```ts
// nuxt.config.ts
import { alias } from './alias'
export default defineNuxtConfig({
alias,
})
```
references/setting-up.md
---
name: setting-up
description: Project setup files including .gitignore, GitHub Actions workflows, and VS Code extensions. Use when initializing new projects or adding CI/editor config.
---
# Project Setup
## .gitignore
Create when `.gitignore` is not present:
```
*.log
*.tgz
.cache
.DS_Store
.eslintcache
.idea
.env
.nuxt
.temp
.output
.turbo
cache
coverage
dist
lib-cov
logs
node_modules
temp
```
## GitHub Actions
Add these workflows when setting up a new project. Skip if workflows already exist. All use [sxzz/workflows](https://github.com/sxzz/workflows) reusable workflows.
### Autofix Workflow
**`.github/workflows/autofix.yml`** - Auto-fix linting on PRs:
```yaml
name: autofix.ci
on: [pull_request]
jobs:
autofix:
uses: sxzz/workflows/.github/workflows/autofix.yml@main
permissions:
contents: read
```
### Unit Test Workflow
**`.github/workflows/unit-test.yml`** - Run tests on push/PR:
```yaml
name: Unit Test
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions: {}
jobs:
unit-test:
uses: sxzz/workflows/.github/workflows/unit-test.yml@main
```
### Release Workflow
**`.github/workflows/release.yml`** - Publish on tag (library projects only):
```yaml
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
uses: sxzz/workflows/.github/workflows/release.yml@main
with:
publish: true
permissions:
contents: write
id-token: write
```
## VS Code Extensions
Configure in `.vscode/extensions.json`:
```json
{
"recommendations": [
"dbaeumer.vscode-eslint",
"antfu.pnpm-catalog-lens",
"antfu.iconify",
"antfu.unocss",
"antfu.slidev",
"vue.volar"
]
}
```
| Extension | Description |
|-----------|-------------|
| `dbaeumer.vscode-eslint` | ESLint integration for linting and formatting |
| `antfu.pnpm-catalog-lens` | Shows pnpm catalog version hints inline |
| `antfu.iconify` | Iconify icon preview and autocomplete |
| `antfu.unocss` | UnoCSS IntelliSense and syntax highlighting |
| `antfu.slidev` | Slidev preview and syntax highlighting |
| `vue.volar` | Vue Language Features |