Antfu skill
Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects.
by antfu·MIT license·★ 5,945 Stars on the repo·GitHub ↗
npx degit antfu/skills/skills/antfu#main ~/.claude/skills/antfuChecked ·commit main
Files of Antfu
Show the full text205 lines
Reference template: antfu/starter-ts. When scaffolding a new TypeScript project, mirror its
package.jsonscripts,eslint.config.js,knip.json,tsdown.config.tsand workflows rather than inventing a new layout.
Coding Practices
Code Organization
- Single responsibility: Each source file should have a clear, focused scope/purpose
- Split large files: Break files when they become large or handle too many concerns
- Type separation: Always separate types and interfaces into
types.tsortypes/*.ts - Constants extraction: Move constants to a dedicated
constants.tsfile
Runtime Environment
- Prefer isomorphic code: Write runtime-agnostic code that works in Node, browser, and workers whenever possible
- Clear runtime indicators: When code is environment-specific, add a comment at the top of the file:
// @env node
// @env browser
TypeScript
- Explicit return types: Declare return types explicitly when possible
- Avoid complex inline types: Extract complex types into dedicated
typeorinterfacedeclarations
Explicitness
Favor explicit, traceable code over implicit "magic". A reader (human or agent) should be able to follow where every name comes from without running tooling.
- Explicit imports: Prefer explicit
importstatements. Avoid auto-imports — when a framework provides them (e.g. Nuxt/Nitro), turn them off for new projects (see app-development). - No path aliases by default: Use relative imports (
./foo,../bar). Only use path aliases (@/,~/,#imports, etc.) when they are already configured in the project; don't introduce new ones for greenfield code.
Comments
- Avoid unnecessary comments: Code should be self-explanatory
- Explain "why" not "how": Comments should describe the reasoning or intent, not what the code does
Testing (Vitest)
- Test files:
foo.ts→foo.test.ts(same directory) - Use
describe/itAPI (nottest) - Use
toMatchSnapshotfor complex outputs - Use
toMatchFileSnapshotwith explicit path for language-specific snapshots
Tooling Choices
@antfu/ni Commands
| Command | Description |
|---|---|
ni |
Install dependencies |
ni <pkg> / ni -D <pkg> |
Add dependency / dev dependency |
nr <script> |
Run script |
nu |
Upgrade dependencies |
nun <pkg> |
Uninstall dependency |
nci |
Clean install (pnpm i --frozen-lockfile) |
nlx <pkg> |
Execute package (npx) |
Checking npm Package Versions
Use fast-npm-meta to look up the latest version of a package — it queries a small metadata endpoint instead of downloading the full registry payload (which can be megabytes per package).
nlx fast-npm-meta version vite # 7.3.1
nlx fast-npm-meta version "nuxt@^3.5" # 3.5.22 — range-aware
nlx fast-npm-meta version vite nuxt vue # multiple at once
nlx fast-npm-meta version vite --json # JSON for scripting
nlx fast-npm-meta full vite # full version list + dist-tags
Prefer this over npm view <pkg> version when you only need the latest version, and over reading package.json from the registry directly.
TypeScript Config
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
}
}
ESLint Setup
// eslint.config.js
import antfu from '@antfu/eslint-config'
export default antfu({
type: 'lib', // or 'app' (default)
pnpm: true, // enforce pnpm catalogs in package.json
antislop: true, // flag redundant/duplicated AI-style code, ban explicit `any`
})
Always enable antislop: true. It requires eslint-plugin-slop and eslint-plugin-sonarjs as dev dependencies.
For detailed configuration options: antfu-eslint-config
Knip
Use Knip to catch unused files, exports and dependencies — the linter only sees one file at a time.
// knip.json
{
"$schema": "https://unpkg.com/knip@6/schema.json",
"project": ["src/**/*.ts"],
"ignoreDependencies": []
}
Fix what Knip reports by deleting; only add to ignoreDependencies for tools invoked outside package.json scripts (e.g. taze).
Scripts and the ci Gate
Every project exposes a ci script that runs all checks in one command:
{
"scripts": {
"build": "tsdown",
"lint": "eslint --cache",
"typecheck": "tsc",
"knip": "knip",
"test": "pnpm run build && vitest",
"ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run"
}
}
Before every commit, run nr lint --fix to format, then nr ci and make sure it passes. Do not commit with a failing ci; fix the root cause instead of silencing rules or adding ignores.
Git Hooks
simple-git-hooks + nano-staged. The pre-commit hook runs the full ci gate; prepare also installs agent skills via skills-npm:
{
"scripts": {
"prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm"
},
"simple-git-hooks": {
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged"
},
"nano-staged": {
"*": "eslint --fix --no-warn-ignored"
}
}
Add .githooks to .gitignore and simple-git-hooks: true under allowBuilds in pnpm-workspace.yaml.
skills-npm
skills-npm symlinks agent skills shipped inside installed npm packages (and fetches those declared in a skills field) into the agent's skills directory on every install. Install as a dev dependency and wire it into prepare as above; add skills/npm-* to .gitignore. Commit the generated skills-npm-lock.json.
Publishing
Prefer npm Trusted Publishing (OIDC) over NPM_TOKEN secrets: releases run on CI from a v* tag via release.yml with id-token: write, and nr release (bumpp) only bumps, tags and pushes. Details: setting-up.
pnpm Catalogs
Use named catalogs in pnpm-workspace.yaml for version management:
| Catalog | Purpose |
|---|---|
prod |
Production dependencies |
inlined |
Bundler-inlined dependencies |
dev |
Dev tools (linter, bundler, testing) |
frontend |
Frontend libraries |
Avoid the default catalog. Catalog names can be adjusted per project needs.
References
| Topic | Description | Reference |
|---|---|---|
| ESLint Config | Framework support, antislop, formatters, rule overrides, VS Code settings | antfu-eslint-config |
| Project Setup | .gitignore, GitHub Actions, OIDC trusted publishing, VS Code extensions | setting-up |
| App Development | Vue/Nuxt/UnoCSS conventions, auto-import control, Storybook component testing | app-development |
| Library Development | tsdown bundling, pure ESM publishing, publint, API snapshots | library-development |
| Monorepo | pnpm workspaces, centralized alias, Turborepo | monorepo |
| 1 | |
| 2 | name antfu |
| 3 | description Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects. Use when setting up new projects, configuring ESLint/Prettier alternatives, monorepos, library publishing, or when the user mentions Anthony Fu's preferences. |
| 4 | metadata |
| 5 | author Anthony Fu |
| 6 | version "2026.09.30" |
| 7 | |
| 8 | |
| 9 | > Reference template: [antfu/starter-ts]. When scaffolding a new TypeScript project, mirror its `package.json` scripts, `eslint.config.js`, `knip.json`, `tsdown.config.ts` and workflows rather than inventing a new layout. |
| 10 | |
| 11 | ## Coding Practices |
| 12 | |
| 13 | ### Code Organization |
| 14 | |
| 15 | **Single responsibility**: Each source file should have a clear, focused scope/purpose |
| 16 | **Split large files**: Break files when they become large or handle too many concerns |
| 17 | **Type separation**: Always separate types and interfaces into `types.ts` or `types/*.ts` |
| 18 | **Constants extraction**: Move constants to a dedicated `constants.ts` file |
| 19 | |
| 20 | ### Runtime Environment |
| 21 | |
| 22 | **Prefer isomorphic code**: Write runtime-agnostic code that works in Node, browser, and workers whenever possible |
| 23 | **Clear runtime indicators**: When code is environment-specific, add a comment at the top of the file: |
| 24 | |
| 25 | |
| 26 | // @env node |
| 27 | // @env browser |
| 28 | |
| 29 | |
| 30 | ### TypeScript |
| 31 | |
| 32 | **Explicit return types**: Declare return types explicitly when possible |
| 33 | **Avoid complex inline types**: Extract complex types into dedicated `type` or `interface` declarations |
| 34 | |
| 35 | ### Explicitness |
| 36 | |
| 37 | Favor explicit, traceable code over implicit "magic". A reader (human or agent) should be able to follow where every name comes from without running tooling. |
| 38 | |
| 39 | **Explicit imports**: Prefer explicit `import` statements. Avoid auto-imports — when a framework provides them (e.g. Nuxt/Nitro), turn them off for new projects (see [app-development]). |
| 40 | **No path aliases by default**: Use relative imports (`./foo`, `../bar`). Only use path aliases (`@/`, `~/`, `#imports`, etc.) when they are *already* configured in the project; don't introduce new ones for greenfield code. |
| 41 | |
| 42 | ### Comments |
| 43 | |
| 44 | **Avoid unnecessary comments**: Code should be self-explanatory |
| 45 | **Explain "why" not "how"**: Comments should describe the reasoning or intent, not what the code does |
| 46 | |
| 47 | ### Testing (Vitest) |
| 48 | |
| 49 | Test files: `foo.ts` → `foo.test.ts` (same directory) |
| 50 | Use `describe`/`it` API (not `test`) |
| 51 | Use `toMatchSnapshot` for complex outputs |
| 52 | Use `toMatchFileSnapshot` with explicit path for language-specific snapshots |
| 53 | |
| 54 | |
| 55 | |
| 56 | ## Tooling Choices |
| 57 | |
| 58 | ### @antfu/ni Commands |
| 59 | |
| 60 | | Command | Description | |
| 61 | |---------|-------------| |
| 62 | | `ni` | Install dependencies | |
| 63 | | `ni <pkg>` / `ni -D <pkg>` | Add dependency / dev dependency | |
| 64 | | `nr <script>` | Run script | |
| 65 | | `nu` | Upgrade dependencies | |
| 66 | | `nun <pkg>` | Uninstall dependency | |
| 67 | | `nci` | Clean install (`pnpm i --frozen-lockfile`) | |
| 68 | | `nlx <pkg>` | Execute package (`npx`) | |
| 69 | |
| 70 | ### Checking npm Package Versions |
| 71 | |
| 72 | Use [`fast-npm-meta`] to look up the latest version of a package — it queries a small metadata endpoint instead of downloading the full registry payload (which can be megabytes per package). |
| 73 | |
| 74 | |
| 75 | nlx fast-npm-meta version vite # 7.3.1 |
| 76 | nlx fast-npm-meta version "nuxt@^3.5" # 3.5.22 — range-aware |
| 77 | nlx fast-npm-meta version vite nuxt vue # multiple at once |
| 78 | nlx fast-npm-meta version vite --json # JSON for scripting |
| 79 | nlx fast-npm-meta full vite # full version list + dist-tags |
| 80 | |
| 81 | |
| 82 | Prefer this over `npm view <pkg> version` when you only need the latest version, and over reading `package.json` from the registry directly. |
| 83 | |
| 84 | ### TypeScript Config |
| 85 | |
| 86 | |
| 87 | { |
| 88 | "compilerOptions": { |
| 89 | "target": "ESNext", |
| 90 | "module": "ESNext", |
| 91 | "moduleResolution": "bundler", |
| 92 | "strict": true, |
| 93 | "esModuleInterop": true, |
| 94 | "skipLibCheck": true, |
| 95 | "resolveJsonModule": true, |
| 96 | "isolatedModules": true, |
| 97 | "noEmit": true |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | |
| 102 | ### ESLint Setup |
| 103 | |
| 104 | |
| 105 | // eslint.config.js |
| 106 | import antfu from '@antfu/eslint-config' |
| 107 | |
| 108 | export default antfu({ |
| 109 | type: 'lib', // or 'app' (default) |
| 110 | pnpm: true, // enforce pnpm catalogs in package.json |
| 111 | antislop: true, // flag redundant/duplicated AI-style code, ban explicit `any` |
| 112 | }) |
| 113 | |
| 114 | |
| 115 | Always enable `antislop: true`. It requires `eslint-plugin-slop` and `eslint-plugin-sonarjs` as dev dependencies. |
| 116 | |
| 117 | For detailed configuration options: [antfu-eslint-config] |
| 118 | |
| 119 | ### Knip |
| 120 | |
| 121 | Use [Knip] to catch unused files, exports and dependencies — the linter only sees one file at a time. |
| 122 | |
| 123 | |
| 124 | // knip.json |
| 125 | { |
| 126 | "$schema": "https://unpkg.com/knip@6/schema.json", |
| 127 | "project": ["src/**/*.ts"], |
| 128 | "ignoreDependencies": [] |
| 129 | } |
| 130 | |
| 131 | |
| 132 | Fix what Knip reports by deleting; only add to `ignoreDependencies` for tools invoked outside `package.json` scripts (e.g. `taze`). |
| 133 | |
| 134 | ### Scripts and the `ci` Gate |
| 135 | |
| 136 | Every project exposes a `ci` script that runs all checks in one command: |
| 137 | |
| 138 | |
| 139 | { |
| 140 | "scripts": { |
| 141 | "build": "tsdown", |
| 142 | "lint": "eslint --cache", |
| 143 | "typecheck": "tsc", |
| 144 | "knip": "knip", |
| 145 | "test": "pnpm run build && vitest", |
| 146 | "ci": "pnpm run lint && pnpm run typecheck && pnpm run knip && pnpm run test --run" |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | |
| 151 | **Before every commit, run `nr lint --fix` to format, then `nr ci` and make sure it passes.** Do not commit with a failing `ci`; fix the root cause instead of silencing rules or adding ignores. |
| 152 | |
| 153 | ### Git Hooks |
| 154 | |
| 155 | `simple-git-hooks` + `nano-staged`. The pre-commit hook runs the full `ci` gate; `prepare` also installs agent skills via `skills-npm`: |
| 156 | |
| 157 | |
| 158 | { |
| 159 | "scripts": { |
| 160 | "prepare": "git config core.hooksPath .githooks && simple-git-hooks && skills-npm" |
| 161 | }, |
| 162 | "simple-git-hooks": { |
| 163 | "pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && pnpm run ci && pnpx nano-staged" |
| 164 | }, |
| 165 | "nano-staged": { |
| 166 | "*": "eslint --fix --no-warn-ignored" |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | |
| 171 | Add `.githooks` to `.gitignore` and `simple-git-hooks: true` under `allowBuilds` in `pnpm-workspace.yaml`. |
| 172 | |
| 173 | ### skills-npm |
| 174 | |
| 175 | [`skills-npm`] symlinks agent skills shipped inside installed npm packages (and fetches those declared in a `skills` field) into the agent's skills directory on every install. Install as a dev dependency and wire it into `prepare` as above; add `skills/npm-*` to `.gitignore`. Commit the generated `skills-npm-lock.json`. |
| 176 | |
| 177 | ### Publishing |
| 178 | |
| 179 | Prefer npm Trusted Publishing (OIDC) over `NPM_TOKEN` secrets: releases run on CI from a `v*` tag via `release.yml` with `id-token: write`, and `nr release` (`bumpp`) only bumps, tags and pushes. Details: [setting-up]. |
| 180 | |
| 181 | ### pnpm Catalogs |
| 182 | |
| 183 | Use named catalogs in `pnpm-workspace.yaml` for version management: |
| 184 | |
| 185 | | Catalog | Purpose | |
| 186 | |---------|---------| |
| 187 | | `prod` | Production dependencies | |
| 188 | | `inlined` | Bundler-inlined dependencies | |
| 189 | | `dev` | Dev tools (linter, bundler, testing) | |
| 190 | | `frontend` | Frontend libraries | |
| 191 | |
| 192 | Avoid the default catalog. Catalog names can be adjusted per project needs. |
| 193 | |
| 194 | |
| 195 | |
| 196 | ## References |
| 197 | |
| 198 | | Topic | Description | Reference | |
| 199 | |-------|-------------|-----------| |
| 200 | | ESLint Config | Framework support, antislop, formatters, rule overrides, VS Code settings | [antfu-eslint-config] | |
| 201 | | Project Setup | .gitignore, GitHub Actions, OIDC trusted publishing, VS Code extensions | [setting-up] | |
| 202 | | App Development | Vue/Nuxt/UnoCSS conventions, auto-import control, Storybook component testing | [app-development] | |
| 203 | | Library Development | tsdown bundling, pure ESM publishing, publint, API snapshots | [library-development] | |
| 204 | | Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo] | |
| 205 |
Discussion
Browse more free Claude skills.