first
This commit is contained in:
222
.agents/skills/antfu/SKILL.md
Normal file
222
.agents/skills/antfu/SKILL.md
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
name: antfu
|
||||||
|
description: Anthony Fu's {Opinionated} preferences and best practices for web development
|
||||||
|
metadata:
|
||||||
|
author: Anthony Fu
|
||||||
|
version: "2026.1.28"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Anthony Fu's Preferences
|
||||||
|
|
||||||
|
This skill covers Anthony Fu's preferred tooling, configurations, and best practices for web development. This skill is opinionated.
|
||||||
|
|
||||||
|
## Quick Summary
|
||||||
|
|
||||||
|
| Category | Preference |
|
||||||
|
|----------|------------|
|
||||||
|
| Package Manager | pnpm |
|
||||||
|
| Language | TypeScript (strict mode) |
|
||||||
|
| Module System | ESM (`"type": "module"`) |
|
||||||
|
| Linting & Formatting | @antfu/eslint-config (no Prettier) |
|
||||||
|
| Testing | Vitest |
|
||||||
|
| Git Hooks | simple-git-hooks + lint-staged |
|
||||||
|
| Documentation | VitePress (in `docs/`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Stack
|
||||||
|
|
||||||
|
### Package Manager (pnpm)
|
||||||
|
|
||||||
|
Use pnpm as the package manager.
|
||||||
|
|
||||||
|
For monorepo setups, use pnpm workspaces:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Use pnpm named catalogs in `pnpm-workspace.yaml` to manage dependency versions:
|
||||||
|
|
||||||
|
| Catalog | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `prod` | Production dependencies |
|
||||||
|
| `inlined` | Dependencies inlined by bundler |
|
||||||
|
| `dev` | Development tools (linter, bundler, testing, dev-server) |
|
||||||
|
| `frontend` | Frontend libraries bundled into frontend |
|
||||||
|
|
||||||
|
Catalog names are not limited to the above and can be adjusted based on needs. Avoid using default catalog.
|
||||||
|
|
||||||
|
#### @antfu/ni
|
||||||
|
|
||||||
|
Use `@antfu/ni` for unified package manager commands. It auto-detects the package manager (pnpm/npm/yarn/bun) based on lockfile.
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `ni` | Install dependencies |
|
||||||
|
| `ni <pkg>` | Add dependency |
|
||||||
|
| `ni -D <pkg>` | Add dev dependency |
|
||||||
|
| `nr <script>` | Run script |
|
||||||
|
| `nu` | Upgrade dependencies |
|
||||||
|
| `nun <pkg>` | Uninstall dependency |
|
||||||
|
| `nci` | Clean install (like `pnpm i --frozen-lockfile`) |
|
||||||
|
| `nlx <pkg>` | Execute package (like `npx`) |
|
||||||
|
|
||||||
|
Install globally with `pnpm i -g @antfu/ni` if the commands are not found.
|
||||||
|
|
||||||
|
### TypeScript (Strict Mode)
|
||||||
|
|
||||||
|
Always use TypeScript with strict mode enabled.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ESM (ECMAScript Modules)
|
||||||
|
|
||||||
|
Always work in ESM mode. Set `"type": "module"` in `package.json`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
### ESLint (@antfu/eslint-config)
|
||||||
|
|
||||||
|
Use `@antfu/eslint-config` for both formatting and linting. This eliminates the need for Prettier.
|
||||||
|
|
||||||
|
Create `eslint.config.js` with `// @ts-check` comment:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// @ts-check
|
||||||
|
import antfu from '@antfu/eslint-config'
|
||||||
|
|
||||||
|
export default antfu()
|
||||||
|
```
|
||||||
|
|
||||||
|
Add script to `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint ."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When getting linting errors, try to fix them with `nr lint --fix`. Don't add `lint:fix` script.
|
||||||
|
|
||||||
|
### Git Hooks (simple-git-hooks + lint-staged)
|
||||||
|
|
||||||
|
Use `simple-git-hooks` with `lint-staged` for pre-commit linting:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*": "eslint --fix"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "npx simple-git-hooks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unit Testing (Vitest)
|
||||||
|
|
||||||
|
Use Vitest for unit testing.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Conventions:**
|
||||||
|
|
||||||
|
- Place test files next to source files: `foo.ts` → `foo.test.ts` (same directory)
|
||||||
|
- High-level tests go in `tests/` directory in each package
|
||||||
|
- Use `describe` and `it` API (not `test`)
|
||||||
|
- Use `expect` API for assertions
|
||||||
|
- Use `assert` only for TypeScript null assertions
|
||||||
|
- Use `toMatchSnapshot` for complex output assertions
|
||||||
|
- Use `toMatchFileSnapshot` with explicit file path and extension for language-specific output (exclude those files from linting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
### Publishing (Library Projects)
|
||||||
|
|
||||||
|
For library projects, publish through GitHub Releases triggered by `bumpp`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"release": "bumpp -r"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation (VitePress)
|
||||||
|
|
||||||
|
Use VitePress for documentation. Place docs under `docs/` directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── .vitepress/
|
||||||
|
│ └── config.ts
|
||||||
|
├── index.md
|
||||||
|
└── guide/
|
||||||
|
└── getting-started.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Add script to `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"docs:dev": "vitepress dev docs",
|
||||||
|
"docs:build": "vitepress build docs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
### Project Setup
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| @antfu/eslint-config | ESLint flat config for formatting and linting | [antfu-eslint-config](references/antfu-eslint-config.md) |
|
||||||
|
| GitHub Actions | Preferred workflows using sxzz/workflows | [github-actions](references/github-actions.md) |
|
||||||
|
| .gitignore | Preferred .gitignore for JS/TS projects | [gitignore](references/gitignore.md) |
|
||||||
|
| VS Code Extensions | Recommended extensions for development | [vscode-extensions](references/vscode-extensions.md) |
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| App Development | Preferences for Vue/Vite/Nuxt/UnoCSS web applications | [app-development](references/app-development.md) |
|
||||||
|
| Library Development | Preferences for bundling and publishing TypeScript libraries | [library-development](references/library-development.md) |
|
||||||
|
| Monorepo | pnpm workspaces, centralized alias, Turborepo | [monorepo](references/monorepo.md) |
|
||||||
328
.agents/skills/antfu/references/antfu-eslint-config.md
Normal file
328
.agents/skills/antfu/references/antfu-eslint-config.md
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
---
|
||||||
|
name: antfu-eslint-config
|
||||||
|
description: ESLint flat config for formatting and linting - replaces Prettier
|
||||||
|
---
|
||||||
|
|
||||||
|
# @antfu/eslint-config
|
||||||
|
|
||||||
|
A comprehensive ESLint flat config that handles both linting and formatting. Designed to replace Prettier entirely.
|
||||||
|
|
||||||
|
## Key Characteristics
|
||||||
|
|
||||||
|
- **No Prettier needed** - Handles all formatting via ESLint
|
||||||
|
- **ESLint Flat config** - Uses the new `eslint.config.js` format
|
||||||
|
- **Auto-detection** - TypeScript and Vue are detected automatically
|
||||||
|
- **Style principle**: Single quotes, no semicolons, sorted imports, dangling commas
|
||||||
|
- **Respects `.gitignore`** by default
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
Create `eslint.config.mjs` (or `eslint.config.js` with `// @ts-check`):
|
||||||
|
|
||||||
|
```js
|
||||||
|
// eslint.config.mjs
|
||||||
|
import antfu from '@antfu/eslint-config'
|
||||||
|
|
||||||
|
export default antfu()
|
||||||
|
```
|
||||||
|
|
||||||
|
Add scripts to `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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
|
||||||
|
-->
|
||||||
60
.agents/skills/antfu/references/app-development.md
Normal file
60
.agents/skills/antfu/references/app-development.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
name: app-development
|
||||||
|
description: Anthony Fu's preferences for building web applications with Vue, Vite/Nuxt, and UnoCSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# App Development Preferences
|
||||||
|
|
||||||
|
Preferences for building web applications.
|
||||||
|
|
||||||
|
## Stack Overview
|
||||||
|
|
||||||
|
| Aspect | Choice |
|
||||||
|
|--------|--------|
|
||||||
|
| Framework | Vue 3 (Composition API) |
|
||||||
|
| Build Tool | Vite (SPA) or Nuxt (SSR/SSG) |
|
||||||
|
| Styling | UnoCSS |
|
||||||
|
| Utilities | VueUse |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Framework Selection
|
||||||
|
|
||||||
|
| Use Case | Choice |
|
||||||
|
|----------|--------|
|
||||||
|
| SPA, client-only, library playgrounds | Vite + Vue |
|
||||||
|
| SSR, SSG, SEO-critical, file-based routing, API routes | Nuxt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Vue Conventions
|
||||||
|
|
||||||
|
| Convention | Preference |
|
||||||
|
|------------|------------|
|
||||||
|
| Script syntax | Always `<script setup lang="ts">` |
|
||||||
|
| State | Prefer `shallowRef()` over `ref()` |
|
||||||
|
| Objects | Use `ref()`, avoid `reactive()` |
|
||||||
|
|
||||||
|
### Props and Emits
|
||||||
|
|
||||||
|
Use TypeScript interfaces:
|
||||||
|
|
||||||
|
```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>
|
||||||
|
```
|
||||||
68
.agents/skills/antfu/references/github-actions.md
Normal file
68
.agents/skills/antfu/references/github-actions.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
name: github-actions
|
||||||
|
description: Anthony Fu's preferred GitHub Actions workflows using sxzz/workflows
|
||||||
|
---
|
||||||
|
|
||||||
|
# GitHub Actions
|
||||||
|
|
||||||
|
When setting up a new project, add these workflows. Skip if workflows already exist.
|
||||||
|
|
||||||
|
## 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@v1
|
||||||
|
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@v1
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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@v1
|
||||||
|
with:
|
||||||
|
publish: true
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write
|
||||||
|
```
|
||||||
|
|
||||||
|
All workflows use [sxzz/workflows](https://github.com/sxzz/workflows) reusable workflows.
|
||||||
29
.agents/skills/antfu/references/gitignore.md
Normal file
29
.agents/skills/antfu/references/gitignore.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
name: gitignore
|
||||||
|
description: Anthony Fu's preferred .gitignore for JavaScript/TypeScript projects
|
||||||
|
---
|
||||||
|
|
||||||
|
# .gitignore
|
||||||
|
|
||||||
|
When `.gitignore` is not present, create it with:
|
||||||
|
|
||||||
|
```
|
||||||
|
*.log
|
||||||
|
*.tgz
|
||||||
|
.cache
|
||||||
|
.DS_Store
|
||||||
|
.eslintcache
|
||||||
|
.idea
|
||||||
|
.env
|
||||||
|
.nuxt
|
||||||
|
.temp
|
||||||
|
.output
|
||||||
|
.turbo
|
||||||
|
cache
|
||||||
|
coverage
|
||||||
|
dist
|
||||||
|
lib-cov
|
||||||
|
logs
|
||||||
|
node_modules
|
||||||
|
temp
|
||||||
|
```
|
||||||
85
.agents/skills/antfu/references/library-development.md
Normal file
85
.agents/skills/antfu/references/library-development.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: library-development
|
||||||
|
description: Anthony Fu's preferences for building and publishing JavaScript/TypeScript libraries
|
||||||
|
---
|
||||||
|
|
||||||
|
# Library Development Preferences
|
||||||
|
|
||||||
|
Preferences for bundling and publishing TypeScript libraries.
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
|
||||||
|
| 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`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.
|
||||||
126
.agents/skills/antfu/references/monorepo.md
Normal file
126
.agents/skills/antfu/references/monorepo.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: monorepo
|
||||||
|
description: Anthony Fu's monorepo setup conventions using pnpm workspaces
|
||||||
|
---
|
||||||
|
|
||||||
|
# Monorepo Setup
|
||||||
|
|
||||||
|
Conventions for setting up and managing monorepos.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
```
|
||||||
34
.agents/skills/antfu/references/vscode-extensions.md
Normal file
34
.agents/skills/antfu/references/vscode-extensions.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
name: vscode-extensions
|
||||||
|
description: Recommended VS Code extensions
|
||||||
|
---
|
||||||
|
|
||||||
|
# VS Code Extensions
|
||||||
|
|
||||||
|
## Recommended Extensions
|
||||||
|
|
||||||
|
Configure in `.vscode/extensions.json` (choose based on the needs):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"antfu.pnpm-catalog-lens",
|
||||||
|
"antfu.iconify",
|
||||||
|
"antfu.unocss",
|
||||||
|
"antfu.slidev",
|
||||||
|
"vue.volar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extension Details
|
||||||
|
|
||||||
|
| 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 for presentations |
|
||||||
|
| `vue.volar` | Vue Language Features (official Vue extension) |
|
||||||
5
.agents/skills/pinia/GENERATION.md
Normal file
5
.agents/skills/pinia/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/pinia`
|
||||||
|
- **Git SHA:** `55dbfc5c20d4461748996aa74d8c0913e89fb98e`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
59
.agents/skills/pinia/SKILL.md
Normal file
59
.agents/skills/pinia/SKILL.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
name: pinia
|
||||||
|
description: Pinia official Vue state management library, type-safe and extensible. Use when defining stores, working with state/getters/actions, or implementing store patterns in Vue apps.
|
||||||
|
metadata:
|
||||||
|
author: Anthony Fu
|
||||||
|
version: "2026.1.28"
|
||||||
|
source: Generated from https://github.com/vuejs/pinia, scripts located at https://github.com/antfu/skills
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pinia
|
||||||
|
|
||||||
|
Pinia is the official state management library for Vue, designed to be intuitive and type-safe. It supports both Options API and Composition API styles, with first-class TypeScript support and devtools integration.
|
||||||
|
|
||||||
|
> The skill is based on Pinia v3.0.4, generated at 2026-01-28.
|
||||||
|
|
||||||
|
## Core References
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Stores | Defining stores, state, getters, actions, storeToRefs, subscriptions | [core-stores](references/core-stores.md) |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Extensibility
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Plugins | Extend stores with custom properties, state, and behavior | [features-plugins](references/features-plugins.md) |
|
||||||
|
|
||||||
|
### Composability
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Composables | Using Vue composables within stores (VueUse, etc.) | [features-composables](references/features-composables.md) |
|
||||||
|
| Composing Stores | Store-to-store communication, avoiding circular dependencies | [features-composing-stores](references/features-composing-stores.md) |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Testing | Unit testing with @pinia/testing, mocking, stubbing | [best-practices-testing](references/best-practices-testing.md) |
|
||||||
|
| Outside Components | Using stores in navigation guards, plugins, middlewares | [best-practices-outside-component](references/best-practices-outside-component.md) |
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| SSR | Server-side rendering, state hydration | [advanced-ssr](references/advanced-ssr.md) |
|
||||||
|
| Nuxt | Nuxt integration, auto-imports, SSR best practices | [advanced-nuxt](references/advanced-nuxt.md) |
|
||||||
|
| HMR | Hot module replacement for development | [advanced-hmr](references/advanced-hmr.md) |
|
||||||
|
|
||||||
|
## Key Recommendations
|
||||||
|
|
||||||
|
- **Prefer Setup Stores** for complex logic, composables, and watchers
|
||||||
|
- **Use `storeToRefs()`** when destructuring state/getters to preserve reactivity
|
||||||
|
- **Actions can be destructured directly** - they're bound to the store
|
||||||
|
- **Call stores inside functions** not at module scope, especially for SSR
|
||||||
|
- **Add HMR support** to each store for better development experience
|
||||||
|
- **Use `@pinia/testing`** for component tests with mocked stores
|
||||||
61
.agents/skills/pinia/references/advanced-hmr.md
Normal file
61
.agents/skills/pinia/references/advanced-hmr.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
name: hot-module-replacement
|
||||||
|
description: Enable HMR to preserve store state during development
|
||||||
|
---
|
||||||
|
|
||||||
|
# Hot Module Replacement (HMR)
|
||||||
|
|
||||||
|
Pinia supports HMR to edit stores without page reload, preserving existing state.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Add this snippet after each store definition:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
|
||||||
|
export const useAuth = defineStore('auth', {
|
||||||
|
// store options...
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useAuth, import.meta.hot))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Store Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore, acceptHMRUpdate } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const increment = () => count.value++
|
||||||
|
return { count, increment }
|
||||||
|
})
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept(acceptHMRUpdate(useCounterStore, import.meta.hot))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bundler Support
|
||||||
|
|
||||||
|
- **Vite:** Officially supported via `import.meta.hot`
|
||||||
|
- **Webpack:** Uses `import.meta.webpackHot`
|
||||||
|
- Any bundler implementing the `import.meta.hot` spec should work
|
||||||
|
|
||||||
|
## Nuxt
|
||||||
|
|
||||||
|
With `@pinia/nuxt`, `acceptHMRUpdate` is auto-imported but you still need to add the HMR snippet manually.
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
- Edit store logic without losing state
|
||||||
|
- Add/remove state, actions, and getters on the fly
|
||||||
|
- Faster development iteration
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/cookbook/hot-module-replacement.html
|
||||||
|
-->
|
||||||
119
.agents/skills/pinia/references/advanced-nuxt.md
Normal file
119
.agents/skills/pinia/references/advanced-nuxt.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
name: nuxt-integration
|
||||||
|
description: Using Pinia with Nuxt - auto-imports, SSR, and best practices
|
||||||
|
---
|
||||||
|
|
||||||
|
# Nuxt Integration
|
||||||
|
|
||||||
|
Pinia works seamlessly with Nuxt 3/4, handling SSR, serialization, and XSS protection automatically.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx nuxi@latest module add pinia
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs both `@pinia/nuxt` and `pinia`. If `pinia` isn't installed, add it manually.
|
||||||
|
|
||||||
|
> **npm users:** If you get `ERESOLVE unable to resolve dependency tree`, add to `package.json`:
|
||||||
|
> ```json
|
||||||
|
> "overrides": { "vue": "latest" }
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// nuxt.config.ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: ['@pinia/nuxt'],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Auto Imports
|
||||||
|
|
||||||
|
These are automatically available:
|
||||||
|
- `usePinia()` - get pinia instance
|
||||||
|
- `defineStore()` - define stores
|
||||||
|
- `storeToRefs()` - extract reactive refs
|
||||||
|
- `acceptHMRUpdate()` - HMR support
|
||||||
|
|
||||||
|
**All stores in `app/stores/` (Nuxt 4) or `stores/` are auto-imported.**
|
||||||
|
|
||||||
|
### Custom Store Directories
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// nuxt.config.ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: ['@pinia/nuxt'],
|
||||||
|
pinia: {
|
||||||
|
storesDirs: ['./stores/**', './custom-folder/stores/**'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fetching Data in Pages
|
||||||
|
|
||||||
|
Use `callOnce()` for SSR-friendly data fetching:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
// Run once, data persists across navigations
|
||||||
|
await callOnce('user', () => store.fetchUser())
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Refetch on Navigation
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
// Refetch on every navigation (like useFetch)
|
||||||
|
await callOnce('user', () => store.fetchUser(), { mode: 'navigation' })
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Stores Outside Components
|
||||||
|
|
||||||
|
In navigation guards, middlewares, or other stores, pass the `pinia` instance:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// middleware/auth.ts
|
||||||
|
export default defineNuxtRouteMiddleware((to) => {
|
||||||
|
const nuxtApp = useNuxtApp()
|
||||||
|
const store = useStore(nuxtApp.$pinia)
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !store.isLoggedIn) {
|
||||||
|
return navigateTo('/login')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Most of the time, you don't need this - just use stores in components or other injection-aware contexts.
|
||||||
|
|
||||||
|
## Pinia Plugins with Nuxt
|
||||||
|
|
||||||
|
Create a Nuxt plugin:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// plugins/myPiniaPlugin.ts
|
||||||
|
import { PiniaPluginContext } from 'pinia'
|
||||||
|
|
||||||
|
function MyPiniaPlugin({ store }: PiniaPluginContext) {
|
||||||
|
store.$subscribe((mutation) => {
|
||||||
|
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`)
|
||||||
|
})
|
||||||
|
return { creationTime: new Date() }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(({ $pinia }) => {
|
||||||
|
$pinia.use(MyPiniaPlugin)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/ssr/nuxt.html
|
||||||
|
-->
|
||||||
121
.agents/skills/pinia/references/advanced-ssr.md
Normal file
121
.agents/skills/pinia/references/advanced-ssr.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
name: server-side-rendering
|
||||||
|
description: SSR setup, state hydration, and avoiding cross-request state pollution
|
||||||
|
---
|
||||||
|
|
||||||
|
# Server Side Rendering (SSR)
|
||||||
|
|
||||||
|
Pinia works with SSR when stores are called at the top of `setup`, getters, or actions.
|
||||||
|
|
||||||
|
> **Using Nuxt?** See the [Nuxt integration](advanced-nuxt.md) instead.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
// ✅ Works - pinia knows the app context in setup
|
||||||
|
const main = useMainStore()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Store Outside setup()
|
||||||
|
|
||||||
|
Pass the `pinia` instance explicitly:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
// ✅ Pass pinia for correct SSR context
|
||||||
|
const main = useMainStore(pinia)
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !main.isLoggedIn) {
|
||||||
|
return '/login'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## serverPrefetch()
|
||||||
|
|
||||||
|
Access pinia via `this.$pinia`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
serverPrefetch() {
|
||||||
|
const store = useStore(this.$pinia)
|
||||||
|
return store.fetchData()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## onServerPrefetch()
|
||||||
|
|
||||||
|
Works normally:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
onServerPrefetch(async () => {
|
||||||
|
await store.fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## State Hydration
|
||||||
|
|
||||||
|
Serialize state on server and hydrate on client.
|
||||||
|
|
||||||
|
### Server Side
|
||||||
|
|
||||||
|
Use [devalue](https://github.com/Rich-Harris/devalue) for XSS-safe serialization:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import devalue from 'devalue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
// After rendering, state is available
|
||||||
|
const serializedState = devalue(pinia.state.value)
|
||||||
|
// Inject into HTML as global variable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Side
|
||||||
|
|
||||||
|
Hydrate before any `useStore()` call:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
// Hydrate from serialized state (e.g., from window.__pinia)
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
pinia.state.value = JSON.parse(window.__pinia)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR Examples
|
||||||
|
|
||||||
|
- [Vitesse template](https://github.com/antfu/vitesse/blob/main/src/modules/pinia.ts)
|
||||||
|
- [vite-plugin-ssr](https://vite-plugin-ssr.com/pinia)
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
1. Call stores inside functions, not at module scope
|
||||||
|
2. Pass `pinia` instance when using stores outside components in SSR
|
||||||
|
3. Hydrate state before calling any `useStore()`
|
||||||
|
4. Use `devalue` or similar for safe serialization
|
||||||
|
5. Avoid cross-request state pollution by creating fresh pinia per request
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/ssr/
|
||||||
|
-->
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
name: using-stores-outside-components
|
||||||
|
description: Correctly using stores in navigation guards, plugins, and other non-component contexts
|
||||||
|
---
|
||||||
|
|
||||||
|
# Using Stores Outside Components
|
||||||
|
|
||||||
|
Stores need the `pinia` instance, which is automatically injected in components. Outside components, you may need to provide it manually.
|
||||||
|
|
||||||
|
## Single Page Applications
|
||||||
|
|
||||||
|
Call stores **after** pinia is installed:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
|
||||||
|
// ❌ Fails - pinia not created yet
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
// ✅ Works - pinia is active
|
||||||
|
const userStore = useUserStore()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Navigation Guards
|
||||||
|
|
||||||
|
**Wrong:** Call at module level
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createRouter } from 'vue-router'
|
||||||
|
const router = createRouter({ /* ... */ })
|
||||||
|
|
||||||
|
// ❌ May fail depending on import order
|
||||||
|
const store = useUserStore()
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
if (store.isLoggedIn) { /* ... */ }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:** Call inside the guard
|
||||||
|
|
||||||
|
```ts
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
// ✅ Called after pinia is installed
|
||||||
|
const store = useUserStore()
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !store.isLoggedIn) {
|
||||||
|
return '/login'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR Applications
|
||||||
|
|
||||||
|
Always pass the `pinia` instance to `useStore()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.use(pinia)
|
||||||
|
|
||||||
|
router.beforeEach((to) => {
|
||||||
|
// ✅ Pass pinia instance
|
||||||
|
const main = useMainStore(pinia)
|
||||||
|
|
||||||
|
if (to.meta.requiresAuth && !main.isLoggedIn) {
|
||||||
|
return '/login'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## serverPrefetch()
|
||||||
|
|
||||||
|
Access pinia via `this.$pinia`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default {
|
||||||
|
serverPrefetch() {
|
||||||
|
const store = useStore(this.$pinia)
|
||||||
|
return store.fetchData()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## onServerPrefetch()
|
||||||
|
|
||||||
|
Works normally in `<script setup>`:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
onServerPrefetch(async () => {
|
||||||
|
// ✅ Just works
|
||||||
|
await store.fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Takeaway
|
||||||
|
|
||||||
|
Defer `useStore()` calls to functions that run after pinia is installed, rather than calling at module scope.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/core-concepts/outside-component-usage.html
|
||||||
|
-->
|
||||||
212
.agents/skills/pinia/references/best-practices-testing.md
Normal file
212
.agents/skills/pinia/references/best-practices-testing.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
---
|
||||||
|
name: testing
|
||||||
|
description: Unit testing stores and components with @pinia/testing
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing Stores
|
||||||
|
|
||||||
|
## Unit Testing Stores
|
||||||
|
|
||||||
|
Create a fresh pinia instance for each test:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
|
import { useCounterStore } from '../src/stores/counter'
|
||||||
|
|
||||||
|
describe('Counter Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('increments', () => {
|
||||||
|
const counter = useCounterStore()
|
||||||
|
expect(counter.n).toBe(0)
|
||||||
|
counter.increment()
|
||||||
|
expect(counter.n).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Plugins
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { somePlugin } from '../src/stores/plugin'
|
||||||
|
|
||||||
|
const app = createApp({})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const pinia = createPinia().use(somePlugin)
|
||||||
|
app.use(pinia)
|
||||||
|
setActivePinia(pinia)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Components
|
||||||
|
|
||||||
|
Install `@pinia/testing`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i -D @pinia/testing
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `createTestingPinia()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
|
import { useSomeStore } from '@/stores/myStore'
|
||||||
|
|
||||||
|
const wrapper = mount(Counter, {
|
||||||
|
global: {
|
||||||
|
plugins: [createTestingPinia()],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useSomeStore()
|
||||||
|
|
||||||
|
// Manipulate state directly
|
||||||
|
store.name = 'new name'
|
||||||
|
store.$patch({ name: 'new name' })
|
||||||
|
|
||||||
|
// Actions are stubbed by default
|
||||||
|
store.someAction()
|
||||||
|
expect(store.someAction).toHaveBeenCalledTimes(1)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Initial State
|
||||||
|
|
||||||
|
Set initial state for tests:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const wrapper = mount(Counter, {
|
||||||
|
global: {
|
||||||
|
plugins: [
|
||||||
|
createTestingPinia({
|
||||||
|
initialState: {
|
||||||
|
counter: { n: 20 }, // Store name → initial state
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Action Stubbing
|
||||||
|
|
||||||
|
### Execute Real Actions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
createTestingPinia({ stubActions: false })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Selective Stubbing
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Only stub specific actions
|
||||||
|
createTestingPinia({
|
||||||
|
stubActions: ['increment', 'reset'],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Or use a function
|
||||||
|
createTestingPinia({
|
||||||
|
stubActions: (actionName, store) => {
|
||||||
|
if (actionName.startsWith('set')) return true
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock Action Return Values
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { Mock } from 'vitest'
|
||||||
|
|
||||||
|
// After getting store
|
||||||
|
store.someAction.mockResolvedValue('mocked value')
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mocking Getters
|
||||||
|
|
||||||
|
Getters are writable in tests:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const pinia = createTestingPinia()
|
||||||
|
const counter = useCounterStore(pinia)
|
||||||
|
|
||||||
|
counter.double = 3 // Override computed value
|
||||||
|
|
||||||
|
// Reset to default behavior
|
||||||
|
counter.double = undefined
|
||||||
|
counter.double // Now computed normally
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Spy Function
|
||||||
|
|
||||||
|
If not using Jest/Vitest with globals:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
|
||||||
|
createTestingPinia({
|
||||||
|
createSpy: vi.fn,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
With Sinon:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import sinon from 'sinon'
|
||||||
|
|
||||||
|
createTestingPinia({
|
||||||
|
createSpy: sinon.spy,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pinia Plugins in Tests
|
||||||
|
|
||||||
|
Pass plugins to `createTestingPinia()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { somePlugin } from '../src/stores/plugin'
|
||||||
|
|
||||||
|
createTestingPinia({
|
||||||
|
stubActions: false,
|
||||||
|
plugins: [somePlugin],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Don't use** `testingPinia.use(MyPlugin)` - pass plugins in options.
|
||||||
|
|
||||||
|
## Type-Safe Mocked Store
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { Mock } from 'vitest'
|
||||||
|
import type { Store, StoreDefinition } from 'pinia'
|
||||||
|
|
||||||
|
function mockedStore<TStoreDef extends () => unknown>(
|
||||||
|
useStore: TStoreDef
|
||||||
|
): TStoreDef extends StoreDefinition<infer Id, infer State, infer Getters, infer Actions>
|
||||||
|
? Store<Id, State, Record<string, never>, {
|
||||||
|
[K in keyof Actions]: Actions[K] extends (...args: any[]) => any
|
||||||
|
? Mock<Actions[K]>
|
||||||
|
: Actions[K]
|
||||||
|
}>
|
||||||
|
: ReturnType<TStoreDef> {
|
||||||
|
return useStore() as any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const store = mockedStore(useSomeStore)
|
||||||
|
store.someAction.mockResolvedValue('value') // Typed!
|
||||||
|
```
|
||||||
|
|
||||||
|
## E2E Tests
|
||||||
|
|
||||||
|
No special handling needed - Pinia works normally.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/cookbook/testing.html
|
||||||
|
-->
|
||||||
389
.agents/skills/pinia/references/core-stores.md
Normal file
389
.agents/skills/pinia/references/core-stores.md
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
---
|
||||||
|
name: stores
|
||||||
|
description: Defining stores, state, getters, and actions in Pinia
|
||||||
|
---
|
||||||
|
|
||||||
|
# Pinia Stores
|
||||||
|
|
||||||
|
Stores are defined using `defineStore()` with a unique name. Each store has three core concepts: **state**, **getters**, and **actions**.
|
||||||
|
|
||||||
|
## Defining Stores
|
||||||
|
|
||||||
|
### Option Stores
|
||||||
|
|
||||||
|
Similar to Vue's Options API:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', {
|
||||||
|
state: () => ({
|
||||||
|
count: 0,
|
||||||
|
name: 'Eduardo',
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
doubleCount: (state) => state.count * 2,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
increment() {
|
||||||
|
this.count++
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Think of `state` as `data`, `getters` as `computed`, and `actions` as `methods`.
|
||||||
|
|
||||||
|
### Setup Stores (Recommended)
|
||||||
|
|
||||||
|
Uses Composition API syntax - more flexible and powerful:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const name = ref('Eduardo')
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, name, doubleCount, increment }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
In Setup Stores: `ref()` → state, `computed()` → getters, `function()` → actions.
|
||||||
|
|
||||||
|
**Important:** You must return all state properties for Pinia to track them.
|
||||||
|
|
||||||
|
### Using Stores
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { useCounterStore } from '@/stores/counter'
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
// Access: store.count, store.doubleCount, store.increment()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Destructuring with storeToRefs
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useCounterStore } from '@/stores/counter'
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
|
||||||
|
// ❌ Breaks reactivity
|
||||||
|
const { name, doubleCount } = store
|
||||||
|
|
||||||
|
// ✅ Preserves reactivity for state/getters
|
||||||
|
const { name, doubleCount } = storeToRefs(store)
|
||||||
|
|
||||||
|
// ✅ Actions can be destructured directly
|
||||||
|
const { increment } = store
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
State is defined as a function returning the initial state.
|
||||||
|
|
||||||
|
### TypeScript
|
||||||
|
|
||||||
|
Type inference works automatically. For complex types:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface UserInfo {
|
||||||
|
name: string
|
||||||
|
age: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', {
|
||||||
|
state: () => ({
|
||||||
|
userList: [] as UserInfo[],
|
||||||
|
user: null as UserInfo | null,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use an interface for the return type:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface State {
|
||||||
|
userList: UserInfo[]
|
||||||
|
user: UserInfo | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', {
|
||||||
|
state: (): State => ({
|
||||||
|
userList: [],
|
||||||
|
user: null,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing and Modifying
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const store = useStore()
|
||||||
|
store.count++
|
||||||
|
```
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<input v-model="store.count" type="number" />
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mutating with $patch
|
||||||
|
|
||||||
|
Apply multiple changes at once:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Object syntax
|
||||||
|
store.$patch({
|
||||||
|
count: store.count + 1,
|
||||||
|
name: 'DIO',
|
||||||
|
})
|
||||||
|
|
||||||
|
// Function syntax (for complex mutations)
|
||||||
|
store.$patch((state) => {
|
||||||
|
state.items.push({ name: 'shoes', quantity: 1 })
|
||||||
|
state.hasChanged = true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resetting State
|
||||||
|
|
||||||
|
Option Stores have built-in `$reset()`. For Setup Stores, implement your own:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
function $reset() {
|
||||||
|
count.value = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, $reset }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribing to State Changes
|
||||||
|
|
||||||
|
```ts
|
||||||
|
cartStore.$subscribe((mutation, state) => {
|
||||||
|
mutation.type // 'direct' | 'patch object' | 'patch function'
|
||||||
|
mutation.storeId // 'cart'
|
||||||
|
mutation.payload // patch object (only for 'patch object')
|
||||||
|
|
||||||
|
localStorage.setItem('cart', JSON.stringify(state))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Options
|
||||||
|
cartStore.$subscribe(callback, { flush: 'sync' }) // Immediate
|
||||||
|
cartStore.$subscribe(callback, { detached: true }) // Keep after unmount
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getters
|
||||||
|
|
||||||
|
Getters are computed values, equivalent to Vue's `computed()`.
|
||||||
|
|
||||||
|
### Basic Getters
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getters: {
|
||||||
|
doubleCount: (state) => state.count * 2,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Other Getters
|
||||||
|
|
||||||
|
Use `this` with explicit return type:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getters: {
|
||||||
|
doubleCount: (state) => state.count * 2,
|
||||||
|
doublePlusOne(): number {
|
||||||
|
return this.doubleCount + 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Getters with Arguments
|
||||||
|
|
||||||
|
Return a function (note: loses caching):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getters: {
|
||||||
|
getUserById: (state) => {
|
||||||
|
return (userId: string) => state.users.find((user) => user.id === userId)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
Cache within parameterized getters:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
getters: {
|
||||||
|
getActiveUserById(state) {
|
||||||
|
const activeUsers = state.users.filter((user) => user.active)
|
||||||
|
return (userId: string) => activeUsers.find((user) => user.id === userId)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Other Stores in Getters
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useOtherStore } from './other-store'
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
combined(state) {
|
||||||
|
const otherStore = useOtherStore()
|
||||||
|
return state.localData + otherStore.data
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
Actions are methods for business logic. Unlike getters, they can be asynchronous.
|
||||||
|
|
||||||
|
### Defining Actions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
actions: {
|
||||||
|
increment() {
|
||||||
|
this.count++
|
||||||
|
},
|
||||||
|
randomizeCounter() {
|
||||||
|
this.count = Math.round(100 * Math.random())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Async Actions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
actions: {
|
||||||
|
async registerUser(login: string, password: string) {
|
||||||
|
try {
|
||||||
|
this.userData = await api.post({ login, password })
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Other Stores in Actions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useAuthStore } from './auth-store'
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async fetchUserPreferences() {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
if (auth.isAuthenticated) {
|
||||||
|
this.preferences = await fetchPreferences()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
**SSR:** Call all `useStore()` before any `await`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
async orderCart() {
|
||||||
|
// ✅ Call stores before await
|
||||||
|
const user = useUserStore()
|
||||||
|
|
||||||
|
await apiOrderCart(user.token, this.items)
|
||||||
|
// ❌ Don't call useStore() after await in SSR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subscribing to Actions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const unsubscribe = someStore.$onAction(
|
||||||
|
({ name, store, args, after, onError }) => {
|
||||||
|
const startTime = Date.now()
|
||||||
|
console.log(`Start "${name}" with params [${args.join(', ')}]`)
|
||||||
|
|
||||||
|
after((result) => {
|
||||||
|
console.log(`Finished "${name}" after ${Date.now() - startTime}ms`)
|
||||||
|
})
|
||||||
|
|
||||||
|
onError((error) => {
|
||||||
|
console.warn(`Failed "${name}": ${error}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
unsubscribe() // Cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep subscription after component unmount:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
someStore.$onAction(callback, true)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Options API Helpers
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { mapState, mapWritableState, mapActions } from 'pinia'
|
||||||
|
import { useCounterStore } from '../stores/counter'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
// Readonly state/getters
|
||||||
|
...mapState(useCounterStore, ['count', 'doubleCount']),
|
||||||
|
// Writable state
|
||||||
|
...mapWritableState(useCounterStore, ['count']),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useCounterStore, ['increment']),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accessing Global Providers in Setup Stores
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { inject } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export const useSearchFilters = defineStore('search-filters', () => {
|
||||||
|
const route = useRoute()
|
||||||
|
const appProvided = inject('appProvided')
|
||||||
|
|
||||||
|
// Don't return these - access them directly in components
|
||||||
|
return { /* ... */ }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/core-concepts/
|
||||||
|
- https://pinia.vuejs.org/core-concepts/state.html
|
||||||
|
- https://pinia.vuejs.org/core-concepts/getters.html
|
||||||
|
- https://pinia.vuejs.org/core-concepts/actions.html
|
||||||
|
-->
|
||||||
114
.agents/skills/pinia/references/features-composables.md
Normal file
114
.agents/skills/pinia/references/features-composables.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
name: composables-in-stores
|
||||||
|
description: Using Vue composables within Pinia stores
|
||||||
|
---
|
||||||
|
|
||||||
|
# Composables in Stores
|
||||||
|
|
||||||
|
Pinia stores can leverage Vue composables for reusable stateful logic.
|
||||||
|
|
||||||
|
## Option Stores
|
||||||
|
|
||||||
|
Call composables inside the `state` property, but only those returning writable refs:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: () => ({
|
||||||
|
user: useLocalStorage('pinia/auth/login', 'bob'),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Works:** Composables returning `ref()`:
|
||||||
|
- `useLocalStorage`
|
||||||
|
- `useAsyncState`
|
||||||
|
|
||||||
|
**Doesn't work in Option Stores:**
|
||||||
|
- Composables exposing functions
|
||||||
|
- Composables exposing readonly data
|
||||||
|
|
||||||
|
## Setup Stores
|
||||||
|
|
||||||
|
More flexible - can use almost any composable:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useMediaControls } from '@vueuse/core'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export const useVideoPlayer = defineStore('video', () => {
|
||||||
|
const videoElement = ref<HTMLVideoElement>()
|
||||||
|
const src = ref('/data/video.mp4')
|
||||||
|
const { playing, volume, currentTime, togglePictureInPicture } =
|
||||||
|
useMediaControls(videoElement, { src })
|
||||||
|
|
||||||
|
function loadVideo(element: HTMLVideoElement, newSrc: string) {
|
||||||
|
videoElement.value = element
|
||||||
|
src.value = newSrc
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
src,
|
||||||
|
playing,
|
||||||
|
volume,
|
||||||
|
currentTime,
|
||||||
|
loadVideo,
|
||||||
|
togglePictureInPicture,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Don't return non-serializable DOM refs like `videoElement` - they're internal implementation details.
|
||||||
|
|
||||||
|
## SSR Considerations
|
||||||
|
|
||||||
|
### Option Stores with hydrate()
|
||||||
|
|
||||||
|
Define a `hydrate()` function to handle client-side hydration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: () => ({
|
||||||
|
user: useLocalStorage('pinia/auth/login', 'bob'),
|
||||||
|
}),
|
||||||
|
|
||||||
|
hydrate(state, initialState) {
|
||||||
|
// Ignore server state, read from browser
|
||||||
|
state.user = useLocalStorage('pinia/auth/login', 'bob')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup Stores with skipHydrate()
|
||||||
|
|
||||||
|
Mark state that shouldn't hydrate from server:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore, skipHydrate } from 'pinia'
|
||||||
|
import { useEyeDropper, useLocalStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
export const useColorStore = defineStore('colors', () => {
|
||||||
|
const { isSupported, open, sRGBHex } = useEyeDropper()
|
||||||
|
const lastColor = useLocalStorage('lastColor', sRGBHex)
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Skip hydration for client-only state
|
||||||
|
lastColor: skipHydrate(lastColor),
|
||||||
|
open, // Function - no hydration needed
|
||||||
|
isSupported, // Boolean - not reactive
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
`skipHydrate()` only applies to state properties (refs), not functions or non-reactive values.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/cookbook/composables.html
|
||||||
|
-->
|
||||||
134
.agents/skills/pinia/references/features-composing-stores.md
Normal file
134
.agents/skills/pinia/references/features-composing-stores.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
name: composing-stores
|
||||||
|
description: Store-to-store communication and avoiding circular dependencies
|
||||||
|
---
|
||||||
|
|
||||||
|
# Composing Stores
|
||||||
|
|
||||||
|
Stores can use each other for shared state and logic.
|
||||||
|
|
||||||
|
## Rule: Avoid Circular Dependencies
|
||||||
|
|
||||||
|
Two stores cannot directly read each other's state during setup:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// ❌ Infinite loop
|
||||||
|
const useX = defineStore('x', () => {
|
||||||
|
const y = useY()
|
||||||
|
y.name // Don't read here!
|
||||||
|
return { name: ref('X') }
|
||||||
|
})
|
||||||
|
|
||||||
|
const useY = defineStore('y', () => {
|
||||||
|
const x = useX()
|
||||||
|
x.name // Don't read here!
|
||||||
|
return { name: ref('Y') }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Read in getters, computed, or actions:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const useX = defineStore('x', () => {
|
||||||
|
const y = useY()
|
||||||
|
|
||||||
|
// ✅ Read in computed/actions
|
||||||
|
function doSomething() {
|
||||||
|
const yName = y.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: ref('X'), doSomething }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Stores: Use Store at Top
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { useUserStore } from './user'
|
||||||
|
|
||||||
|
export const useCartStore = defineStore('cart', () => {
|
||||||
|
const user = useUserStore()
|
||||||
|
const list = ref([])
|
||||||
|
|
||||||
|
const summary = computed(() => {
|
||||||
|
return `Hi ${user.name}, you have ${list.value.length} items`
|
||||||
|
})
|
||||||
|
|
||||||
|
function purchase() {
|
||||||
|
return apiPurchase(user.id, list.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list, summary, purchase }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Getters
|
||||||
|
|
||||||
|
Call `useStore()` inside a getter:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useUserStore } from './user'
|
||||||
|
|
||||||
|
export const useCartStore = defineStore('cart', {
|
||||||
|
getters: {
|
||||||
|
summary(state) {
|
||||||
|
const user = useUserStore()
|
||||||
|
return `Hi ${user.name}, you have ${state.list.length} items`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Actions
|
||||||
|
|
||||||
|
Call `useStore()` inside an action:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useUserStore } from './user'
|
||||||
|
import { apiOrderCart } from './api'
|
||||||
|
|
||||||
|
export const useCartStore = defineStore('cart', {
|
||||||
|
actions: {
|
||||||
|
async orderCart() {
|
||||||
|
const user = useUserStore()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiOrderCart(user.token, this.items)
|
||||||
|
this.emptyCart()
|
||||||
|
} catch (err) {
|
||||||
|
displayError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR: Call Stores Before Await
|
||||||
|
|
||||||
|
In async actions, call all stores before any `await`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
actions: {
|
||||||
|
async orderCart() {
|
||||||
|
// ✅ All useStore() calls before await
|
||||||
|
const user = useUserStore()
|
||||||
|
const analytics = useAnalyticsStore()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiOrderCart(user.token, this.items)
|
||||||
|
// ❌ Don't call useStore() after await (SSR issue)
|
||||||
|
// const otherStore = useOtherStore()
|
||||||
|
} catch (err) {
|
||||||
|
displayError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures the correct Pinia instance is used during SSR.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/cookbook/composing-stores.html
|
||||||
|
-->
|
||||||
203
.agents/skills/pinia/references/features-plugins.md
Normal file
203
.agents/skills/pinia/references/features-plugins.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
---
|
||||||
|
name: plugins
|
||||||
|
description: Extend stores with custom properties, methods, and behavior
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugins
|
||||||
|
|
||||||
|
Plugins extend all stores with custom properties, methods, or behavior.
|
||||||
|
|
||||||
|
## Basic Plugin
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
|
||||||
|
function SecretPiniaPlugin() {
|
||||||
|
return { secret: 'the cake is a lie' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(SecretPiniaPlugin)
|
||||||
|
|
||||||
|
// In any store
|
||||||
|
const store = useStore()
|
||||||
|
store.secret // 'the cake is a lie'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin Context
|
||||||
|
|
||||||
|
Plugins receive a context object:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { PiniaPluginContext } from 'pinia'
|
||||||
|
|
||||||
|
export function myPiniaPlugin(context: PiniaPluginContext) {
|
||||||
|
context.pinia // pinia instance
|
||||||
|
context.app // Vue app instance
|
||||||
|
context.store // store being augmented
|
||||||
|
context.options // store definition options
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding Properties
|
||||||
|
|
||||||
|
Return an object to add properties (tracked in devtools):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pinia.use(() => ({ hello: 'world' }))
|
||||||
|
```
|
||||||
|
|
||||||
|
Or set directly on store:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pinia.use(({ store }) => {
|
||||||
|
store.hello = 'world'
|
||||||
|
// For devtools visibility in dev mode
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
store._customProperties.add('hello')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding State
|
||||||
|
|
||||||
|
Add to both `store` and `store.$state` for SSR/devtools:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { toRef, ref } from 'vue'
|
||||||
|
|
||||||
|
pinia.use(({ store }) => {
|
||||||
|
if (!store.$state.hasOwnProperty('hasError')) {
|
||||||
|
const hasError = ref(false)
|
||||||
|
store.$state.hasError = hasError
|
||||||
|
}
|
||||||
|
store.hasError = toRef(store.$state, 'hasError')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding External Properties
|
||||||
|
|
||||||
|
Wrap non-reactive objects with `markRaw()`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { markRaw } from 'vue'
|
||||||
|
import { router } from './router'
|
||||||
|
|
||||||
|
pinia.use(({ store }) => {
|
||||||
|
store.router = markRaw(router)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Store Options
|
||||||
|
|
||||||
|
Define custom options consumed by plugins:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Store definition
|
||||||
|
defineStore('search', {
|
||||||
|
actions: {
|
||||||
|
searchContacts() { /* ... */ },
|
||||||
|
},
|
||||||
|
debounce: {
|
||||||
|
searchContacts: 300,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Plugin reads custom option
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
|
||||||
|
pinia.use(({ options, store }) => {
|
||||||
|
if (options.debounce) {
|
||||||
|
return Object.keys(options.debounce).reduce((acc, action) => {
|
||||||
|
acc[action] = debounce(store[action], options.debounce[action])
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For Setup Stores, pass options as third argument:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
defineStore(
|
||||||
|
'search',
|
||||||
|
() => { /* ... */ },
|
||||||
|
{
|
||||||
|
debounce: { searchContacts: 300 },
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Augmentation
|
||||||
|
|
||||||
|
### Custom Properties
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import 'pinia'
|
||||||
|
import type { Router } from 'vue-router'
|
||||||
|
|
||||||
|
declare module 'pinia' {
|
||||||
|
export interface PiniaCustomProperties {
|
||||||
|
router: Router
|
||||||
|
hello: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom State
|
||||||
|
|
||||||
|
```ts
|
||||||
|
declare module 'pinia' {
|
||||||
|
export interface PiniaCustomStateProperties<S> {
|
||||||
|
hasError: boolean
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
declare module 'pinia' {
|
||||||
|
export interface DefineStoreOptionsBase<S, Store> {
|
||||||
|
debounce?: Partial<Record<keyof StoreActions<Store>, number>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subscribe in Plugins
|
||||||
|
|
||||||
|
```ts
|
||||||
|
pinia.use(({ store }) => {
|
||||||
|
store.$subscribe(() => {
|
||||||
|
// React to state changes
|
||||||
|
})
|
||||||
|
store.$onAction(() => {
|
||||||
|
// React to actions
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Nuxt Plugin
|
||||||
|
|
||||||
|
Create a Nuxt plugin to add Pinia plugins:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// plugins/myPiniaPlugin.ts
|
||||||
|
import { PiniaPluginContext } from 'pinia'
|
||||||
|
|
||||||
|
function MyPiniaPlugin({ store }: PiniaPluginContext) {
|
||||||
|
store.$subscribe((mutation) => {
|
||||||
|
console.log(`[🍍 ${mutation.storeId}]: ${mutation.type}`)
|
||||||
|
})
|
||||||
|
return { creationTime: new Date() }
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineNuxtPlugin(({ $pinia }) => {
|
||||||
|
$pinia.use(MyPiniaPlugin)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pinia.vuejs.org/core-concepts/plugins.html
|
||||||
|
-->
|
||||||
5
.agents/skills/pnpm/GENERATION.md
Normal file
5
.agents/skills/pnpm/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/pnpm`
|
||||||
|
- **Git SHA:** `a1d6d5aef9d5f369fa2f0d8a54f1edbaff8b23b3`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
42
.agents/skills/pnpm/SKILL.md
Normal file
42
.agents/skills/pnpm/SKILL.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
name: pnpm
|
||||||
|
description: Node.js package manager with strict dependency resolution. Use when running pnpm specific commands, configuring workspaces, or managing dependencies with catalogs, patches, or overrides.
|
||||||
|
metadata:
|
||||||
|
author: Anthony Fu
|
||||||
|
version: "2026.1.28"
|
||||||
|
source: Generated from https://github.com/pnpm/pnpm, scripts located at https://github.com/antfu/skills
|
||||||
|
---
|
||||||
|
|
||||||
|
pnpm is a fast, disk space efficient package manager. It uses a content-addressable store to deduplicate packages across all projects on a machine, saving significant disk space. pnpm enforces strict dependency resolution by default, preventing phantom dependencies. Configuration should preferably be placed in `pnpm-workspace.yaml` for pnpm-specific settings.
|
||||||
|
|
||||||
|
**Important:** When working with pnpm projects, agents should check for `pnpm-workspace.yaml` and `.npmrc` files to understand workspace structure and configuration. Always use `--frozen-lockfile` in CI environments.
|
||||||
|
|
||||||
|
> The skill is based on pnpm 10.x, generated at 2026-01-28.
|
||||||
|
|
||||||
|
## Core
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| CLI Commands | Install, add, remove, update, run, exec, dlx, and workspace commands | [core-cli](references/core-cli.md) |
|
||||||
|
| Configuration | pnpm-workspace.yaml, .npmrc settings, and package.json fields | [core-config](references/core-config.md) |
|
||||||
|
| Workspaces | Monorepo support with filtering, workspace protocol, and shared lockfile | [core-workspaces](references/core-workspaces.md) |
|
||||||
|
| Store | Content-addressable storage, hard links, and disk efficiency | [core-store](references/core-store.md) |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Catalogs | Centralized dependency version management for workspaces | [features-catalogs](references/features-catalogs.md) |
|
||||||
|
| Overrides | Force specific versions of dependencies including transitive | [features-overrides](references/features-overrides.md) |
|
||||||
|
| Patches | Modify third-party packages with custom fixes | [features-patches](references/features-patches.md) |
|
||||||
|
| Aliases | Install packages under custom names using npm: protocol | [features-aliases](references/features-aliases.md) |
|
||||||
|
| Hooks | Customize resolution with .pnpmfile.cjs hooks | [features-hooks](references/features-hooks.md) |
|
||||||
|
| Peer Dependencies | Auto-install, strict mode, and dependency rules | [features-peer-deps](references/features-peer-deps.md) |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| CI/CD Setup | GitHub Actions, GitLab CI, Docker, and caching strategies | [best-practices-ci](references/best-practices-ci.md) |
|
||||||
|
| Migration | Migrating from npm/Yarn, handling phantom deps, monorepo migration | [best-practices-migration](references/best-practices-migration.md) |
|
||||||
|
| Performance | Install optimizations, store caching, workspace parallelization | [best-practices-performance](references/best-practices-performance.md) |
|
||||||
285
.agents/skills/pnpm/references/best-practices-ci.md
Normal file
285
.agents/skills/pnpm/references/best-practices-ci.md
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-ci-cd-setup
|
||||||
|
description: Optimizing pnpm for continuous integration and deployment workflows
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm CI/CD Setup
|
||||||
|
|
||||||
|
Best practices for using pnpm in CI/CD environments for fast, reliable builds.
|
||||||
|
|
||||||
|
## GitHub Actions
|
||||||
|
|
||||||
|
### Basic Setup
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: 'pnpm'
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm test
|
||||||
|
- run: pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Store Caching
|
||||||
|
|
||||||
|
For larger projects, cache the pnpm store:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
name: Setup pnpm cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Matrix Testing
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
node: [18, 20, 22]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
cache: 'pnpm'
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitLab CI
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: node:20
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- install
|
||||||
|
- test
|
||||||
|
- build
|
||||||
|
|
||||||
|
variables:
|
||||||
|
PNPM_HOME: /root/.local/share/pnpm
|
||||||
|
PATH: $PNPM_HOME:$PATH
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- corepack enable
|
||||||
|
- corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
cache:
|
||||||
|
key: ${CI_COMMIT_REF_SLUG}
|
||||||
|
paths:
|
||||||
|
- .pnpm-store
|
||||||
|
|
||||||
|
install:
|
||||||
|
stage: install
|
||||||
|
script:
|
||||||
|
- pnpm config set store-dir .pnpm-store
|
||||||
|
- pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- pnpm test
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
script:
|
||||||
|
- pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
### Multi-Stage Build
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Build stage
|
||||||
|
FROM node:20-slim AS builder
|
||||||
|
|
||||||
|
# Enable corepack for pnpm
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files first for layer caching
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
COPY packages/*/package.json ./packages/
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source and build
|
||||||
|
COPY . .
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-slim AS runner
|
||||||
|
|
||||||
|
RUN corepack enable
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/package.json ./
|
||||||
|
COPY --from=builder /app/pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Production install
|
||||||
|
RUN pnpm install --frozen-lockfile --prod
|
||||||
|
|
||||||
|
CMD ["node", "dist/index.js"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optimized for Monorepos
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
FROM node:20-slim AS builder
|
||||||
|
RUN corepack enable
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy workspace config
|
||||||
|
COPY pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||||
|
|
||||||
|
# Copy all package.json files maintaining structure
|
||||||
|
COPY packages/core/package.json ./packages/core/
|
||||||
|
COPY packages/api/package.json ./packages/api/
|
||||||
|
|
||||||
|
# Install all dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build specific package
|
||||||
|
RUN pnpm --filter @myorg/api build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key CI Flags
|
||||||
|
|
||||||
|
### --frozen-lockfile
|
||||||
|
|
||||||
|
**Always use in CI.** Fails if `pnpm-lock.yaml` needs updates:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### --prefer-offline
|
||||||
|
|
||||||
|
Use cached packages when available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --frozen-lockfile --prefer-offline
|
||||||
|
```
|
||||||
|
|
||||||
|
### --ignore-scripts
|
||||||
|
|
||||||
|
Skip lifecycle scripts for faster installs (use cautiously):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --frozen-lockfile --ignore-scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Corepack Integration
|
||||||
|
|
||||||
|
Use Corepack to manage pnpm version:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"packageManager": "pnpm@9.0.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions
|
||||||
|
- run: corepack enable
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monorepo CI Strategies
|
||||||
|
|
||||||
|
### Build Changed Packages Only
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Build changed packages
|
||||||
|
run: |
|
||||||
|
pnpm --filter "...[origin/main]" build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parallel Jobs per Package
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
detect-changes:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
packages: ${{ steps.changes.outputs.packages }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- id: changes
|
||||||
|
run: |
|
||||||
|
echo "packages=$(pnpm --filter '...[origin/main]' list --json | jq -c '[.[].name]')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
test:
|
||||||
|
needs: detect-changes
|
||||||
|
if: needs.detect-changes.outputs.packages != '[]'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm --filter ${{ matrix.package }} test
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices Summary
|
||||||
|
|
||||||
|
1. **Always use `--frozen-lockfile`** in CI
|
||||||
|
2. **Cache the pnpm store** for faster installs
|
||||||
|
3. **Use Corepack** for consistent pnpm versions
|
||||||
|
4. **Specify `packageManager`** in package.json
|
||||||
|
5. **Use `--filter`** in monorepos to build only what changed
|
||||||
|
6. **Multi-stage Docker builds** for smaller images
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/continuous-integration
|
||||||
|
- https://github.com/pnpm/action-setup
|
||||||
|
-->
|
||||||
291
.agents/skills/pnpm/references/best-practices-migration.md
Normal file
291
.agents/skills/pnpm/references/best-practices-migration.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
---
|
||||||
|
name: migration-to-pnpm
|
||||||
|
description: Migrating from npm or Yarn to pnpm with minimal friction
|
||||||
|
---
|
||||||
|
|
||||||
|
# Migration to pnpm
|
||||||
|
|
||||||
|
Guide for migrating existing projects from npm or Yarn to pnpm.
|
||||||
|
|
||||||
|
## Quick Migration
|
||||||
|
|
||||||
|
### From npm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove npm lockfile and node_modules
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
|
||||||
|
# Install with pnpm
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Yarn
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove yarn lockfile and node_modules
|
||||||
|
rm -rf node_modules yarn.lock
|
||||||
|
|
||||||
|
# Install with pnpm
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import Existing Lockfile
|
||||||
|
|
||||||
|
pnpm can import existing lockfiles:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Import from npm or yarn lockfile
|
||||||
|
pnpm import
|
||||||
|
|
||||||
|
# This creates pnpm-lock.yaml from:
|
||||||
|
# - package-lock.json (npm)
|
||||||
|
# - yarn.lock (yarn)
|
||||||
|
# - npm-shrinkwrap.json (npm)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling Common Issues
|
||||||
|
|
||||||
|
### Phantom Dependencies
|
||||||
|
|
||||||
|
pnpm is strict about dependencies. If code imports a package not in `package.json`, it will fail.
|
||||||
|
|
||||||
|
**Problem:**
|
||||||
|
```js
|
||||||
|
// Works with npm (hoisted), fails with pnpm
|
||||||
|
import lodash from 'lodash' // Not in dependencies, installed by another package
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Add missing dependencies explicitly:
|
||||||
|
```bash
|
||||||
|
pnpm add lodash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Missing Peer Dependencies
|
||||||
|
|
||||||
|
pnpm reports peer dependency issues by default.
|
||||||
|
|
||||||
|
**Option 1:** Let pnpm auto-install:
|
||||||
|
```ini
|
||||||
|
# .npmrc (default in pnpm v8+)
|
||||||
|
auto-install-peers=true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2:** Install manually:
|
||||||
|
```bash
|
||||||
|
pnpm add react react-dom
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3:** Suppress warnings if acceptable:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": ["react"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Symlink Issues
|
||||||
|
|
||||||
|
Some tools don't work with symlinks. Use hoisted mode:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
node-linker=hoisted
|
||||||
|
```
|
||||||
|
|
||||||
|
Or hoist specific packages:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
public-hoist-pattern[]=*eslint*
|
||||||
|
public-hoist-pattern[]=*babel*
|
||||||
|
```
|
||||||
|
|
||||||
|
### Native Module Rebuilds
|
||||||
|
|
||||||
|
If native modules fail, try:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebuild all native modules
|
||||||
|
pnpm rebuild
|
||||||
|
|
||||||
|
# Or reinstall
|
||||||
|
rm -rf node_modules
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monorepo Migration
|
||||||
|
|
||||||
|
### From npm Workspaces
|
||||||
|
|
||||||
|
1. Create `pnpm-workspace.yaml`:
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update internal dependencies to use workspace protocol:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@myorg/utils": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install:
|
||||||
|
```bash
|
||||||
|
rm -rf node_modules packages/*/node_modules package-lock.json
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Yarn Workspaces
|
||||||
|
|
||||||
|
1. Remove Yarn-specific files:
|
||||||
|
```bash
|
||||||
|
rm yarn.lock .yarnrc.yml
|
||||||
|
rm -rf .yarn
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create `pnpm-workspace.yaml` matching `workspaces` in package.json:
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update `package.json` - remove Yarn workspace config if not needed:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
// Remove "workspaces" field (optional, pnpm uses pnpm-workspace.yaml)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Convert workspace references:
|
||||||
|
```json
|
||||||
|
// From Yarn
|
||||||
|
"@myorg/utils": "*"
|
||||||
|
|
||||||
|
// To pnpm
|
||||||
|
"@myorg/utils": "workspace:*"
|
||||||
|
```
|
||||||
|
|
||||||
|
### From Lerna
|
||||||
|
|
||||||
|
pnpm can replace Lerna for most use cases:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lerna: run script in all packages
|
||||||
|
lerna run build
|
||||||
|
|
||||||
|
# pnpm equivalent
|
||||||
|
pnpm -r run build
|
||||||
|
|
||||||
|
# Lerna: run in specific package
|
||||||
|
lerna run build --scope=@myorg/app
|
||||||
|
|
||||||
|
# pnpm equivalent
|
||||||
|
pnpm --filter @myorg/app run build
|
||||||
|
|
||||||
|
# Lerna: publish
|
||||||
|
lerna publish
|
||||||
|
|
||||||
|
# pnpm: use changesets instead
|
||||||
|
pnpm add -Dw @changesets/cli
|
||||||
|
pnpm changeset
|
||||||
|
pnpm changeset version
|
||||||
|
pnpm publish -r
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Migration
|
||||||
|
|
||||||
|
### .npmrc Settings
|
||||||
|
|
||||||
|
Most npm/Yarn settings work in pnpm's `.npmrc`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Registry settings (same as npm)
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
|
@myorg:registry=https://npm.myorg.com/
|
||||||
|
|
||||||
|
# Auth tokens (same as npm)
|
||||||
|
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||||
|
|
||||||
|
# pnpm-specific additions
|
||||||
|
auto-install-peers=true
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scripts Migration
|
||||||
|
|
||||||
|
Most scripts work unchanged. Update pnpm-specific patterns:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
// npm: recursive scripts
|
||||||
|
"build:all": "npm run build --workspaces",
|
||||||
|
// pnpm: use -r flag
|
||||||
|
"build:all": "pnpm -r run build",
|
||||||
|
|
||||||
|
// npm: run in specific workspace
|
||||||
|
"dev:app": "npm run dev -w packages/app",
|
||||||
|
// pnpm: use --filter
|
||||||
|
"dev:app": "pnpm --filter @myorg/app run dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Migration
|
||||||
|
|
||||||
|
Update CI configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Before (npm)
|
||||||
|
- run: npm ci
|
||||||
|
|
||||||
|
# After (pnpm)
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `package.json` for Corepack:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"packageManager": "pnpm@9.0.0"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gradual Migration
|
||||||
|
|
||||||
|
For large projects, migrate gradually:
|
||||||
|
|
||||||
|
1. **Start with CI**: Use pnpm in CI, keep npm/yarn locally
|
||||||
|
2. **Add pnpm-lock.yaml**: Run `pnpm import` to create lockfile
|
||||||
|
3. **Test thoroughly**: Ensure builds work with pnpm
|
||||||
|
4. **Update documentation**: Update README, CONTRIBUTING
|
||||||
|
5. **Remove old files**: Delete old lockfiles after team adoption
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If migration causes issues:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove pnpm files
|
||||||
|
rm -rf node_modules pnpm-lock.yaml pnpm-workspace.yaml
|
||||||
|
|
||||||
|
# Restore npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Or restore Yarn
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep old lockfile in git history for easy rollback.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/installation
|
||||||
|
- https://pnpm.io/cli/import
|
||||||
|
- https://pnpm.io/limitations
|
||||||
|
-->
|
||||||
284
.agents/skills/pnpm/references/best-practices-performance.md
Normal file
284
.agents/skills/pnpm/references/best-practices-performance.md
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-performance-optimization
|
||||||
|
description: Tips and tricks for faster installs and better performance
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Performance Optimization
|
||||||
|
|
||||||
|
pnpm is fast by default, but these optimizations can make it even faster.
|
||||||
|
|
||||||
|
## Install Optimizations
|
||||||
|
|
||||||
|
### Use Frozen Lockfile
|
||||||
|
|
||||||
|
Skip resolution when lockfile exists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
This is faster because pnpm skips the resolution phase entirely.
|
||||||
|
|
||||||
|
### Prefer Offline Mode
|
||||||
|
|
||||||
|
Use cached packages when available:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --prefer-offline
|
||||||
|
```
|
||||||
|
|
||||||
|
Or configure globally:
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
prefer-offline=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip Optional Dependencies
|
||||||
|
|
||||||
|
If you don't need optional deps:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --no-optional
|
||||||
|
```
|
||||||
|
|
||||||
|
### Skip Scripts
|
||||||
|
|
||||||
|
For CI or when scripts aren't needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --ignore-scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Caution:** Some packages require postinstall scripts to work correctly.
|
||||||
|
|
||||||
|
### Only Build Specific Dependencies
|
||||||
|
|
||||||
|
Only run build scripts for specific packages:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
onlyBuiltDependencies[]=esbuild
|
||||||
|
onlyBuiltDependencies[]=sharp
|
||||||
|
onlyBuiltDependencies[]=@swc/core
|
||||||
|
```
|
||||||
|
|
||||||
|
Or skip builds entirely for deps that don't need them:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"neverBuiltDependencies": ["fsevents", "cpu-features"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Store Optimizations
|
||||||
|
|
||||||
|
### Side Effects Cache
|
||||||
|
|
||||||
|
Cache native module build results:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
side-effects-cache=true
|
||||||
|
```
|
||||||
|
|
||||||
|
This caches the results of postinstall scripts, speeding up subsequent installs.
|
||||||
|
|
||||||
|
### Shared Store
|
||||||
|
|
||||||
|
Use a single store for all projects (default behavior):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
store-dir=~/.pnpm-store
|
||||||
|
```
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
- Packages downloaded once for all projects
|
||||||
|
- Hard links save disk space
|
||||||
|
- Faster installs from cache
|
||||||
|
|
||||||
|
### Store Maintenance
|
||||||
|
|
||||||
|
Periodically clean unused packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Remove unreferenced packages
|
||||||
|
pnpm store prune
|
||||||
|
|
||||||
|
# Check store integrity
|
||||||
|
pnpm store status
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace Optimizations
|
||||||
|
|
||||||
|
### Parallel Execution
|
||||||
|
|
||||||
|
Run workspace scripts in parallel:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm -r --parallel run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Control concurrency:
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
workspace-concurrency=8
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream Output
|
||||||
|
|
||||||
|
See output in real-time:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm -r --stream run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter to Changed Packages
|
||||||
|
|
||||||
|
Only build what changed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build packages changed since main branch
|
||||||
|
pnpm --filter "...[origin/main]" run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Topological Order
|
||||||
|
|
||||||
|
Build dependencies before dependents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm -r run build
|
||||||
|
# Automatically runs in topological order
|
||||||
|
```
|
||||||
|
|
||||||
|
For explicit sequential builds:
|
||||||
|
```bash
|
||||||
|
pnpm -r --workspace-concurrency=1 run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Optimizations
|
||||||
|
|
||||||
|
### Configure Registry
|
||||||
|
|
||||||
|
Use closest/fastest registry:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
registry=https://registry.npmmirror.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Settings
|
||||||
|
|
||||||
|
Tune network settings:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
fetch-retries=3
|
||||||
|
fetch-retry-mintimeout=10000
|
||||||
|
fetch-retry-maxtimeout=60000
|
||||||
|
network-concurrency=16
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proxy Configuration
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
proxy=http://proxy.company.com:8080
|
||||||
|
https-proxy=http://proxy.company.com:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lockfile Optimization
|
||||||
|
|
||||||
|
### Single Lockfile (Monorepos)
|
||||||
|
|
||||||
|
Use shared lockfile for all packages (default):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
shared-workspace-lockfile=true
|
||||||
|
```
|
||||||
|
|
||||||
|
Benefits:
|
||||||
|
- Single source of truth
|
||||||
|
- Faster resolution
|
||||||
|
- Consistent versions across workspace
|
||||||
|
|
||||||
|
### Lockfile-only Mode
|
||||||
|
|
||||||
|
Only update lockfile without installing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install --lockfile-only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benchmarking
|
||||||
|
|
||||||
|
### Compare Install Times
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean install
|
||||||
|
rm -rf node_modules pnpm-lock.yaml
|
||||||
|
time pnpm install
|
||||||
|
|
||||||
|
# Cached install (with lockfile)
|
||||||
|
rm -rf node_modules
|
||||||
|
time pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# With store cache
|
||||||
|
time pnpm install --frozen-lockfile --prefer-offline
|
||||||
|
```
|
||||||
|
|
||||||
|
### Profile Resolution
|
||||||
|
|
||||||
|
Debug slow installs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verbose logging
|
||||||
|
pnpm install --reporter=append-only
|
||||||
|
|
||||||
|
# Debug mode
|
||||||
|
DEBUG=pnpm:* pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Summary
|
||||||
|
|
||||||
|
Optimized `.npmrc` for performance:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Install behavior
|
||||||
|
prefer-offline=true
|
||||||
|
auto-install-peers=true
|
||||||
|
|
||||||
|
# Build optimization
|
||||||
|
side-effects-cache=true
|
||||||
|
# Only build what's necessary
|
||||||
|
onlyBuiltDependencies[]=esbuild
|
||||||
|
onlyBuiltDependencies[]=@swc/core
|
||||||
|
|
||||||
|
# Network
|
||||||
|
fetch-retries=3
|
||||||
|
network-concurrency=16
|
||||||
|
|
||||||
|
# Workspace
|
||||||
|
workspace-concurrency=4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Scenario | Command/Setting |
|
||||||
|
|----------|-----------------|
|
||||||
|
| CI installs | `pnpm install --frozen-lockfile` |
|
||||||
|
| Offline development | `--prefer-offline` |
|
||||||
|
| Skip native builds | `neverBuiltDependencies` |
|
||||||
|
| Parallel workspace | `pnpm -r --parallel run build` |
|
||||||
|
| Build changed only | `pnpm --filter "...[origin/main]" build` |
|
||||||
|
| Clean store | `pnpm store prune` |
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/npmrc
|
||||||
|
- https://pnpm.io/cli/install
|
||||||
|
- https://pnpm.io/filtering
|
||||||
|
-->
|
||||||
229
.agents/skills/pnpm/references/core-cli.md
Normal file
229
.agents/skills/pnpm/references/core-cli.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-cli-commands
|
||||||
|
description: Essential pnpm commands for package management, running scripts, and workspace operations
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm CLI Commands
|
||||||
|
|
||||||
|
pnpm provides a comprehensive CLI for package management with commands similar to npm/yarn but with unique features.
|
||||||
|
|
||||||
|
## Installation Commands
|
||||||
|
|
||||||
|
### Install all dependencies
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
# or
|
||||||
|
pnpm i
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a dependency
|
||||||
|
```bash
|
||||||
|
# Production dependency
|
||||||
|
pnpm add <pkg>
|
||||||
|
|
||||||
|
# Dev dependency
|
||||||
|
pnpm add -D <pkg>
|
||||||
|
pnpm add --save-dev <pkg>
|
||||||
|
|
||||||
|
# Optional dependency
|
||||||
|
pnpm add -O <pkg>
|
||||||
|
|
||||||
|
# Global package
|
||||||
|
pnpm add -g <pkg>
|
||||||
|
|
||||||
|
# Specific version
|
||||||
|
pnpm add <pkg>@<version>
|
||||||
|
pnpm add <pkg>@next
|
||||||
|
pnpm add <pkg>@^1.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove a dependency
|
||||||
|
```bash
|
||||||
|
pnpm remove <pkg>
|
||||||
|
pnpm rm <pkg>
|
||||||
|
pnpm uninstall <pkg>
|
||||||
|
pnpm un <pkg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update dependencies
|
||||||
|
```bash
|
||||||
|
# Update all
|
||||||
|
pnpm update
|
||||||
|
pnpm up
|
||||||
|
|
||||||
|
# Update specific package
|
||||||
|
pnpm update <pkg>
|
||||||
|
|
||||||
|
# Update to latest (ignore semver)
|
||||||
|
pnpm update --latest
|
||||||
|
pnpm up -L
|
||||||
|
|
||||||
|
# Interactive update
|
||||||
|
pnpm update --interactive
|
||||||
|
pnpm up -i
|
||||||
|
```
|
||||||
|
|
||||||
|
## Script Commands
|
||||||
|
|
||||||
|
### Run scripts
|
||||||
|
```bash
|
||||||
|
pnpm run <script>
|
||||||
|
# or shorthand
|
||||||
|
pnpm <script>
|
||||||
|
|
||||||
|
# Pass arguments to script
|
||||||
|
pnpm run build -- --watch
|
||||||
|
|
||||||
|
# Run script if exists (no error if missing)
|
||||||
|
pnpm run --if-present build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute binaries
|
||||||
|
```bash
|
||||||
|
# Run local binary
|
||||||
|
pnpm exec <command>
|
||||||
|
|
||||||
|
# Example
|
||||||
|
pnpm exec eslint .
|
||||||
|
```
|
||||||
|
|
||||||
|
### dlx - Run without installing
|
||||||
|
```bash
|
||||||
|
# Like npx but for pnpm
|
||||||
|
pnpm dlx <pkg>
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
pnpm dlx create-vite my-app
|
||||||
|
pnpm dlx degit user/repo my-project
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace Commands
|
||||||
|
|
||||||
|
### Run in all packages
|
||||||
|
```bash
|
||||||
|
# Run script in all workspace packages
|
||||||
|
pnpm -r run <script>
|
||||||
|
pnpm --recursive run <script>
|
||||||
|
|
||||||
|
# Run in specific packages
|
||||||
|
pnpm --filter <pattern> run <script>
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
pnpm --filter "./packages/**" run build
|
||||||
|
pnpm --filter "!./packages/internal/**" run test
|
||||||
|
pnpm --filter "@myorg/*" run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter patterns
|
||||||
|
```bash
|
||||||
|
# By package name
|
||||||
|
pnpm --filter <pkg-name> <command>
|
||||||
|
pnpm --filter "@scope/pkg" build
|
||||||
|
|
||||||
|
# By directory
|
||||||
|
pnpm --filter "./packages/core" test
|
||||||
|
|
||||||
|
# Dependencies of a package
|
||||||
|
pnpm --filter "...@scope/app" build
|
||||||
|
|
||||||
|
# Dependents of a package
|
||||||
|
pnpm --filter "@scope/core..." test
|
||||||
|
|
||||||
|
# Changed packages since commit/branch
|
||||||
|
pnpm --filter "...[origin/main]" build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Other Useful Commands
|
||||||
|
|
||||||
|
### Link packages
|
||||||
|
```bash
|
||||||
|
# Link global package
|
||||||
|
pnpm link --global
|
||||||
|
pnpm link -g
|
||||||
|
|
||||||
|
# Use linked package
|
||||||
|
pnpm link --global <pkg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Patch packages
|
||||||
|
```bash
|
||||||
|
# Create patch for a package
|
||||||
|
pnpm patch <pkg>@<version>
|
||||||
|
|
||||||
|
# After editing, commit the patch
|
||||||
|
pnpm patch-commit <path>
|
||||||
|
|
||||||
|
# Remove a patch
|
||||||
|
pnpm patch-remove <pkg>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Store management
|
||||||
|
```bash
|
||||||
|
# Show store path
|
||||||
|
pnpm store path
|
||||||
|
|
||||||
|
# Remove unreferenced packages
|
||||||
|
pnpm store prune
|
||||||
|
|
||||||
|
# Check store integrity
|
||||||
|
pnpm store status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Other commands
|
||||||
|
```bash
|
||||||
|
# Clean install (like npm ci)
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# List installed packages
|
||||||
|
pnpm list
|
||||||
|
pnpm ls
|
||||||
|
|
||||||
|
# Why is package installed?
|
||||||
|
pnpm why <pkg>
|
||||||
|
|
||||||
|
# Outdated packages
|
||||||
|
pnpm outdated
|
||||||
|
|
||||||
|
# Audit for vulnerabilities
|
||||||
|
pnpm audit
|
||||||
|
|
||||||
|
# Rebuild native modules
|
||||||
|
pnpm rebuild
|
||||||
|
|
||||||
|
# Import from npm/yarn lockfile
|
||||||
|
pnpm import
|
||||||
|
|
||||||
|
# Create tarball
|
||||||
|
pnpm pack
|
||||||
|
|
||||||
|
# Publish package
|
||||||
|
pnpm publish
|
||||||
|
```
|
||||||
|
|
||||||
|
## Useful Flags
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ignore scripts
|
||||||
|
pnpm install --ignore-scripts
|
||||||
|
|
||||||
|
# Prefer offline (use cache)
|
||||||
|
pnpm install --prefer-offline
|
||||||
|
|
||||||
|
# Strict peer dependencies
|
||||||
|
pnpm install --strict-peer-dependencies
|
||||||
|
|
||||||
|
# Production only
|
||||||
|
pnpm install --prod
|
||||||
|
pnpm install -P
|
||||||
|
|
||||||
|
# No optional dependencies
|
||||||
|
pnpm install --no-optional
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/cli/install
|
||||||
|
- https://pnpm.io/cli/add
|
||||||
|
- https://pnpm.io/cli/run
|
||||||
|
- https://pnpm.io/filtering
|
||||||
|
-->
|
||||||
188
.agents/skills/pnpm/references/core-config.md
Normal file
188
.agents/skills/pnpm/references/core-config.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-configuration
|
||||||
|
description: Configuration options via pnpm-workspace.yaml and .npmrc settings
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Configuration
|
||||||
|
|
||||||
|
pnpm uses two main configuration files: `pnpm-workspace.yaml` for workspace and pnpm-specific settings, and `.npmrc` for npm-compatible and pnpm-specific settings.
|
||||||
|
|
||||||
|
## pnpm-workspace.yaml
|
||||||
|
|
||||||
|
The recommended location for pnpm-specific configurations. Place at project root.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Define workspace packages
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
- 'apps/*'
|
||||||
|
- '!**/test/**' # Exclude pattern
|
||||||
|
|
||||||
|
# Catalog for shared dependency versions
|
||||||
|
catalog:
|
||||||
|
react: ^18.2.0
|
||||||
|
typescript: ~5.3.0
|
||||||
|
|
||||||
|
# Named catalogs for different dependency groups
|
||||||
|
catalogs:
|
||||||
|
react17:
|
||||||
|
react: ^17.0.2
|
||||||
|
react-dom: ^17.0.2
|
||||||
|
react18:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
|
||||||
|
# Override resolutions (preferred location)
|
||||||
|
overrides:
|
||||||
|
lodash: ^4.17.21
|
||||||
|
'foo@^1.0.0>bar': ^2.0.0
|
||||||
|
|
||||||
|
# pnpm settings (alternative to .npmrc)
|
||||||
|
settings:
|
||||||
|
auto-install-peers: true
|
||||||
|
strict-peer-dependencies: false
|
||||||
|
link-workspace-packages: true
|
||||||
|
prefer-workspace-packages: true
|
||||||
|
shared-workspace-lockfile: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## .npmrc Settings
|
||||||
|
|
||||||
|
pnpm reads settings from `.npmrc` files. Create at project root or user home.
|
||||||
|
|
||||||
|
### Common pnpm Settings
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Automatically install peer dependencies
|
||||||
|
auto-install-peers=true
|
||||||
|
|
||||||
|
# Fail on peer dependency issues
|
||||||
|
strict-peer-dependencies=false
|
||||||
|
|
||||||
|
# Hoist patterns for dependencies
|
||||||
|
public-hoist-pattern[]=*types*
|
||||||
|
public-hoist-pattern[]=*eslint*
|
||||||
|
shamefully-hoist=false
|
||||||
|
|
||||||
|
# Store location
|
||||||
|
store-dir=~/.pnpm-store
|
||||||
|
|
||||||
|
# Virtual store location
|
||||||
|
virtual-store-dir=node_modules/.pnpm
|
||||||
|
|
||||||
|
# Lockfile settings
|
||||||
|
lockfile=true
|
||||||
|
prefer-frozen-lockfile=true
|
||||||
|
|
||||||
|
# Side effects cache (speeds up rebuilds)
|
||||||
|
side-effects-cache=true
|
||||||
|
|
||||||
|
# Registry settings
|
||||||
|
registry=https://registry.npmjs.org/
|
||||||
|
@myorg:registry=https://npm.myorg.com/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workspace Settings
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Link workspace packages
|
||||||
|
link-workspace-packages=true
|
||||||
|
|
||||||
|
# Prefer workspace packages over registry
|
||||||
|
prefer-workspace-packages=true
|
||||||
|
|
||||||
|
# Single lockfile for all packages
|
||||||
|
shared-workspace-lockfile=true
|
||||||
|
|
||||||
|
# Save prefix for workspace dependencies
|
||||||
|
save-workspace-protocol=rolling
|
||||||
|
```
|
||||||
|
|
||||||
|
### Node.js Settings
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Use specific Node.js version
|
||||||
|
use-node-version=20.10.0
|
||||||
|
|
||||||
|
# Node.js version file
|
||||||
|
node-version-file=.nvmrc
|
||||||
|
|
||||||
|
# Manage Node.js versions
|
||||||
|
manage-package-manager-versions=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security Settings
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Ignore specific scripts
|
||||||
|
ignore-scripts=false
|
||||||
|
|
||||||
|
# Allow specific build scripts
|
||||||
|
onlyBuiltDependencies[]=esbuild
|
||||||
|
onlyBuiltDependencies[]=sharp
|
||||||
|
|
||||||
|
# Package extensions for missing peer deps
|
||||||
|
package-extensions[foo@1].peerDependencies.bar=*
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Hierarchy
|
||||||
|
|
||||||
|
Settings are read in order (later overrides earlier):
|
||||||
|
|
||||||
|
1. `/etc/npmrc` - Global config
|
||||||
|
2. `~/.npmrc` - User config
|
||||||
|
3. `<project>/.npmrc` - Project config
|
||||||
|
4. Environment variables: `npm_config_<key>=<value>`
|
||||||
|
5. `pnpm-workspace.yaml` settings field
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set config via env
|
||||||
|
npm_config_registry=https://registry.npmjs.org/
|
||||||
|
|
||||||
|
# pnpm-specific env vars
|
||||||
|
PNPM_HOME=~/.local/share/pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package.json Fields
|
||||||
|
|
||||||
|
pnpm reads specific fields from `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"lodash": "^4.17.21"
|
||||||
|
},
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": ["@babel/*"],
|
||||||
|
"allowedVersions": {
|
||||||
|
"react": "17 || 18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"neverBuiltDependencies": ["fsevents"],
|
||||||
|
"onlyBuiltDependencies": ["esbuild"],
|
||||||
|
"allowedDeprecatedVersions": {
|
||||||
|
"request": "*"
|
||||||
|
},
|
||||||
|
"patchedDependencies": {
|
||||||
|
"express@4.18.2": "patches/express@4.18.2.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences from npm/yarn
|
||||||
|
|
||||||
|
1. **Strict by default**: No phantom dependencies
|
||||||
|
2. **Workspace protocol**: `workspace:*` for local packages
|
||||||
|
3. **Catalogs**: Centralized version management
|
||||||
|
4. **Content-addressable store**: Shared across projects
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/pnpm-workspace_yaml
|
||||||
|
- https://pnpm.io/npmrc
|
||||||
|
- https://pnpm.io/package_json
|
||||||
|
-->
|
||||||
179
.agents/skills/pnpm/references/core-store.md
Normal file
179
.agents/skills/pnpm/references/core-store.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-store
|
||||||
|
description: Content-addressable storage system that makes pnpm fast and disk-efficient
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Store
|
||||||
|
|
||||||
|
pnpm uses a content-addressable store to save disk space and speed up installations. All packages are stored once globally and hard-linked to project `node_modules`.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Global Store**: Packages are downloaded once to a central store
|
||||||
|
2. **Hard Links**: Projects link to store instead of copying files
|
||||||
|
3. **Content-Addressable**: Files are stored by content hash, deduplicating identical files
|
||||||
|
|
||||||
|
### Storage Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.pnpm-store/ # Global store (default location)
|
||||||
|
└── v3/
|
||||||
|
└── files/
|
||||||
|
└── <hash>/ # Files stored by content hash
|
||||||
|
|
||||||
|
project/
|
||||||
|
└── node_modules/
|
||||||
|
├── .pnpm/ # Virtual store (hard links to global store)
|
||||||
|
│ ├── lodash@4.17.21/
|
||||||
|
│ │ └── node_modules/
|
||||||
|
│ │ └── lodash/
|
||||||
|
│ └── express@4.18.2/
|
||||||
|
│ └── node_modules/
|
||||||
|
│ ├── express/
|
||||||
|
│ └── <deps>/ # Flat structure for dependencies
|
||||||
|
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
|
||||||
|
└── express -> .pnpm/express@4.18.2/node_modules/express
|
||||||
|
```
|
||||||
|
|
||||||
|
## Store Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show store location
|
||||||
|
pnpm store path
|
||||||
|
|
||||||
|
# Remove unreferenced packages
|
||||||
|
pnpm store prune
|
||||||
|
|
||||||
|
# Check store integrity
|
||||||
|
pnpm store status
|
||||||
|
|
||||||
|
# Add package to store without installing
|
||||||
|
pnpm store add <pkg>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Store Location
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc
|
||||||
|
store-dir=~/.pnpm-store
|
||||||
|
|
||||||
|
# Or use environment variable
|
||||||
|
PNPM_HOME=~/.local/share/pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
### Virtual Store
|
||||||
|
|
||||||
|
The virtual store (`.pnpm` in `node_modules`) contains symlinks to the global store:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Customize virtual store location
|
||||||
|
virtual-store-dir=node_modules/.pnpm
|
||||||
|
|
||||||
|
# Alternative flat layout
|
||||||
|
node-linker=hoisted
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disk Space Benefits
|
||||||
|
|
||||||
|
pnpm saves significant disk space:
|
||||||
|
|
||||||
|
- **Deduplication**: Same package version stored once across all projects
|
||||||
|
- **Content deduplication**: Identical files across different packages stored once
|
||||||
|
- **Hard links**: No copying, just linking
|
||||||
|
|
||||||
|
### Check disk usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compare actual vs apparent size
|
||||||
|
du -sh node_modules # Apparent size
|
||||||
|
du -sh --apparent-size node_modules # With hard links counted
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node Linker Modes
|
||||||
|
|
||||||
|
Configure how `node_modules` is structured:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Default: Symlinked structure (recommended)
|
||||||
|
node-linker=isolated
|
||||||
|
|
||||||
|
# Flat node_modules (npm-like, for compatibility)
|
||||||
|
node-linker=hoisted
|
||||||
|
|
||||||
|
# PnP mode (experimental, like Yarn PnP)
|
||||||
|
node-linker=pnp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Isolated Mode (Default)
|
||||||
|
|
||||||
|
- Strict dependency resolution
|
||||||
|
- No phantom dependencies
|
||||||
|
- Packages can only access declared dependencies
|
||||||
|
|
||||||
|
### Hoisted Mode
|
||||||
|
|
||||||
|
- Flat `node_modules` like npm
|
||||||
|
- For compatibility with tools that don't support symlinks
|
||||||
|
- Loses strictness benefits
|
||||||
|
|
||||||
|
## Side Effects Cache
|
||||||
|
|
||||||
|
Cache build outputs for native modules:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Enable side effects caching
|
||||||
|
side-effects-cache=true
|
||||||
|
|
||||||
|
# Store side effects in project (instead of global store)
|
||||||
|
side-effects-cache-readonly=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shared Store Across Machines
|
||||||
|
|
||||||
|
For CI/CD, you can share the store:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions example
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
shell: bash
|
||||||
|
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Store corruption
|
||||||
|
```bash
|
||||||
|
# Verify and fix store
|
||||||
|
pnpm store status
|
||||||
|
pnpm store prune
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hard link issues (network drives, Docker)
|
||||||
|
```ini
|
||||||
|
# Use copying instead of hard links
|
||||||
|
package-import-method=copy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Permission issues
|
||||||
|
```bash
|
||||||
|
# Fix store permissions
|
||||||
|
chmod -R u+w ~/.pnpm-store
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/symlinked-node-modules-structure
|
||||||
|
- https://pnpm.io/cli/store
|
||||||
|
- https://pnpm.io/npmrc#store-dir
|
||||||
|
-->
|
||||||
205
.agents/skills/pnpm/references/core-workspaces.md
Normal file
205
.agents/skills/pnpm/references/core-workspaces.md
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-workspaces
|
||||||
|
description: Monorepo support with workspaces for managing multiple packages
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Workspaces
|
||||||
|
|
||||||
|
pnpm has built-in support for monorepos (multi-package repositories) through workspaces.
|
||||||
|
|
||||||
|
## Setting Up Workspaces
|
||||||
|
|
||||||
|
Create `pnpm-workspace.yaml` at the repository root:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
# Include all packages in packages/ directory
|
||||||
|
- 'packages/*'
|
||||||
|
# Include all apps
|
||||||
|
- 'apps/*'
|
||||||
|
# Include nested packages
|
||||||
|
- 'tools/*/packages/*'
|
||||||
|
# Exclude test directories
|
||||||
|
- '!**/test/**'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace Protocol
|
||||||
|
|
||||||
|
Use `workspace:` protocol to reference local packages:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@myorg/utils": "workspace:*",
|
||||||
|
"@myorg/core": "workspace:^",
|
||||||
|
"@myorg/types": "workspace:~"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protocol Variants
|
||||||
|
|
||||||
|
| Protocol | Behavior | Published As |
|
||||||
|
|----------|----------|--------------|
|
||||||
|
| `workspace:*` | Any version | Actual version (e.g., `1.2.3`) |
|
||||||
|
| `workspace:^` | Compatible version | `^1.2.3` |
|
||||||
|
| `workspace:~` | Patch version | `~1.2.3` |
|
||||||
|
| `workspace:^1.0.0` | Semver range | `^1.0.0` |
|
||||||
|
|
||||||
|
## Filtering Packages
|
||||||
|
|
||||||
|
Run commands on specific packages using `--filter`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# By package name
|
||||||
|
pnpm --filter @myorg/app build
|
||||||
|
pnpm -F @myorg/app build
|
||||||
|
|
||||||
|
# By directory path
|
||||||
|
pnpm --filter "./packages/core" test
|
||||||
|
|
||||||
|
# Glob patterns
|
||||||
|
pnpm --filter "@myorg/*" lint
|
||||||
|
pnpm --filter "!@myorg/internal-*" publish
|
||||||
|
|
||||||
|
# All packages
|
||||||
|
pnpm -r build
|
||||||
|
pnpm --recursive build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dependency-based Filtering
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Package and all its dependencies
|
||||||
|
pnpm --filter "...@myorg/app" build
|
||||||
|
|
||||||
|
# Package and all its dependents
|
||||||
|
pnpm --filter "@myorg/core..." test
|
||||||
|
|
||||||
|
# Both directions
|
||||||
|
pnpm --filter "...@myorg/shared..." build
|
||||||
|
|
||||||
|
# Changed since git ref
|
||||||
|
pnpm --filter "...[origin/main]" test
|
||||||
|
pnpm --filter "[HEAD~5]" lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace Commands
|
||||||
|
|
||||||
|
### Install dependencies
|
||||||
|
```bash
|
||||||
|
# Install all workspace packages
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Add dependency to specific package
|
||||||
|
pnpm --filter @myorg/app add lodash
|
||||||
|
|
||||||
|
# Add workspace dependency
|
||||||
|
pnpm --filter @myorg/app add @myorg/utils
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run scripts
|
||||||
|
```bash
|
||||||
|
# Run in all packages with that script
|
||||||
|
pnpm -r run build
|
||||||
|
|
||||||
|
# Run in topological order (dependencies first)
|
||||||
|
pnpm -r --workspace-concurrency=1 run build
|
||||||
|
|
||||||
|
# Run in parallel
|
||||||
|
pnpm -r --parallel run test
|
||||||
|
|
||||||
|
# Stream output
|
||||||
|
pnpm -r --stream run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute commands
|
||||||
|
```bash
|
||||||
|
# Run command in all packages
|
||||||
|
pnpm -r exec pwd
|
||||||
|
|
||||||
|
# Run in specific packages
|
||||||
|
pnpm --filter "./packages/**" exec rm -rf dist
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspace Settings
|
||||||
|
|
||||||
|
Configure in `.npmrc` or `pnpm-workspace.yaml`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Link workspace packages automatically
|
||||||
|
link-workspace-packages=true
|
||||||
|
|
||||||
|
# Prefer workspace packages over registry
|
||||||
|
prefer-workspace-packages=true
|
||||||
|
|
||||||
|
# Single lockfile (recommended)
|
||||||
|
shared-workspace-lockfile=true
|
||||||
|
|
||||||
|
# Workspace protocol handling
|
||||||
|
save-workspace-protocol=rolling
|
||||||
|
|
||||||
|
# Concurrent workspace scripts
|
||||||
|
workspace-concurrency=4
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publishing Workspaces
|
||||||
|
|
||||||
|
When publishing, `workspace:` protocols are converted:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Before publish
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@myorg/utils": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After publish
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@myorg/utils": "^1.2.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--no-git-checks` for publishing from CI:
|
||||||
|
```bash
|
||||||
|
pnpm publish -r --no-git-checks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use workspace protocol** for internal dependencies
|
||||||
|
2. **Enable `link-workspace-packages`** for automatic linking
|
||||||
|
3. **Use shared lockfile** for consistency
|
||||||
|
4. **Filter by dependencies** when building to ensure correct order
|
||||||
|
5. **Use catalogs** for shared external dependency versions
|
||||||
|
|
||||||
|
## Example Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
my-monorepo/
|
||||||
|
├── pnpm-workspace.yaml
|
||||||
|
├── package.json
|
||||||
|
├── pnpm-lock.yaml
|
||||||
|
├── packages/
|
||||||
|
│ ├── core/
|
||||||
|
│ │ └── package.json
|
||||||
|
│ ├── utils/
|
||||||
|
│ │ └── package.json
|
||||||
|
│ └── types/
|
||||||
|
│ └── package.json
|
||||||
|
└── apps/
|
||||||
|
├── web/
|
||||||
|
│ └── package.json
|
||||||
|
└── api/
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/workspaces
|
||||||
|
- https://pnpm.io/filtering
|
||||||
|
- https://pnpm.io/npmrc#workspace-settings
|
||||||
|
-->
|
||||||
168
.agents/skills/pnpm/references/features-aliases.md
Normal file
168
.agents/skills/pnpm/references/features-aliases.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-aliases
|
||||||
|
description: Install packages under custom names for versioning, forks, or alternatives
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Aliases
|
||||||
|
|
||||||
|
pnpm supports package aliases using the `npm:` protocol. This lets you install packages under different names, use multiple versions of the same package, or substitute packages.
|
||||||
|
|
||||||
|
## Basic Syntax
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add <alias>@npm:<package>@<version>
|
||||||
|
```
|
||||||
|
|
||||||
|
In `package.json`:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"<alias>": "npm:<package>@<version>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
### Multiple Versions of Same Package
|
||||||
|
|
||||||
|
Install different versions side by side:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"lodash3": "npm:lodash@3",
|
||||||
|
"lodash4": "npm:lodash@4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```js
|
||||||
|
import lodash3 from 'lodash3'
|
||||||
|
import lodash4 from 'lodash4'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace Package with Fork
|
||||||
|
|
||||||
|
Substitute a package with a fork or alternative:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"original-pkg": "npm:my-fork@^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All imports of `original-pkg` will resolve to `my-fork`.
|
||||||
|
|
||||||
|
### Replace Deprecated Package
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"request": "npm:@cypress/request@^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Scoped to Unscoped (or vice versa)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "npm:@anthropic/vue@^3.0.0",
|
||||||
|
"@myorg/utils": "npm:lodash@^4.17.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI Usage
|
||||||
|
|
||||||
|
### Add with alias
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add lodash under alias
|
||||||
|
pnpm add lodash4@npm:lodash@4
|
||||||
|
|
||||||
|
# Add fork as original name
|
||||||
|
pnpm add request@npm:@cypress/request
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add multiple versions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add react17@npm:react@17 react18@npm:react@18
|
||||||
|
```
|
||||||
|
|
||||||
|
## With TypeScript
|
||||||
|
|
||||||
|
For type resolution with aliases, you may need to configure TypeScript:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"lodash3": ["node_modules/lodash3"],
|
||||||
|
"lodash4": ["node_modules/lodash4"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `@types` packages with aliases:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/lodash3": "npm:@types/lodash@3",
|
||||||
|
"@types/lodash4": "npm:@types/lodash@4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combined with Overrides
|
||||||
|
|
||||||
|
Force all transitive dependencies to use an alias:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
overrides:
|
||||||
|
"underscore": "npm:lodash@^4.17.21"
|
||||||
|
```
|
||||||
|
|
||||||
|
This replaces all `underscore` imports (including in dependencies) with lodash.
|
||||||
|
|
||||||
|
## Git and Local Aliases
|
||||||
|
|
||||||
|
Aliases work with any valid pnpm specifier:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"my-fork": "npm:user/repo#commit",
|
||||||
|
"local-pkg": "file:../local-package"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Clear naming**: Use descriptive alias names that indicate purpose
|
||||||
|
```json
|
||||||
|
"lodash-legacy": "npm:lodash@3"
|
||||||
|
"lodash-modern": "npm:lodash@4"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Document aliases**: Add comments or documentation explaining why aliases exist
|
||||||
|
|
||||||
|
3. **Prefer overrides for global replacement**: If you want to replace a package everywhere, use overrides instead of aliases
|
||||||
|
|
||||||
|
4. **Test thoroughly**: Aliased packages may have subtle differences in behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/aliases
|
||||||
|
-->
|
||||||
159
.agents/skills/pnpm/references/features-catalogs.md
Normal file
159
.agents/skills/pnpm/references/features-catalogs.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-catalogs
|
||||||
|
description: Centralized dependency version management for workspaces
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Catalogs
|
||||||
|
|
||||||
|
Catalogs provide a centralized way to manage dependency versions across a workspace. Define versions once, use everywhere.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
Define a catalog in `pnpm-workspace.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
|
||||||
|
catalog:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
typescript: ~5.3.0
|
||||||
|
vite: ^5.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference in `package.json` with `catalog:`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "catalog:",
|
||||||
|
"react-dom": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "catalog:",
|
||||||
|
"vite": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Named Catalogs
|
||||||
|
|
||||||
|
Create multiple catalogs for different scenarios:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
|
||||||
|
# Default catalog
|
||||||
|
catalog:
|
||||||
|
lodash: ^4.17.21
|
||||||
|
|
||||||
|
# Named catalogs
|
||||||
|
catalogs:
|
||||||
|
react17:
|
||||||
|
react: ^17.0.2
|
||||||
|
react-dom: ^17.0.2
|
||||||
|
|
||||||
|
react18:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
|
||||||
|
testing:
|
||||||
|
vitest: ^1.0.0
|
||||||
|
"@testing-library/react": ^14.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference named catalogs:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "catalog:react18",
|
||||||
|
"react-dom": "catalog:react18"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "catalog:testing"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **Single source of truth**: Update version in one place
|
||||||
|
2. **Consistency**: All packages use the same version
|
||||||
|
3. **Easy upgrades**: Change version once, affects entire workspace
|
||||||
|
4. **Type-safe**: TypeScript support in pnpm-workspace.yaml
|
||||||
|
|
||||||
|
## Catalog vs Overrides
|
||||||
|
|
||||||
|
| Feature | Catalogs | Overrides |
|
||||||
|
|---------|----------|-----------|
|
||||||
|
| Purpose | Define versions for direct dependencies | Force versions for any dependency |
|
||||||
|
| Scope | Direct dependencies only | All dependencies (including transitive) |
|
||||||
|
| Usage | `"pkg": "catalog:"` | Applied automatically |
|
||||||
|
| Opt-in | Explicit per package.json | Global to workspace |
|
||||||
|
|
||||||
|
## Publishing with Catalogs
|
||||||
|
|
||||||
|
When publishing, `catalog:` references are replaced with actual versions:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Before publish (source)
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// After publish (published package)
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from Overrides
|
||||||
|
|
||||||
|
If you're using overrides for version consistency:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Before (using overrides)
|
||||||
|
overrides:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Migrate to catalogs for cleaner dependency management:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# After (using catalogs)
|
||||||
|
catalog:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Then update package.json files to use `catalog:`.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use default catalog** for commonly shared dependencies
|
||||||
|
2. **Use named catalogs** for version variants (e.g., different React versions)
|
||||||
|
3. **Keep catalog minimal** - only include shared dependencies
|
||||||
|
4. **Combine with workspace protocol** for internal packages
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
catalog:
|
||||||
|
# External shared dependencies
|
||||||
|
lodash: ^4.17.21
|
||||||
|
zod: ^3.22.0
|
||||||
|
|
||||||
|
# Internal packages use workspace: protocol instead
|
||||||
|
# "dependencies": { "@myorg/utils": "workspace:^" }
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/catalogs
|
||||||
|
-->
|
||||||
233
.agents/skills/pnpm/references/features-hooks.md
Normal file
233
.agents/skills/pnpm/references/features-hooks.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-hooks
|
||||||
|
description: Customize package resolution and dependency behavior with pnpmfile hooks
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Hooks
|
||||||
|
|
||||||
|
pnpm provides hooks via `.pnpmfile.cjs` to customize how packages are resolved and their metadata is processed.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Create `.pnpmfile.cjs` at workspace root:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// .pnpmfile.cjs
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Modify package metadata
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterAllResolved(lockfile, context) {
|
||||||
|
// Modify lockfile
|
||||||
|
return lockfile
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
readPackage,
|
||||||
|
afterAllResolved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## readPackage Hook
|
||||||
|
|
||||||
|
Called for every package before resolution. Use to modify dependencies, add missing peer deps, or fix broken packages.
|
||||||
|
|
||||||
|
### Add Missing Peer Dependency
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
if (pkg.name === 'some-broken-package') {
|
||||||
|
pkg.peerDependencies = {
|
||||||
|
...pkg.peerDependencies,
|
||||||
|
react: '*'
|
||||||
|
}
|
||||||
|
context.log(`Added react peer dep to ${pkg.name}`)
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Override Dependency Version
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Fix all lodash versions
|
||||||
|
if (pkg.dependencies?.lodash) {
|
||||||
|
pkg.dependencies.lodash = '^4.17.21'
|
||||||
|
}
|
||||||
|
if (pkg.devDependencies?.lodash) {
|
||||||
|
pkg.devDependencies.lodash = '^4.17.21'
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove Unwanted Dependency
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Remove optional dependency that causes issues
|
||||||
|
if (pkg.optionalDependencies?.fsevents) {
|
||||||
|
delete pkg.optionalDependencies.fsevents
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace Package
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Replace deprecated package
|
||||||
|
if (pkg.dependencies?.['old-package']) {
|
||||||
|
pkg.dependencies['new-package'] = pkg.dependencies['old-package']
|
||||||
|
delete pkg.dependencies['old-package']
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix Broken Package
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Fix incorrect exports field
|
||||||
|
if (pkg.name === 'broken-esm-package') {
|
||||||
|
pkg.exports = {
|
||||||
|
'.': {
|
||||||
|
import: './dist/index.mjs',
|
||||||
|
require: './dist/index.cjs'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## afterAllResolved Hook
|
||||||
|
|
||||||
|
Called after the lockfile is generated. Use for post-resolution modifications.
|
||||||
|
|
||||||
|
```js
|
||||||
|
function afterAllResolved(lockfile, context) {
|
||||||
|
// Log all resolved packages
|
||||||
|
context.log(`Resolved ${Object.keys(lockfile.packages || {}).length} packages`)
|
||||||
|
|
||||||
|
// Modify lockfile if needed
|
||||||
|
return lockfile
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Context Object
|
||||||
|
|
||||||
|
The `context` object provides utilities:
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Log messages
|
||||||
|
context.log('Processing package...')
|
||||||
|
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use with TypeScript
|
||||||
|
|
||||||
|
For type hints, use JSDoc:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// .pnpmfile.cjs
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('type-fest').PackageJson} pkg
|
||||||
|
* @param {{ log: (msg: string) => void }} context
|
||||||
|
* @returns {import('type-fest').PackageJson}
|
||||||
|
*/
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
readPackage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Conditional by Package Name
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
switch (pkg.name) {
|
||||||
|
case 'package-a':
|
||||||
|
pkg.dependencies.foo = '^2.0.0'
|
||||||
|
break
|
||||||
|
case 'package-b':
|
||||||
|
delete pkg.optionalDependencies.bar
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Apply to All Packages
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Remove all optional fsevents
|
||||||
|
if (pkg.optionalDependencies) {
|
||||||
|
delete pkg.optionalDependencies.fsevents
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Resolution
|
||||||
|
|
||||||
|
```js
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
if (process.env.DEBUG_PNPM) {
|
||||||
|
context.log(`${pkg.name}@${pkg.version}`)
|
||||||
|
context.log(` deps: ${Object.keys(pkg.dependencies || {}).join(', ')}`)
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hooks vs Overrides
|
||||||
|
|
||||||
|
| Feature | Hooks (.pnpmfile.cjs) | Overrides |
|
||||||
|
|---------|----------------------|-----------|
|
||||||
|
| Complexity | Can use JavaScript logic | Declarative only |
|
||||||
|
| Scope | Any package metadata | Version only |
|
||||||
|
| Use case | Complex fixes, conditional logic | Simple version pins |
|
||||||
|
|
||||||
|
**Prefer overrides** for simple version fixes. **Use hooks** when you need:
|
||||||
|
- Conditional logic
|
||||||
|
- Non-version modifications (exports, peer deps)
|
||||||
|
- Logging/debugging
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Hook not running
|
||||||
|
|
||||||
|
1. Ensure file is named `.pnpmfile.cjs` (not `.js`)
|
||||||
|
2. Check file is at workspace root
|
||||||
|
3. Run `pnpm install` to trigger hooks
|
||||||
|
|
||||||
|
### Debug hooks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# See hook logs
|
||||||
|
pnpm install --reporter=append-only
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/pnpmfile
|
||||||
|
-->
|
||||||
184
.agents/skills/pnpm/references/features-overrides.md
Normal file
184
.agents/skills/pnpm/references/features-overrides.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-overrides
|
||||||
|
description: Force specific versions of dependencies including transitive dependencies
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Overrides
|
||||||
|
|
||||||
|
Overrides let you force specific versions of packages, including transitive dependencies. Useful for fixing security vulnerabilities or compatibility issues.
|
||||||
|
|
||||||
|
## Basic Syntax
|
||||||
|
|
||||||
|
Define overrides in `pnpm-workspace.yaml` (recommended) or `package.json`:
|
||||||
|
|
||||||
|
### In pnpm-workspace.yaml (Recommended)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
packages:
|
||||||
|
- 'packages/*'
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
# Override all versions of a package
|
||||||
|
lodash: ^4.17.21
|
||||||
|
|
||||||
|
# Override specific version range
|
||||||
|
"foo@^1.0.0": ^1.2.3
|
||||||
|
|
||||||
|
# Override nested dependency
|
||||||
|
"express>cookie": ^0.6.0
|
||||||
|
|
||||||
|
# Override to different package
|
||||||
|
"underscore": "npm:lodash@^4.17.21"
|
||||||
|
```
|
||||||
|
|
||||||
|
### In package.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"foo@^1.0.0": "^1.2.3",
|
||||||
|
"bar@^2.0.0>qux": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Override Patterns
|
||||||
|
|
||||||
|
### Override all instances
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
lodash: ^4.17.21
|
||||||
|
```
|
||||||
|
Forces all lodash installations to use ^4.17.21.
|
||||||
|
|
||||||
|
### Override specific parent version
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"foo@^1.0.0": ^1.2.3
|
||||||
|
```
|
||||||
|
Only override foo when the requested version matches ^1.0.0.
|
||||||
|
|
||||||
|
### Override nested dependency
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"express>cookie": ^0.6.0
|
||||||
|
"foo@1.x>bar@^2.0.0>qux": ^1.0.0
|
||||||
|
```
|
||||||
|
Override cookie only when it's a dependency of express.
|
||||||
|
|
||||||
|
### Replace with different package
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
# Replace underscore with lodash
|
||||||
|
"underscore": "npm:lodash@^4.17.21"
|
||||||
|
|
||||||
|
# Use local file
|
||||||
|
"some-pkg": "file:./local-pkg"
|
||||||
|
|
||||||
|
# Use git
|
||||||
|
"some-pkg": "github:user/repo#commit"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove a dependency
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"unwanted-pkg": "-"
|
||||||
|
```
|
||||||
|
The `-` removes the package entirely.
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Security Fix
|
||||||
|
|
||||||
|
Force patched version of vulnerable package:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
# Fix CVE in transitive dependency
|
||||||
|
"minimist": "^1.2.6"
|
||||||
|
"json5": "^2.2.3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deduplicate Dependencies
|
||||||
|
|
||||||
|
Force single version when multiple are installed:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"react": "^18.2.0"
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fix Peer Dependency Issues
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"@types/react": "^18.2.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace Deprecated Package
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
overrides:
|
||||||
|
"request": "npm:@cypress/request@^3.0.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hooks Alternative
|
||||||
|
|
||||||
|
For more complex scenarios, use `.pnpmfile.cjs`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// .pnpmfile.cjs
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Override dependency version
|
||||||
|
if (pkg.dependencies?.lodash) {
|
||||||
|
pkg.dependencies.lodash = '^4.17.21'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add missing peer dependency
|
||||||
|
if (pkg.name === 'some-package') {
|
||||||
|
pkg.peerDependencies = {
|
||||||
|
...pkg.peerDependencies,
|
||||||
|
react: '*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
readPackage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Overrides vs Catalogs
|
||||||
|
|
||||||
|
| Feature | Overrides | Catalogs |
|
||||||
|
|---------|-----------|----------|
|
||||||
|
| Affects | All dependencies (including transitive) | Direct dependencies only |
|
||||||
|
| Usage | Automatic | Explicit `catalog:` reference |
|
||||||
|
| Purpose | Force versions, fix issues | Version management |
|
||||||
|
| Granularity | Can target specific parents | Package-wide only |
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
Check which version is resolved:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# See resolved versions
|
||||||
|
pnpm why lodash
|
||||||
|
|
||||||
|
# List all versions
|
||||||
|
pnpm list lodash --depth=Infinity
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/package_json#pnpmoverrides
|
||||||
|
- https://pnpm.io/pnpmfile
|
||||||
|
-->
|
||||||
201
.agents/skills/pnpm/references/features-patches.md
Normal file
201
.agents/skills/pnpm/references/features-patches.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-patches
|
||||||
|
description: Patch third-party packages directly with customized fixes
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Patches
|
||||||
|
|
||||||
|
pnpm's patching feature lets you modify third-party packages directly. Useful for applying fixes before upstream releases or customizing package behavior.
|
||||||
|
|
||||||
|
## Creating a Patch
|
||||||
|
|
||||||
|
### Step 1: Initialize Patch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm patch <pkg>@<version>
|
||||||
|
|
||||||
|
# Example
|
||||||
|
pnpm patch express@4.18.2
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a temporary directory with the package source and outputs the path:
|
||||||
|
|
||||||
|
```
|
||||||
|
You can now edit the following folder: /tmp/abc123...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Edit Files
|
||||||
|
|
||||||
|
Navigate to the temporary directory and make your changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /tmp/abc123...
|
||||||
|
# Edit files as needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Commit Patch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm patch-commit <path-from-step-1>
|
||||||
|
|
||||||
|
# Example
|
||||||
|
pnpm patch-commit /tmp/abc123...
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `.patch` file in `patches/` and updates `package.json`:
|
||||||
|
|
||||||
|
```
|
||||||
|
patches/
|
||||||
|
└── express@4.18.2.patch
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"patchedDependencies": {
|
||||||
|
"express@4.18.2": "patches/express@4.18.2.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Patch File Format
|
||||||
|
|
||||||
|
Patches use standard unified diff format:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
diff --git a/lib/router/index.js b/lib/router/index.js
|
||||||
|
index abc123..def456 100644
|
||||||
|
--- a/lib/router/index.js
|
||||||
|
+++ b/lib/router/index.js
|
||||||
|
@@ -100,6 +100,7 @@ function createRouter() {
|
||||||
|
// Original code
|
||||||
|
- const timeout = 30000;
|
||||||
|
+ const timeout = 60000; // Extended timeout
|
||||||
|
return router;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Managing Patches
|
||||||
|
|
||||||
|
### List Patched Packages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm list --depth=0
|
||||||
|
# Shows (patched) marker for patched packages
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update a Patch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Edit existing patch
|
||||||
|
pnpm patch express@4.18.2
|
||||||
|
|
||||||
|
# After editing
|
||||||
|
pnpm patch-commit <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Remove a Patch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm patch-remove <pkg>@<version>
|
||||||
|
|
||||||
|
# Example
|
||||||
|
pnpm patch-remove express@4.18.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
1. Delete the patch file from `patches/`
|
||||||
|
2. Remove entry from `patchedDependencies` in `package.json`
|
||||||
|
3. Run `pnpm install`
|
||||||
|
|
||||||
|
## Patch Configuration
|
||||||
|
|
||||||
|
### Custom Patches Directory
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"patchedDependencies": {
|
||||||
|
"express@4.18.2": "custom-patches/my-express-fix.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Packages
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"patchedDependencies": {
|
||||||
|
"express@4.18.2": "patches/express@4.18.2.patch",
|
||||||
|
"lodash@4.17.21": "patches/lodash@4.17.21.patch",
|
||||||
|
"@types/node@20.10.0": "patches/@types__node@20.10.0.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Workspaces
|
||||||
|
|
||||||
|
Patches are shared across the workspace. Define in the root `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Root package.json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"patchedDependencies": {
|
||||||
|
"express@4.18.2": "patches/express@4.18.2.patch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
All workspace packages using `express@4.18.2` will have the patch applied.
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Version specificity**: Patches are tied to exact versions. Update patches when upgrading dependencies.
|
||||||
|
|
||||||
|
2. **Document patches**: Add comments explaining why the patch exists:
|
||||||
|
```bash
|
||||||
|
# In patches/README.md
|
||||||
|
## express@4.18.2.patch
|
||||||
|
Fixes timeout issue. PR pending: https://github.com/expressjs/express/pull/1234
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Minimize patches**: Keep patches small and focused. Large patches are hard to maintain.
|
||||||
|
|
||||||
|
4. **Track upstream**: Note upstream issues/PRs so you can remove patches when fixed.
|
||||||
|
|
||||||
|
5. **Test patches**: Ensure patched code works correctly in your use case.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Patch fails to apply
|
||||||
|
|
||||||
|
```
|
||||||
|
ERR_PNPM_PATCH_FAILED Cannot apply patch
|
||||||
|
```
|
||||||
|
|
||||||
|
The package version changed. Recreate the patch:
|
||||||
|
```bash
|
||||||
|
pnpm patch-remove express@4.18.2
|
||||||
|
pnpm patch express@4.18.2
|
||||||
|
# Reapply changes
|
||||||
|
pnpm patch-commit <path>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Patch not applied
|
||||||
|
|
||||||
|
Ensure:
|
||||||
|
1. Version in `patchedDependencies` matches installed version exactly
|
||||||
|
2. Run `pnpm install` after adding patch configuration
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/cli/patch
|
||||||
|
- https://pnpm.io/cli/patch-commit
|
||||||
|
- https://pnpm.io/package_json#pnpmpatcheddependencies
|
||||||
|
-->
|
||||||
250
.agents/skills/pnpm/references/features-peer-deps.md
Normal file
250
.agents/skills/pnpm/references/features-peer-deps.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
---
|
||||||
|
name: pnpm-peer-dependencies
|
||||||
|
description: Handling peer dependencies with auto-install and resolution rules
|
||||||
|
---
|
||||||
|
|
||||||
|
# pnpm Peer Dependencies
|
||||||
|
|
||||||
|
pnpm has strict peer dependency handling by default. It provides configuration options to control how peer dependencies are resolved and reported.
|
||||||
|
|
||||||
|
## Auto-Install Peer Dependencies
|
||||||
|
|
||||||
|
By default, pnpm automatically installs peer dependencies:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# .npmrc (default is true since pnpm v8)
|
||||||
|
auto-install-peers=true
|
||||||
|
```
|
||||||
|
|
||||||
|
When enabled, pnpm automatically adds missing peer dependencies based on the best matching version.
|
||||||
|
|
||||||
|
## Strict Peer Dependencies
|
||||||
|
|
||||||
|
Control whether peer dependency issues cause errors:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# Fail on peer dependency issues (default: false)
|
||||||
|
strict-peer-dependencies=true
|
||||||
|
```
|
||||||
|
|
||||||
|
When strict, pnpm will fail if:
|
||||||
|
- Peer dependency is missing
|
||||||
|
- Installed version doesn't match required range
|
||||||
|
|
||||||
|
## Peer Dependency Rules
|
||||||
|
|
||||||
|
Configure peer dependency behavior in `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": ["@babel/*", "eslint"],
|
||||||
|
"allowedVersions": {
|
||||||
|
"react": "17 || 18"
|
||||||
|
},
|
||||||
|
"allowAny": ["@types/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### ignoreMissing
|
||||||
|
|
||||||
|
Suppress warnings for missing peer dependencies:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": [
|
||||||
|
"@babel/*",
|
||||||
|
"eslint",
|
||||||
|
"webpack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Use patterns:
|
||||||
|
- `"react"` - exact package name
|
||||||
|
- `"@babel/*"` - all packages in scope
|
||||||
|
- `"*"` - all packages (not recommended)
|
||||||
|
|
||||||
|
### allowedVersions
|
||||||
|
|
||||||
|
Allow specific versions that would otherwise cause warnings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"allowedVersions": {
|
||||||
|
"react": "17 || 18",
|
||||||
|
"webpack": "4 || 5",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### allowAny
|
||||||
|
|
||||||
|
Allow any version for specified peer dependencies:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"allowAny": ["@types/*", "eslint"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding Peer Dependencies via Hooks
|
||||||
|
|
||||||
|
Use `.pnpmfile.cjs` to add missing peer dependencies:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// .pnpmfile.cjs
|
||||||
|
function readPackage(pkg, context) {
|
||||||
|
// Add missing peer dependency
|
||||||
|
if (pkg.name === 'problematic-package') {
|
||||||
|
pkg.peerDependencies = {
|
||||||
|
...pkg.peerDependencies,
|
||||||
|
react: '*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pkg
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
hooks: {
|
||||||
|
readPackage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Peer Dependencies in Workspaces
|
||||||
|
|
||||||
|
Workspace packages can satisfy peer dependencies:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/app/package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"@myorg/components": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packages/components/package.json
|
||||||
|
{
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The workspace `app` provides `react` which satisfies `components`' peer dependency.
|
||||||
|
|
||||||
|
## Common Scenarios
|
||||||
|
|
||||||
|
### Monorepo with Shared React
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# pnpm-workspace.yaml
|
||||||
|
catalog:
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
// packages/ui/package.json
|
||||||
|
{
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// apps/web/package.json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"react": "catalog:",
|
||||||
|
"react-dom": "catalog:",
|
||||||
|
"@myorg/ui": "workspace:^"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suppress ESLint Plugin Warnings
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"ignoreMissing": [
|
||||||
|
"eslint",
|
||||||
|
"@typescript-eslint/parser"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Allow Multiple Major Versions
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pnpm": {
|
||||||
|
"peerDependencyRules": {
|
||||||
|
"allowedVersions": {
|
||||||
|
"webpack": "4 || 5",
|
||||||
|
"postcss": "7 || 8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Peer Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# See why a package is installed
|
||||||
|
pnpm why <package>
|
||||||
|
|
||||||
|
# List all peer dependency warnings
|
||||||
|
pnpm install --reporter=append-only 2>&1 | grep -i peer
|
||||||
|
|
||||||
|
# Check dependency tree
|
||||||
|
pnpm list --depth=Infinity
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Enable auto-install-peers** for convenience (default in pnpm v8+)
|
||||||
|
|
||||||
|
2. **Use peerDependencyRules** instead of ignoring all warnings
|
||||||
|
|
||||||
|
3. **Document suppressed warnings** explaining why they're safe
|
||||||
|
|
||||||
|
4. **Keep peer deps ranges wide** in libraries:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Test with different peer versions** if you support multiple majors
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://pnpm.io/package_json#pnpmpeerdependencyrules
|
||||||
|
- https://pnpm.io/npmrc#auto-install-peers
|
||||||
|
-->
|
||||||
5
.agents/skills/unocss/GENERATION.md
Normal file
5
.agents/skills/unocss/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/unocss`
|
||||||
|
- **Git SHA:** `2f7f267d0cc0c43d44357208aabb35b049359a08`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
64
.agents/skills/unocss/SKILL.md
Normal file
64
.agents/skills/unocss/SKILL.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: unocss
|
||||||
|
description: UnoCSS instant atomic CSS engine, superset of Tailwind CSS. Use when configuring UnoCSS, writing utility rules, shortcuts, or working with presets like Wind, Icons, Attributify.
|
||||||
|
metadata:
|
||||||
|
author: Anthony Fu
|
||||||
|
version: "2026.1.28"
|
||||||
|
source: Generated from https://github.com/unocss/unocss, scripts located at https://github.com/antfu/skills
|
||||||
|
---
|
||||||
|
|
||||||
|
UnoCSS is an instant atomic CSS engine designed to be flexible and extensible. The core is un-opinionated - all CSS utilities are provided via presets. It's a superset of Tailwind CSS, so you can reuse your Tailwind knowledge for basic syntax usage.
|
||||||
|
|
||||||
|
**Important:** Before writing UnoCSS code, agents should check for `uno.config.*` or `unocss.config.*` files in the project root to understand what presets, rules, and shortcuts are available. If the project setup is unclear, avoid using attributify mode and other advanced features - stick to basic `class` usage.
|
||||||
|
|
||||||
|
> The skill is based on UnoCSS 66.x, generated at 2026-01-28.
|
||||||
|
|
||||||
|
## Core
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Configuration | Config file setup and all configuration options | [core-config](references/core-config.md) |
|
||||||
|
| Rules | Static and dynamic rules for generating CSS utilities | [core-rules](references/core-rules.md) |
|
||||||
|
| Shortcuts | Combine multiple rules into single shorthands | [core-shortcuts](references/core-shortcuts.md) |
|
||||||
|
| Theme | Theming system for colors, breakpoints, and design tokens | [core-theme](references/core-theme.md) |
|
||||||
|
| Variants | Apply variations like hover:, dark:, responsive to rules | [core-variants](references/core-variants.md) |
|
||||||
|
| Extracting | How UnoCSS extracts utilities from source code | [core-extracting](references/core-extracting.md) |
|
||||||
|
| Safelist & Blocklist | Force include or exclude specific utilities | [core-safelist](references/core-safelist.md) |
|
||||||
|
| Layers & Preflights | CSS layer ordering and raw CSS injection | [core-layers](references/core-layers.md) |
|
||||||
|
|
||||||
|
## Presets
|
||||||
|
|
||||||
|
### Main Presets
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Preset Wind3 | Tailwind CSS v3 / Windi CSS compatible preset (most common) | [preset-wind3](references/preset-wind3.md) |
|
||||||
|
| Preset Wind4 | Tailwind CSS v4 compatible preset with modern CSS features | [preset-wind4](references/preset-wind4.md) |
|
||||||
|
| Preset Mini | Minimal preset with essential utilities for custom builds | [preset-mini](references/preset-mini.md) |
|
||||||
|
|
||||||
|
### Feature Presets
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Preset Icons | Pure CSS icons using Iconify with any icon set | [preset-icons](references/preset-icons.md) |
|
||||||
|
| Preset Attributify | Group utilities in HTML attributes instead of class | [preset-attributify](references/preset-attributify.md) |
|
||||||
|
| Preset Typography | Prose classes for typographic defaults | [preset-typography](references/preset-typography.md) |
|
||||||
|
| Preset Web Fonts | Easy Google Fonts and other web fonts integration | [preset-web-fonts](references/preset-web-fonts.md) |
|
||||||
|
| Preset Tagify | Use utilities as HTML tag names | [preset-tagify](references/preset-tagify.md) |
|
||||||
|
| Preset Rem to Px | Convert rem units to px for utilities | [preset-rem-to-px](references/preset-rem-to-px.md) |
|
||||||
|
|
||||||
|
## Transformers
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Variant Group | Shorthand for grouping utilities with common prefixes | [transformer-variant-group](references/transformer-variant-group.md) |
|
||||||
|
| Directives | CSS directives: @apply, @screen, theme(), icon() | [transformer-directives](references/transformer-directives.md) |
|
||||||
|
| Compile Class | Compile multiple classes into one hashed class | [transformer-compile-class](references/transformer-compile-class.md) |
|
||||||
|
| Attributify JSX | Support valueless attributify in JSX/TSX | [transformer-attributify-jsx](references/transformer-attributify-jsx.md) |
|
||||||
|
|
||||||
|
## Integrations
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Vite Integration | Setting up UnoCSS with Vite and framework-specific tips | [integrations-vite](references/integrations-vite.md) |
|
||||||
|
| Nuxt Integration | UnoCSS module for Nuxt applications | [integrations-nuxt](references/integrations-nuxt.md) |
|
||||||
187
.agents/skills/unocss/references/core-config.md
Normal file
187
.agents/skills/unocss/references/core-config.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
---
|
||||||
|
name: unocss-configuration
|
||||||
|
description: Config file setup and all configuration options for UnoCSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Configuration
|
||||||
|
|
||||||
|
UnoCSS is configured via a dedicated config file in your project root.
|
||||||
|
|
||||||
|
## Config File
|
||||||
|
|
||||||
|
**Recommended:** Use a dedicated `uno.config.ts` file for best IDE support and HMR.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetIcons,
|
||||||
|
presetTypography,
|
||||||
|
presetWebFonts,
|
||||||
|
presetWind3,
|
||||||
|
transformerDirectives,
|
||||||
|
transformerVariantGroup
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
shortcuts: [
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetAttributify(),
|
||||||
|
presetIcons(),
|
||||||
|
presetTypography(),
|
||||||
|
presetWebFonts({
|
||||||
|
fonts: {
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives(),
|
||||||
|
transformerVariantGroup(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
UnoCSS automatically looks for `uno.config.{js,ts,mjs,mts}` or `unocss.config.{js,ts,mjs,mts}` in the project root.
|
||||||
|
|
||||||
|
## Key Configuration Options
|
||||||
|
|
||||||
|
### rules
|
||||||
|
Define CSS utility rules. Later entries have higher priority.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
['m-1', { margin: '0.25rem' }],
|
||||||
|
[/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### shortcuts
|
||||||
|
Combine multiple rules into a single shorthand.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcuts: {
|
||||||
|
'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### theme
|
||||||
|
Theme object for design tokens shared between rules.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
brand: '#942192',
|
||||||
|
},
|
||||||
|
breakpoints: {
|
||||||
|
sm: '640px',
|
||||||
|
md: '768px',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### presets
|
||||||
|
Predefined configurations bundling rules, variants, and themes.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetIcons(),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### transformers
|
||||||
|
Transform source code to support special syntax.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives(),
|
||||||
|
transformerVariantGroup(),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### variants
|
||||||
|
Preprocess selectors with ability to rewrite CSS output.
|
||||||
|
|
||||||
|
### extractors
|
||||||
|
Handle source files and extract utility class names.
|
||||||
|
|
||||||
|
### preflights
|
||||||
|
Inject raw CSS globally.
|
||||||
|
|
||||||
|
### layers
|
||||||
|
Control the order of CSS layers. Default is `0`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
layers: {
|
||||||
|
'components': -1,
|
||||||
|
'default': 1,
|
||||||
|
'utilities': 2,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### safelist
|
||||||
|
Utilities that are always included in output.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
safelist: ['p-1', 'p-2', 'p-3']
|
||||||
|
```
|
||||||
|
|
||||||
|
### blocklist
|
||||||
|
Utilities that are always excluded.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
blocklist: ['p-1', /^p-[2-4]$/]
|
||||||
|
```
|
||||||
|
|
||||||
|
### content
|
||||||
|
Configure where to extract utilities from.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
content: {
|
||||||
|
pipeline: {
|
||||||
|
include: [/\.(vue|svelte|tsx|html)($|\?)/],
|
||||||
|
},
|
||||||
|
filesystem: ['src/**/*.php'],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### separators
|
||||||
|
Variant separator characters. Default: `[':', '-']`
|
||||||
|
|
||||||
|
### outputToCssLayers
|
||||||
|
Output UnoCSS layers as CSS Cascade Layers.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
outputToCssLayers: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specifying Config File Location
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
UnoCSS({
|
||||||
|
configFile: '../my-uno.config.ts',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/guide/config-file
|
||||||
|
- https://unocss.dev/config/
|
||||||
|
-->
|
||||||
137
.agents/skills/unocss/references/core-extracting.md
Normal file
137
.agents/skills/unocss/references/core-extracting.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
name: unocss-extracting
|
||||||
|
description: How UnoCSS extracts utilities from source code
|
||||||
|
---
|
||||||
|
|
||||||
|
# Extracting
|
||||||
|
|
||||||
|
UnoCSS searches for utility usages in your codebase and generates CSS on-demand.
|
||||||
|
|
||||||
|
## Content Sources
|
||||||
|
|
||||||
|
### Pipeline Extraction (Vite/Webpack)
|
||||||
|
|
||||||
|
Most efficient - extracts from build tool pipeline.
|
||||||
|
|
||||||
|
**Default file types:** `.jsx`, `.tsx`, `.vue`, `.md`, `.html`, `.svelte`, `.astro`, `.marko`
|
||||||
|
|
||||||
|
**Not included by default:** `.js`, `.ts`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
content: {
|
||||||
|
pipeline: {
|
||||||
|
include: [
|
||||||
|
/\.(vue|svelte|[jt]sx|mdx?|astro|html)($|\?)/,
|
||||||
|
'src/**/*.{js,ts}', // Add js/ts
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filesystem Extraction
|
||||||
|
|
||||||
|
For files not in build pipeline:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
content: {
|
||||||
|
filesystem: [
|
||||||
|
'src/**/*.php',
|
||||||
|
'public/*.html',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline Text Extraction
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
content: {
|
||||||
|
inline: [
|
||||||
|
'<div class="p-4 text-red">Some text</div>',
|
||||||
|
async () => (await fetch('https://example.com')).text(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Magic Comments
|
||||||
|
|
||||||
|
### @unocss-include
|
||||||
|
|
||||||
|
Force scan a file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// @unocss-include
|
||||||
|
export const classes = {
|
||||||
|
active: 'bg-primary text-white',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @unocss-ignore
|
||||||
|
|
||||||
|
Skip entire file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// @unocss-ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
### @unocss-skip-start / @unocss-skip-end
|
||||||
|
|
||||||
|
Skip specific blocks:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="text-green">Extracted</p>
|
||||||
|
<!-- @unocss-skip-start -->
|
||||||
|
<p class="text-red">NOT extracted</p>
|
||||||
|
<!-- @unocss-skip-end -->
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
UnoCSS works at **build time** - dynamic classes don't work:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Won't work! -->
|
||||||
|
<div class="p-${size}"></div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Solutions
|
||||||
|
|
||||||
|
**1. Safelist** - Pre-generate known values:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
safelist: ['p-1', 'p-2', 'p-3', 'p-4']
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Static mapping** - List combinations statically:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const colors = {
|
||||||
|
red: 'text-red border-red',
|
||||||
|
blue: 'text-blue border-blue',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Runtime** - Use `@unocss/runtime` for true runtime generation.
|
||||||
|
|
||||||
|
## Custom Extractors
|
||||||
|
|
||||||
|
```ts
|
||||||
|
extractors: [
|
||||||
|
{
|
||||||
|
name: 'my-extractor',
|
||||||
|
extract({ code }) {
|
||||||
|
return code.match(/class:[\w-]+/g) || []
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/guide/extracting
|
||||||
|
-->
|
||||||
104
.agents/skills/unocss/references/core-layers.md
Normal file
104
.agents/skills/unocss/references/core-layers.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
name: unocss-layers-preflights
|
||||||
|
description: CSS layer ordering and raw CSS injection
|
||||||
|
---
|
||||||
|
|
||||||
|
# Layers and Preflights
|
||||||
|
|
||||||
|
Control CSS output order and inject global CSS.
|
||||||
|
|
||||||
|
## Layers
|
||||||
|
|
||||||
|
Set layer on rules:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
[/^m-(\d)$/, ([, d]) => ({ margin: `${d / 4}rem` }), { layer: 'utilities' }],
|
||||||
|
['btn', { padding: '4px' }], // default layer
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Layer Ordering
|
||||||
|
|
||||||
|
```ts
|
||||||
|
layers: {
|
||||||
|
'components': -1,
|
||||||
|
'default': 1,
|
||||||
|
'utilities': 2,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import Layers Separately
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import 'uno:components.css'
|
||||||
|
import 'uno.css'
|
||||||
|
import './my-custom.css'
|
||||||
|
import 'uno:utilities.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Cascade Layers
|
||||||
|
|
||||||
|
```ts
|
||||||
|
outputToCssLayers: true
|
||||||
|
|
||||||
|
// Or with custom names
|
||||||
|
outputToCssLayers: {
|
||||||
|
cssLayerName: (layer) => {
|
||||||
|
if (layer === 'default') return 'utilities'
|
||||||
|
if (layer === 'shortcuts') return 'utilities.shortcuts'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Layer Variants
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- UnoCSS layer -->
|
||||||
|
<p class="uno-layer-my-layer:text-xl">
|
||||||
|
|
||||||
|
<!-- CSS @layer -->
|
||||||
|
<p class="layer-my-layer:text-xl">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preflights
|
||||||
|
|
||||||
|
Inject raw CSS globally:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
preflights: [
|
||||||
|
{
|
||||||
|
getCSS: ({ theme }) => `
|
||||||
|
* {
|
||||||
|
color: ${theme.colors.gray?.[700] ?? '#333'};
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
With layer:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
preflights: [
|
||||||
|
{
|
||||||
|
layer: 'base',
|
||||||
|
getCSS: () => `html { font-family: system-ui; }`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## preset-wind4 Layers
|
||||||
|
|
||||||
|
| Layer | Description | Order |
|
||||||
|
|-------|-------------|-------|
|
||||||
|
| `properties` | CSS @property rules | -200 |
|
||||||
|
| `theme` | Theme CSS variables | -150 |
|
||||||
|
| `base` | Reset styles | -100 |
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/layers
|
||||||
|
- https://unocss.dev/config/preflights
|
||||||
|
-->
|
||||||
166
.agents/skills/unocss/references/core-rules.md
Normal file
166
.agents/skills/unocss/references/core-rules.md
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
name: unocss-rules
|
||||||
|
description: Static and dynamic rules for generating CSS utilities in UnoCSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Rules
|
||||||
|
|
||||||
|
Rules define utility classes and the CSS they generate. UnoCSS has many built-in rules via presets and allows custom rules.
|
||||||
|
|
||||||
|
## Static Rules
|
||||||
|
|
||||||
|
Simple mapping from class name to CSS properties:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
['m-1', { margin: '0.25rem' }],
|
||||||
|
['font-bold', { 'font-weight': 700 }],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage: `<div class="m-1">` generates `.m-1 { margin: 0.25rem; }`
|
||||||
|
|
||||||
|
**Note:** Use CSS property syntax with hyphens (e.g., `font-weight` not `fontWeight`). Quote properties with hyphens.
|
||||||
|
|
||||||
|
## Dynamic Rules
|
||||||
|
|
||||||
|
Use RegExp matcher with function body for flexible utilities:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
// Match m-1, m-2, m-100, etc.
|
||||||
|
[/^m-(\d+)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
|
||||||
|
|
||||||
|
// Access theme and context
|
||||||
|
[/^p-(\d+)$/, (match, ctx) => ({ padding: `${match[1] / 4}rem` })],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
The function receives:
|
||||||
|
1. RegExp match result (destructure to get captured groups)
|
||||||
|
2. Context object with `theme`, `symbols`, etc.
|
||||||
|
|
||||||
|
## CSS Fallback Values
|
||||||
|
|
||||||
|
Return 2D array for CSS property fallbacks (browser compatibility):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
[/^h-(\d+)dvh$/, ([_, d]) => [
|
||||||
|
['height', `${d}vh`],
|
||||||
|
['height', `${d}dvh`],
|
||||||
|
]],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates: `.h-100dvh { height: 100vh; height: 100dvh; }`
|
||||||
|
|
||||||
|
## Special Symbols
|
||||||
|
|
||||||
|
Control CSS output with symbols from `@unocss/core`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { symbols } from '@unocss/core'
|
||||||
|
|
||||||
|
rules: [
|
||||||
|
['grid', {
|
||||||
|
[symbols.parent]: '@supports (display: grid)',
|
||||||
|
display: 'grid',
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Symbols
|
||||||
|
|
||||||
|
| Symbol | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `symbols.parent` | Parent wrapper (e.g., `@supports`, `@media`) |
|
||||||
|
| `symbols.selector` | Function to modify the selector |
|
||||||
|
| `symbols.layer` | Set the UnoCSS layer |
|
||||||
|
| `symbols.variants` | Array of variant handlers |
|
||||||
|
| `symbols.shortcutsNoMerge` | Disable merging in shortcuts |
|
||||||
|
| `symbols.noMerge` | Disable rule merging |
|
||||||
|
| `symbols.sort` | Override sorting order |
|
||||||
|
| `symbols.body` | Full control of CSS body |
|
||||||
|
|
||||||
|
## Multi-Selector Rules
|
||||||
|
|
||||||
|
Use generator functions to yield multiple CSS rules:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
[/^button-(.*)$/, function* ([, color], { symbols }) {
|
||||||
|
yield { background: color }
|
||||||
|
yield {
|
||||||
|
[symbols.selector]: selector => `${selector}:hover`,
|
||||||
|
background: `color-mix(in srgb, ${color} 90%, black)`
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates both `.button-red { background: red; }` and `.button-red:hover { ... }`
|
||||||
|
|
||||||
|
## Fully Controlled Rules
|
||||||
|
|
||||||
|
Return a string for complete CSS control (advanced):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, toEscapedSelector as e } from 'unocss'
|
||||||
|
|
||||||
|
rules: [
|
||||||
|
[/^custom-(.+)$/, ([, name], { rawSelector, theme }) => {
|
||||||
|
const selector = e(rawSelector)
|
||||||
|
return `
|
||||||
|
${selector} { font-size: ${theme.fontSize.sm}; }
|
||||||
|
${selector}::after { content: 'after'; }
|
||||||
|
@media (min-width: ${theme.breakpoints.sm}) {
|
||||||
|
${selector} { font-size: ${theme.fontSize.lg}; }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning:** Fully controlled rules don't work with variants like `hover:`.
|
||||||
|
|
||||||
|
## Symbols.body for Variant Support
|
||||||
|
|
||||||
|
Use `symbols.body` to keep variant support with custom CSS:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
['custom-red', {
|
||||||
|
[symbols.body]: `
|
||||||
|
font-size: 1rem;
|
||||||
|
&::after { content: 'after'; }
|
||||||
|
& > .bar { color: red; }
|
||||||
|
`,
|
||||||
|
[symbols.selector]: selector => `:is(${selector})`,
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rule Ordering
|
||||||
|
|
||||||
|
Later rules have higher priority. Dynamic rules output is sorted alphabetically within the group.
|
||||||
|
|
||||||
|
## Rule Merging
|
||||||
|
|
||||||
|
UnoCSS merges rules with identical CSS bodies:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="m-2 hover:m2">
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```css
|
||||||
|
.hover\:m2:hover, .m-2 { margin: 0.5rem; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `symbols.noMerge` to disable.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/rules
|
||||||
|
-->
|
||||||
105
.agents/skills/unocss/references/core-safelist.md
Normal file
105
.agents/skills/unocss/references/core-safelist.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
name: unocss-safelist-blocklist
|
||||||
|
description: Force include or exclude specific utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Safelist and Blocklist
|
||||||
|
|
||||||
|
Control which utilities are always included or excluded.
|
||||||
|
|
||||||
|
## Safelist
|
||||||
|
|
||||||
|
Utilities always included, regardless of detection:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
safelist: [
|
||||||
|
'p-1', 'p-2', 'p-3',
|
||||||
|
// Dynamic generation
|
||||||
|
...Array.from({ length: 4 }, (_, i) => `p-${i + 1}`),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Form
|
||||||
|
|
||||||
|
```ts
|
||||||
|
safelist: [
|
||||||
|
'p-1',
|
||||||
|
() => ['m-1', 'm-2'],
|
||||||
|
(context) => {
|
||||||
|
const colors = Object.keys(context.theme.colors || {})
|
||||||
|
return colors.map(c => `bg-${c}-500`)
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Use Cases
|
||||||
|
|
||||||
|
```ts
|
||||||
|
safelist: [
|
||||||
|
// Dynamic colors from CMS
|
||||||
|
() => ['primary', 'secondary'].flatMap(c => [
|
||||||
|
`bg-${c}`, `text-${c}`, `border-${c}`,
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Component variants
|
||||||
|
() => {
|
||||||
|
const variants = ['primary', 'danger']
|
||||||
|
const sizes = ['sm', 'md', 'lg']
|
||||||
|
return variants.flatMap(v => sizes.map(s => `btn-${v}-${s}`))
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Blocklist
|
||||||
|
|
||||||
|
Utilities never generated:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
blocklist: [
|
||||||
|
'p-1', // Exact match
|
||||||
|
/^p-[2-4]$/, // Regex
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Messages
|
||||||
|
|
||||||
|
```ts
|
||||||
|
blocklist: [
|
||||||
|
['bg-red-500', { message: 'Use bg-red-600 instead' }],
|
||||||
|
[/^text-xs$/, { message: 'Use text-sm for accessibility' }],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Safelist vs Blocklist
|
||||||
|
|
||||||
|
| Feature | Safelist | Blocklist |
|
||||||
|
|---------|----------|-----------|
|
||||||
|
| Purpose | Always include | Always exclude |
|
||||||
|
| Strings | ✅ | ✅ |
|
||||||
|
| Regex | ❌ | ✅ |
|
||||||
|
| Functions | ✅ | ❌ |
|
||||||
|
|
||||||
|
**Note:** Blocklist wins if utility is in both.
|
||||||
|
|
||||||
|
## Best Practice
|
||||||
|
|
||||||
|
Prefer static mappings over safelist:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Better: UnoCSS extracts automatically
|
||||||
|
const sizes = {
|
||||||
|
sm: 'text-sm p-2',
|
||||||
|
md: 'text-base p-4',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid: Large safelist
|
||||||
|
safelist: ['text-sm', 'text-base', 'p-2', 'p-4']
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/safelist
|
||||||
|
- https://unocss.dev/guide/extracting
|
||||||
|
-->
|
||||||
89
.agents/skills/unocss/references/core-shortcuts.md
Normal file
89
.agents/skills/unocss/references/core-shortcuts.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
---
|
||||||
|
name: unocss-shortcuts
|
||||||
|
description: Combine multiple utility rules into single shorthand classes
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Shortcuts
|
||||||
|
|
||||||
|
Shortcuts combine multiple rules into a single shorthand, inspired by Windi CSS.
|
||||||
|
|
||||||
|
## Static Shortcuts
|
||||||
|
|
||||||
|
Define as an object mapping shortcut names to utility combinations:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcuts: {
|
||||||
|
// Multiple utilities combined
|
||||||
|
'btn': 'py-2 px-4 font-semibold rounded-lg shadow-md',
|
||||||
|
'btn-green': 'text-white bg-green-500 hover:bg-green-700',
|
||||||
|
// Single utility alias
|
||||||
|
'red': 'text-red-100',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```html
|
||||||
|
<button class="btn btn-green">Click me</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic Shortcuts
|
||||||
|
|
||||||
|
Use RegExp matcher with function, similar to dynamic rules:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcuts: [
|
||||||
|
// Static shortcuts can be in array too
|
||||||
|
{
|
||||||
|
btn: 'py-2 px-4 font-semibold rounded-lg shadow-md',
|
||||||
|
},
|
||||||
|
// Dynamic shortcut
|
||||||
|
[/^btn-(.*)$/, ([, c]) => `bg-${c}-400 text-${c}-100 py-2 px-4 rounded-lg`],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Now `btn-green` and `btn-red` generate:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.btn-green {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
--un-bg-opacity: 1;
|
||||||
|
background-color: rgb(74 222 128 / var(--un-bg-opacity));
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
--un-text-opacity: 1;
|
||||||
|
color: rgb(220 252 231 / var(--un-text-opacity));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessing Theme in Shortcuts
|
||||||
|
|
||||||
|
Dynamic shortcuts receive context with theme access:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcuts: [
|
||||||
|
[/^badge-(.*)$/, ([, c], { theme }) => {
|
||||||
|
if (Object.keys(theme.colors).includes(c))
|
||||||
|
return `bg-${c}4:10 text-${c}5 rounded`
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Shortcuts Layer
|
||||||
|
|
||||||
|
Shortcuts are output to the `shortcuts` layer by default. Configure with:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcutsLayer: 'my-shortcuts-layer'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Later shortcuts have higher priority
|
||||||
|
- Shortcuts can reference other shortcuts
|
||||||
|
- Dynamic shortcuts work like dynamic rules
|
||||||
|
- Shortcuts are expanded at build time, not runtime
|
||||||
|
- All variants work with shortcuts (`hover:btn`, `dark:btn`, etc.)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/shortcuts
|
||||||
|
-->
|
||||||
172
.agents/skills/unocss/references/core-theme.md
Normal file
172
.agents/skills/unocss/references/core-theme.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
name: unocss-theme
|
||||||
|
description: Theming system for colors, breakpoints, and design tokens
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Theme
|
||||||
|
|
||||||
|
UnoCSS supports theming similar to Tailwind CSS / Windi CSS. The `theme` property is deep-merged with the default theme.
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```ts
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
veryCool: '#0000ff', // class="text-very-cool"
|
||||||
|
brand: {
|
||||||
|
primary: 'hsl(var(--hue, 217) 78% 51%)', // class="bg-brand-primary"
|
||||||
|
DEFAULT: '#942192' // class="bg-brand"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Theme in Rules
|
||||||
|
|
||||||
|
Access theme values in dynamic rules:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
rules: [
|
||||||
|
[/^text-(.*)$/, ([, c], { theme }) => {
|
||||||
|
if (theme.colors[c])
|
||||||
|
return { color: theme.colors[c] }
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Theme in Variants
|
||||||
|
|
||||||
|
```ts
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
name: 'variant-name',
|
||||||
|
match(matcher, { theme }) {
|
||||||
|
// Access theme.breakpoints, theme.colors, etc.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Theme in Shortcuts
|
||||||
|
|
||||||
|
```ts
|
||||||
|
shortcuts: [
|
||||||
|
[/^badge-(.*)$/, ([, c], { theme }) => {
|
||||||
|
if (Object.keys(theme.colors).includes(c))
|
||||||
|
return `bg-${c}4:10 text-${c}5 rounded`
|
||||||
|
}],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breakpoints
|
||||||
|
|
||||||
|
**Warning:** Custom `breakpoints` object **overrides** the default, not merges.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
theme: {
|
||||||
|
breakpoints: {
|
||||||
|
sm: '320px',
|
||||||
|
md: '640px',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Only `sm:` and `md:` variants will be available.
|
||||||
|
|
||||||
|
### Inherit Default Breakpoints
|
||||||
|
|
||||||
|
Use `extendTheme` to merge with defaults:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
extendTheme: (theme) => {
|
||||||
|
return {
|
||||||
|
...theme,
|
||||||
|
breakpoints: {
|
||||||
|
...theme.breakpoints,
|
||||||
|
sm: '320px',
|
||||||
|
md: '640px',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `verticalBreakpoints` works the same for vertical layout.
|
||||||
|
|
||||||
|
### Breakpoint Sorting
|
||||||
|
|
||||||
|
Breakpoints are sorted by size. Use consistent units to avoid errors:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
theme: {
|
||||||
|
breakpoints: {
|
||||||
|
sm: '320px',
|
||||||
|
// Don't mix units - convert rem to px
|
||||||
|
// md: '40rem', // Bad
|
||||||
|
md: `${40 * 16}px`, // Good
|
||||||
|
lg: '960px',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## ExtendTheme
|
||||||
|
|
||||||
|
`extendTheme` lets you modify the merged theme object:
|
||||||
|
|
||||||
|
### Mutate Theme
|
||||||
|
|
||||||
|
```ts
|
||||||
|
extendTheme: (theme) => {
|
||||||
|
theme.colors.veryCool = '#0000ff'
|
||||||
|
theme.colors.brand = {
|
||||||
|
primary: 'hsl(var(--hue, 217) 78% 51%)',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Replace Theme
|
||||||
|
|
||||||
|
Return a new object to completely replace:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
extendTheme: (theme) => {
|
||||||
|
return {
|
||||||
|
...theme,
|
||||||
|
colors: {
|
||||||
|
...theme.colors,
|
||||||
|
veryCool: '#0000ff',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Differences in Presets
|
||||||
|
|
||||||
|
### preset-wind3 vs preset-wind4
|
||||||
|
|
||||||
|
| preset-wind3 | preset-wind4 |
|
||||||
|
|--------------|--------------|
|
||||||
|
| `fontFamily` | `font` |
|
||||||
|
| `fontSize` | `text.fontSize` |
|
||||||
|
| `lineHeight` | `text.lineHeight` or `leading` |
|
||||||
|
| `letterSpacing` | `text.letterSpacing` or `tracking` |
|
||||||
|
| `borderRadius` | `radius` |
|
||||||
|
| `easing` | `ease` |
|
||||||
|
| `breakpoints` | `breakpoint` |
|
||||||
|
| `boxShadow` | `shadow` |
|
||||||
|
| `transitionProperty` | `property` |
|
||||||
|
|
||||||
|
## Common Theme Keys
|
||||||
|
|
||||||
|
- `colors` - Color palette
|
||||||
|
- `breakpoints` - Responsive breakpoints
|
||||||
|
- `fontFamily` - Font stacks
|
||||||
|
- `fontSize` - Text sizes
|
||||||
|
- `spacing` - Spacing scale
|
||||||
|
- `borderRadius` - Border radius values
|
||||||
|
- `boxShadow` - Shadow definitions
|
||||||
|
- `animation` - Animation keyframes and timing
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/theme
|
||||||
|
-->
|
||||||
175
.agents/skills/unocss/references/core-variants.md
Normal file
175
.agents/skills/unocss/references/core-variants.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
---
|
||||||
|
name: unocss-variants
|
||||||
|
description: Apply variations like hover:, dark:, responsive to rules
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Variants
|
||||||
|
|
||||||
|
Variants apply modifications to utility rules, like `hover:`, `dark:`, or responsive prefixes.
|
||||||
|
|
||||||
|
## How Variants Work
|
||||||
|
|
||||||
|
When matching `hover:m-2`:
|
||||||
|
|
||||||
|
1. `hover:m-2` is extracted from source
|
||||||
|
2. Sent to all variants for matching
|
||||||
|
3. `hover:` variant matches and returns `m-2`
|
||||||
|
4. Result `m-2` continues to next variants
|
||||||
|
5. Finally matches the rule `.m-2 { margin: 0.5rem; }`
|
||||||
|
6. Variant transformation applied: `.hover\:m-2:hover { margin: 0.5rem; }`
|
||||||
|
|
||||||
|
## Creating Custom Variants
|
||||||
|
|
||||||
|
```ts
|
||||||
|
variants: [
|
||||||
|
// hover: variant
|
||||||
|
(matcher) => {
|
||||||
|
if (!matcher.startsWith('hover:'))
|
||||||
|
return matcher
|
||||||
|
return {
|
||||||
|
// Remove prefix, pass to next variants/rules
|
||||||
|
matcher: matcher.slice(6),
|
||||||
|
// Modify the selector
|
||||||
|
selector: s => `${s}:hover`,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rules: [
|
||||||
|
[/^m-(\d)$/, ([, d]) => ({ margin: `${d / 4}rem` })],
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Variant Return Object
|
||||||
|
|
||||||
|
- `matcher` - The processed class name to pass forward
|
||||||
|
- `selector` - Function to customize the CSS selector
|
||||||
|
- `parent` - Wrapper like `@media`, `@supports`
|
||||||
|
- `layer` - Specify output layer
|
||||||
|
- `sort` - Control ordering
|
||||||
|
|
||||||
|
## Built-in Variants (preset-wind3)
|
||||||
|
|
||||||
|
### Pseudo-classes
|
||||||
|
- `hover:`, `focus:`, `active:`, `visited:`
|
||||||
|
- `first:`, `last:`, `odd:`, `even:`
|
||||||
|
- `disabled:`, `checked:`, `required:`
|
||||||
|
- `focus-within:`, `focus-visible:`
|
||||||
|
|
||||||
|
### Pseudo-elements
|
||||||
|
- `before:`, `after:`
|
||||||
|
- `placeholder:`, `selection:`
|
||||||
|
- `marker:`, `file:`
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
- `sm:`, `md:`, `lg:`, `xl:`, `2xl:`
|
||||||
|
- `lt-sm:` (less than sm)
|
||||||
|
- `at-lg:` (at lg only)
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
- `dark:` - Class-based dark mode (default)
|
||||||
|
- `@dark:` - Media query dark mode
|
||||||
|
|
||||||
|
### Group/Peer
|
||||||
|
- `group-hover:`, `group-focus:`
|
||||||
|
- `peer-checked:`, `peer-focus:`
|
||||||
|
|
||||||
|
### Container Queries
|
||||||
|
- `@container`, `@sm:`, `@md:`
|
||||||
|
|
||||||
|
### Print
|
||||||
|
- `print:`
|
||||||
|
|
||||||
|
### Supports
|
||||||
|
- `supports-[display:grid]:`
|
||||||
|
|
||||||
|
### Aria
|
||||||
|
- `aria-checked:`, `aria-disabled:`
|
||||||
|
|
||||||
|
### Data Attributes
|
||||||
|
- `data-[state=open]:`
|
||||||
|
|
||||||
|
## Dark Mode Configuration
|
||||||
|
|
||||||
|
### Class-based (default)
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
dark: 'class'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="dark:bg-gray-800">
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates: `.dark .dark\:bg-gray-800 { ... }`
|
||||||
|
|
||||||
|
### Media Query
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
dark: 'media'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates: `@media (prefers-color-scheme: dark) { ... }`
|
||||||
|
|
||||||
|
### Opt-in Media Query
|
||||||
|
Use `@dark:` regardless of config:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@dark:bg-gray-800">
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Selectors
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
dark: {
|
||||||
|
light: '.light-mode',
|
||||||
|
dark: '.dark-mode',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS @layer Variant
|
||||||
|
|
||||||
|
Native CSS `@layer` support:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="layer-foo:p-4" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```css
|
||||||
|
@layer foo {
|
||||||
|
.layer-foo\:p-4 { padding: 1rem; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Breakpoint Differences from Windi CSS
|
||||||
|
|
||||||
|
| Windi CSS | UnoCSS |
|
||||||
|
|-----------|--------|
|
||||||
|
| `<sm:p-1` | `lt-sm:p-1` |
|
||||||
|
| `@lg:p-1` | `at-lg:p-1` |
|
||||||
|
| `>xl:p-1` | `xl:p-1` |
|
||||||
|
|
||||||
|
## Media Hover (Experimental)
|
||||||
|
|
||||||
|
Addresses sticky hover on touch devices:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@hover-text-red">
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```css
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.\@hover-text-red:hover { color: rgb(248 113 113); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/config/variants
|
||||||
|
- https://unocss.dev/presets/wind3
|
||||||
|
- https://unocss.dev/presets/mini
|
||||||
|
-->
|
||||||
199
.agents/skills/unocss/references/integrations-nuxt.md
Normal file
199
.agents/skills/unocss/references/integrations-nuxt.md
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
name: unocss-nuxt-integration
|
||||||
|
description: UnoCSS module for Nuxt applications
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Nuxt Integration
|
||||||
|
|
||||||
|
The official Nuxt module for UnoCSS.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D unocss @unocss/nuxt
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to Nuxt config:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// nuxt.config.ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
'@unocss/nuxt',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Create config file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import { defineConfig, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The `uno.css` entry is automatically injected by the module.
|
||||||
|
|
||||||
|
## Support Status
|
||||||
|
|
||||||
|
| Build Tool | Nuxt 2 | Nuxt Bridge | Nuxt 3 |
|
||||||
|
|------------|--------|-------------|--------|
|
||||||
|
| Webpack Dev | ✅ | ✅ | 🚧 |
|
||||||
|
| Webpack Build | ✅ | ✅ | ✅ |
|
||||||
|
| Vite Dev | - | ✅ | ✅ |
|
||||||
|
| Vite Build | - | ✅ | ✅ |
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Using uno.config.ts (Recommended)
|
||||||
|
|
||||||
|
Use a dedicated config file for best IDE support:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import { defineConfig, presetWind3, presetIcons } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetIcons(),
|
||||||
|
],
|
||||||
|
shortcuts: {
|
||||||
|
'btn': 'py-2 px-4 font-semibold rounded-lg',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nuxt Layers Support
|
||||||
|
|
||||||
|
Enable automatic config merging from Nuxt layers:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// nuxt.config.ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
unocss: {
|
||||||
|
nuxtLayers: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Then in your root config:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import config from './.nuxt/uno.config.mjs'
|
||||||
|
|
||||||
|
export default config
|
||||||
|
```
|
||||||
|
|
||||||
|
Or extend the merged config:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import { mergeConfigs } from '@unocss/core'
|
||||||
|
import config from './.nuxt/uno.config.mjs'
|
||||||
|
|
||||||
|
export default mergeConfigs([config, {
|
||||||
|
// Your overrides
|
||||||
|
shortcuts: {
|
||||||
|
'custom': 'text-red-500',
|
||||||
|
},
|
||||||
|
}])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Setup Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// nuxt.config.ts
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
modules: [
|
||||||
|
'@unocss/nuxt',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetIcons,
|
||||||
|
presetTypography,
|
||||||
|
presetWebFonts,
|
||||||
|
presetWind3,
|
||||||
|
transformerDirectives,
|
||||||
|
transformerVariantGroup,
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetAttributify(),
|
||||||
|
presetIcons({
|
||||||
|
scale: 1.2,
|
||||||
|
}),
|
||||||
|
presetTypography(),
|
||||||
|
presetWebFonts({
|
||||||
|
fonts: {
|
||||||
|
sans: 'DM Sans',
|
||||||
|
mono: 'DM Mono',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives(),
|
||||||
|
transformerVariantGroup(),
|
||||||
|
],
|
||||||
|
shortcuts: [
|
||||||
|
['btn', 'px-4 py-1 rounded inline-block bg-teal-600 text-white cursor-pointer hover:bg-teal-700 disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50'],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage in Components
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div class="p-4 text-center">
|
||||||
|
<h1 class="text-3xl font-bold text-blue-600">
|
||||||
|
Hello UnoCSS!
|
||||||
|
</h1>
|
||||||
|
<button class="btn mt-4">
|
||||||
|
Click me
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
With attributify mode:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<div p="4" text="center">
|
||||||
|
<h1 text="3xl blue-600" font="bold">
|
||||||
|
Hello UnoCSS!
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Inspector
|
||||||
|
|
||||||
|
In development, visit `/_nuxt/__unocss` to access the UnoCSS inspector.
|
||||||
|
|
||||||
|
## Key Differences from Vite
|
||||||
|
|
||||||
|
- No need to import `virtual:uno.css` - automatically injected
|
||||||
|
- Config file discovery works the same
|
||||||
|
- All Vite plugin features available
|
||||||
|
- Nuxt layers config merging available
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/integrations/nuxt
|
||||||
|
-->
|
||||||
283
.agents/skills/unocss/references/integrations-vite.md
Normal file
283
.agents/skills/unocss/references/integrations-vite.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
---
|
||||||
|
name: unocss-vite-integration
|
||||||
|
description: Setting up UnoCSS with Vite and framework-specific tips
|
||||||
|
---
|
||||||
|
|
||||||
|
# UnoCSS Vite Integration
|
||||||
|
|
||||||
|
The Vite plugin is the most common way to use UnoCSS.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D unocss
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Create config file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import { defineConfig, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to entry:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// main.ts
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
### global (default)
|
||||||
|
|
||||||
|
Standard mode - generates global CSS injected via `uno.css` import.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
### vue-scoped
|
||||||
|
|
||||||
|
Injects generated CSS into Vue SFC `<style scoped>`.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
UnoCSS({
|
||||||
|
mode: 'vue-scoped',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### shadow-dom
|
||||||
|
|
||||||
|
For Web Components using Shadow DOM:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
UnoCSS({
|
||||||
|
mode: 'shadow-dom',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Add placeholder in component styles:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const template = document.createElement('template')
|
||||||
|
template.innerHTML = `
|
||||||
|
<style>
|
||||||
|
:host { ... }
|
||||||
|
@unocss-placeholder
|
||||||
|
</style>
|
||||||
|
<div class="m-1em">...</div>
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
### per-module (experimental)
|
||||||
|
|
||||||
|
Generates CSS per module with optional scoping.
|
||||||
|
|
||||||
|
### dist-chunk (experimental)
|
||||||
|
|
||||||
|
Generates CSS per chunk on build for MPA.
|
||||||
|
|
||||||
|
## DevTools Support
|
||||||
|
|
||||||
|
Edit classes directly in browser DevTools:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
import 'virtual:unocss-devtools'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warning:** Uses MutationObserver to detect changes. Dynamic classes from scripts will also be included.
|
||||||
|
|
||||||
|
## Framework-Specific Setup
|
||||||
|
|
||||||
|
### React
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import React from '@vitejs/plugin-react'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(), // Must be before React when using attributify
|
||||||
|
React(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Remove `tsc` from build script if using `@unocss/preset-attributify`.
|
||||||
|
|
||||||
|
### Vue
|
||||||
|
|
||||||
|
Works out of the box with `@vitejs/plugin-vue`.
|
||||||
|
|
||||||
|
### Svelte
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
import extractorSvelte from '@unocss/extractor-svelte'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS({
|
||||||
|
extractors: [extractorSvelte()],
|
||||||
|
}),
|
||||||
|
svelte(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports `class:foo` and `class:foo={bar}` syntax.
|
||||||
|
|
||||||
|
### SvelteKit
|
||||||
|
|
||||||
|
Same as Svelte, use `sveltekit()` from `@sveltejs/kit/vite`.
|
||||||
|
|
||||||
|
### Solid
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(),
|
||||||
|
solidPlugin(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preact
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import Preact from '@preact/preset-vite'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(),
|
||||||
|
Preact(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Elm
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import Elm from 'vite-plugin-elm'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
Elm(),
|
||||||
|
UnoCSS(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Components (Lit)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
UnoCSS({
|
||||||
|
mode: 'shadow-dom',
|
||||||
|
shortcuts: [
|
||||||
|
{ 'cool-blue': 'bg-blue-500 text-white' },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// my-element.ts
|
||||||
|
@customElement('my-element')
|
||||||
|
export class MyElement extends LitElement {
|
||||||
|
static styles = css`
|
||||||
|
:host { ... }
|
||||||
|
@unocss-placeholder
|
||||||
|
`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supports `part-[<part-name>]:<utility>` for `::part` styling.
|
||||||
|
|
||||||
|
## Inspector
|
||||||
|
|
||||||
|
Visit `http://localhost:5173/__unocss` in dev mode to:
|
||||||
|
|
||||||
|
- Inspect generated CSS rules
|
||||||
|
- See applied classes per file
|
||||||
|
- Test utilities in REPL
|
||||||
|
|
||||||
|
## Legacy Browser Support
|
||||||
|
|
||||||
|
With `@vitejs/plugin-legacy`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import legacy from '@vitejs/plugin-legacy'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS({
|
||||||
|
legacy: {
|
||||||
|
renderModernChunks: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
legacy({
|
||||||
|
targets: ['defaults', 'not IE 11'],
|
||||||
|
renderModernChunks: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## VanillaJS / TypeScript
|
||||||
|
|
||||||
|
By default, `.js` and `.ts` files are not extracted. Configure to include:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
content: {
|
||||||
|
pipeline: {
|
||||||
|
include: [
|
||||||
|
/\.(vue|svelte|[jt]sx|html)($|\?)/,
|
||||||
|
'src/**/*.{js,ts}',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use magic comment in files:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// @unocss-include
|
||||||
|
export const classes = {
|
||||||
|
active: 'bg-primary text-white',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/integrations/vite
|
||||||
|
-->
|
||||||
142
.agents/skills/unocss/references/preset-attributify.md
Normal file
142
.agents/skills/unocss/references/preset-attributify.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
name: preset-attributify
|
||||||
|
description: Group utilities in HTML attributes instead of class
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Attributify
|
||||||
|
|
||||||
|
Group utilities in HTML attributes for better readability.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetAttributify, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetAttributify(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
Instead of long class strings:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="bg-blue-400 hover:bg-blue-500 text-sm text-white font-mono font-light py-2 px-4 rounded border-2 border-blue-200">
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Group by prefix in attributes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button
|
||||||
|
bg="blue-400 hover:blue-500"
|
||||||
|
text="sm white"
|
||||||
|
font="mono light"
|
||||||
|
p="y-2 x-4"
|
||||||
|
border="2 rounded blue-200"
|
||||||
|
>
|
||||||
|
Button
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prefix Self-Referencing
|
||||||
|
|
||||||
|
For utilities matching their prefix (`flex`, `grid`, `border`), use `~`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Before -->
|
||||||
|
<button class="border border-red">Button</button>
|
||||||
|
|
||||||
|
<!-- After -->
|
||||||
|
<button border="~ red">Button</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Valueless Attributify
|
||||||
|
|
||||||
|
Use utilities as boolean attributes:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div m-2 rounded text-teal-400 />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Handling Property Conflicts
|
||||||
|
|
||||||
|
When attribute names conflict with HTML properties:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Use un- prefix -->
|
||||||
|
<a un-text="red">Text color to red</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enforce Prefix
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetAttributify({
|
||||||
|
prefix: 'un-',
|
||||||
|
prefixedOnly: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetAttributify({
|
||||||
|
strict: false, // Only generate CSS for attributify
|
||||||
|
prefix: 'un-', // Attribute prefix
|
||||||
|
prefixedOnly: false, // Require prefix for all
|
||||||
|
nonValuedAttribute: true, // Support valueless attributes
|
||||||
|
ignoreAttributes: [], // Attributes to ignore
|
||||||
|
trueToNonValued: false, // Treat value="true" as valueless
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Support
|
||||||
|
|
||||||
|
### Vue 3
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// html.d.ts
|
||||||
|
declare module '@vue/runtime-dom' {
|
||||||
|
interface HTMLAttributes { [key: string]: any }
|
||||||
|
}
|
||||||
|
declare module '@vue/runtime-core' {
|
||||||
|
interface AllowedComponentProps { [key: string]: any }
|
||||||
|
}
|
||||||
|
export {}
|
||||||
|
```
|
||||||
|
|
||||||
|
### React
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { AttributifyAttributes } from '@unocss/preset-attributify'
|
||||||
|
|
||||||
|
declare module 'react' {
|
||||||
|
interface HTMLAttributes<T> extends AttributifyAttributes {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## JSX Support
|
||||||
|
|
||||||
|
For JSX where `<div foo>` becomes `<div foo={true}>`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { transformerAttributifyJsx } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerAttributifyJsx(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Only use attributify if `uno.config.*` shows `presetAttributify()` is enabled.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/attributify
|
||||||
|
-->
|
||||||
184
.agents/skills/unocss/references/preset-icons.md
Normal file
184
.agents/skills/unocss/references/preset-icons.md
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
---
|
||||||
|
name: preset-icons
|
||||||
|
description: Pure CSS icons using Iconify with any icon set
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Icons
|
||||||
|
|
||||||
|
Use any icon as a pure CSS class, powered by Iconify.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add -D @unocss/preset-icons @iconify-json/[collection-name]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example: `@iconify-json/mdi` for Material Design Icons, `@iconify-json/carbon` for Carbon icons.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetIcons } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetIcons(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Two naming conventions:
|
||||||
|
- `<prefix><collection>-<icon>` → `i-ph-anchor-simple-thin`
|
||||||
|
- `<prefix><collection>:<icon>` → `i-ph:anchor-simple-thin`
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Phosphor anchor icon -->
|
||||||
|
<div class="i-ph-anchor-simple-thin" />
|
||||||
|
|
||||||
|
<!-- Material Design alarm with color -->
|
||||||
|
<div class="i-mdi-alarm text-orange-400" />
|
||||||
|
|
||||||
|
<!-- Large Vue logo -->
|
||||||
|
<div class="i-logos-vue text-3xl" />
|
||||||
|
|
||||||
|
<!-- Dynamic: Sun in light mode, Moon in dark -->
|
||||||
|
<button class="i-carbon-sun dark:i-carbon-moon" />
|
||||||
|
|
||||||
|
<!-- Hover effect -->
|
||||||
|
<div class="i-twemoji-grinning-face-with-smiling-eyes hover:i-twemoji-face-with-tears-of-joy" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Browse icons at [icones.js.org](https://icones.js.org/) or [Iconify](https://icon-sets.iconify.design/).
|
||||||
|
|
||||||
|
## Icon Modes
|
||||||
|
|
||||||
|
Icons automatically choose between `mask` (monochrome) and `background-img` (colorful).
|
||||||
|
|
||||||
|
### Force Specific Mode
|
||||||
|
|
||||||
|
- `?mask` - Render as mask (colorable with `currentColor`)
|
||||||
|
- `?bg` - Render as background image (preserves original colors)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Original with colors -->
|
||||||
|
<div class="i-vscode-icons:file-type-light-pnpm" />
|
||||||
|
|
||||||
|
<!-- Force mask mode, apply custom color -->
|
||||||
|
<div class="i-vscode-icons:file-type-light-pnpm?mask text-red-300" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetIcons({
|
||||||
|
scale: 1.2, // Scale relative to font size
|
||||||
|
prefix: 'i-', // Class prefix (default)
|
||||||
|
mode: 'auto', // 'auto' | 'mask' | 'bg'
|
||||||
|
extraProperties: {
|
||||||
|
'display': 'inline-block',
|
||||||
|
'vertical-align': 'middle',
|
||||||
|
},
|
||||||
|
warn: true, // Warn on missing icons
|
||||||
|
autoInstall: true, // Auto-install missing icon sets
|
||||||
|
cdn: 'https://esm.sh/', // CDN for browser usage
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Icon Collections
|
||||||
|
|
||||||
|
### Inline SVGs
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetIcons({
|
||||||
|
collections: {
|
||||||
|
custom: {
|
||||||
|
circle: '<svg viewBox="0 0 120 120"><circle cx="60" cy="60" r="50"></circle></svg>',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage: `<span class="i-custom:circle"></span>`
|
||||||
|
|
||||||
|
### File System Loader
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
|
||||||
|
|
||||||
|
presetIcons({
|
||||||
|
collections: {
|
||||||
|
'my-icons': FileSystemIconLoader(
|
||||||
|
'./assets/icons',
|
||||||
|
svg => svg.replace(/#fff/, 'currentColor')
|
||||||
|
),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dynamic Import (Browser)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import presetIcons from '@unocss/preset-icons/browser'
|
||||||
|
|
||||||
|
presetIcons({
|
||||||
|
collections: {
|
||||||
|
carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Icon Customization
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetIcons({
|
||||||
|
customizations: {
|
||||||
|
// Transform SVG
|
||||||
|
transform(svg, collection, icon) {
|
||||||
|
return svg.replace(/#fff/, 'currentColor')
|
||||||
|
},
|
||||||
|
// Global sizing
|
||||||
|
customize(props) {
|
||||||
|
props.width = '2em'
|
||||||
|
props.height = '2em'
|
||||||
|
return props
|
||||||
|
},
|
||||||
|
// Per-collection
|
||||||
|
iconCustomizer(collection, icon, props) {
|
||||||
|
if (collection === 'mdi') {
|
||||||
|
props.width = '2em'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Directive
|
||||||
|
|
||||||
|
Use `icon()` in CSS (requires transformer-directives):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.icon {
|
||||||
|
background-image: icon('i-carbon-sun');
|
||||||
|
}
|
||||||
|
.icon-colored {
|
||||||
|
background-image: icon('i-carbon-moon', '#fff');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accessibility
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- With label -->
|
||||||
|
<a href="/profile" aria-label="Profile" class="i-ph:user-duotone"></a>
|
||||||
|
|
||||||
|
<!-- Decorative -->
|
||||||
|
<a href="/profile">
|
||||||
|
<span aria-hidden="true" class="i-ph:user-duotone"></span>
|
||||||
|
My Profile
|
||||||
|
</a>
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/icons
|
||||||
|
-->
|
||||||
158
.agents/skills/unocss/references/preset-mini.md
Normal file
158
.agents/skills/unocss/references/preset-mini.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
---
|
||||||
|
name: preset-mini
|
||||||
|
description: Minimal preset with essential utilities for UnoCSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Mini
|
||||||
|
|
||||||
|
The minimal preset with only essential rules and variants. Good starting point for custom presets.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetMini } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetMini(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## What's Included
|
||||||
|
|
||||||
|
Subset of `preset-wind3` with essential utilities aligned to CSS properties:
|
||||||
|
|
||||||
|
- Basic spacing (margin, padding)
|
||||||
|
- Display (flex, grid, block, etc.)
|
||||||
|
- Positioning (absolute, relative, fixed)
|
||||||
|
- Sizing (width, height)
|
||||||
|
- Colors (text, background, border)
|
||||||
|
- Typography basics (font-size, font-weight)
|
||||||
|
- Borders and border-radius
|
||||||
|
- Basic transforms and transitions
|
||||||
|
|
||||||
|
## What's NOT Included
|
||||||
|
|
||||||
|
Opinionated or complex Tailwind utilities:
|
||||||
|
- `container`
|
||||||
|
- Complex animations
|
||||||
|
- Gradients
|
||||||
|
- Advanced typography
|
||||||
|
- Prose classes
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
1. **Building custom presets** - Start with mini and add only what you need
|
||||||
|
2. **Minimal bundle size** - When you only need basic utilities
|
||||||
|
3. **Learning** - Understand UnoCSS core without Tailwind complexity
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
Same as preset-wind3:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetMini({
|
||||||
|
dark: 'class' // or 'media'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="dark:bg-red:10" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Class-based:
|
||||||
|
```css
|
||||||
|
.dark .dark\:bg-red\:10 {
|
||||||
|
background-color: rgb(248 113 113 / 0.1);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Media query:
|
||||||
|
```css
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.dark\:bg-red\:10 {
|
||||||
|
background-color: rgb(248 113 113 / 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS @layer Variant
|
||||||
|
|
||||||
|
Native CSS layer support:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="layer-foo:p4" />
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
@layer foo {
|
||||||
|
.layer-foo\:p4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme Customization
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetMini({
|
||||||
|
theme: {
|
||||||
|
colors: {
|
||||||
|
veryCool: '#0000ff',
|
||||||
|
brand: {
|
||||||
|
primary: 'hsl(var(--hue, 217) 78% 51%)',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `breakpoints` property is overridden, not merged.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetMini({
|
||||||
|
// Dark mode: 'class' | 'media' | { light: string, dark: string }
|
||||||
|
dark: 'class',
|
||||||
|
|
||||||
|
// Generate [group=""] instead of .group for attributify
|
||||||
|
attributifyPseudo: false,
|
||||||
|
|
||||||
|
// CSS variable prefix (default: 'un-')
|
||||||
|
variablePrefix: 'un-',
|
||||||
|
|
||||||
|
// Utility prefix
|
||||||
|
prefix: undefined,
|
||||||
|
|
||||||
|
// Preflight generation: true | false | 'on-demand'
|
||||||
|
preflight: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building on Mini
|
||||||
|
|
||||||
|
Create custom preset extending mini:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { presetMini } from 'unocss'
|
||||||
|
import type { Preset } from 'unocss'
|
||||||
|
|
||||||
|
export const myPreset: Preset = {
|
||||||
|
name: 'my-preset',
|
||||||
|
presets: [presetMini()],
|
||||||
|
rules: [
|
||||||
|
// Add custom rules
|
||||||
|
['card', { 'border-radius': '8px', 'box-shadow': '0 2px 8px rgba(0,0,0,0.1)' }],
|
||||||
|
],
|
||||||
|
shortcuts: {
|
||||||
|
'btn': 'px-4 py-2 rounded bg-blue-500 text-white',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/mini
|
||||||
|
-->
|
||||||
97
.agents/skills/unocss/references/preset-rem-to-px.md
Normal file
97
.agents/skills/unocss/references/preset-rem-to-px.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: preset-rem-to-px
|
||||||
|
description: Convert rem units to px for utilities
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Rem to Px
|
||||||
|
|
||||||
|
Converts `rem` units to `px` in generated utilities.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetRemToPx, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetRemToPx(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
Transforms all rem values to px:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="p-4">
|
||||||
|
```
|
||||||
|
|
||||||
|
Without preset:
|
||||||
|
```css
|
||||||
|
.p-4 { padding: 1rem; }
|
||||||
|
```
|
||||||
|
|
||||||
|
With preset:
|
||||||
|
```css
|
||||||
|
.p-4 { padding: 16px; }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- Projects requiring pixel-perfect designs
|
||||||
|
- Environments where rem doesn't work well
|
||||||
|
- Consistency with pixel-based design systems
|
||||||
|
- Email templates (better compatibility)
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetRemToPx({
|
||||||
|
// Base font size for conversion (default: 16)
|
||||||
|
baseFontSize: 16,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom base:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetRemToPx({
|
||||||
|
baseFontSize: 14, // 1rem = 14px
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## With Preset Wind4
|
||||||
|
|
||||||
|
**Note:** `presetRemToPx` is not needed with `preset-wind4`. Use the built-in processor instead:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createRemToPxProcessor } from '@unocss/preset-wind4/utils'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
theme: {
|
||||||
|
process: createRemToPxProcessor(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// Also apply to utilities
|
||||||
|
postprocess: [createRemToPxProcessor()],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Order matters: place after the preset that generates rem values
|
||||||
|
- Affects all utilities with rem units
|
||||||
|
- Theme values in rem are also converted
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/rem-to-px
|
||||||
|
- https://unocss.dev/presets/wind4
|
||||||
|
-->
|
||||||
134
.agents/skills/unocss/references/preset-tagify.md
Normal file
134
.agents/skills/unocss/references/preset-tagify.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
---
|
||||||
|
name: preset-tagify
|
||||||
|
description: Use utilities as HTML tag names
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Tagify
|
||||||
|
|
||||||
|
Use CSS utilities directly as HTML tag names.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetTagify } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetTagify(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
Instead of:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<span class="text-red">red text</span>
|
||||||
|
<div class="flex">flexbox</div>
|
||||||
|
<span class="i-line-md-emoji-grin"></span>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use tag names directly:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<text-red>red text</text-red>
|
||||||
|
<flex>flexbox</flex>
|
||||||
|
<i-line-md-emoji-grin />
|
||||||
|
```
|
||||||
|
|
||||||
|
Works exactly the same!
|
||||||
|
|
||||||
|
## With Prefix
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTagify({
|
||||||
|
prefix: 'un-'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Matched -->
|
||||||
|
<un-flex></un-flex>
|
||||||
|
|
||||||
|
<!-- Not matched -->
|
||||||
|
<flex></flex>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extra Properties
|
||||||
|
|
||||||
|
Add CSS properties to matched tags:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTagify({
|
||||||
|
// Add display: inline-block to icons
|
||||||
|
extraProperties: matched => matched.startsWith('i-')
|
||||||
|
? { display: 'inline-block' }
|
||||||
|
: {},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or apply to all:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTagify({
|
||||||
|
extraProperties: { display: 'block' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTagify({
|
||||||
|
// Tag prefix
|
||||||
|
prefix: '',
|
||||||
|
|
||||||
|
// Excluded tags (won't be processed)
|
||||||
|
excludedTags: ['b', /^h\d+$/, 'table'],
|
||||||
|
|
||||||
|
// Extra CSS properties
|
||||||
|
extraProperties: {},
|
||||||
|
|
||||||
|
// Enable default extractor
|
||||||
|
defaultExtractor: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excluded Tags
|
||||||
|
|
||||||
|
By default, these tags are excluded:
|
||||||
|
- `b` (bold)
|
||||||
|
- `h1` through `h6` (headings)
|
||||||
|
- `table`
|
||||||
|
|
||||||
|
Add your own:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTagify({
|
||||||
|
excludedTags: [
|
||||||
|
'b',
|
||||||
|
/^h\d+$/,
|
||||||
|
'table',
|
||||||
|
'article', // Add custom exclusions
|
||||||
|
/^my-/, // Exclude tags starting with 'my-'
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- Quick prototyping
|
||||||
|
- Cleaner HTML for simple pages
|
||||||
|
- Icon embedding: `<i-carbon-sun />`
|
||||||
|
- Semantic-like styling: `<flex-center>`, `<text-red>`
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Custom element names must contain a hyphen (HTML spec)
|
||||||
|
- Some frameworks may not support all custom elements
|
||||||
|
- Utilities without hyphens need the prefix option
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/tagify
|
||||||
|
-->
|
||||||
95
.agents/skills/unocss/references/preset-typography.md
Normal file
95
.agents/skills/unocss/references/preset-typography.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
---
|
||||||
|
name: preset-typography
|
||||||
|
description: Prose classes for typographic defaults on vanilla HTML content
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Typography
|
||||||
|
|
||||||
|
Prose classes for adding typographic defaults to vanilla HTML content.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetTypography, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(), // Required!
|
||||||
|
presetTypography(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article class="prose">
|
||||||
|
<h1>My Article</h1>
|
||||||
|
<p>This is styled with typographic defaults...</p>
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article class="prose prose-sm">Small</article>
|
||||||
|
<article class="prose prose-base">Base (default)</article>
|
||||||
|
<article class="prose prose-lg">Large</article>
|
||||||
|
<article class="prose prose-xl">Extra large</article>
|
||||||
|
<article class="prose prose-2xl">2X large</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
Responsive:
|
||||||
|
```html
|
||||||
|
<article class="prose prose-sm md:prose-base lg:prose-lg">
|
||||||
|
Responsive typography
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Colors
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article class="prose prose-gray">Gray (default)</article>
|
||||||
|
<article class="prose prose-slate">Slate</article>
|
||||||
|
<article class="prose prose-blue">Blue</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article class="prose dark:prose-invert">
|
||||||
|
Dark mode typography
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Excluding Elements
|
||||||
|
|
||||||
|
```html
|
||||||
|
<article class="prose">
|
||||||
|
<p>Styled</p>
|
||||||
|
<div class="not-prose">
|
||||||
|
<p>NOT styled</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `not-prose` only works as a class.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetTypography({
|
||||||
|
selectorName: 'prose', // Custom selector
|
||||||
|
cssVarPrefix: '--un-prose', // CSS variable prefix
|
||||||
|
important: false, // Make !important
|
||||||
|
cssExtend: {
|
||||||
|
'code': { color: '#8b5cf6' },
|
||||||
|
'a:hover': { color: '#f43f5e' },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/typography
|
||||||
|
-->
|
||||||
91
.agents/skills/unocss/references/preset-web-fonts.md
Normal file
91
.agents/skills/unocss/references/preset-web-fonts.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
---
|
||||||
|
name: preset-web-fonts
|
||||||
|
description: Easy Google Fonts and other web fonts integration
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Web Fonts
|
||||||
|
|
||||||
|
Easily use web fonts from Google Fonts and other providers.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetWebFonts, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetWebFonts({
|
||||||
|
provider: 'google',
|
||||||
|
fonts: {
|
||||||
|
sans: 'Roboto',
|
||||||
|
mono: 'Fira Code',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
- `google` - Google Fonts (default)
|
||||||
|
- `bunny` - Privacy-friendly alternative
|
||||||
|
- `fontshare` - Quality fonts by ITF
|
||||||
|
- `fontsource` - Self-hosted open source fonts
|
||||||
|
- `coollabs` - Privacy-friendly drop-in replacement
|
||||||
|
- `none` - Treat as system font
|
||||||
|
|
||||||
|
## Font Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
fonts: {
|
||||||
|
// Simple
|
||||||
|
sans: 'Roboto',
|
||||||
|
|
||||||
|
// Multiple (fallback)
|
||||||
|
mono: ['Fira Code', 'Fira Mono:400,700'],
|
||||||
|
|
||||||
|
// Detailed
|
||||||
|
lato: [
|
||||||
|
{
|
||||||
|
name: 'Lato',
|
||||||
|
weights: ['400', '700'],
|
||||||
|
italic: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sans-serif',
|
||||||
|
provider: 'none',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p class="font-sans">Roboto</p>
|
||||||
|
<code class="font-mono">Fira Code</code>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Local Fonts
|
||||||
|
|
||||||
|
Self-host fonts:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createLocalFontProcessor } from '@unocss/preset-web-fonts/local'
|
||||||
|
|
||||||
|
presetWebFonts({
|
||||||
|
provider: 'none',
|
||||||
|
fonts: { sans: 'Roboto' },
|
||||||
|
processors: createLocalFontProcessor({
|
||||||
|
cacheDir: 'node_modules/.cache/unocss/fonts',
|
||||||
|
fontAssetsDir: 'public/assets/fonts',
|
||||||
|
fontServeBaseUrl: '/assets/fonts',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/web-fonts
|
||||||
|
-->
|
||||||
194
.agents/skills/unocss/references/preset-wind3.md
Normal file
194
.agents/skills/unocss/references/preset-wind3.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
name: preset-wind3
|
||||||
|
description: Tailwind CSS / Windi CSS compatible preset for UnoCSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Wind3
|
||||||
|
|
||||||
|
The Tailwind CSS / Windi CSS compatible preset. Most commonly used preset for UnoCSS.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetWind3 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** `@unocss/preset-uno` and `@unocss/preset-wind` are deprecated and renamed to `@unocss/preset-wind3`.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Full Tailwind CSS v3 compatibility
|
||||||
|
- Dark mode (`dark:`, `@dark:`)
|
||||||
|
- All responsive variants (`sm:`, `md:`, `lg:`, `xl:`, `2xl:`)
|
||||||
|
- All standard utilities (flex, grid, spacing, colors, typography, etc.)
|
||||||
|
- Animation support (includes Animate.css animations)
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
### Class-based (default)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="dark:bg-gray-800">
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates: `.dark .dark\:bg-gray-800 { ... }`
|
||||||
|
|
||||||
|
### Media Query Based
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
dark: 'media'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates: `@media (prefers-color-scheme: dark) { ... }`
|
||||||
|
|
||||||
|
### Opt-in Media Query
|
||||||
|
|
||||||
|
Use `@dark:` regardless of config:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@dark:bg-gray-800">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
// Dark mode strategy
|
||||||
|
dark: 'class', // 'class' | 'media' | { light: '.light', dark: '.dark' }
|
||||||
|
|
||||||
|
// Generate pseudo selector as [group=""] instead of .group
|
||||||
|
attributifyPseudo: false,
|
||||||
|
|
||||||
|
// CSS custom properties prefix
|
||||||
|
variablePrefix: 'un-',
|
||||||
|
|
||||||
|
// Utils prefix
|
||||||
|
prefix: '',
|
||||||
|
|
||||||
|
// Generate preflight CSS
|
||||||
|
preflight: true, // true | false | 'on-demand'
|
||||||
|
|
||||||
|
// Mark all utilities as !important
|
||||||
|
important: false, // boolean | string (selector)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important Option
|
||||||
|
|
||||||
|
Make all utilities `!important`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
important: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Or scope with selector to increase specificity without `!important`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind3({
|
||||||
|
important: '#app',
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `#app :is(.dark .dark\:bg-blue) { ... }`
|
||||||
|
|
||||||
|
## Differences from Tailwind CSS
|
||||||
|
|
||||||
|
### Quotes Not Supported
|
||||||
|
|
||||||
|
Template quotes don't work due to extractor:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Won't work -->
|
||||||
|
<div class="before:content-['']">
|
||||||
|
|
||||||
|
<!-- Use shortcut instead -->
|
||||||
|
<div class="before:content-empty">
|
||||||
|
```
|
||||||
|
|
||||||
|
### Background Position
|
||||||
|
|
||||||
|
Use `position:` prefix for custom values:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Tailwind -->
|
||||||
|
<div class="bg-[center_top_1rem]">
|
||||||
|
|
||||||
|
<!-- UnoCSS -->
|
||||||
|
<div class="bg-[position:center_top_1rem]">
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animations
|
||||||
|
|
||||||
|
UnoCSS integrates Animate.css. Use `-alt` suffix for Animate.css versions when names conflict:
|
||||||
|
|
||||||
|
- `animate-bounce` - Tailwind version
|
||||||
|
- `animate-bounce-alt` - Animate.css version
|
||||||
|
|
||||||
|
Custom animations:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
theme: {
|
||||||
|
animation: {
|
||||||
|
keyframes: {
|
||||||
|
custom: '{0%, 100% { opacity: 0; } 50% { opacity: 1; }}',
|
||||||
|
},
|
||||||
|
durations: {
|
||||||
|
custom: '1s',
|
||||||
|
},
|
||||||
|
timingFns: {
|
||||||
|
custom: 'ease-in-out',
|
||||||
|
},
|
||||||
|
counts: {
|
||||||
|
custom: 'infinite',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Differences from Windi CSS
|
||||||
|
|
||||||
|
| Windi CSS | UnoCSS |
|
||||||
|
|-----------|--------|
|
||||||
|
| `<sm:p-1` | `lt-sm:p-1` |
|
||||||
|
| `@lg:p-1` | `at-lg:p-1` |
|
||||||
|
| `>xl:p-1` | `xl:p-1` |
|
||||||
|
|
||||||
|
Bracket syntax uses `_` instead of `,`:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Windi CSS -->
|
||||||
|
<div class="grid-cols-[1fr,10px,max-content]">
|
||||||
|
|
||||||
|
<!-- UnoCSS -->
|
||||||
|
<div class="grid-cols-[1fr_10px_max-content]">
|
||||||
|
```
|
||||||
|
|
||||||
|
## Experimental: Media Hover
|
||||||
|
|
||||||
|
Addresses sticky hover on touch devices:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="@hover-text-red">
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
```css
|
||||||
|
@media (hover: hover) and (pointer: fine) {
|
||||||
|
.\@hover-text-red:hover { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/wind3
|
||||||
|
-->
|
||||||
247
.agents/skills/unocss/references/preset-wind4.md
Normal file
247
.agents/skills/unocss/references/preset-wind4.md
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
---
|
||||||
|
name: preset-wind4
|
||||||
|
description: Tailwind CSS v4 compatible preset with enhanced features
|
||||||
|
---
|
||||||
|
|
||||||
|
# Preset Wind4
|
||||||
|
|
||||||
|
The Tailwind CSS v4 compatible preset. Enhances preset-wind3 with modern CSS features.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, presetWind4 } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind4(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Differences from Wind3
|
||||||
|
|
||||||
|
### Built-in CSS Reset
|
||||||
|
|
||||||
|
No need for `@unocss/reset` - reset is built-in:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Remove these imports
|
||||||
|
import '@unocss/reset/tailwind.css' // ❌ Not needed
|
||||||
|
import '@unocss/reset/tailwind-compat.css' // ❌ Not needed
|
||||||
|
|
||||||
|
// Enable in config
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
reset: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### OKLCH Color Model
|
||||||
|
|
||||||
|
Uses `oklch` for better color perception and contrast. Not compatible with `presetLegacyCompat`.
|
||||||
|
|
||||||
|
### Theme CSS Variables
|
||||||
|
|
||||||
|
Automatically generates CSS variables from theme:
|
||||||
|
|
||||||
|
```css
|
||||||
|
:root, :host {
|
||||||
|
--spacing: 0.25rem;
|
||||||
|
--font-sans: ui-sans-serif, system-ui, sans-serif;
|
||||||
|
--colors-black: #000;
|
||||||
|
--colors-white: #fff;
|
||||||
|
/* ... */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### @property CSS Rules
|
||||||
|
|
||||||
|
Uses `@property` for better browser optimization:
|
||||||
|
|
||||||
|
```css
|
||||||
|
@property --un-text-opacity {
|
||||||
|
syntax: '<percentage>';
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 100%;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Key Changes
|
||||||
|
|
||||||
|
| preset-wind3 | preset-wind4 |
|
||||||
|
|--------------|--------------|
|
||||||
|
| `fontFamily` | `font` |
|
||||||
|
| `fontSize` | `text.fontSize` |
|
||||||
|
| `lineHeight` | `text.lineHeight` or `leading` |
|
||||||
|
| `letterSpacing` | `text.letterSpacing` or `tracking` |
|
||||||
|
| `borderRadius` | `radius` |
|
||||||
|
| `easing` | `ease` |
|
||||||
|
| `breakpoints` | `breakpoint` |
|
||||||
|
| `verticalBreakpoints` | `verticalBreakpoint` |
|
||||||
|
| `boxShadow` | `shadow` |
|
||||||
|
| `transitionProperty` | `property` |
|
||||||
|
| `container.maxWidth` | `containers.maxWidth` |
|
||||||
|
| Size properties (`width`, `height`, etc.) | Unified to `spacing` |
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
// Built-in reset styles
|
||||||
|
reset: true,
|
||||||
|
|
||||||
|
// Theme CSS variables generation
|
||||||
|
theme: 'on-demand', // true | false | 'on-demand'
|
||||||
|
|
||||||
|
// @property CSS rules
|
||||||
|
property: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Variable Processing
|
||||||
|
|
||||||
|
Convert rem to px for theme variables:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createRemToPxProcessor } from '@unocss/preset-wind4/utils'
|
||||||
|
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
theme: {
|
||||||
|
mode: 'on-demand',
|
||||||
|
process: createRemToPxProcessor(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Also apply to utilities
|
||||||
|
export default defineConfig({
|
||||||
|
postprocess: [createRemToPxProcessor()],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Property Layer Customization
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
property: {
|
||||||
|
// Custom parent wrapper
|
||||||
|
parent: '@layer custom-properties',
|
||||||
|
// Custom selector
|
||||||
|
selector: ':where(*, ::before, ::after)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove `@supports` wrapper:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
property: {
|
||||||
|
parent: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generated Layers
|
||||||
|
|
||||||
|
| Layer | Description | Order |
|
||||||
|
|-------|-------------|-------|
|
||||||
|
| `properties` | CSS `@property` rules | -200 |
|
||||||
|
| `theme` | Theme CSS variables | -150 |
|
||||||
|
| `base` | Reset/preflight styles | -100 |
|
||||||
|
|
||||||
|
## Theme.defaults
|
||||||
|
|
||||||
|
Global default configuration for reset styles:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import type { Theme } from '@unocss/preset-wind4/theme'
|
||||||
|
|
||||||
|
const defaults: Theme['default'] = {
|
||||||
|
transition: {
|
||||||
|
duration: '150ms',
|
||||||
|
timingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
},
|
||||||
|
font: {
|
||||||
|
family: 'var(--font-sans)',
|
||||||
|
featureSettings: 'var(--font-sans--font-feature-settings)',
|
||||||
|
variationSettings: 'var(--font-sans--font-variation-settings)',
|
||||||
|
},
|
||||||
|
monoFont: {
|
||||||
|
family: 'var(--font-mono)',
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibility Notes
|
||||||
|
|
||||||
|
### presetRemToPx
|
||||||
|
|
||||||
|
Not needed - use built-in processor instead:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
presetWind4({
|
||||||
|
preflights: {
|
||||||
|
theme: {
|
||||||
|
process: createRemToPxProcessor(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### presetLegacyCompat
|
||||||
|
|
||||||
|
**Not compatible** with preset-wind4 due to `oklch` color model.
|
||||||
|
|
||||||
|
## Migration from Wind3
|
||||||
|
|
||||||
|
1. Update theme keys according to the table above
|
||||||
|
2. Remove `@unocss/reset` imports
|
||||||
|
3. Enable `preflights.reset: true`
|
||||||
|
4. Test color outputs (oklch vs rgb)
|
||||||
|
5. Update any custom theme extensions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Before (wind3)
|
||||||
|
theme: {
|
||||||
|
fontFamily: { sans: 'Roboto' },
|
||||||
|
fontSize: { lg: '1.125rem' },
|
||||||
|
breakpoints: { sm: '640px' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// After (wind4)
|
||||||
|
theme: {
|
||||||
|
font: { sans: 'Roboto' },
|
||||||
|
text: { lg: { fontSize: '1.125rem' } },
|
||||||
|
breakpoint: { sm: '640px' },
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Wind4
|
||||||
|
|
||||||
|
Choose **preset-wind4** when:
|
||||||
|
- Starting a new project
|
||||||
|
- Targeting modern browsers
|
||||||
|
- Want built-in reset and CSS variables
|
||||||
|
- Following Tailwind v4 conventions
|
||||||
|
|
||||||
|
Choose **preset-wind3** when:
|
||||||
|
- Need legacy browser support
|
||||||
|
- Migrating from Tailwind v3
|
||||||
|
- Using presetLegacyCompat
|
||||||
|
- Want stable, proven preset
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/presets/wind4
|
||||||
|
-->
|
||||||
156
.agents/skills/unocss/references/transformer-attributify-jsx.md
Normal file
156
.agents/skills/unocss/references/transformer-attributify-jsx.md
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
---
|
||||||
|
name: transformer-attributify-jsx
|
||||||
|
description: Support valueless attributify in JSX/TSX
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transformer Attributify JSX
|
||||||
|
|
||||||
|
Fixes valueless attributify mode in JSX where `<div foo>` becomes `<div foo={true}>`.
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
In JSX, valueless attributes are transformed:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// You write
|
||||||
|
<div m-2 rounded text-teal-400 />
|
||||||
|
|
||||||
|
// JSX compiles to
|
||||||
|
<div m-2={true} rounded={true} text-teal-400={true} />
|
||||||
|
```
|
||||||
|
|
||||||
|
The `={true}` breaks UnoCSS attributify detection.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
transformerAttributifyJsx
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetAttributify(),
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerAttributifyJsx(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
The transformer converts JSX boolean attributes back to strings:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// Input (after JSX compilation)
|
||||||
|
<div m-2={true} rounded={true} />
|
||||||
|
|
||||||
|
// Output (transformed)
|
||||||
|
<div m-2="" rounded="" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Now UnoCSS can properly extract the attributify classes.
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
transformerAttributifyJsx({
|
||||||
|
// Only transform specific attributes
|
||||||
|
// Default: transforms all that match attributify patterns
|
||||||
|
blocklist: ['text', 'font'],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
Required when using:
|
||||||
|
- React
|
||||||
|
- Preact
|
||||||
|
- Solid
|
||||||
|
- Any JSX-based framework
|
||||||
|
|
||||||
|
With valueless attributify syntax:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
// This needs the transformer
|
||||||
|
<div flex items-center gap-4 />
|
||||||
|
|
||||||
|
// This works without transformer (has values)
|
||||||
|
<div flex="~" items="center" gap="4" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Framework Setup
|
||||||
|
|
||||||
|
### React
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import React from '@vitejs/plugin-react'
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(), // Must be before React
|
||||||
|
React(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// uno.config.ts
|
||||||
|
import {
|
||||||
|
defineConfig,
|
||||||
|
presetAttributify,
|
||||||
|
presetWind3,
|
||||||
|
transformerAttributifyJsx
|
||||||
|
} from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
presets: [
|
||||||
|
presetWind3(),
|
||||||
|
presetAttributify(),
|
||||||
|
],
|
||||||
|
transformers: [
|
||||||
|
transformerAttributifyJsx(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preact
|
||||||
|
|
||||||
|
Same as React, use `@preact/preset-vite` or `@prefresh/vite`.
|
||||||
|
|
||||||
|
### Solid
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import UnoCSS from 'unocss/vite'
|
||||||
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
UnoCSS(),
|
||||||
|
solidPlugin(),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Support
|
||||||
|
|
||||||
|
Add type declarations:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// shims.d.ts
|
||||||
|
import type { AttributifyAttributes } from '@unocss/preset-attributify'
|
||||||
|
|
||||||
|
declare module 'react' {
|
||||||
|
interface HTMLAttributes<T> extends AttributifyAttributes {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/transformers/attributify-jsx
|
||||||
|
-->
|
||||||
128
.agents/skills/unocss/references/transformer-compile-class.md
Normal file
128
.agents/skills/unocss/references/transformer-compile-class.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
---
|
||||||
|
name: transformer-compile-class
|
||||||
|
description: Compile multiple classes into one hashed class
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transformer Compile Class
|
||||||
|
|
||||||
|
Compiles multiple utility classes into a single hashed class for smaller HTML.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, transformerCompileClass } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerCompileClass(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add `:uno:` prefix to mark classes for compilation:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Before -->
|
||||||
|
<div class=":uno: text-center sm:text-left">
|
||||||
|
<div class=":uno: text-sm font-bold hover:text-red" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- After -->
|
||||||
|
<div class="uno-qlmcrp">
|
||||||
|
<div class="uno-0qw2gr" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Generated CSS
|
||||||
|
|
||||||
|
```css
|
||||||
|
.uno-qlmcrp {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.uno-0qw2gr {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.uno-0qw2gr:hover {
|
||||||
|
--un-text-opacity: 1;
|
||||||
|
color: rgb(248 113 113 / var(--un-text-opacity));
|
||||||
|
}
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.uno-qlmcrp {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
transformerCompileClass({
|
||||||
|
// Custom trigger string (default: ':uno:')
|
||||||
|
trigger: ':uno:',
|
||||||
|
|
||||||
|
// Custom class prefix (default: 'uno-')
|
||||||
|
classPrefix: 'uno-',
|
||||||
|
|
||||||
|
// Hash function for class names
|
||||||
|
hashFn: (str) => /* custom hash */,
|
||||||
|
|
||||||
|
// Keep original classes alongside compiled
|
||||||
|
keepOriginal: false,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- **Smaller HTML** - Reduce repetitive class strings
|
||||||
|
- **Obfuscation** - Hide utility class names in production
|
||||||
|
- **Performance** - Fewer class attributes to parse
|
||||||
|
|
||||||
|
## ESLint Integration
|
||||||
|
|
||||||
|
Enforce compile class usage across project:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"@unocss/enforce-class-compile": "warn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This rule:
|
||||||
|
- Warns when class attribute doesn't start with `:uno:`
|
||||||
|
- Auto-fixes by adding the prefix
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"@unocss/enforce-class-compile": ["warn", {
|
||||||
|
"prefix": ":uno:",
|
||||||
|
"enableFix": true
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Combining with Other Transformers
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerVariantGroup(), // Process variant groups first
|
||||||
|
transformerDirectives(), // Then directives
|
||||||
|
transformerCompileClass(), // Compile last
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/transformers/compile-class
|
||||||
|
-->
|
||||||
157
.agents/skills/unocss/references/transformer-directives.md
Normal file
157
.agents/skills/unocss/references/transformer-directives.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
name: transformer-directives
|
||||||
|
description: CSS directives @apply, @screen, theme(), and icon()
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transformer Directives
|
||||||
|
|
||||||
|
Enables `@apply`, `@screen`, `theme()`, and `icon()` directives in CSS.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, transformerDirectives } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerDirectives(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## @apply
|
||||||
|
|
||||||
|
Apply utility classes in CSS:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.custom-btn {
|
||||||
|
@apply py-2 px-4 font-semibold rounded-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With variants - use quotes */
|
||||||
|
.custom-btn {
|
||||||
|
@apply 'hover:bg-blue-600 focus:ring-2';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### CSS Custom Property Alternative
|
||||||
|
|
||||||
|
For vanilla CSS compatibility:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.custom-div {
|
||||||
|
--at-apply: text-center my-0 font-medium;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported aliases: `--at-apply`, `--uno-apply`, `--uno`
|
||||||
|
|
||||||
|
Configure aliases:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
transformerDirectives({
|
||||||
|
applyVariable: ['--at-apply', '--uno-apply', '--uno'],
|
||||||
|
// or disable: applyVariable: false
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## @screen
|
||||||
|
|
||||||
|
Create breakpoint media queries:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen lg {
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Breakpoint Variants
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* Less than breakpoint */
|
||||||
|
@screen lt-sm {
|
||||||
|
.item { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* At specific breakpoint only */
|
||||||
|
@screen at-md {
|
||||||
|
.item { width: 50%; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## theme()
|
||||||
|
|
||||||
|
Access theme values in CSS:
|
||||||
|
|
||||||
|
```css
|
||||||
|
.btn-blue {
|
||||||
|
background-color: theme('colors.blue.500');
|
||||||
|
padding: theme('spacing.4');
|
||||||
|
border-radius: theme('borderRadius.lg');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dot notation paths into your theme config.
|
||||||
|
|
||||||
|
## icon()
|
||||||
|
|
||||||
|
Convert icon utility to SVG (requires preset-icons):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.icon-sun {
|
||||||
|
background-image: icon('i-carbon-sun');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With custom color */
|
||||||
|
.icon-moon {
|
||||||
|
background-image: icon('i-carbon-moon', '#fff');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Using theme color */
|
||||||
|
.icon-alert {
|
||||||
|
background-image: icon('i-carbon-warning', 'theme("colors.red.500")');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```css
|
||||||
|
.card {
|
||||||
|
@apply rounded-lg shadow-md p-4;
|
||||||
|
background-color: theme('colors.white');
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
@apply 'font-bold text-lg border-b';
|
||||||
|
padding-bottom: theme('spacing.2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@screen md {
|
||||||
|
.card {
|
||||||
|
@apply flex gap-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
background-image: icon('i-carbon-document');
|
||||||
|
@apply w-6 h-6;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/transformers/directives
|
||||||
|
-->
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
name: transformer-variant-group
|
||||||
|
description: Shorthand for grouping utilities with common prefixes
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transformer Variant Group
|
||||||
|
|
||||||
|
Enables shorthand syntax for grouping utilities with common prefixes.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, transformerVariantGroup } from 'unocss'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
transformers: [
|
||||||
|
transformerVariantGroup(),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Group multiple utilities under one variant prefix using parentheses:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Before transformation -->
|
||||||
|
<div class="hover:(bg-gray-400 font-medium) font-(light mono)" />
|
||||||
|
|
||||||
|
<!-- After transformation -->
|
||||||
|
<div class="hover:bg-gray-400 hover:font-medium font-light font-mono" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Hover States
|
||||||
|
|
||||||
|
```html
|
||||||
|
<button class="hover:(bg-blue-600 text-white scale-105)">
|
||||||
|
Hover me
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expands to: `hover:bg-blue-600 hover:text-white hover:scale-105`
|
||||||
|
|
||||||
|
### Dark Mode
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="dark:(bg-gray-800 text-white)">
|
||||||
|
Dark content
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expands to: `dark:bg-gray-800 dark:text-white`
|
||||||
|
|
||||||
|
### Responsive
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="md:(flex items-center gap-4)">
|
||||||
|
Responsive flex
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expands to: `md:flex md:items-center md:gap-4`
|
||||||
|
|
||||||
|
### Nested Groups
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="lg:hover:(bg-blue-500 text-white)">
|
||||||
|
Large screen hover
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expands to: `lg:hover:bg-blue-500 lg:hover:text-white`
|
||||||
|
|
||||||
|
### Multiple Prefixes
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="text-(sm gray-600) font-(medium mono)">
|
||||||
|
Styled text
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Expands to: `text-sm text-gray-600 font-medium font-mono`
|
||||||
|
|
||||||
|
## Key Points
|
||||||
|
|
||||||
|
- Use parentheses `()` to group utilities
|
||||||
|
- The prefix applies to all utilities inside the group
|
||||||
|
- Can be combined with any variant (hover, dark, responsive, etc.)
|
||||||
|
- Nesting is supported
|
||||||
|
- Works in class attributes and other extraction sources
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://unocss.dev/transformers/variant-group
|
||||||
|
-->
|
||||||
5
.agents/skills/vite/GENERATION.md
Normal file
5
.agents/skills/vite/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/vite`
|
||||||
|
- **Git SHA:** `b40292ce6a7dbbbbac9c6dae5f126b7f44c3e1b7`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
50
.agents/skills/vite/SKILL.md
Normal file
50
.agents/skills/vite/SKILL.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
name: vite
|
||||||
|
description: Vite next-generation frontend build tool with fast HMR and optimized builds. Use when configuring Vite, adding plugins, working with dev server, or building for production.
|
||||||
|
metadata:
|
||||||
|
author: Anthony Fu
|
||||||
|
version: "2026.1.28"
|
||||||
|
source: Generated from https://github.com/vitejs/vite, scripts located at https://github.com/antfu/skills
|
||||||
|
---
|
||||||
|
|
||||||
|
Vite is a modern build tool for frontend development featuring instant server start with native ES modules, lightning-fast HMR, and optimized production builds using Rolldown/Rollup. It supports TypeScript, JSX, CSS pre-processors out of the box and has a rich plugin ecosystem.
|
||||||
|
|
||||||
|
> The skill is based on Vite 6.x, generated at 2026-01-28.
|
||||||
|
|
||||||
|
## Core
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Configuration | Config file setup, defineConfig, conditional and async configs | [core-config](references/core-config.md) |
|
||||||
|
| CLI Commands | Dev server, build, preview commands and options | [core-cli](references/core-cli.md) |
|
||||||
|
| Core Features | TypeScript, JSX, CSS, HTML processing, JSON handling | [core-features](references/core-features.md) |
|
||||||
|
| Using Plugins | Adding, configuring, and ordering plugins | [core-plugins](references/core-plugins.md) |
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| CSS Handling | CSS modules, pre-processors, PostCSS, Lightning CSS | [features-css](references/features-css.md) |
|
||||||
|
| Static Assets | Asset imports, public directory, URL handling | [features-assets](references/features-assets.md) |
|
||||||
|
| Glob Import | import.meta.glob, dynamic imports, batch loading | [features-glob-import](references/features-glob-import.md) |
|
||||||
|
| Environment Variables | .env files, modes, import.meta.env constants | [features-env](references/features-env.md) |
|
||||||
|
| HMR API | Hot Module Replacement client API | [features-hmr](references/features-hmr.md) |
|
||||||
|
| Web Workers | Worker imports and configuration | [features-workers](references/features-workers.md) |
|
||||||
|
| Dependency Pre-Bundling | optimizeDeps, caching, monorepo setup | [features-dep-bundling](references/features-dep-bundling.md) |
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| Production Build | Build options, browser targets, multi-page apps | [build-production](references/build-production.md) |
|
||||||
|
| Library Mode | Building libraries with proper package exports | [build-library](references/build-library.md) |
|
||||||
|
| SSR | Server-side rendering setup and configuration | [build-ssr](references/build-ssr.md) |
|
||||||
|
|
||||||
|
## Advanced
|
||||||
|
|
||||||
|
| Topic | Description | Reference |
|
||||||
|
|-------|-------------|-----------|
|
||||||
|
| JavaScript API | createServer, build, preview programmatic APIs | [advanced-api](references/advanced-api.md) |
|
||||||
|
| Plugin API | Creating Vite plugins, hooks, virtual modules | [advanced-plugin-api](references/advanced-plugin-api.md) |
|
||||||
|
| Performance | Optimization tips for dev server and builds | [advanced-performance](references/advanced-performance.md) |
|
||||||
|
| Backend Integration | Integrating Vite with traditional backends | [advanced-backend](references/advanced-backend.md) |
|
||||||
218
.agents/skills/vite/references/advanced-api.md
Normal file
218
.agents/skills/vite/references/advanced-api.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
---
|
||||||
|
name: advanced-api
|
||||||
|
description: Vite's JavaScript API for programmatic usage
|
||||||
|
---
|
||||||
|
|
||||||
|
# JavaScript API
|
||||||
|
|
||||||
|
Vite's APIs are fully typed. Use TypeScript or enable JS type checking for intellisense.
|
||||||
|
|
||||||
|
## `createServer`
|
||||||
|
|
||||||
|
Create a development server programmatically:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createServer } from 'vite'
|
||||||
|
|
||||||
|
const server = await createServer({
|
||||||
|
configFile: false,
|
||||||
|
root: __dirname,
|
||||||
|
server: {
|
||||||
|
port: 1337
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await server.listen()
|
||||||
|
server.printUrls()
|
||||||
|
server.bindCLIShortcuts({ print: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
### ViteDevServer Interface
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ViteDevServer {
|
||||||
|
config: ResolvedConfig
|
||||||
|
middlewares: Connect.Server // Connect app for custom middleware
|
||||||
|
httpServer: http.Server | null // Node HTTP server
|
||||||
|
watcher: FSWatcher // Chokidar watcher
|
||||||
|
ws: WebSocketServer // WebSocket for HMR
|
||||||
|
moduleGraph: ModuleGraph // Module import relationships
|
||||||
|
|
||||||
|
// Transform without HTTP
|
||||||
|
transformRequest(url: string): Promise<TransformResult | null>
|
||||||
|
|
||||||
|
// Apply HTML transforms
|
||||||
|
transformIndexHtml(url: string, html: string): Promise<string>
|
||||||
|
|
||||||
|
// Load module for SSR
|
||||||
|
ssrLoadModule(url: string): Promise<Record<string, any>>
|
||||||
|
|
||||||
|
// Fix SSR error stack traces
|
||||||
|
ssrFixStacktrace(e: Error): void
|
||||||
|
|
||||||
|
// Control
|
||||||
|
listen(port?: number): Promise<ViteDevServer>
|
||||||
|
restart(): Promise<void>
|
||||||
|
close(): Promise<void>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `build`
|
||||||
|
|
||||||
|
Build for production:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { build } from 'vite'
|
||||||
|
|
||||||
|
await build({
|
||||||
|
root: './project',
|
||||||
|
base: '/foo/',
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## `preview`
|
||||||
|
|
||||||
|
Preview production build locally:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { preview } from 'vite'
|
||||||
|
|
||||||
|
const previewServer = await preview({
|
||||||
|
preview: {
|
||||||
|
port: 8080,
|
||||||
|
open: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
previewServer.printUrls()
|
||||||
|
```
|
||||||
|
|
||||||
|
## `resolveConfig`
|
||||||
|
|
||||||
|
Resolve config without starting server:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { resolveConfig } from 'vite'
|
||||||
|
|
||||||
|
const config = await resolveConfig(
|
||||||
|
{ root: './project' },
|
||||||
|
'serve', // 'serve' | 'build'
|
||||||
|
'development' // default mode
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `mergeConfig`
|
||||||
|
|
||||||
|
Deep merge two configs:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { mergeConfig } from 'vite'
|
||||||
|
|
||||||
|
const merged = mergeConfig(baseConfig, overrideConfig)
|
||||||
|
```
|
||||||
|
|
||||||
|
Merge callback config:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, mergeConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig((env) =>
|
||||||
|
mergeConfig(configAsCallback(env), configAsObject)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `loadEnv`
|
||||||
|
|
||||||
|
Load .env files:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { loadEnv } from 'vite'
|
||||||
|
|
||||||
|
// Load VITE_* vars
|
||||||
|
const env = loadEnv('development', process.cwd())
|
||||||
|
|
||||||
|
// Load all vars (empty prefix)
|
||||||
|
const allEnv = loadEnv('development', process.cwd(), '')
|
||||||
|
```
|
||||||
|
|
||||||
|
## `searchForWorkspaceRoot`
|
||||||
|
|
||||||
|
Find monorepo workspace root:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { searchForWorkspaceRoot } from 'vite'
|
||||||
|
|
||||||
|
const workspaceRoot = searchForWorkspaceRoot(process.cwd())
|
||||||
|
```
|
||||||
|
|
||||||
|
## `normalizePath`
|
||||||
|
|
||||||
|
Normalize paths for cross-platform:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { normalizePath } from 'vite'
|
||||||
|
|
||||||
|
normalizePath('foo\\bar') // 'foo/bar'
|
||||||
|
```
|
||||||
|
|
||||||
|
## `transformWithOxc`
|
||||||
|
|
||||||
|
Transform JS/TS with Oxc Transformer:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { transformWithOxc } from 'vite'
|
||||||
|
|
||||||
|
const result = await transformWithOxc(
|
||||||
|
code,
|
||||||
|
'file.ts',
|
||||||
|
{ target: 'es2020' }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## `preprocessCSS`
|
||||||
|
|
||||||
|
Pre-process CSS files:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { preprocessCSS, resolveConfig } from 'vite'
|
||||||
|
|
||||||
|
const config = await resolveConfig({}, 'serve')
|
||||||
|
const result = await preprocessCSS(code, 'styles.scss', config)
|
||||||
|
// result.code - plain CSS
|
||||||
|
// result.modules - CSS modules mapping
|
||||||
|
```
|
||||||
|
|
||||||
|
## `loadConfigFromFile`
|
||||||
|
|
||||||
|
Load config file manually:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { loadConfigFromFile } from 'vite'
|
||||||
|
|
||||||
|
const result = await loadConfigFromFile(
|
||||||
|
{ command: 'serve', mode: 'development' },
|
||||||
|
'vite.config.ts'
|
||||||
|
)
|
||||||
|
// result.config, result.path, result.dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
## InlineConfig
|
||||||
|
|
||||||
|
Extends UserConfig with:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface InlineConfig extends UserConfig {
|
||||||
|
configFile?: string | false // Config file path or false to skip
|
||||||
|
mode?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/api-javascript.html
|
||||||
|
-->
|
||||||
164
.agents/skills/vite/references/advanced-backend.md
Normal file
164
.agents/skills/vite/references/advanced-backend.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
---
|
||||||
|
name: advanced-backend
|
||||||
|
description: Integrating Vite with traditional backend frameworks
|
||||||
|
---
|
||||||
|
|
||||||
|
# Backend Integration
|
||||||
|
|
||||||
|
Integrate Vite with traditional backends (Rails, Laravel, etc.) for asset serving.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
cors: {
|
||||||
|
origin: 'http://my-backend.example.com'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
manifest: true, // Generate .vite/manifest.json
|
||||||
|
rolldownOptions: {
|
||||||
|
input: '/path/to/main.js' // Override HTML entry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Import polyfill in entry:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// main.js
|
||||||
|
import 'vite/modulepreload-polyfill'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Inject Vite client and entry in your backend template:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Development only -->
|
||||||
|
<script type="module" src="http://localhost:5173/@vite/client"></script>
|
||||||
|
<script type="module" src="http://localhost:5173/main.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### React Setup
|
||||||
|
|
||||||
|
Add before other scripts:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script type="module">
|
||||||
|
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
|
||||||
|
RefreshRuntime.injectIntoGlobalHook(window)
|
||||||
|
window.$RefreshReg$ = () => {}
|
||||||
|
window.$RefreshSig$ = () => (type) => type
|
||||||
|
window.__vite_plugin_react_preamble_installed__ = true
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Proxying
|
||||||
|
|
||||||
|
Either:
|
||||||
|
1. Proxy static asset requests to Vite
|
||||||
|
2. Set `server.origin`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
origin: 'http://localhost:5173'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build generates `.vite/manifest.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"views/foo.js": {
|
||||||
|
"file": "assets/foo-BRBmoGS9.js",
|
||||||
|
"src": "views/foo.js",
|
||||||
|
"isEntry": true,
|
||||||
|
"imports": ["_shared-B7PI925R.js"],
|
||||||
|
"css": ["assets/foo-5UjPuW-k.css"]
|
||||||
|
},
|
||||||
|
"_shared-B7PI925R.js": {
|
||||||
|
"file": "assets/shared-B7PI925R.js",
|
||||||
|
"css": ["assets/shared-ChJ_j-JJ.css"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rendering Tags
|
||||||
|
|
||||||
|
For entry `views/foo.js`, render in this order:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 1. Entry CSS -->
|
||||||
|
<link rel="stylesheet" href="/assets/foo-5UjPuW-k.css" />
|
||||||
|
|
||||||
|
<!-- 2. Imported chunks' CSS (recursive) -->
|
||||||
|
<link rel="stylesheet" href="/assets/shared-ChJ_j-JJ.css" />
|
||||||
|
|
||||||
|
<!-- 3. Entry script -->
|
||||||
|
<script type="module" src="/assets/foo-BRBmoGS9.js"></script>
|
||||||
|
|
||||||
|
<!-- 4. Optional: preload imports -->
|
||||||
|
<link rel="modulepreload" href="/assets/shared-B7PI925R.js" />
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manifest Structure
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ManifestChunk {
|
||||||
|
src?: string // Input file name
|
||||||
|
file: string // Output file name
|
||||||
|
css?: string[] // CSS files (JS chunks only)
|
||||||
|
assets?: string[] // Non-CSS assets (JS chunks only)
|
||||||
|
isEntry?: boolean // Is entry point
|
||||||
|
isDynamicEntry?: boolean // Is dynamic import
|
||||||
|
imports?: string[] // Static imports (manifest keys)
|
||||||
|
dynamicImports?: string[] // Dynamic imports (manifest keys)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Processing Imports
|
||||||
|
|
||||||
|
Recursively collect all imported chunks:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function getImportedChunks(manifest, name) {
|
||||||
|
const seen = new Set()
|
||||||
|
const chunks = []
|
||||||
|
|
||||||
|
function collect(chunk) {
|
||||||
|
for (const file of chunk.imports ?? []) {
|
||||||
|
if (seen.has(file)) continue
|
||||||
|
seen.add(file)
|
||||||
|
|
||||||
|
const importee = manifest[file]
|
||||||
|
collect(importee)
|
||||||
|
chunks.push(importee)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collect(manifest[name])
|
||||||
|
return chunks
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Existing Integrations
|
||||||
|
|
||||||
|
Check [Awesome Vite](https://github.com/vitejs/awesome-vite#integrations-with-backends) for:
|
||||||
|
- Laravel (laravel-vite)
|
||||||
|
- Rails
|
||||||
|
- Django
|
||||||
|
- Flask
|
||||||
|
- And more
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/backend-integration.html
|
||||||
|
-->
|
||||||
168
.agents/skills/vite/references/advanced-performance.md
Normal file
168
.agents/skills/vite/references/advanced-performance.md
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
---
|
||||||
|
name: advanced-performance
|
||||||
|
description: Performance optimization tips for Vite dev server and builds
|
||||||
|
---
|
||||||
|
|
||||||
|
# Performance Optimization
|
||||||
|
|
||||||
|
## Browser Setup
|
||||||
|
|
||||||
|
- Use dev-only browser profile without extensions
|
||||||
|
- Disable "Disable Cache" in DevTools when using Vite
|
||||||
|
- Extensions can interfere with requests and slow startup
|
||||||
|
|
||||||
|
## Audit Plugin Performance
|
||||||
|
|
||||||
|
1. **Lazy load large dependencies** in plugins
|
||||||
|
2. **Avoid long operations** in `buildStart`, `config`, `configResolved`
|
||||||
|
3. **Optimize transform hooks** - check `id` extension before processing
|
||||||
|
|
||||||
|
Debug transform times:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite --debug plugin-transform
|
||||||
|
```
|
||||||
|
|
||||||
|
Use [vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect) to inspect transforms.
|
||||||
|
|
||||||
|
## Profiling
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite --profile
|
||||||
|
# Visit site, press 'p + enter' to record .cpuprofile
|
||||||
|
# Open in https://www.speedscope.app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reduce Resolve Operations
|
||||||
|
|
||||||
|
Be explicit with extensions to avoid filesystem checks:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Slow: checks .mjs, .js, .mts, .ts, .jsx, .tsx, .json
|
||||||
|
import Component from './Component'
|
||||||
|
|
||||||
|
// Fast: direct hit
|
||||||
|
import Component from './Component.tsx'
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable TypeScript path resolution for explicit imports:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Avoid Barrel Files
|
||||||
|
|
||||||
|
Barrel files (`index.js` re-exporting everything) cause all files to load:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Slow: loads all utils
|
||||||
|
import { slash } from './utils'
|
||||||
|
|
||||||
|
// Fast: loads only slash.js
|
||||||
|
import { slash } from './utils/slash.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warm Up Frequently Used Files
|
||||||
|
|
||||||
|
Pre-transform files that are always loaded:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
warmup: {
|
||||||
|
clientFiles: [
|
||||||
|
'./src/components/BigComponent.vue',
|
||||||
|
'./src/utils/big-utils.js'
|
||||||
|
],
|
||||||
|
ssrFiles: ['./src/server/modules/*.js']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Find files to warm up:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite --debug transform
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Native/Less Tooling
|
||||||
|
|
||||||
|
**Do less work:**
|
||||||
|
- CSS instead of Sass/Less (PostCSS has nesting)
|
||||||
|
- Don't transform SVGs to components - import as strings/URLs
|
||||||
|
- Skip Babel in `@vitejs/plugin-react` if not needed
|
||||||
|
|
||||||
|
**Use native tools:**
|
||||||
|
- Try Lightning CSS for faster CSS processing
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
transformer: 'lightningcss'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Server Options
|
||||||
|
|
||||||
|
### Open Browser Automatically
|
||||||
|
|
||||||
|
Triggers warmup of entry point:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
open: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Limit File Watching
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
watch: {
|
||||||
|
ignored: ['**/large-folder/**']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Performance
|
||||||
|
|
||||||
|
### Disable Reporting
|
||||||
|
|
||||||
|
Skip gzip size calculation for large projects:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
reportCompressedSize: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sourcemaps
|
||||||
|
|
||||||
|
Disable if not needed:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
sourcemap: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/performance.html
|
||||||
|
-->
|
||||||
258
.agents/skills/vite/references/advanced-plugin-api.md
Normal file
258
.agents/skills/vite/references/advanced-plugin-api.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
---
|
||||||
|
name: advanced-plugin-api
|
||||||
|
description: Creating Vite plugins, hooks, virtual modules, and client-server communication
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plugin API
|
||||||
|
|
||||||
|
Vite plugins extend Rolldown's plugin interface with Vite-specific hooks.
|
||||||
|
|
||||||
|
## Basic Plugin Structure
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default function myPlugin(options = {}) {
|
||||||
|
return {
|
||||||
|
name: 'vite-plugin-my-plugin',
|
||||||
|
|
||||||
|
// Hooks...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
- Vite-only plugins: `vite-plugin-*`
|
||||||
|
- Rollup-compatible: `rollup-plugin-*`
|
||||||
|
- Framework-specific: `vite-plugin-vue-*`, `vite-plugin-react-*`
|
||||||
|
|
||||||
|
## Universal Hooks (from Rolldown)
|
||||||
|
|
||||||
|
Called on server start:
|
||||||
|
- `options` - Modify Rolldown options
|
||||||
|
- `buildStart` - Build starting
|
||||||
|
|
||||||
|
Called per module request:
|
||||||
|
- `resolveId` - Resolve import paths
|
||||||
|
- `load` - Load module content
|
||||||
|
- `transform` - Transform module code
|
||||||
|
|
||||||
|
Called on server close:
|
||||||
|
- `buildEnd`
|
||||||
|
- `closeBundle`
|
||||||
|
|
||||||
|
## Vite-Specific Hooks
|
||||||
|
|
||||||
|
### `config`
|
||||||
|
|
||||||
|
Modify config before resolution:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'modify-config',
|
||||||
|
config(config, { command, mode }) {
|
||||||
|
if (command === 'build') {
|
||||||
|
return {
|
||||||
|
resolve: {
|
||||||
|
alias: { foo: 'bar' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `configResolved`
|
||||||
|
|
||||||
|
Access final resolved config:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'read-config',
|
||||||
|
configResolved(config) {
|
||||||
|
this.config = config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `configureServer`
|
||||||
|
|
||||||
|
Add dev server middleware:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'configure-server',
|
||||||
|
configureServer(server) {
|
||||||
|
// Before Vite's middlewares
|
||||||
|
server.middlewares.use((req, res, next) => {
|
||||||
|
// Handle request
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return function to run after Vite's middlewares
|
||||||
|
return () => {
|
||||||
|
server.middlewares.use((req, res, next) => {
|
||||||
|
// Post middleware
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `transformIndexHtml`
|
||||||
|
|
||||||
|
Transform HTML files:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'html-transform',
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
return html.replace(/<title>(.*?)<\/title>/, '<title>New Title</title>')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Inject tags:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'html-inject',
|
||||||
|
transformIndexHtml() {
|
||||||
|
return {
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tag: 'script',
|
||||||
|
attrs: { src: '/inject.js' },
|
||||||
|
injectTo: 'body' // 'head' | 'body' | 'head-prepend' | 'body-prepend'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `handleHotUpdate`
|
||||||
|
|
||||||
|
Custom HMR handling:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'custom-hmr',
|
||||||
|
handleHotUpdate({ file, server, modules }) {
|
||||||
|
if (file.endsWith('.custom')) {
|
||||||
|
server.ws.send({
|
||||||
|
type: 'custom',
|
||||||
|
event: 'custom-update',
|
||||||
|
data: { file }
|
||||||
|
})
|
||||||
|
return [] // Prevent default HMR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Virtual Modules
|
||||||
|
|
||||||
|
Provide build-time information to source code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default function myPlugin() {
|
||||||
|
const virtualModuleId = 'virtual:my-module'
|
||||||
|
const resolvedId = '\0' + virtualModuleId
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'virtual-module',
|
||||||
|
|
||||||
|
resolveId(id) {
|
||||||
|
if (id === virtualModuleId) {
|
||||||
|
return resolvedId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
load(id) {
|
||||||
|
if (id === resolvedId) {
|
||||||
|
return `export const msg = "from virtual module"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { msg } from 'virtual:my-module'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client-Server Communication
|
||||||
|
|
||||||
|
### Server to Client
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
configureServer(server) {
|
||||||
|
server.ws.on('connection', () => {
|
||||||
|
server.ws.send('my:greetings', { msg: 'hello' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Client receives:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.on('my:greetings', (data) => {
|
||||||
|
console.log(data.msg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client to Server
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Client
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.send('my:from-client', { msg: 'Hey!' })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server (in plugin)
|
||||||
|
{
|
||||||
|
configureServer(server) {
|
||||||
|
server.ws.on('my:from-client', (data, client) => {
|
||||||
|
console.log(data.msg)
|
||||||
|
client.send('my:reply', { msg: 'Got it!' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transform with Filtering
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
name: 'transform-js',
|
||||||
|
transform: {
|
||||||
|
filter: {
|
||||||
|
id: /\.js$/ // Only .js files
|
||||||
|
},
|
||||||
|
handler(code, id) {
|
||||||
|
return transformCode(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Path Normalization
|
||||||
|
|
||||||
|
Use POSIX separators for cross-platform compatibility:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { normalizePath } from 'vite'
|
||||||
|
|
||||||
|
normalizePath('foo\\bar') // 'foo/bar'
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/api-plugin.html
|
||||||
|
-->
|
||||||
172
.agents/skills/vite/references/build-library.md
Normal file
172
.agents/skills/vite/references/build-library.md
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
name: build-library
|
||||||
|
description: Building libraries with Vite including proper package.json exports
|
||||||
|
---
|
||||||
|
|
||||||
|
# Library Mode
|
||||||
|
|
||||||
|
Build browser-oriented libraries for distribution.
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { resolve } from 'path'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'lib/main.js'),
|
||||||
|
name: 'MyLib', // Global variable name for UMD
|
||||||
|
fileName: 'my-lib' // Output filename (without extension)
|
||||||
|
},
|
||||||
|
rolldownOptions: {
|
||||||
|
external: ['vue'], // Don't bundle these
|
||||||
|
output: {
|
||||||
|
globals: {
|
||||||
|
vue: 'Vue' // Global var for externals in UMD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple Entry Points
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: {
|
||||||
|
'my-lib': resolve(__dirname, 'lib/main.js'),
|
||||||
|
'secondary': resolve(__dirname, 'lib/secondary.js')
|
||||||
|
},
|
||||||
|
name: 'MyLib'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
Single entry defaults: `['es', 'umd']`
|
||||||
|
Multiple entries defaults: `['es', 'cjs']`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'lib/main.js'),
|
||||||
|
formats: ['es', 'cjs', 'umd', 'iife']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom File Names
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'lib/main.js'),
|
||||||
|
fileName: (format, entryName) => `${entryName}.${format}.js`,
|
||||||
|
cssFileName: 'styles' // For bundled CSS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package.json Configuration
|
||||||
|
|
||||||
|
### Single Entry
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-lib",
|
||||||
|
"type": "module",
|
||||||
|
"files": ["dist"],
|
||||||
|
"main": "./dist/my-lib.umd.cjs",
|
||||||
|
"module": "./dist/my-lib.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/my-lib.js",
|
||||||
|
"require": "./dist/my-lib.umd.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Entries
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-lib",
|
||||||
|
"type": "module",
|
||||||
|
"files": ["dist"],
|
||||||
|
"main": "./dist/my-lib.cjs",
|
||||||
|
"module": "./dist/my-lib.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/my-lib.js",
|
||||||
|
"require": "./dist/my-lib.cjs"
|
||||||
|
},
|
||||||
|
"./secondary": {
|
||||||
|
"import": "./dist/secondary.js",
|
||||||
|
"require": "./dist/secondary.cjs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With CSS
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/my-lib.js",
|
||||||
|
"require": "./dist/my-lib.umd.cjs"
|
||||||
|
},
|
||||||
|
"./style.css": "./dist/my-lib.css"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Library Entry File
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// lib/main.js
|
||||||
|
import Foo from './Foo.vue'
|
||||||
|
import Bar from './Bar.vue'
|
||||||
|
|
||||||
|
export { Foo, Bar }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
In library mode:
|
||||||
|
- `import.meta.env.*` is statically replaced
|
||||||
|
- `process.env.*` is NOT replaced (consumers can change it)
|
||||||
|
|
||||||
|
To replace `process.env`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
define: {
|
||||||
|
'process.env.NODE_ENV': '"production"'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- `assetsInlineLimit` is ignored - assets always inlined
|
||||||
|
- `cssCodeSplit` defaults to `false`
|
||||||
|
- For non-browser libraries, consider using [tsdown](https://tsdown.dev/) or Rolldown directly
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/build.html#library-mode
|
||||||
|
-->
|
||||||
220
.agents/skills/vite/references/build-production.md
Normal file
220
.agents/skills/vite/references/build-production.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
---
|
||||||
|
name: build-production
|
||||||
|
description: Building for production including targets, multi-page apps, and optimizations
|
||||||
|
---
|
||||||
|
|
||||||
|
# Building for Production
|
||||||
|
|
||||||
|
## Basic Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite build
|
||||||
|
```
|
||||||
|
|
||||||
|
Uses `<root>/index.html` as entry point, outputs to `dist/`.
|
||||||
|
|
||||||
|
## Browser Compatibility
|
||||||
|
|
||||||
|
Default target: Baseline Widely Available browsers (Chrome 111+, Edge 111+, Firefox 114+, Safari 16.4+).
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
target: 'es2020', // Or specific browsers
|
||||||
|
// target: ['chrome64', 'firefox78', 'safari12']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For legacy browsers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm add -D @vitejs/plugin-legacy
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import legacy from '@vitejs/plugin-legacy'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
legacy({
|
||||||
|
targets: ['defaults', 'not IE 11']
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
outDir: 'dist', // Output directory
|
||||||
|
assetsDir: 'assets', // Assets subdirectory
|
||||||
|
emptyOutDir: true, // Clear outDir before build
|
||||||
|
sourcemap: true, // Generate sourcemaps
|
||||||
|
// sourcemap: 'inline' | 'hidden'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Public Base Path
|
||||||
|
|
||||||
|
For deploying under a subpath:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
base: '/my-app/'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Relative base (works anywhere):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
base: './'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Access in code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const base = import.meta.env.BASE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-Page App
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
input: {
|
||||||
|
main: resolve(__dirname, 'index.html'),
|
||||||
|
nested: resolve(__dirname, 'nested/index.html')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Minification
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
minify: 'oxc', // Default, fastest
|
||||||
|
// minify: 'terser', // More options, slower
|
||||||
|
// minify: false, // Disable
|
||||||
|
|
||||||
|
terserOptions: { // If using terser
|
||||||
|
compress: {
|
||||||
|
drop_console: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chunk Strategy
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
output: {
|
||||||
|
codeSplitting: {
|
||||||
|
// Manual chunks configuration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunkSizeWarningLimit: 500 // KB
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
cssCodeSplit: true, // CSS per async chunk
|
||||||
|
cssMinify: 'lightningcss', // or 'esbuild'
|
||||||
|
cssTarget: 'chrome61' // Different from JS target
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asset Handling
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
assetsInlineLimit: 4096, // Inline assets < 4KB as base64
|
||||||
|
copyPublicDir: true // Copy public/ to outDir
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manifest
|
||||||
|
|
||||||
|
Generate manifest for backend integration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
manifest: true // .vite/manifest.json
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Watch Mode
|
||||||
|
|
||||||
|
Rebuild on file changes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite build --watch
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
watch: {} // Enable programmatically
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Load Error Handling
|
||||||
|
|
||||||
|
Handle dynamic import failures (e.g., after deployment):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
window.addEventListener('vite:preloadError', (event) => {
|
||||||
|
window.location.reload()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Optimizations (Automatic)
|
||||||
|
|
||||||
|
- **CSS code splitting** - CSS per async chunk
|
||||||
|
- **Preload directives** - `<link rel="modulepreload">`
|
||||||
|
- **Async chunk optimization** - Parallel fetching of dependencies
|
||||||
|
|
||||||
|
## License Generation
|
||||||
|
|
||||||
|
Generate license file for dependencies:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
license: true // .vite/license.md
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/build.html
|
||||||
|
- https://vite.dev/config/build-options.html
|
||||||
|
-->
|
||||||
194
.agents/skills/vite/references/build-ssr.md
Normal file
194
.agents/skills/vite/references/build-ssr.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
---
|
||||||
|
name: build-ssr
|
||||||
|
description: Server-side rendering setup and configuration with Vite
|
||||||
|
---
|
||||||
|
|
||||||
|
# Server-Side Rendering (SSR)
|
||||||
|
|
||||||
|
Low-level API for framework authors. For applications, use higher-level tools from [Awesome Vite SSR](https://github.com/vitejs/awesome-vite#ssr).
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
├── index.html
|
||||||
|
├── server.js # Express/Node server
|
||||||
|
└── src/
|
||||||
|
├── main.js # Universal app code
|
||||||
|
├── entry-client.js # Mounts app to DOM
|
||||||
|
└── entry-server.js # Renders app with SSR API
|
||||||
|
```
|
||||||
|
|
||||||
|
## index.html
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--ssr-outlet--></div>
|
||||||
|
<script type="module" src="/src/entry-client.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// server.js
|
||||||
|
import express from 'express'
|
||||||
|
import { createServer as createViteServer } from 'vite'
|
||||||
|
|
||||||
|
async function createServer() {
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
const vite = await createViteServer({
|
||||||
|
server: { middlewareMode: true },
|
||||||
|
appType: 'custom'
|
||||||
|
})
|
||||||
|
|
||||||
|
app.use(vite.middlewares)
|
||||||
|
|
||||||
|
app.use('*all', async (req, res, next) => {
|
||||||
|
const url = req.originalUrl
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Read index.html
|
||||||
|
let template = fs.readFileSync(
|
||||||
|
path.resolve(__dirname, 'index.html'),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. Apply Vite transforms
|
||||||
|
template = await vite.transformIndexHtml(url, template)
|
||||||
|
|
||||||
|
// 3. Load server entry
|
||||||
|
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
|
||||||
|
|
||||||
|
// 4. Render app HTML
|
||||||
|
const appHtml = await render(url)
|
||||||
|
|
||||||
|
// 5. Inject into template
|
||||||
|
const html = template.replace('<!--ssr-outlet-->', appHtml)
|
||||||
|
|
||||||
|
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
|
||||||
|
} catch (e) {
|
||||||
|
vite.ssrFixStacktrace(e)
|
||||||
|
next(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.listen(5173)
|
||||||
|
}
|
||||||
|
|
||||||
|
createServer()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Logic
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.env.SSR) {
|
||||||
|
// Server-only code (tree-shaken on client)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production Build
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build:client": "vite build --outDir dist/client",
|
||||||
|
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Server
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Differences from dev:
|
||||||
|
// 1. Use dist/client/index.html as template
|
||||||
|
// 2. Use import('./dist/server/entry-server.js') instead of ssrLoadModule
|
||||||
|
// 3. Serve static files from dist/client
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR Manifest
|
||||||
|
|
||||||
|
For preload directives:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite build --outDir dist/client --ssrManifest
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates `dist/client/.vite/ssr-manifest.json` with module-to-chunk mappings.
|
||||||
|
|
||||||
|
## SSR Externals
|
||||||
|
|
||||||
|
Dependencies are externalized by default. To transform with Vite:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
ssr: {
|
||||||
|
noExternal: ['package-that-needs-transform'],
|
||||||
|
external: ['package-to-externalize']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR-specific Plugin Logic
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function mySSRPlugin() {
|
||||||
|
return {
|
||||||
|
name: 'my-ssr',
|
||||||
|
transform(code, id, options) {
|
||||||
|
if (options?.ssr) {
|
||||||
|
// SSR-specific transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR Target
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
ssr: {
|
||||||
|
target: 'node', // Default
|
||||||
|
// target: 'webworker' // For edge runtimes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSR Bundle
|
||||||
|
|
||||||
|
Bundle all dependencies (for workers):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
ssr: {
|
||||||
|
noExternal: true // Bundle everything
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resolve Conditions
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
ssr: {
|
||||||
|
resolve: {
|
||||||
|
conditions: ['node'],
|
||||||
|
externalConditions: ['node']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pre-Rendering / SSG
|
||||||
|
|
||||||
|
Pre-render routes with known data into static HTML at build time.
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/ssr.html
|
||||||
|
-->
|
||||||
137
.agents/skills/vite/references/core-cli.md
Normal file
137
.agents/skills/vite/references/core-cli.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
---
|
||||||
|
name: core-cli
|
||||||
|
description: Vite CLI commands for development, building, and previewing
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vite CLI
|
||||||
|
|
||||||
|
## Dev Server
|
||||||
|
|
||||||
|
Start the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite [root]
|
||||||
|
vite dev [root] # alias
|
||||||
|
vite serve [root] # alias
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dev Server Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--host [host]` | Specify hostname (use `0.0.0.0` for LAN access) |
|
||||||
|
| `--port <port>` | Specify port (default: 5173) |
|
||||||
|
| `--open [path]` | Open browser on startup |
|
||||||
|
| `--cors` | Enable CORS |
|
||||||
|
| `--strictPort` | Exit if port is in use |
|
||||||
|
| `--force` | Force optimizer to re-bundle dependencies |
|
||||||
|
| `-c, --config <file>` | Use specified config file |
|
||||||
|
| `--base <path>` | Public base path |
|
||||||
|
| `-m, --mode <mode>` | Set env mode |
|
||||||
|
| `-l, --logLevel <level>` | info \| warn \| error \| silent |
|
||||||
|
| `--clearScreen` | Allow/disable clear screen when logging |
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite build [root]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--target <target>` | Transpile target (default: `"modules"`) |
|
||||||
|
| `--outDir <dir>` | Output directory (default: `dist`) |
|
||||||
|
| `--assetsDir <dir>` | Assets directory under outDir (default: `"assets"`) |
|
||||||
|
| `--assetsInlineLimit <number>` | Inline threshold in bytes (default: 4096) |
|
||||||
|
| `--ssr [entry]` | Build for SSR |
|
||||||
|
| `--sourcemap [output]` | Generate source maps (`boolean \| "inline" \| "hidden"`) |
|
||||||
|
| `--minify [minifier]` | Minifier (`boolean \| "oxc" \| "terser" \| "esbuild"`) |
|
||||||
|
| `--manifest [name]` | Generate build manifest JSON |
|
||||||
|
| `--ssrManifest [name]` | Generate SSR manifest JSON |
|
||||||
|
| `--emptyOutDir` | Force empty outDir |
|
||||||
|
| `-w, --watch` | Watch mode for rebuilding |
|
||||||
|
|
||||||
|
## Preview
|
||||||
|
|
||||||
|
Locally preview the production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite preview [root]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Preview Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--host [host]` | Specify hostname |
|
||||||
|
| `--port <port>` | Specify port |
|
||||||
|
| `--strictPort` | Exit if port is in use |
|
||||||
|
| `--open [path]` | Open browser on startup |
|
||||||
|
| `--outDir <dir>` | Output directory (default: `dist`) |
|
||||||
|
|
||||||
|
## Package Scripts
|
||||||
|
|
||||||
|
Typical `package.json` scripts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Vite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# With npm
|
||||||
|
npx vite
|
||||||
|
|
||||||
|
# With pnpm
|
||||||
|
pnpm vite
|
||||||
|
|
||||||
|
# With yarn
|
||||||
|
yarn vite
|
||||||
|
|
||||||
|
# With bun
|
||||||
|
bunx vite
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scaffolding New Project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive prompts
|
||||||
|
npm create vite@latest
|
||||||
|
|
||||||
|
# With project name and template
|
||||||
|
npm create vite@latest my-app -- --template vue-ts
|
||||||
|
|
||||||
|
# Available templates: vanilla, vanilla-ts, vue, vue-ts, react, react-ts,
|
||||||
|
# react-swc, react-swc-ts, preact, preact-ts, lit, lit-ts, svelte, svelte-ts,
|
||||||
|
# solid, solid-ts, qwik, qwik-ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debug plugin transforms
|
||||||
|
vite --debug plugin-transform
|
||||||
|
|
||||||
|
# Debug with profiling
|
||||||
|
vite --profile
|
||||||
|
# Then press 'p + enter' to record .cpuprofile
|
||||||
|
|
||||||
|
# Filter debug logs
|
||||||
|
vite --debug -f plugin-transform
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/cli.html
|
||||||
|
-->
|
||||||
176
.agents/skills/vite/references/core-config.md
Normal file
176
.agents/skills/vite/references/core-config.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
---
|
||||||
|
name: core-config
|
||||||
|
description: Vite configuration file setup, defineConfig helper, conditional and async configs
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vite Configuration
|
||||||
|
|
||||||
|
Vite automatically resolves a config file named `vite.config.*` in the project root.
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
// config options
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `defineConfig` for TypeScript intellisense. Alternatively, use JSDoc annotations:
|
||||||
|
|
||||||
|
```js
|
||||||
|
/** @type {import('vite').UserConfig} */
|
||||||
|
export default {
|
||||||
|
// config options
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Config
|
||||||
|
|
||||||
|
Export a function to conditionally determine options based on command, mode, or build type:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => {
|
||||||
|
if (command === 'serve') {
|
||||||
|
// dev specific config
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__DEV__: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// build specific config
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__DEV__: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- `command` is `'serve'` during dev (`vite`, `vite dev`, `vite serve`) and `'build'` for production
|
||||||
|
- `mode` defaults to `'development'` for serve, `'production'` for build
|
||||||
|
|
||||||
|
## Async Config
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig(async ({ command, mode }) => {
|
||||||
|
const data = await fetchRemoteConfig()
|
||||||
|
return {
|
||||||
|
// config using fetched data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Configuration Options
|
||||||
|
|
||||||
|
### Root and Base
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
root: './src', // Project root directory (where index.html is)
|
||||||
|
base: '/my-app/', // Public base path for assets
|
||||||
|
publicDir: 'public', // Static assets directory
|
||||||
|
cacheDir: 'node_modules/.vite' // Cache directory
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resolve Aliases
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, 'src'),
|
||||||
|
'~': resolve(__dirname, 'src/components')
|
||||||
|
},
|
||||||
|
// File extensions to try for imports without extension
|
||||||
|
extensions: ['.mjs', '.js', '.mts', '.ts', '.jsx', '.tsx', '.json']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Define Global Constants
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
define: {
|
||||||
|
__APP_VERSION__: JSON.stringify('1.0.0'),
|
||||||
|
__API_URL__: JSON.stringify('https://api.example.com')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Values must be JSON-serializable or a single identifier. Add TypeScript declarations:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite-env.d.ts
|
||||||
|
declare const __APP_VERSION__: string
|
||||||
|
declare const __API_URL__: string
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON Handling
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
json: {
|
||||||
|
namedExports: true, // Support named imports from JSON
|
||||||
|
stringify: 'auto' // Stringify large JSON for performance
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Environment Variables in Config
|
||||||
|
|
||||||
|
Variables from `.env` files are NOT automatically available in config. Use `loadEnv`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
// Load env vars from .env files
|
||||||
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
|
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__APP_ENV__: JSON.stringify(env.APP_ENV)
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: env.APP_PORT ? Number(env.APP_PORT) : 5173
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specifying Config File
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite --config my-config.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Config Loading Methods
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Default: bundle with Rolldown (may have issues in monorepos)
|
||||||
|
vite
|
||||||
|
|
||||||
|
# Use module runner (no temp file, transforms on the fly)
|
||||||
|
vite --configLoader runner
|
||||||
|
|
||||||
|
# Use native runtime (requires Node.js with TypeScript support)
|
||||||
|
vite --configLoader native
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/config/
|
||||||
|
-->
|
||||||
170
.agents/skills/vite/references/core-features.md
Normal file
170
.agents/skills/vite/references/core-features.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
---
|
||||||
|
name: core-features
|
||||||
|
description: Core Vite features including TypeScript, JSX, CSS, and HTML processing
|
||||||
|
---
|
||||||
|
|
||||||
|
# Core Features
|
||||||
|
|
||||||
|
## TypeScript
|
||||||
|
|
||||||
|
Vite supports `.ts` files out of the box with transpilation via Oxc Transformer (20-30x faster than tsc).
|
||||||
|
|
||||||
|
### Important: Transpile Only
|
||||||
|
|
||||||
|
Vite does NOT perform type checking. Run type checking separately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Production build
|
||||||
|
tsc --noEmit && vite build
|
||||||
|
|
||||||
|
# During development (separate process)
|
||||||
|
tsc --noEmit --watch
|
||||||
|
|
||||||
|
# Or use vite-plugin-checker for browser error reporting
|
||||||
|
```
|
||||||
|
|
||||||
|
### TypeScript Configuration
|
||||||
|
|
||||||
|
Required `tsconfig.json` settings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"isolatedModules": true,
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Types
|
||||||
|
|
||||||
|
Add Vite's client types for `import.meta.env` and asset imports:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides types for:
|
||||||
|
- Asset imports (`.svg`, `.png`, etc.)
|
||||||
|
- `import.meta.env` constants
|
||||||
|
- `import.meta.hot` HMR API
|
||||||
|
|
||||||
|
### Custom Type Overrides
|
||||||
|
|
||||||
|
Override default asset import types:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite-env-override.d.ts
|
||||||
|
declare module '*.svg' {
|
||||||
|
const content: React.FC<React.SVGProps<SVGElement>>
|
||||||
|
export default content
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Path Aliases with tsconfig
|
||||||
|
|
||||||
|
Enable tsconfig paths resolution:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
tsconfigPaths: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## JSX
|
||||||
|
|
||||||
|
`.jsx` and `.tsx` files are supported out of the box. Custom JSX configuration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
oxc: {
|
||||||
|
jsx: {
|
||||||
|
runtime: 'classic', // or 'automatic'
|
||||||
|
pragma: 'h',
|
||||||
|
pragmaFrag: 'Fragment'
|
||||||
|
},
|
||||||
|
// Auto-inject JSX helpers
|
||||||
|
jsxInject: `import React from 'react'`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTML
|
||||||
|
|
||||||
|
`index.html` is the entry point, not tucked away in `public/`. Vite processes it as part of the module graph.
|
||||||
|
|
||||||
|
### Supported Elements
|
||||||
|
|
||||||
|
Vite processes these HTML element attributes:
|
||||||
|
|
||||||
|
- `<script type="module" src>`
|
||||||
|
- `<link href>` (stylesheets)
|
||||||
|
- `<img src>`, `<img srcset>`
|
||||||
|
- `<video src>`, `<video poster>`
|
||||||
|
- `<audio src>`
|
||||||
|
- `<source src>`, `<source srcset>`
|
||||||
|
- `<meta content>` (for og:image, twitter:image, etc.)
|
||||||
|
|
||||||
|
### Opt-out of Processing
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script vite-ignore type="module" src="https://cdn.example.com/lib.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Page Apps
|
||||||
|
|
||||||
|
Access any HTML file by its path:
|
||||||
|
|
||||||
|
- `<root>/index.html` → `http://localhost:5173/`
|
||||||
|
- `<root>/about.html` → `http://localhost:5173/about.html`
|
||||||
|
- `<root>/blog/index.html` → `http://localhost:5173/blog/index.html`
|
||||||
|
|
||||||
|
## JSON
|
||||||
|
|
||||||
|
Direct import with named exports support:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Import entire object
|
||||||
|
import json from './data.json'
|
||||||
|
|
||||||
|
// Named imports (tree-shakeable)
|
||||||
|
import { field } from './data.json'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Framework Support
|
||||||
|
|
||||||
|
Official framework plugins:
|
||||||
|
|
||||||
|
| Framework | Plugin |
|
||||||
|
|-----------|--------|
|
||||||
|
| Vue 3 | `@vitejs/plugin-vue` |
|
||||||
|
| Vue 3 JSX | `@vitejs/plugin-vue-jsx` |
|
||||||
|
| React | `@vitejs/plugin-react` |
|
||||||
|
| React (SWC) | `@vitejs/plugin-react-swc` |
|
||||||
|
| React Server Components | `@vitejs/plugin-rsc` |
|
||||||
|
| Legacy browsers | `@vitejs/plugin-legacy` |
|
||||||
|
|
||||||
|
## Content Security Policy
|
||||||
|
|
||||||
|
Configure nonce for CSP:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
html: {
|
||||||
|
cspNonce: 'PLACEHOLDER' // Replace per-request
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/features.html
|
||||||
|
-->
|
||||||
154
.agents/skills/vite/references/core-plugins.md
Normal file
154
.agents/skills/vite/references/core-plugins.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
name: core-plugins
|
||||||
|
description: Adding, configuring, and ordering Vite plugins
|
||||||
|
---
|
||||||
|
|
||||||
|
# Using Plugins
|
||||||
|
|
||||||
|
Vite extends Rolldown's plugin interface with extra Vite-specific options.
|
||||||
|
|
||||||
|
## Adding Plugins
|
||||||
|
|
||||||
|
Install and add to config:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm add -D @vitejs/plugin-vue
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin Arrays
|
||||||
|
|
||||||
|
Plugins can return arrays (for complex features):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Framework plugin returning multiple plugins
|
||||||
|
export default function framework(config) {
|
||||||
|
return [
|
||||||
|
frameworkRefresh(config),
|
||||||
|
frameworkDevtools(config)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional Plugins
|
||||||
|
|
||||||
|
Falsy values are ignored:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
process.env.ANALYZE && visualizer() // Only if ANALYZE is set
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enforcing Plugin Order
|
||||||
|
|
||||||
|
Control when plugin runs relative to Vite core:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
...somePlugin(),
|
||||||
|
enforce: 'pre' // Before Vite core plugins
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...anotherPlugin(),
|
||||||
|
enforce: 'post' // After Vite build plugins
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Order:**
|
||||||
|
1. Alias
|
||||||
|
2. Plugins with `enforce: 'pre'`
|
||||||
|
3. Vite core plugins
|
||||||
|
4. Plugins without enforce
|
||||||
|
5. Vite build plugins
|
||||||
|
6. Plugins with `enforce: 'post'`
|
||||||
|
7. Vite post-build plugins (minify, manifest)
|
||||||
|
|
||||||
|
## Conditional Application
|
||||||
|
|
||||||
|
Apply only during serve or build:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
{
|
||||||
|
...typescript2(),
|
||||||
|
apply: 'build' // Only during build
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...devOnlyPlugin(),
|
||||||
|
apply: 'serve' // Only during dev
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Function form for more control:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
...myPlugin(),
|
||||||
|
apply(config, { command }) {
|
||||||
|
// Apply only on build but not for SSR
|
||||||
|
return command === 'build' && !config.build.ssr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finding Plugins
|
||||||
|
|
||||||
|
1. Check [Vite Features Guide](https://vite.dev/guide/features.html) - many use cases are built-in
|
||||||
|
2. Official plugins in [Vite Plugins](https://vite.dev/plugins/)
|
||||||
|
3. Community plugins in [awesome-vite](https://github.com/vitejs/awesome-vite#plugins)
|
||||||
|
4. Search npm for `vite-plugin-*` or `rollup-plugin-*`
|
||||||
|
|
||||||
|
## Official Plugins
|
||||||
|
|
||||||
|
| Plugin | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `@vitejs/plugin-vue` | Vue 3 SFC support |
|
||||||
|
| `@vitejs/plugin-vue-jsx` | Vue 3 JSX support |
|
||||||
|
| `@vitejs/plugin-react` | React with Babel/Oxc |
|
||||||
|
| `@vitejs/plugin-react-swc` | React with SWC |
|
||||||
|
| `@vitejs/plugin-rsc` | React Server Components |
|
||||||
|
| `@vitejs/plugin-legacy` | Legacy browser support |
|
||||||
|
|
||||||
|
## Rollup/Rolldown Plugin Compatibility
|
||||||
|
|
||||||
|
Many Rollup plugins work directly with Vite if they:
|
||||||
|
- Don't use `moduleParsed` hook
|
||||||
|
- Don't rely on Rolldown-specific options
|
||||||
|
- Don't have strong coupling between bundle and output phases
|
||||||
|
|
||||||
|
For build-only Rollup plugins:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
plugins: [rollupPluginForBuildOnly()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/using-plugins.html
|
||||||
|
-->
|
||||||
138
.agents/skills/vite/references/features-assets.md
Normal file
138
.agents/skills/vite/references/features-assets.md
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
name: features-assets
|
||||||
|
description: Static asset handling in Vite including imports, public directory, and URL handling
|
||||||
|
---
|
||||||
|
|
||||||
|
# Static Asset Handling
|
||||||
|
|
||||||
|
## Importing Assets as URL
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import imgUrl from './img.png'
|
||||||
|
document.getElementById('hero-img').src = imgUrl
|
||||||
|
// Dev: /src/img.png
|
||||||
|
// Build: /assets/img.2d8efhg.png
|
||||||
|
```
|
||||||
|
|
||||||
|
Common image, media, and font types are detected automatically.
|
||||||
|
|
||||||
|
## Import Queries
|
||||||
|
|
||||||
|
### Explicit URL Import
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import workletURL from './worklet.js?url'
|
||||||
|
CSS.paintWorklet.addModule(workletURL)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Import as String (Raw)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import shaderString from './shader.glsl?raw'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Control Inlining
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import imgUrl1 from './img.svg?no-inline' // Never inline
|
||||||
|
import imgUrl2 from './img.png?inline' // Always inline as base64
|
||||||
|
```
|
||||||
|
|
||||||
|
## Asset Inlining
|
||||||
|
|
||||||
|
Assets smaller than `assetsInlineLimit` (default 4KB) are inlined as base64:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
assetsInlineLimit: 4096, // 4KB
|
||||||
|
// Or use callback for fine control
|
||||||
|
assetsInlineLimit: (filePath) => {
|
||||||
|
return !filePath.endsWith('.svg')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## The `public` Directory
|
||||||
|
|
||||||
|
Files in `public/` are:
|
||||||
|
- Served at root path `/` during dev
|
||||||
|
- Copied as-is to `dist/` root during build
|
||||||
|
- Not processed or hashed
|
||||||
|
|
||||||
|
```
|
||||||
|
public/
|
||||||
|
favicon.ico → /favicon.ico
|
||||||
|
robots.txt → /robots.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Reference with absolute paths in source:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="/icon.png" />
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure directory:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
publicDir: 'static' // or false to disable
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extending Asset Types
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
assetsInclude: ['**/*.gltf', '**/*.hdr']
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dynamic URLs with import.meta.url
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Works natively in modern browsers
|
||||||
|
const imgUrl = new URL('./img.png', import.meta.url).href
|
||||||
|
|
||||||
|
// Dynamic pattern (limited)
|
||||||
|
function getImageUrl(name) {
|
||||||
|
return new URL(`./dir/${name}.png`, import.meta.url).href
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Limitations:**
|
||||||
|
- URL string must be static for build analysis
|
||||||
|
- Does not work with SSR (different semantics in Node.js vs browser)
|
||||||
|
|
||||||
|
## TypeScript Support
|
||||||
|
|
||||||
|
Add `vite/client` to types for asset import recognition:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## URL Handling in CSS
|
||||||
|
|
||||||
|
```css
|
||||||
|
.hero {
|
||||||
|
background: url('./img.png'); /* Processed and rebased */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For dynamically constructed SVG URLs:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import imgUrl from './img.svg'
|
||||||
|
element.style.background = `url("${imgUrl}")` // Note double quotes
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/assets.html
|
||||||
|
-->
|
||||||
215
.agents/skills/vite/references/features-css.md
Normal file
215
.agents/skills/vite/references/features-css.md
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
---
|
||||||
|
name: features-css
|
||||||
|
description: CSS handling in Vite including modules, pre-processors, PostCSS, and Lightning CSS
|
||||||
|
---
|
||||||
|
|
||||||
|
# CSS Handling
|
||||||
|
|
||||||
|
Vite provides rich CSS support with HMR, `@import` inlining, and automatic URL rebasing.
|
||||||
|
|
||||||
|
## Basic CSS Import
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import './styles.css' // Injected into page with HMR support
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Modules
|
||||||
|
|
||||||
|
Files ending with `.module.css` are treated as CSS modules:
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* example.module.css */
|
||||||
|
.red {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import classes from './example.module.css'
|
||||||
|
element.className = classes.red
|
||||||
|
```
|
||||||
|
|
||||||
|
### Named Imports with camelCase
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
modules: {
|
||||||
|
localsConvention: 'camelCaseOnly'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// .apply-color -> applyColor
|
||||||
|
import { applyColor } from './example.module.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Pre-processors
|
||||||
|
|
||||||
|
Install the pre-processor, no Vite plugin needed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Sass (sass-embedded recommended for performance)
|
||||||
|
npm add -D sass-embedded
|
||||||
|
|
||||||
|
# Less
|
||||||
|
npm add -D less
|
||||||
|
|
||||||
|
# Stylus
|
||||||
|
npm add -D stylus
|
||||||
|
```
|
||||||
|
|
||||||
|
Use by file extension:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import './styles.scss'
|
||||||
|
import './styles.less'
|
||||||
|
import './styles.styl'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pre-processor Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
additionalData: `$injectedColor: orange;`,
|
||||||
|
importers: [/* ... */]
|
||||||
|
},
|
||||||
|
less: {
|
||||||
|
math: 'parens-division'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
preprocessorMaxWorkers: true // Use multiple threads
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combined with CSS Modules
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import styles from './component.module.scss'
|
||||||
|
```
|
||||||
|
|
||||||
|
## PostCSS
|
||||||
|
|
||||||
|
Automatically applied if `postcss.config.js` exists:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// postcss.config.js
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
require('postcss-nesting'),
|
||||||
|
require('autoprefixer')
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or configure inline:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: [
|
||||||
|
postcssNesting(),
|
||||||
|
autoprefixer()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Lightning CSS
|
||||||
|
|
||||||
|
Experimental faster CSS processing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm add -D lightningcss
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
transformer: 'lightningcss',
|
||||||
|
lightningcss: {
|
||||||
|
targets: {
|
||||||
|
chrome: 111
|
||||||
|
},
|
||||||
|
cssModules: {
|
||||||
|
// Lightning CSS modules config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Use Lightning CSS for minification only:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
cssMinify: 'lightningcss'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disable CSS Injection
|
||||||
|
|
||||||
|
Import CSS as string without injecting:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import styles from './styles.css?inline' // Returns CSS string, not injected
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Maps
|
||||||
|
|
||||||
|
Enable CSS source maps in development:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
css: {
|
||||||
|
devSourcemap: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Code Splitting
|
||||||
|
|
||||||
|
By default, CSS is extracted per async chunk. Disable to get single CSS file:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
cssCodeSplit: false // Single CSS file for entire app
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## CSS Target
|
||||||
|
|
||||||
|
Set different browser target for CSS:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
cssTarget: 'chrome61' // For Android WeChat WebView
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## @import and URL Handling
|
||||||
|
|
||||||
|
- `@import` statements are inlined automatically
|
||||||
|
- Vite aliases work in `@import`
|
||||||
|
- `url()` references are rebased for correctness
|
||||||
|
- Works across Sass/Less files in different directories
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/features.html#css
|
||||||
|
-->
|
||||||
148
.agents/skills/vite/references/features-dep-bundling.md
Normal file
148
.agents/skills/vite/references/features-dep-bundling.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
---
|
||||||
|
name: features-dep-bundling
|
||||||
|
description: Dependency pre-bundling configuration and caching
|
||||||
|
---
|
||||||
|
|
||||||
|
# Dependency Pre-Bundling
|
||||||
|
|
||||||
|
Vite pre-bundles dependencies on first run for faster dev server startup.
|
||||||
|
|
||||||
|
## Why Pre-Bundling
|
||||||
|
|
||||||
|
1. **CommonJS/UMD to ESM** - Convert non-ESM dependencies
|
||||||
|
2. **Performance** - Bundle many internal modules into single file (e.g., lodash-es has 600+ modules)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Works thanks to smart import analysis
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Automatic Discovery
|
||||||
|
|
||||||
|
Vite crawls source code to find bare imports and pre-bundles them with Rolldown.
|
||||||
|
|
||||||
|
New dependencies discovered after server start trigger re-bundling.
|
||||||
|
|
||||||
|
## Including Dependencies
|
||||||
|
|
||||||
|
Force pre-bundling for dependencies not auto-discovered:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
include: [
|
||||||
|
'some-package',
|
||||||
|
'another-package/nested' // Deep imports
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to include:**
|
||||||
|
- Dynamically imported (via plugin transform)
|
||||||
|
- Large dependencies with many internal modules
|
||||||
|
- CommonJS dependencies
|
||||||
|
|
||||||
|
## Excluding Dependencies
|
||||||
|
|
||||||
|
Skip pre-bundling for small ESM-only dependencies:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
exclude: ['small-esm-dep']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Monorepo Linked Dependencies
|
||||||
|
|
||||||
|
Linked packages are treated as source code by default. If not ESM:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['linked-dep']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart with `--force` after making changes to linked deps.
|
||||||
|
|
||||||
|
## Custom Rolldown Options
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
rolldownOptions: {
|
||||||
|
plugins: [/* Rolldown plugins */],
|
||||||
|
// Other Rolldown options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching
|
||||||
|
|
||||||
|
### File System Cache
|
||||||
|
|
||||||
|
Located in `node_modules/.vite`. Re-runs when:
|
||||||
|
|
||||||
|
- Package lockfile changes (`package-lock.json`, `pnpm-lock.yaml`, etc.)
|
||||||
|
- Patches folder modified
|
||||||
|
- `vite.config.js` changes
|
||||||
|
- `NODE_ENV` changes
|
||||||
|
|
||||||
|
Force re-bundle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vite --force
|
||||||
|
# Or delete node_modules/.vite
|
||||||
|
```
|
||||||
|
|
||||||
|
### Browser Cache
|
||||||
|
|
||||||
|
Pre-bundled deps are cached with `max-age=31536000,immutable`.
|
||||||
|
|
||||||
|
To debug dependencies with local edits:
|
||||||
|
|
||||||
|
1. Disable cache in browser DevTools Network tab
|
||||||
|
2. Restart Vite with `--force`
|
||||||
|
3. Reload page
|
||||||
|
|
||||||
|
## Entries
|
||||||
|
|
||||||
|
Specify custom entry points for discovery:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
entries: [
|
||||||
|
'src/main.ts',
|
||||||
|
'src/other-entry.ts'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, all HTML files are used as entries.
|
||||||
|
|
||||||
|
## esbuildOptions (Deprecated)
|
||||||
|
|
||||||
|
Use `rolldownOptions` instead:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
optimizeDeps: {
|
||||||
|
// Deprecated
|
||||||
|
esbuildOptions: {},
|
||||||
|
// Use instead
|
||||||
|
rolldownOptions: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/dep-pre-bundling.html
|
||||||
|
-->
|
||||||
161
.agents/skills/vite/references/features-env.md
Normal file
161
.agents/skills/vite/references/features-env.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
name: features-env
|
||||||
|
description: Environment variables, .env files, and modes in Vite
|
||||||
|
---
|
||||||
|
|
||||||
|
# Environment Variables and Modes
|
||||||
|
|
||||||
|
## Built-in Constants
|
||||||
|
|
||||||
|
Available via `import.meta.env`:
|
||||||
|
|
||||||
|
| Constant | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `import.meta.env.MODE` | App mode (`'development'` or `'production'`) |
|
||||||
|
| `import.meta.env.BASE_URL` | Base URL from `base` config |
|
||||||
|
| `import.meta.env.PROD` | `true` in production |
|
||||||
|
| `import.meta.env.DEV` | `true` in development |
|
||||||
|
| `import.meta.env.SSR` | `true` in server-side rendering |
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.log('Development mode')
|
||||||
|
// Tree-shaken in production
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Environment Variables
|
||||||
|
|
||||||
|
Only variables prefixed with `VITE_` are exposed to client code:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
VITE_API_URL=https://api.example.com
|
||||||
|
DB_PASSWORD=secret # NOT exposed to client
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
console.log(import.meta.env.VITE_API_URL) // "https://api.example.com"
|
||||||
|
console.log(import.meta.env.DB_PASSWORD) // undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Prefix
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default defineConfig({
|
||||||
|
envPrefix: ['VITE_', 'APP_'] // Expose VITE_* and APP_*
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## .env Files
|
||||||
|
|
||||||
|
Load order (later has higher priority):
|
||||||
|
|
||||||
|
```
|
||||||
|
.env # Always loaded
|
||||||
|
.env.local # Always loaded, gitignored
|
||||||
|
.env.[mode] # Only in specified mode
|
||||||
|
.env.[mode].local # Only in specified mode, gitignored
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variable Expansion
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env
|
||||||
|
KEY=123
|
||||||
|
EXPANDED=test$KEY # test123
|
||||||
|
ESCAPED=test\$foo # test$foo (escaped)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development mode (default for dev)
|
||||||
|
vite
|
||||||
|
|
||||||
|
# Production mode (default for build)
|
||||||
|
vite build
|
||||||
|
|
||||||
|
# Custom mode
|
||||||
|
vite build --mode staging
|
||||||
|
```
|
||||||
|
|
||||||
|
Create mode-specific env file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.staging
|
||||||
|
VITE_APP_TITLE=My App (staging)
|
||||||
|
NODE_ENV=production # Still production build
|
||||||
|
```
|
||||||
|
|
||||||
|
### NODE_ENV vs Mode
|
||||||
|
|
||||||
|
| Command | NODE_ENV | Mode |
|
||||||
|
|---------|----------|------|
|
||||||
|
| `vite build` | `production` | `production` |
|
||||||
|
| `vite build --mode development` | `production` | `development` |
|
||||||
|
| `NODE_ENV=development vite build` | `development` | `production` |
|
||||||
|
|
||||||
|
## TypeScript IntelliSense
|
||||||
|
|
||||||
|
Create type declarations for custom env variables:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite-env.d.ts
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_APP_TITLE: string
|
||||||
|
readonly VITE_API_URL: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For strict typing (disallow unknown keys):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
interface ViteTypeOptions {
|
||||||
|
strictImportMetaEnv: unknown
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTML Replacement
|
||||||
|
|
||||||
|
Use `%VARIABLE%` syntax in HTML:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<title>%VITE_APP_TITLE%</title>
|
||||||
|
<p>Mode: %MODE%</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
Non-existent variables are left as-is (not replaced with `undefined`).
|
||||||
|
|
||||||
|
## Loading Env in Config
|
||||||
|
|
||||||
|
Env vars are NOT available in `vite.config.ts` automatically:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd(), '') // '' loads all vars
|
||||||
|
|
||||||
|
return {
|
||||||
|
define: {
|
||||||
|
__APP_ENV__: JSON.stringify(env.APP_ENV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- Add `*.local` to `.gitignore`
|
||||||
|
- `VITE_*` variables end up in client bundle - no secrets
|
||||||
|
- Never set `envPrefix` to `''` (exposes everything)
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/env-and-mode.html
|
||||||
|
-->
|
||||||
161
.agents/skills/vite/references/features-glob-import.md
Normal file
161
.agents/skills/vite/references/features-glob-import.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
---
|
||||||
|
name: features-glob-import
|
||||||
|
description: Vite's import.meta.glob for batch importing modules and dynamic imports
|
||||||
|
---
|
||||||
|
|
||||||
|
# Glob Import
|
||||||
|
|
||||||
|
## Basic Usage
|
||||||
|
|
||||||
|
Import multiple modules using glob patterns:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./dir/*.js')
|
||||||
|
// Transformed to:
|
||||||
|
// {
|
||||||
|
// './dir/foo.js': () => import('./dir/foo.js'),
|
||||||
|
// './dir/bar.js': () => import('./dir/bar.js'),
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
Iterate and load:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
for (const path in modules) {
|
||||||
|
modules[path]().then((mod) => {
|
||||||
|
console.log(path, mod)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Eager Loading
|
||||||
|
|
||||||
|
Load all modules immediately (no dynamic import):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./dir/*.js', { eager: true })
|
||||||
|
// Transformed to:
|
||||||
|
// import * as __glob_0 from './dir/foo.js'
|
||||||
|
// import * as __glob_1 from './dir/bar.js'
|
||||||
|
// const modules = {
|
||||||
|
// './dir/foo.js': __glob_0,
|
||||||
|
// './dir/bar.js': __glob_1,
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multiple Patterns
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob([
|
||||||
|
'./dir/*.js',
|
||||||
|
'./another/*.js'
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Negative Patterns
|
||||||
|
|
||||||
|
Exclude files with `!` prefix:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob([
|
||||||
|
'./dir/*.js',
|
||||||
|
'!**/bar.js' // Exclude bar.js
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Named Imports
|
||||||
|
|
||||||
|
Import specific exports for tree-shaking:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./dir/*.js', {
|
||||||
|
import: 'setup'
|
||||||
|
})
|
||||||
|
// './dir/foo.js': () => import('./dir/foo.js').then(m => m.setup)
|
||||||
|
```
|
||||||
|
|
||||||
|
Import default export:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./dir/*.js', {
|
||||||
|
import: 'default',
|
||||||
|
eager: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Queries
|
||||||
|
|
||||||
|
Import as raw strings or URLs:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const moduleStrings = import.meta.glob('./dir/*.svg', {
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default'
|
||||||
|
})
|
||||||
|
|
||||||
|
const moduleUrls = import.meta.glob('./dir/*.svg', {
|
||||||
|
query: '?url',
|
||||||
|
import: 'default'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Custom queries for plugins:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./dir/*.js', {
|
||||||
|
query: { foo: 'bar', bar: true }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Base Path
|
||||||
|
|
||||||
|
Change the base path for imports:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const modules = import.meta.glob('./**/*.js', {
|
||||||
|
base: './base'
|
||||||
|
})
|
||||||
|
// Keys: './dir/foo.js'
|
||||||
|
// Imports: './base/dir/foo.js'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Caveats
|
||||||
|
|
||||||
|
1. **Vite-only feature** - Not a web standard
|
||||||
|
2. **Patterns must be literals** - Cannot use variables
|
||||||
|
3. **Relative or absolute** - Must start with `./`, `/`, or use an alias
|
||||||
|
4. **Glob matching** - Uses [tinyglobby](https://github.com/SuperchupuDev/tinyglobby)
|
||||||
|
|
||||||
|
## Dynamic Import with Variables
|
||||||
|
|
||||||
|
Limited dynamic import support:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const module = await import(`./dir/${file}.js`)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Must start with `./` or `../`
|
||||||
|
- Must end with file extension
|
||||||
|
- Variable represents only one level (no `foo/bar`)
|
||||||
|
- Own directory needs filename pattern: `./prefix-${foo}.js` not `./${foo}.js`
|
||||||
|
|
||||||
|
## Practical Example: Loading Route Components
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Lazy load all page components
|
||||||
|
const pages = import.meta.glob('./pages/*.vue')
|
||||||
|
|
||||||
|
const routes = Object.keys(pages).map((path) => {
|
||||||
|
const name = path.match(/\.\/pages\/(.*)\.vue$/)[1]
|
||||||
|
return {
|
||||||
|
path: `/${name.toLowerCase()}`,
|
||||||
|
component: pages[path] // Lazy loaded
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/features.html#glob-import
|
||||||
|
-->
|
||||||
200
.agents/skills/vite/references/features-hmr.md
Normal file
200
.agents/skills/vite/references/features-hmr.md
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
---
|
||||||
|
name: features-hmr
|
||||||
|
description: Vite's Hot Module Replacement (HMR) client API
|
||||||
|
---
|
||||||
|
|
||||||
|
# HMR API
|
||||||
|
|
||||||
|
The HMR API is exposed via `import.meta.hot`. Primarily for framework and tooling authors.
|
||||||
|
|
||||||
|
## Conditional Guard
|
||||||
|
|
||||||
|
Always guard HMR code for tree-shaking in production:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// HMR code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Support
|
||||||
|
|
||||||
|
Add to `tsconfig.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Self-Accepting Module
|
||||||
|
|
||||||
|
Module handles its own updates:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const count = 1
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept((newModule) => {
|
||||||
|
if (newModule) {
|
||||||
|
console.log('updated: count is now', newModule.count)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Accept Dependency Updates
|
||||||
|
|
||||||
|
React to changes in dependencies without self-reload:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { foo } from './foo.js'
|
||||||
|
|
||||||
|
foo()
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// Single dependency
|
||||||
|
import.meta.hot.accept('./foo.js', (newFoo) => {
|
||||||
|
newFoo?.foo()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Multiple dependencies
|
||||||
|
import.meta.hot.accept(
|
||||||
|
['./foo.js', './bar.js'],
|
||||||
|
([newFooModule, newBarModule]) => {
|
||||||
|
// Handle updates
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cleanup on Update
|
||||||
|
|
||||||
|
Clean up side effects before module is replaced:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function setupSideEffect() {
|
||||||
|
const interval = setInterval(() => {}, 1000)
|
||||||
|
return interval
|
||||||
|
}
|
||||||
|
|
||||||
|
const interval = setupSideEffect()
|
||||||
|
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.dispose((data) => {
|
||||||
|
clearInterval(interval)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prune Callback
|
||||||
|
|
||||||
|
Called when module is no longer imported:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.prune((data) => {
|
||||||
|
// Cleanup when module is removed from page
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Persistent Data
|
||||||
|
|
||||||
|
Pass data between module instances:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
// Mutate properties, don't reassign data itself
|
||||||
|
import.meta.hot.data.count = (import.meta.hot.data.count || 0) + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Invalidate
|
||||||
|
|
||||||
|
Force propagation to importers:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept((module) => {
|
||||||
|
if (cannotHandleUpdate(module)) {
|
||||||
|
import.meta.hot.invalidate() // Propagate to importers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## HMR Events
|
||||||
|
|
||||||
|
Listen to built-in events:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.on('vite:beforeUpdate', (payload) => {
|
||||||
|
console.log('Update incoming')
|
||||||
|
})
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:afterUpdate', (payload) => {
|
||||||
|
console.log('Update applied')
|
||||||
|
})
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:beforeFullReload', () => {
|
||||||
|
console.log('Full reload')
|
||||||
|
})
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:error', (error) => {
|
||||||
|
console.error('HMR error', error)
|
||||||
|
})
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:ws:connect', () => {
|
||||||
|
console.log('WebSocket connected')
|
||||||
|
})
|
||||||
|
|
||||||
|
import.meta.hot.on('vite:ws:disconnect', () => {
|
||||||
|
console.log('WebSocket disconnected')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Events
|
||||||
|
|
||||||
|
Send events to server:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Client
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.send('my:event', { msg: 'Hello from client' })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Receive from server:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Client
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.on('my:response', (data) => {
|
||||||
|
console.log(data.msg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript for Custom Events
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// events.d.ts
|
||||||
|
import 'vite/types/customEvent.d.ts'
|
||||||
|
|
||||||
|
declare module 'vite/types/customEvent.d.ts' {
|
||||||
|
interface CustomEventMap {
|
||||||
|
'my:event': { msg: string }
|
||||||
|
'my:response': { msg: string }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/api-hmr.html
|
||||||
|
-->
|
||||||
115
.agents/skills/vite/references/features-workers.md
Normal file
115
.agents/skills/vite/references/features-workers.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
---
|
||||||
|
name: features-workers
|
||||||
|
description: Web Worker support in Vite
|
||||||
|
---
|
||||||
|
|
||||||
|
# Web Workers
|
||||||
|
|
||||||
|
## Constructor Syntax (Recommended)
|
||||||
|
|
||||||
|
Standard Web Worker creation:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const worker = new Worker(new URL('./worker.js', import.meta.url))
|
||||||
|
```
|
||||||
|
|
||||||
|
Module worker:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const worker = new Worker(new URL('./worker.js', import.meta.url), {
|
||||||
|
type: 'module'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Shared Worker:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const sharedWorker = new SharedWorker(
|
||||||
|
new URL('./shared-worker.js', import.meta.url)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The `new URL()` must be used directly inside `new Worker()` for detection.
|
||||||
|
|
||||||
|
## Query Suffix Syntax
|
||||||
|
|
||||||
|
Import with `?worker` suffix:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import MyWorker from './worker?worker'
|
||||||
|
|
||||||
|
const worker = new MyWorker()
|
||||||
|
```
|
||||||
|
|
||||||
|
Shared worker:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import MySharedWorker from './worker?sharedworker'
|
||||||
|
|
||||||
|
const worker = new MySharedWorker()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inline Worker
|
||||||
|
|
||||||
|
Inline as base64 string (no separate chunk):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import MyWorker from './worker?worker&inline'
|
||||||
|
|
||||||
|
const worker = new MyWorker()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Worker URL Only
|
||||||
|
|
||||||
|
Get URL instead of constructor:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import workerUrl from './worker?worker&url'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Worker Script
|
||||||
|
|
||||||
|
Workers can use ESM `import` statements:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// worker.js
|
||||||
|
import { heavyComputation } from './utils'
|
||||||
|
|
||||||
|
self.onmessage = (e) => {
|
||||||
|
const result = heavyComputation(e.data)
|
||||||
|
self.postMessage(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Worker Options
|
||||||
|
|
||||||
|
Configure worker bundling:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
worker: {
|
||||||
|
format: 'es', // or 'iife'
|
||||||
|
plugins: () => [/* worker-specific plugins */],
|
||||||
|
rollupOptions: {
|
||||||
|
// Rollup options for worker bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebAssembly in Workers
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// worker.js
|
||||||
|
import init from './module.wasm?init'
|
||||||
|
|
||||||
|
init().then((instance) => {
|
||||||
|
instance.exports.compute()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Source references:
|
||||||
|
- https://vite.dev/guide/features.html#web-workers
|
||||||
|
-->
|
||||||
21
.agents/skills/vue-best-practices/LICENSE.md
Normal file
21
.agents/skills/vue-best-practices/LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 hyf0
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
38
.agents/skills/vue-best-practices/SKILL.md
Normal file
38
.agents/skills/vue-best-practices/SKILL.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: vue-best-practices
|
||||||
|
description: Vue 3 and Vue.js best practices for TypeScript, vue-tsc, and Volar. This skill should be used when writing, reviewing, or refactoring Vue components to ensure correct typing patterns. Triggers on tasks involving Vue components, props extraction, wrapper components, template type checking, or Volar configuration.
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
author: hyf0
|
||||||
|
version: "8.0.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Capability Rules
|
||||||
|
|
||||||
|
| Rule | Keywords | Description |
|
||||||
|
|------|----------|-------------|
|
||||||
|
| [extract-component-props](rules/extract-component-props.md) | get props type, wrapper component, extend props, inherit props, ComponentProps | Extract types from .vue components |
|
||||||
|
| [vue-tsc-strict-templates](rules/vue-tsc-strict-templates.md) | undefined component, template error, strictTemplates | Catch undefined components in templates |
|
||||||
|
| [fallthrough-attributes](rules/fallthrough-attributes.md) | fallthrough, $attrs, wrapper component | Type-check fallthrough attributes |
|
||||||
|
| [strict-css-modules](rules/strict-css-modules.md) | css modules, $style, typo | Catch CSS module class typos |
|
||||||
|
| [data-attributes-config](rules/data-attributes-config.md) | data-*, strictTemplates, attribute | Allow data-* attributes |
|
||||||
|
| [volar-3-breaking-changes](rules/volar-3-breaking-changes.md) | volar, vue-language-server, editor | Fix Volar 3.0 upgrade issues |
|
||||||
|
| [module-resolution-bundler](rules/module-resolution-bundler.md) | cannot find module, @vue/tsconfig, moduleResolution | Fix module resolution errors |
|
||||||
|
| [define-model-update-event](rules/define-model-update-event.md) | defineModel, update event, undefined | Fix model update errors |
|
||||||
|
| [with-defaults-union-types](rules/with-defaults-union-types.md) | withDefaults, union type, default | Fix union type defaults |
|
||||||
|
| [deep-watch-numeric](rules/deep-watch-numeric.md) | watch, deep, array, Vue 3.5 | Efficient array watching |
|
||||||
|
| [vue-directive-comments](rules/vue-directive-comments.md) | @vue-ignore, @vue-skip, template | Control template type checking |
|
||||||
|
| [vue-router-typed-params](rules/vue-router-typed-params.md) | route params, typed router, unplugin | Fix route params typing |
|
||||||
|
|
||||||
|
## Efficiency Rules
|
||||||
|
|
||||||
|
| Rule | Keywords | Description |
|
||||||
|
|------|----------|-------------|
|
||||||
|
| [hmr-vue-ssr](rules/hmr-vue-ssr.md) | hmr, ssr, hot reload | Fix HMR in SSR apps |
|
||||||
|
| [pinia-store-mocking](rules/pinia-store-mocking.md) | pinia, mock, vitest, store | Mock Pinia stores |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools](https://github.com/vuejs/language-tools)
|
||||||
|
- [vue-component-type-helpers](https://github.com/vuejs/language-tools/tree/master/packages/component-type-helpers)
|
||||||
|
- [Vue 3 Documentation](https://vuejs.org/)
|
||||||
5
.agents/skills/vue-best-practices/SYNC.md
Normal file
5
.agents/skills/vue-best-practices/SYNC.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Sync Info
|
||||||
|
|
||||||
|
- **Source:** `vendor/vue-best-practices/skills/vue-best-practices`
|
||||||
|
- **Git SHA:** `ee0ceda40b7fabeb0713caf8b4db5ea438069fb5`
|
||||||
|
- **Synced:** 2026-01-28
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
title: Fix Slow Save Times with Code Actions Setting
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: fixes 30-60 second save delays in large Vue projects
|
||||||
|
type: capability
|
||||||
|
tags: performance, save-time, vscode, code-actions, volar
|
||||||
|
---
|
||||||
|
|
||||||
|
# Fix Slow Save Times with Code Actions Setting
|
||||||
|
|
||||||
|
**Impact: HIGH** - fixes 30-60 second save delays in large Vue projects
|
||||||
|
|
||||||
|
In large Vue projects, saving files can take 30-60+ seconds due to VSCode's code actions triggering expensive TypeScript state synchronization.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Symptoms:
|
||||||
|
- Save operation takes 30+ seconds
|
||||||
|
- Editor becomes unresponsive during save
|
||||||
|
- CPU spikes when saving Vue files
|
||||||
|
- Happens more in larger projects
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
VSCode emits document change events multiple times during save cycles. Each event triggers Volar to synchronize with TypeScript, causing expensive re-computation.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Disable code actions or limit their timeout:
|
||||||
|
|
||||||
|
**Option 1: Disable code actions (fastest)**
|
||||||
|
```json
|
||||||
|
// .vscode/settings.json
|
||||||
|
{
|
||||||
|
"vue.codeActions.enabled": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Limit code action time**
|
||||||
|
```json
|
||||||
|
// .vscode/settings.json
|
||||||
|
{
|
||||||
|
"vue.codeActions.savingTimeLimit": 1000
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Disable specific code actions**
|
||||||
|
```json
|
||||||
|
// .vscode/settings.json
|
||||||
|
{
|
||||||
|
"vue.codeActions.enabled": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "never"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## VSCode Version Requirement
|
||||||
|
|
||||||
|
VSCode 1.81.0+ includes fixes that reduce save time issues. Upgrade if using an older version.
|
||||||
|
|
||||||
|
## Additional Optimizations
|
||||||
|
|
||||||
|
```json
|
||||||
|
// .vscode/settings.json
|
||||||
|
{
|
||||||
|
"vue.codeActions.enabled": false,
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "Vue.volar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Discussion #2740](https://github.com/vuejs/language-tools/discussions/2740)
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
title: Allow Data Attributes with Strict Templates
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes data-testid and data-* attribute errors in strict mode
|
||||||
|
type: capability
|
||||||
|
tags: dataAttributes, vueCompilerOptions, strictTemplates, data-testid, testing
|
||||||
|
---
|
||||||
|
|
||||||
|
# Allow Data Attributes with Strict Templates
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes data-testid and data-* attribute errors in strict mode
|
||||||
|
|
||||||
|
With `strictTemplates` enabled, `data-*` attributes on components cause type errors. Use the `dataAttributes` option to allow specific patterns.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- Error: Property 'data-testid' does not exist on type... -->
|
||||||
|
<MyComponent data-testid="submit-button" />
|
||||||
|
|
||||||
|
<!-- Error: Property 'data-cy' does not exist on type... -->
|
||||||
|
<MyComponent data-cy="login-form" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Configure `dataAttributes` to allow specific patterns:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json or tsconfig.app.json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"strictTemplates": true,
|
||||||
|
"dataAttributes": ["data-*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now all `data-*` attributes are allowed on any component.
|
||||||
|
|
||||||
|
## Specific Patterns
|
||||||
|
|
||||||
|
You can be more selective:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"dataAttributes": [
|
||||||
|
"data-testid",
|
||||||
|
"data-cy",
|
||||||
|
"data-test-*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This only allows the specified patterns, not all data attributes.
|
||||||
|
|
||||||
|
## Common Testing Attributes
|
||||||
|
|
||||||
|
For testing libraries, allow their specific attributes:
|
||||||
|
|
||||||
|
| Library | Attribute | Pattern |
|
||||||
|
|---------|-----------|---------|
|
||||||
|
| Testing Library | `data-testid` | `"data-testid"` |
|
||||||
|
| Cypress | `data-cy` | `"data-cy"` |
|
||||||
|
| Playwright | `data-testid` | `"data-testid"` |
|
||||||
|
| Generic | All data attributes | `"data-*"` |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
---
|
||||||
|
title: Vue 3.5+ Deep Watch Numeric Depth
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: enables efficient array mutation watching with numeric deep option
|
||||||
|
type: capability
|
||||||
|
tags: watch, deep, vue-3.5, array, mutation, performance
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue 3.5+ Deep Watch Numeric Depth
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - enables efficient array mutation watching with numeric deep option
|
||||||
|
|
||||||
|
Vue 3.5 introduced `deep: number` for watch depth control. This allows watching array mutations without the performance cost of deep traversal.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- Array mutations not triggering watch callback
|
||||||
|
- Deep watch causing performance issues on large nested objects
|
||||||
|
- Unaware of new Vue 3.5 feature
|
||||||
|
|
||||||
|
> **Note:** TypeScript error "Type 'number' is not assignable to type 'boolean'" no longer occurs with Vue 3.5+ and current TypeScript versions. The types now correctly support numeric `deep` values.
|
||||||
|
|
||||||
|
## The Feature
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Vue 3.5+ only
|
||||||
|
watch(items, (newVal) => {
|
||||||
|
// Triggered on array mutations (push, pop, splice, etc.)
|
||||||
|
}, { deep: 1 })
|
||||||
|
```
|
||||||
|
|
||||||
|
| deep value | Behavior |
|
||||||
|
|------------|----------|
|
||||||
|
| `true` | Full recursive traversal (original behavior) |
|
||||||
|
| `false` | Only reference changes |
|
||||||
|
| `1` | One level deep - array mutations, not nested objects |
|
||||||
|
| `2` | Two levels deep |
|
||||||
|
| `n` | N levels deep |
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Step 1: Ensure Vue 3.5+**
|
||||||
|
```bash
|
||||||
|
npm install vue@^3.5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Use numeric depth**
|
||||||
|
```typescript
|
||||||
|
import { watch, ref } from 'vue'
|
||||||
|
|
||||||
|
const items = ref([{ id: 1, data: { nested: 'value' } }])
|
||||||
|
|
||||||
|
// Watch array mutations only (push, pop, etc.)
|
||||||
|
watch(items, (newItems) => {
|
||||||
|
console.log('Array mutated')
|
||||||
|
}, { deep: 1 })
|
||||||
|
|
||||||
|
// Won't trigger on: items.value[0].data.nested = 'new'
|
||||||
|
// Will trigger on: items.value.push(newItem)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Comparison
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const largeNestedData = ref({ /* deeply nested structure */ })
|
||||||
|
|
||||||
|
// SLOW - traverses entire structure
|
||||||
|
watch(largeNestedData, handler, { deep: true })
|
||||||
|
|
||||||
|
// FAST - only watches top-level changes
|
||||||
|
watch(largeNestedData, handler, { deep: 1 })
|
||||||
|
|
||||||
|
// FASTEST - only reference changes
|
||||||
|
watch(largeNestedData, handler, { deep: false })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Alternative: watchEffect for Selective Tracking
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Only tracks properties actually accessed
|
||||||
|
watchEffect(() => {
|
||||||
|
// Only re-runs when items.value.length or first item changes
|
||||||
|
console.log(items.value.length, items.value[0]?.id)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## TypeScript Note
|
||||||
|
|
||||||
|
If TypeScript complains about numeric deep, ensure:
|
||||||
|
1. Vue version is 3.5+
|
||||||
|
2. TypeScript version is current (types are included with `vue` package)
|
||||||
|
3. tsconfig targets correct node_modules types
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Watchers Docs](https://vuejs.org/guide/essentials/watchers.html)
|
||||||
|
- [Vue 3.5 Release Notes](https://blog.vuejs.org/posts/vue-3-5)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
title: defineModel Fires Update Event with Undefined
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes runtime errors from unexpected undefined in model updates
|
||||||
|
type: capability
|
||||||
|
tags: defineModel, v-model, update-event, undefined, vue-3.5
|
||||||
|
---
|
||||||
|
|
||||||
|
# defineModel Fires Update Event with Undefined
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes runtime errors from unexpected undefined in model updates
|
||||||
|
|
||||||
|
> **Version Note:** This issue may be resolved in Vue 3.5+. Testing with Vue 3.5.26 could not reproduce the double emission with `undefined`. If you're on Vue 3.5+, verify the issue exists in your specific scenario before applying workarounds.
|
||||||
|
|
||||||
|
Components using `defineModel` may fire the `@update:model-value` event with `undefined` in certain edge cases. TypeScript types don't always reflect this behavior, potentially causing runtime errors when the parent expects a non-nullable value.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- Parent component receives `undefined` unexpectedly
|
||||||
|
- Runtime error: "Cannot read property of undefined"
|
||||||
|
- Type mismatch between expected `T` and received `T | undefined`
|
||||||
|
- Issue appears when clearing/resetting the model value
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`defineModel` returns `Ref<T | undefined>` by default, even when `T` is non-nullable. The update event can fire with `undefined` when:
|
||||||
|
- Component unmounts
|
||||||
|
- Model is explicitly cleared
|
||||||
|
- Internal state resets
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Option 1: Use required option (Vue 3.5+)**
|
||||||
|
```typescript
|
||||||
|
// Returns Ref<Item> instead of Ref<Item | undefined>
|
||||||
|
const model = defineModel<Item>({ required: true })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Type parent handler to accept undefined**
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<MyComponent
|
||||||
|
v-model="item"
|
||||||
|
@update:model-value="handleUpdate"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Handle both value and undefined
|
||||||
|
const handleUpdate = (value: Item | undefined) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
item.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Use default value in defineModel**
|
||||||
|
```typescript
|
||||||
|
const model = defineModel<string>({ default: '' })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type Declaration Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// In child component
|
||||||
|
interface Props {
|
||||||
|
modelValue: Item
|
||||||
|
}
|
||||||
|
const model = defineModel<Item>({ required: true })
|
||||||
|
|
||||||
|
// Emits will be typed as (value: Item) not (value: Item | undefined)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vuejs/core#12817](https://github.com/vuejs/core/issues/12817)
|
||||||
|
- [vuejs/core#10103](https://github.com/vuejs/core/issues/10103)
|
||||||
|
- [defineModel docs](https://vuejs.org/api/sfc-script-setup.html#definemodel)
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
title: Duplicate Vue Plugin Detection
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes cryptic build errors from Vue plugin registered twice
|
||||||
|
type: capability
|
||||||
|
tags: vite, plugin, vue, duplicate, config, inline
|
||||||
|
---
|
||||||
|
|
||||||
|
# Duplicate Vue Plugin Detection
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes cryptic build errors from Vue plugin registered twice
|
||||||
|
|
||||||
|
When using Vite's JavaScript API, if the Vue plugin is loaded in `vite.config.js` and specified again in `inlineConfig`, it gets registered twice, causing cryptic build errors.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- Build produces unexpected output or fails silently
|
||||||
|
- "Cannot read property of undefined" during build
|
||||||
|
- Different build behavior between CLI and JavaScript API
|
||||||
|
- Vue components render incorrectly after build
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
Vite doesn't deduplicate plugins by name when merging configs. The Vue plugin's internal state gets corrupted when registered twice.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Option 1: Use configFile: false with inline plugins**
|
||||||
|
```typescript
|
||||||
|
import { build } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
await build({
|
||||||
|
configFile: false, // Don't load vite.config.js
|
||||||
|
plugins: [vue()],
|
||||||
|
// ... rest of config
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Don't specify plugins in inlineConfig**
|
||||||
|
```typescript
|
||||||
|
// vite.config.js already has vue plugin
|
||||||
|
import { build } from 'vite'
|
||||||
|
|
||||||
|
await build({
|
||||||
|
// Don't add vue plugin here - it's in vite.config.js
|
||||||
|
root: './src',
|
||||||
|
build: { outDir: '../dist' }
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Filter out Vue plugin before merging**
|
||||||
|
```typescript
|
||||||
|
import { build, loadConfigFromFile } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
const { config } = await loadConfigFromFile({ command: 'build', mode: 'production' })
|
||||||
|
|
||||||
|
// Remove existing Vue plugin
|
||||||
|
const filteredPlugins = config.plugins?.filter(
|
||||||
|
p => !p || (Array.isArray(p) ? false : p.name !== 'vite:vue')
|
||||||
|
) || []
|
||||||
|
|
||||||
|
await build({
|
||||||
|
...config,
|
||||||
|
plugins: [...filteredPlugins, vue({ /* your options */ })]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Detection Script
|
||||||
|
|
||||||
|
Add this to debug plugin registration:
|
||||||
|
```typescript
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
{
|
||||||
|
name: 'debug-plugins',
|
||||||
|
configResolved(config) {
|
||||||
|
const vuePlugins = config.plugins.filter(p => p.name?.includes('vue'))
|
||||||
|
if (vuePlugins.length > 1) {
|
||||||
|
console.warn('WARNING: Multiple Vue plugins detected:', vuePlugins.map(p => p.name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Scenarios
|
||||||
|
|
||||||
|
| Scenario | Solution |
|
||||||
|
|----------|----------|
|
||||||
|
| Using `vite.createServer()` | Use `configFile: false` |
|
||||||
|
| Build script with custom config | Don't duplicate plugins |
|
||||||
|
| Monorepo with shared config | Check for plugin inheritance |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vite Issue #5335](https://github.com/vitejs/vite/issues/5335)
|
||||||
|
- [Vite JavaScript API](https://vite.dev/guide/api-javascript.html)
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
title: Extract Component Props
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: extract props, emits, slots types from .vue components
|
||||||
|
type: capability
|
||||||
|
tags: typescript, props, emits, slots, vue-component-type-helpers, wrapper, ComponentProps
|
||||||
|
---
|
||||||
|
|
||||||
|
# Extract Component Props
|
||||||
|
|
||||||
|
**Impact: HIGH** - extract props, emits, slots types from .vue components
|
||||||
|
|
||||||
|
Use `vue-component-type-helpers` to extract types from `.vue` components:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -D vue-component-type-helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { ComponentProps, ComponentEmit, ComponentSlots, ComponentExposed } from 'vue-component-type-helpers'
|
||||||
|
import MyButton from './MyButton.vue'
|
||||||
|
|
||||||
|
type Props = ComponentProps<typeof MyButton>
|
||||||
|
type Emits = ComponentEmit<typeof MyButton>
|
||||||
|
type Slots = ComponentSlots<typeof MyButton>
|
||||||
|
type Exposed = ComponentExposed<typeof MyButton>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wrapper Component Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { ComponentProps } from 'vue-component-type-helpers'
|
||||||
|
import BaseButton from './BaseButton.vue'
|
||||||
|
|
||||||
|
type BaseProps = ComponentProps<typeof BaseButton>
|
||||||
|
|
||||||
|
interface Props extends BaseProps {
|
||||||
|
size: 'sm' | 'md' | 'lg'
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<Props>()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Do NOT Use
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Includes Vue internal properties (onUpdate:*, class, style, etc.)
|
||||||
|
type Props = InstanceType<typeof MyButton>['$props']
|
||||||
|
```
|
||||||
|
|
||||||
|
## Note
|
||||||
|
|
||||||
|
Vue's built-in `ExtractPropTypes` is for runtime props objects (`props: { foo: String }`), not for `.vue` components.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vue-component-type-helpers](https://github.com/vuejs/language-tools/tree/master/packages/component-type-helpers)
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
title: Enable Fallthrough Attributes Type Checking
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: enables IDE autocomplete for fallthrough attributes in wrapper components
|
||||||
|
type: capability
|
||||||
|
tags: fallthroughAttributes, vueCompilerOptions, component-library, wrapper-components
|
||||||
|
---
|
||||||
|
|
||||||
|
# Enable Fallthrough Attributes Type Checking
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - enables type-aware attribute forwarding in component libraries
|
||||||
|
|
||||||
|
When building component libraries with wrapper components, enable `fallthroughAttributes` to get IDE autocomplete for attributes that will be forwarded to child elements.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
Wrapper components that pass attributes to child elements can benefit from type-aware completion:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<!-- MyButton.vue - wrapper around native button -->
|
||||||
|
<template>
|
||||||
|
<button v-bind="$attrs"><slot /></button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Enable `fallthroughAttributes` in your tsconfig:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json or tsconfig.app.json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"fallthroughAttributes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
When `fallthroughAttributes: true`:
|
||||||
|
- Vue Language Server analyzes which element receives `$attrs`
|
||||||
|
- IDE autocomplete suggests valid attributes for the target element
|
||||||
|
- Helps developers discover available attributes
|
||||||
|
|
||||||
|
> **Note:** This primarily enables IDE autocomplete for valid fallthrough attributes. It does NOT reject invalid attributes as type errors - arbitrary attributes are still allowed.
|
||||||
|
|
||||||
|
## Related Options
|
||||||
|
|
||||||
|
Combine with `strictTemplates` for comprehensive checking:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"strictTemplates": true,
|
||||||
|
"fallthroughAttributes": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
|
||||||
124
.agents/skills/vue-best-practices/rules/hmr-vue-ssr.md
Normal file
124
.agents/skills/vue-best-practices/rules/hmr-vue-ssr.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
---
|
||||||
|
title: HMR Debugging for Vue SSR
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes Hot Module Replacement breaking in Vue SSR applications
|
||||||
|
type: efficiency
|
||||||
|
tags: vite, hmr, ssr, vue, hot-reload, server-side-rendering
|
||||||
|
---
|
||||||
|
|
||||||
|
# HMR Debugging for Vue SSR
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes Hot Module Replacement breaking in Vue SSR applications
|
||||||
|
|
||||||
|
Hot Module Replacement breaks when modifying Vue component `<script setup>` sections in SSR applications. Changes cause errors instead of smooth updates, requiring full page reloads.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- HMR works for `<template>` changes but breaks for `<script setup>`
|
||||||
|
- "Cannot read property of undefined" after saving
|
||||||
|
- Full page reload required after script changes
|
||||||
|
- HMR works in dev:client but not dev:ssr
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
SSR mode has a different transformation pipeline. The Vue plugin's HMR boundary detection doesn't handle SSR modules the same way as client modules.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Step 1: Ensure correct SSR plugin configuration**
|
||||||
|
```typescript
|
||||||
|
// vite.config.ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
ssr: {
|
||||||
|
// Don't externalize these for HMR to work
|
||||||
|
noExternal: ['vue', '@vue/runtime-core', '@vue/runtime-dom']
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: Configure dev server for SSR HMR**
|
||||||
|
```typescript
|
||||||
|
// server.ts
|
||||||
|
import { createServer } from 'vite'
|
||||||
|
|
||||||
|
const vite = await createServer({
|
||||||
|
server: { middlewareMode: true },
|
||||||
|
appType: 'custom'
|
||||||
|
})
|
||||||
|
|
||||||
|
// Use vite.ssrLoadModule for server-side imports
|
||||||
|
const { render } = await vite.ssrLoadModule('/src/entry-server.ts')
|
||||||
|
|
||||||
|
// Handle HMR
|
||||||
|
vite.watcher.on('change', async (file) => {
|
||||||
|
if (file.endsWith('.vue')) {
|
||||||
|
// Invalidate the module
|
||||||
|
const mod = vite.moduleGraph.getModuleById(file)
|
||||||
|
if (mod) {
|
||||||
|
vite.moduleGraph.invalidateModule(mod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: Add HMR acceptance in entry-server**
|
||||||
|
```typescript
|
||||||
|
// entry-server.ts
|
||||||
|
import { createApp } from './main'
|
||||||
|
|
||||||
|
export async function render(url: string) {
|
||||||
|
const app = createApp()
|
||||||
|
// ... render logic
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept HMR updates
|
||||||
|
if (import.meta.hot) {
|
||||||
|
import.meta.hot.accept()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Framework-Specific Solutions
|
||||||
|
|
||||||
|
### Nuxt 3
|
||||||
|
HMR should work out of the box. If not:
|
||||||
|
```bash
|
||||||
|
rm -rf .nuxt node_modules/.vite
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vite SSR Template
|
||||||
|
Ensure you're using the latest `@vitejs/plugin-vue`:
|
||||||
|
```bash
|
||||||
|
npm install @vitejs/plugin-vue@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
Enable verbose HMR logging:
|
||||||
|
```typescript
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
server: {
|
||||||
|
hmr: {
|
||||||
|
overlay: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logLevel: 'info' // Shows HMR updates
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
- HMR for `<script>` (not `<script setup>`) may require full reload
|
||||||
|
- SSR components with external dependencies may not hot-reload
|
||||||
|
- State is not preserved for SSR components (expected behavior)
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vite-plugin-vue#525](https://github.com/vitejs/vite-plugin-vue/issues/525)
|
||||||
|
- [Vite SSR Guide](https://vite.dev/guide/ssr.html)
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: moduleResolution Bundler Migration Issues
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: fixes "Cannot find module" errors after @vue/tsconfig upgrade
|
||||||
|
type: capability
|
||||||
|
tags: moduleResolution, bundler, tsconfig, vue-tsconfig, node, esm
|
||||||
|
---
|
||||||
|
|
||||||
|
# moduleResolution Bundler Migration Issues
|
||||||
|
|
||||||
|
**Impact: HIGH** - fixes "Cannot find module" errors after @vue/tsconfig upgrade
|
||||||
|
|
||||||
|
Recent versions of `@vue/tsconfig` changed `moduleResolution` from `"node"` to `"bundler"`. This can break existing projects with errors like "Cannot find module 'vue'" or issues with `resolveJsonModule`.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- `Cannot find module 'vue'` or other packages
|
||||||
|
- `Option '--resolveJsonModule' cannot be specified without 'node' module resolution`
|
||||||
|
- Errors appear after updating `@vue/tsconfig`
|
||||||
|
- Some third-party packages no longer resolve
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`moduleResolution: "bundler"` requires:
|
||||||
|
1. TypeScript 5.0+
|
||||||
|
2. Packages to have proper `exports` field in package.json
|
||||||
|
3. Different resolution rules than Node.js classic resolution
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Option 1: Ensure TypeScript 5.0+ everywhere**
|
||||||
|
```bash
|
||||||
|
npm install -D typescript@^5.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
In monorepos, ALL packages must use TypeScript 5.0+.
|
||||||
|
|
||||||
|
**Option 2: Add compatibility workaround**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolvePackageJsonExports": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting `resolvePackageJsonExports: false` restores compatibility with packages that don't have proper exports.
|
||||||
|
|
||||||
|
**Option 3: Revert to Node resolution**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "node"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Which Packages Break?
|
||||||
|
|
||||||
|
Packages break if they:
|
||||||
|
- Lack `exports` field in package.json
|
||||||
|
- Have incorrect `exports` configuration
|
||||||
|
- Rely on Node.js-specific resolution behavior
|
||||||
|
|
||||||
|
## Diagnosis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check which resolution is being used
|
||||||
|
cat tsconfig.json | grep moduleResolution
|
||||||
|
|
||||||
|
# Test if a specific module resolves
|
||||||
|
npx tsc --traceResolution 2>&1 | grep "module-name"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vuejs/tsconfig#8](https://github.com/vuejs/tsconfig/issues/8)
|
||||||
|
- [TypeScript moduleResolution docs](https://www.typescriptlang.org/tsconfig#moduleResolution)
|
||||||
|
- [Vite discussion#14001](https://github.com/vitejs/vite/discussions/14001)
|
||||||
159
.agents/skills/vue-best-practices/rules/pinia-store-mocking.md
Normal file
159
.agents/skills/vue-best-practices/rules/pinia-store-mocking.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
title: Mocking Pinia Stores with Vitest
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: properly mocks Pinia stores in component tests
|
||||||
|
type: efficiency
|
||||||
|
tags: pinia, vitest, testing, mock, createTestingPinia, store
|
||||||
|
---
|
||||||
|
|
||||||
|
# Mocking Pinia Stores with Vitest
|
||||||
|
|
||||||
|
**Impact: HIGH** - properly mocks Pinia stores in component tests
|
||||||
|
|
||||||
|
Developers struggle to properly mock Pinia stores: `createTestingPinia` requires explicit `createSpy` configuration, and "injection Symbol(pinia) not found" errors occur without proper setup.
|
||||||
|
|
||||||
|
> **Important (@pinia/testing 1.0+):** The `createSpy` option is **REQUIRED**, not optional. Omitting it throws an error: "You must configure the `createSpy` option."
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- "injection Symbol(pinia) not found" error
|
||||||
|
- "You must configure the `createSpy` option" error
|
||||||
|
- Actions not properly mocked
|
||||||
|
- Store state not reset between tests
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Pattern 1: Basic setup with createTestingPinia**
|
||||||
|
```typescript
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import { createTestingPinia } from '@pinia/testing'
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
import MyComponent from './MyComponent.vue'
|
||||||
|
import { useCounterStore } from '@/stores/counter'
|
||||||
|
|
||||||
|
test('component uses store', async () => {
|
||||||
|
const wrapper = mount(MyComponent, {
|
||||||
|
global: {
|
||||||
|
plugins: [
|
||||||
|
createTestingPinia({
|
||||||
|
createSpy: vi.fn, // REQUIRED in @pinia/testing 1.0+
|
||||||
|
initialState: {
|
||||||
|
counter: { count: 10 } // Set initial state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get the store instance AFTER mounting
|
||||||
|
const store = useCounterStore()
|
||||||
|
|
||||||
|
// Actions are automatically stubbed
|
||||||
|
await wrapper.find('button').trigger('click')
|
||||||
|
expect(store.increment).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern 2: Customize action behavior**
|
||||||
|
```typescript
|
||||||
|
test('component handles async action', async () => {
|
||||||
|
const wrapper = mount(MyComponent, {
|
||||||
|
global: {
|
||||||
|
plugins: [
|
||||||
|
createTestingPinia({
|
||||||
|
createSpy: vi.fn,
|
||||||
|
stubActions: false // Don't stub, use real actions
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
|
||||||
|
// Override specific action
|
||||||
|
store.fetchData = vi.fn().mockResolvedValue({ items: [] })
|
||||||
|
|
||||||
|
await wrapper.find('.load-button').trigger('click')
|
||||||
|
expect(store.fetchData).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern 3: Testing store directly**
|
||||||
|
```typescript
|
||||||
|
import { setActivePinia, createPinia } from 'pinia'
|
||||||
|
import { useCounterStore } from '@/stores/counter'
|
||||||
|
|
||||||
|
describe('Counter Store', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
setActivePinia(createPinia())
|
||||||
|
})
|
||||||
|
|
||||||
|
test('increments count', () => {
|
||||||
|
const store = useCounterStore()
|
||||||
|
expect(store.count).toBe(0)
|
||||||
|
|
||||||
|
store.increment()
|
||||||
|
expect(store.count).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup Store with Vitest
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// stores/counter.ts - Setup store syntax
|
||||||
|
export const useCounterStore = defineStore('counter', () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const doubleCount = computed(() => count.value * 2)
|
||||||
|
|
||||||
|
function increment() {
|
||||||
|
count.value++
|
||||||
|
}
|
||||||
|
|
||||||
|
return { count, doubleCount, increment }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test file
|
||||||
|
test('setup store works', async () => {
|
||||||
|
const pinia = createTestingPinia({
|
||||||
|
createSpy: vi.fn,
|
||||||
|
initialState: {
|
||||||
|
counter: { count: 5 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapper = mount(MyComponent, {
|
||||||
|
global: { plugins: [pinia] }
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useCounterStore()
|
||||||
|
expect(store.count).toBe(5)
|
||||||
|
expect(store.doubleCount).toBe(10)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reset Between Tests
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
describe('Store Tests', () => {
|
||||||
|
let pinia: Pinia
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
pinia = createTestingPinia({
|
||||||
|
createSpy: vi.fn
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('test 1', () => { /* ... */ })
|
||||||
|
test('test 2', () => { /* ... */ })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Pinia Testing Guide](https://pinia.vuejs.org/cookbook/testing.html)
|
||||||
|
- [Pinia Discussion #2092](https://github.com/vuejs/pinia/discussions/2092)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
title: JSDoc Documentation for Script Setup Components
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: enables proper documentation for composition API components
|
||||||
|
type: capability
|
||||||
|
tags: jsdoc, script-setup, documentation, composition-api, component
|
||||||
|
---
|
||||||
|
|
||||||
|
# JSDoc Documentation for Script Setup Components
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - enables proper documentation for composition API components
|
||||||
|
|
||||||
|
`<script setup>` doesn't have an obvious place to attach JSDoc comments for the component itself. Use a dual-script pattern.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
/**
|
||||||
|
* This comment doesn't appear in IDE hover or docs
|
||||||
|
* @component
|
||||||
|
*/
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
JSDoc comments inside `<script setup>` don't attach to the component export because there's no explicit export statement.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Use both `<script>` and `<script setup>` blocks:
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
```vue
|
||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* A counter component that displays and increments a value.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```vue
|
||||||
|
* <Counter :initial="5" @update="handleUpdate" />
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
*/
|
||||||
|
export default {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
/** Starting value for the counter */
|
||||||
|
initial?: number
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
/** Emitted when counter value changes */
|
||||||
|
update: [value: number]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const count = ref(props.initial ?? 0)
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
- The regular `<script>` block's default export is merged with `<script setup>`
|
||||||
|
- JSDoc on `export default {}` attaches to the component
|
||||||
|
- Props and emits JSDoc in `<script setup>` still work normally
|
||||||
|
|
||||||
|
## What Gets Documented
|
||||||
|
|
||||||
|
| Location | Shows In |
|
||||||
|
|----------|----------|
|
||||||
|
| `export default {}` JSDoc | Component import hover |
|
||||||
|
| `defineProps` JSDoc | Prop hover in templates |
|
||||||
|
| `defineEmits` JSDoc | Event handler hover |
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Discussion #5932](https://github.com/vuejs/language-tools/discussions/5932)
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
title: Enable Strict CSS Modules Type Checking
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: catches typos in CSS module class names at compile time
|
||||||
|
type: capability
|
||||||
|
tags: strictCssModules, vueCompilerOptions, css-modules, style-module
|
||||||
|
---
|
||||||
|
|
||||||
|
# Enable Strict CSS Modules Type Checking
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - catches typos in CSS module class names at compile time
|
||||||
|
|
||||||
|
When using CSS modules with `<style module>`, Vue doesn't validate class names by default. Enable `strictCssModules` to catch typos and undefined classes.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
CSS module class name errors go undetected:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
// No error for typo in class name
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="$style.buttn">Click me</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style module>
|
||||||
|
.button {
|
||||||
|
background: blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
The typo `buttn` instead of `button` silently fails at runtime.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
Enable `strictCssModules` in your tsconfig:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json or tsconfig.app.json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"strictCssModules": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now `$style.buttn` will show a type error because `buttn` doesn't exist in the CSS module.
|
||||||
|
|
||||||
|
## What Gets Checked
|
||||||
|
|
||||||
|
| Access | With strictCssModules |
|
||||||
|
|--------|----------------------|
|
||||||
|
| `$style.validClass` | OK |
|
||||||
|
| `$style.typo` | Error: Property 'typo' does not exist |
|
||||||
|
| `$style['dynamic']` | OK (dynamic access not checked) |
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- Only checks static property access (`$style.className`)
|
||||||
|
- Dynamic access (`$style[variable]`) is not validated
|
||||||
|
- Only works with `<style module>`, not external CSS files
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Wiki - Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
---
|
||||||
|
title: Volar 3.0 Breaking Changes
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: fixes editor integration after Volar/vue-language-server upgrade
|
||||||
|
type: capability
|
||||||
|
tags: volar, vue-language-server, neovim, vscode, ide, ts_ls, vtsls
|
||||||
|
---
|
||||||
|
|
||||||
|
# Volar 3.0 Breaking Changes
|
||||||
|
|
||||||
|
**Impact: HIGH** - fixes editor integration after Volar/vue-language-server upgrade
|
||||||
|
|
||||||
|
Volar 3.0 (vue-language-server 3.x) introduced breaking changes to the language server protocol. Editors configured for Volar 2.x will break with errors like "vue_ls doesn't work with ts_ls.. it expects vtsls".
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- `vue_ls doesn't work with ts_ls`
|
||||||
|
- TypeScript features stop working in Vue files
|
||||||
|
- No autocomplete, type hints, or error highlighting
|
||||||
|
- Editor shows "Language server initialization failed"
|
||||||
|
|
||||||
|
## Fix by Editor
|
||||||
|
|
||||||
|
### VSCode
|
||||||
|
|
||||||
|
Update the "Vue - Official" extension to latest version. It manages the language server automatically.
|
||||||
|
|
||||||
|
### NeoVim (nvim-lspconfig)
|
||||||
|
|
||||||
|
**Option 1: Use vtsls instead of ts_ls**
|
||||||
|
```lua
|
||||||
|
-- Replace ts_ls/tsserver with vtsls
|
||||||
|
require('lspconfig').vtsls.setup({})
|
||||||
|
require('lspconfig').volar.setup({})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Downgrade vue-language-server**
|
||||||
|
```bash
|
||||||
|
npm install -g @vue/language-server@2.1.10
|
||||||
|
```
|
||||||
|
|
||||||
|
### JetBrains IDEs
|
||||||
|
|
||||||
|
Update to latest Vue plugin. If issues persist, disable and re-enable the Vue plugin.
|
||||||
|
|
||||||
|
## What Changed in 3.0
|
||||||
|
|
||||||
|
| Feature | Volar 2.x | Volar 3.0 |
|
||||||
|
|---------|-----------|-----------|
|
||||||
|
| TypeScript integration | ts_ls/tsserver | vtsls recommended (Neovim) |
|
||||||
|
| Hybrid mode | Optional | Default |
|
||||||
|
|
||||||
|
## Workaround: Stay on 2.x
|
||||||
|
|
||||||
|
If upgrading is not possible:
|
||||||
|
```bash
|
||||||
|
npm install -g @vue/language-server@^2.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Pin in your project's package.json to prevent accidental upgrades.
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vuejs/language-tools#5598](https://github.com/vuejs/language-tools/issues/5598)
|
||||||
|
- [NeoVim Vue Setup Guide](https://dev.to/danwalsh/solved-vue-3-typescript-inlay-hint-support-in-neovim-53ej)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
title: Vue Template Directive Comments
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: enables fine-grained control over template type checking
|
||||||
|
type: capability
|
||||||
|
tags: vue-directive, vue-ignore, vue-expect-error, vue-skip, template, type-checking
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Template Directive Comments
|
||||||
|
|
||||||
|
**Impact: HIGH** - enables fine-grained control over template type checking
|
||||||
|
|
||||||
|
Vue Language Tools supports special directive comments to control type checking behavior in templates.
|
||||||
|
|
||||||
|
## Available Directives
|
||||||
|
|
||||||
|
### @vue-ignore
|
||||||
|
|
||||||
|
Suppress type errors for the next line:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- @vue-ignore -->
|
||||||
|
<Component :prop="valueWithTypeError" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### @vue-expect-error
|
||||||
|
|
||||||
|
Assert that the next line should have a type error (useful for testing):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- @vue-expect-error -->
|
||||||
|
<Component :invalid-prop="value" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### @vue-skip
|
||||||
|
|
||||||
|
Skip type checking for an entire block:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- @vue-skip -->
|
||||||
|
<div>
|
||||||
|
<!-- Everything in here is not type-checked -->
|
||||||
|
<LegacyComponent :any="props" :go="here" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
### @vue-generic
|
||||||
|
|
||||||
|
Declare template-level generic types:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<!-- @vue-generic {T extends string} -->
|
||||||
|
<GenericList :items="items as T[]" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
- Migrating legacy components with incomplete types
|
||||||
|
- Working with third-party components that have incorrect type definitions
|
||||||
|
- Temporarily suppressing errors during refactoring
|
||||||
|
- Testing that certain patterns produce expected type errors
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Language Tools Wiki - Directive Comments](https://github.com/vuejs/language-tools/wiki/Directive-Comments)
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
title: Vue Router useRoute Params Union Type Narrowing
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes "Property does not exist" errors with typed route params
|
||||||
|
type: capability
|
||||||
|
tags: vue-router, useRoute, unplugin-vue-router, typed-routes, params
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vue Router useRoute Params Union Type Narrowing
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes "Property does not exist" errors with typed route params
|
||||||
|
|
||||||
|
With `unplugin-vue-router` typed routes, `route.params` becomes a union of ALL page param types. TypeScript cannot narrow `Record<never, never> | { id: string }` properly, causing "Property 'id' does not exist" errors even on the correct page.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- "Property 'id' does not exist on type 'RouteParams'"
|
||||||
|
- `route.params.id` shows as `string | undefined` everywhere
|
||||||
|
- Union type of all route params instead of specific route
|
||||||
|
- Type narrowing with `if (route.name === 'users-id')` doesn't work
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`unplugin-vue-router` generates a union type of all possible route params. TypeScript's control flow analysis can't narrow this union based on route name checks.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Option 1: Pass route name to useRoute (recommended)**
|
||||||
|
```typescript
|
||||||
|
// pages/users/[id].vue
|
||||||
|
import { useRoute } from 'vue-router/auto'
|
||||||
|
|
||||||
|
// Specify the route path for proper typing
|
||||||
|
const route = useRoute('/users/[id]')
|
||||||
|
|
||||||
|
// Now properly typed as { id: string }
|
||||||
|
console.log(route.params.id) // string, not string | undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Type assertion with specific route**
|
||||||
|
```typescript
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import type { RouteLocationNormalized } from 'vue-router/auto-routes'
|
||||||
|
|
||||||
|
const route = useRoute() as RouteLocationNormalized<'/users/[id]'>
|
||||||
|
route.params.id // Properly typed
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Define route-specific param type**
|
||||||
|
```typescript
|
||||||
|
// In your page component
|
||||||
|
interface UserRouteParams {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const { id } = route.params as UserRouteParams
|
||||||
|
```
|
||||||
|
|
||||||
|
## Required tsconfig Setting
|
||||||
|
|
||||||
|
Ensure `moduleResolution: "bundler"` for unplugin-vue-router:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caveat: Route Name Format
|
||||||
|
|
||||||
|
The route name matches the file path pattern:
|
||||||
|
- `pages/users/[id].vue` → `/users/[id]`
|
||||||
|
- `pages/posts/[slug]/comments.vue` → `/posts/[slug]/comments`
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [unplugin-vue-router#337](https://github.com/posva/unplugin-vue-router/issues/337)
|
||||||
|
- [unplugin-vue-router#176](https://github.com/posva/unplugin-vue-router/discussions/176)
|
||||||
|
- [unplugin-vue-router TypeScript docs](https://uvr.esm.is/guide/typescript.html)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
title: Enable Strict Template Checking
|
||||||
|
impact: HIGH
|
||||||
|
impactDescription: catches undefined components and props at compile time
|
||||||
|
type: capability
|
||||||
|
tags: vue-tsc, typescript, type-checking, templates, vueCompilerOptions
|
||||||
|
---
|
||||||
|
|
||||||
|
# Enable Strict Template Checking
|
||||||
|
|
||||||
|
**Impact: HIGH** - catches undefined components and props at compile time
|
||||||
|
|
||||||
|
By default, vue-tsc does not report errors for undefined components in templates. Enable `strictTemplates` to catch these issues during type checking.
|
||||||
|
|
||||||
|
## Which tsconfig?
|
||||||
|
|
||||||
|
Add `vueCompilerOptions` to the tsconfig that includes Vue source files. In projects with multiple tsconfigs (like those created with `create-vue`), this is typically `tsconfig.app.json`, not the root `tsconfig.json` or `tsconfig.node.json`.
|
||||||
|
|
||||||
|
**Incorrect (missing strict checking):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true
|
||||||
|
}
|
||||||
|
// vueCompilerOptions not configured - undefined components won't error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (strict template checking enabled):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Options
|
||||||
|
|
||||||
|
| Option | Default | Effect |
|
||||||
|
|--------|---------|--------|
|
||||||
|
| `strictTemplates` | `false` | Enables all checkUnknown* options below |
|
||||||
|
| `checkUnknownComponents` | `false` | Error on undefined/unregistered components |
|
||||||
|
| `checkUnknownProps` | `false` | Error on props not declared in component definition |
|
||||||
|
| `checkUnknownEvents` | `false` | Error on events not declared via `defineEmits` |
|
||||||
|
| `checkUnknownDirectives` | `false` | Error on unregistered custom directives |
|
||||||
|
|
||||||
|
## Granular Control
|
||||||
|
|
||||||
|
If `strictTemplates` is too strict, enable individual checks:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vueCompilerOptions": {
|
||||||
|
"checkUnknownComponents": true,
|
||||||
|
"checkUnknownProps": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [Vue Compiler Options](https://github.com/vuejs/language-tools/wiki/Vue-Compiler-Options)
|
||||||
|
- [Vite Vue+TS Template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-vue-ts)
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
title: withDefaults Incorrect Default with Union Types
|
||||||
|
impact: MEDIUM
|
||||||
|
impactDescription: fixes spurious "Missing required prop" warning with union type props
|
||||||
|
type: capability
|
||||||
|
tags: withDefaults, defineProps, union-types, defaults, vue-3.5
|
||||||
|
---
|
||||||
|
|
||||||
|
# withDefaults Incorrect Default with Union Types
|
||||||
|
|
||||||
|
**Impact: MEDIUM** - fixes spurious "Missing required prop" warning with union type props
|
||||||
|
|
||||||
|
Using `withDefaults` with union types like `false | string` may produce a Vue runtime warning "Missing required prop" even when a default is provided. The runtime value IS applied correctly, but the warning can be confusing.
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- Vue warns "Missing required prop" despite default being set
|
||||||
|
- Warning appears only with union types like `false | string`
|
||||||
|
- TypeScript types are correct
|
||||||
|
- Runtime value IS correct (the default is applied)
|
||||||
|
|
||||||
|
## Problematic Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// This produces a spurious warning (but works at runtime)
|
||||||
|
interface Props {
|
||||||
|
value: false | string // Union type
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
value: 'default' // Runtime value IS correct, but Vue warns about missing prop
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
**Option 1: Use Reactive Props Destructure (Vue 3.5+)**
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
interface Props {
|
||||||
|
value: false | string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preferred in Vue 3.5+
|
||||||
|
const { value = 'default' } = defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Use runtime declaration**
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps({
|
||||||
|
value: {
|
||||||
|
type: [Boolean, String] as PropType<false | string>,
|
||||||
|
default: 'default'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Split into separate props**
|
||||||
|
```typescript
|
||||||
|
interface Props {
|
||||||
|
enabled: boolean
|
||||||
|
customValue?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
enabled: false,
|
||||||
|
customValue: 'default'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why Reactive Props Destructure Works
|
||||||
|
|
||||||
|
Vue 3.5's Reactive Props Destructure handles default values at the destructuring level, bypassing the type inference issues with `withDefaults`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// The default is applied during destructuring, not type inference
|
||||||
|
const { prop = 'default' } = defineProps<{ prop?: string }>()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enable Reactive Props Destructure
|
||||||
|
|
||||||
|
This is enabled by default in Vue 3.5+. For older versions:
|
||||||
|
```javascript
|
||||||
|
// vite.config.js
|
||||||
|
export default {
|
||||||
|
plugins: [
|
||||||
|
vue({
|
||||||
|
script: {
|
||||||
|
propsDestructure: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
- [vuejs/core#12897](https://github.com/vuejs/core/issues/12897)
|
||||||
|
- [Reactive Props Destructure RFC](https://github.com/vuejs/rfcs/discussions/502)
|
||||||
5
.agents/skills/vue/GENERATION.md
Normal file
5
.agents/skills/vue/GENERATION.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generation Info
|
||||||
|
|
||||||
|
- **Source:** `sources/vue`
|
||||||
|
- **Git SHA:** `dcf363038ab2f5b4571028669cf893d084b6b207`
|
||||||
|
- **Generated:** 2026-01-28
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user