Antfu skill

Anthony Fu's opinionated tooling and conventions for JavaScript/TypeScript projects.

by antfu·MIT license·★ 5,945 Stars on the repo·GitHub ↗

Use now

Files of Antfu

antfu/main1 file shown
SKILL.md
Show the full text205 lines

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.

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.ts or types/*.ts
  • Constants extraction: Move constants to a dedicated constants.ts file
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 type or interface declarations
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 import statements. 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/it API (not test)
  • Use toMatchSnapshot for complex outputs
  • Use toMatchFileSnapshot with 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---
2name: antfu
3description: 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.
4metadata:
5 author: Anthony Fu
6 version: "2026.09.30"
7---
8 
9> Reference template: [antfu/starter-ts](https://github.com/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```ts
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 
37Favor 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](references/app-development.md)).
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 
72Use [`fast-npm-meta`](https://github.com/antfu/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```bash
75nlx fast-npm-meta version vite # 7.3.1
76nlx fast-npm-meta version "nuxt@^3.5" # 3.5.22 — range-aware
77nlx fast-npm-meta version vite nuxt vue # multiple at once
78nlx fast-npm-meta version vite --json # JSON for scripting
79nlx fast-npm-meta full vite # full version list + dist-tags
80```
81 
82Prefer 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```json
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```js
105// eslint.config.js
106import antfu from '@antfu/eslint-config'
107 
108export 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 
115Always enable `antislop: true`. It requires `eslint-plugin-slop` and `eslint-plugin-sonarjs` as dev dependencies.
116 
117For detailed configuration options: [antfu-eslint-config](references/antfu-eslint-config.md)
118 
119### Knip
120 
121Use [Knip](https://knip.dev) to catch unused files, exports and dependencies — the linter only sees one file at a time.
122 
123```json
124// knip.json
125{
126 "$schema": "https://unpkg.com/knip@6/schema.json",
127 "project": ["src/**/*.ts"],
128 "ignoreDependencies": []
129}
130```
131 
132Fix 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 
136Every project exposes a `ci` script that runs all checks in one command:
137 
138```json
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```json
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 
171Add `.githooks` to `.gitignore` and `simple-git-hooks: true` under `allowBuilds` in `pnpm-workspace.yaml`.
172 
173### skills-npm
174 
175[`skills-npm`](https://github.com/antfu/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 
179Prefer 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](references/setting-up.md#publishing-with-npm-trusted-publishing-oidc).
180 
181### pnpm Catalogs
182 
183Use 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 
192Avoid 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](references/antfu-eslint-config.md) |
201| Project Setup | .gitignore, GitHub Actions, OIDC trusted publishing, VS Code extensions | [setting-up](references/setting-up.md) |
202| App Development | Vue/Nuxt/UnoCSS conventions, auto-import control, Storybook component testing | [app-development](references/app-development.md) |
203| Library Development | tsdown bundling, pure ESM publishing, publint, API snapshots | [library-development](references/library-development.md) |
204| Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) |
205 

Discussion