|
| 1 | +# ESLint Config Development Guide |
| 2 | + |
| 3 | +## Architecture |
| 4 | + |
| 5 | +``` |
| 6 | +packages/eslint-config/src/ |
| 7 | +├── vdustr.ts # Main entry point, orchestrates all configs |
| 8 | +├── configs/ # Optional feature configs (emotion, mdx, prettier, etc.) |
| 9 | +├── extends/ # Extensions for antfu configs (react, yaml, etc.) |
| 10 | +├── lib/ # Dynamic import wrappers for optional dependencies |
| 11 | +├── utils/ # Shared utilities (mergeConfig, extendsConfig, etc.) |
| 12 | +└── types.ts # Type definitions |
| 13 | +``` |
| 14 | + |
| 15 | +## Key Patterns |
| 16 | + |
| 17 | +### Dynamic Imports for Optional Dependencies |
| 18 | + |
| 19 | +Optional ESLint plugins must use dynamic imports to avoid loading when disabled: |
| 20 | + |
| 21 | +```typescript |
| 22 | +// src/lib/eslint-react.ts |
| 23 | +import type EslintReact from "@eslint-react/eslint-plugin"; |
| 24 | +import { interopDefault } from "@antfu/eslint-config"; |
| 25 | + |
| 26 | +// IMPORTANT: Explicit return type required to avoid TS2742 build errors |
| 27 | +const eslintReact = (): Promise<typeof EslintReact> => |
| 28 | + interopDefault(import("@eslint-react/eslint-plugin")); |
| 29 | + |
| 30 | +export { eslintReact }; |
| 31 | +``` |
| 32 | + |
| 33 | +**Why explicit types?** Without them, TypeScript cannot infer types referencing internal modules (e.g., `@eslint-react/shared`), causing TS2742 errors during `unbuild`. |
| 34 | + |
| 35 | +### extendsConfig Pattern |
| 36 | + |
| 37 | +Use `extendsConfig` to extend antfu's configs conditionally: |
| 38 | + |
| 39 | +```typescript |
| 40 | +extendsConfig(composer, "antfu/react/rules", async (config) => { |
| 41 | + // Only executes if "antfu/react/rules" exists |
| 42 | + const plugin = await importPlugin(); |
| 43 | + return [config, modifiedConfig]; |
| 44 | +}); |
| 45 | +``` |
| 46 | + |
| 47 | +### Config Structure |
| 48 | + |
| 49 | +- `configs/` - Standalone configs that use `config.append()`, return arrays |
| 50 | +- `extends/` - Modify existing antfu configs via `extendsConfig()` |
| 51 | +- `lib/` - Dynamic import wrappers, always require explicit return types |
| 52 | + |
| 53 | +## Commands |
| 54 | + |
| 55 | +```bash |
| 56 | +pnpm build # Build @vp-tw/eslint-config (in packages/eslint-config) |
| 57 | +pnpm test # Run tests for @vp-tw/eslint-config |
| 58 | +pnpm run checkTypes # Type check entire workspace (root) |
| 59 | +pnpm lint # Lint entire workspace (root) |
| 60 | +``` |
| 61 | + |
| 62 | +## Release |
| 63 | + |
| 64 | +Managed by changesets. CI auto-publishes on main branch merge. |
| 65 | + |
| 66 | +```bash |
| 67 | +pnpm changeset # Create changeset (interactive) |
| 68 | +# Or manually create .changeset/<name>.md |
| 69 | +``` |
| 70 | + |
| 71 | +## Common Issues |
| 72 | + |
| 73 | +### TS2742: Inferred type cannot be named |
| 74 | + |
| 75 | +**Cause:** Dynamic import returns type referencing internal module. |
| 76 | +**Fix:** Add explicit return type annotation. |
| 77 | + |
| 78 | +```typescript |
| 79 | +// Bad - causes TS2742 |
| 80 | +const fn = () => interopDefault(import("pkg")); |
| 81 | + |
| 82 | +// Good |
| 83 | +const fn = (): Promise<(typeof import("pkg"))["default"]> => interopDefault(import("pkg")); |
| 84 | +``` |
| 85 | + |
| 86 | +### Feature not loading when enabled |
| 87 | + |
| 88 | +Check `vdustr.ts` for conditional logic. Features disabled by default need explicit enable check: |
| 89 | + |
| 90 | +```typescript |
| 91 | +const reactEnabled = Boolean(antfuOptions?.react ?? false); |
| 92 | +if (reactEnabled) { |
| 93 | + react(config, vpOptions?.extends?.react); |
| 94 | +} |
| 95 | +``` |
0 commit comments