feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
554
skills/nuxt/references/advanced-module-authoring.md
Normal file
554
skills/nuxt/references/advanced-module-authoring.md
Normal file
@@ -0,0 +1,554 @@
|
||||
---
|
||||
name: module-authoring
|
||||
description: Complete guide to creating publishable Nuxt modules with best practices
|
||||
---
|
||||
|
||||
# Module Authoring
|
||||
|
||||
This guide covers creating publishable Nuxt modules with proper structure, type safety, and best practices.
|
||||
|
||||
## Module Structure
|
||||
|
||||
Recommended structure for a publishable module:
|
||||
|
||||
```
|
||||
my-nuxt-module/
|
||||
├── src/
|
||||
│ ├── module.ts # Module entry
|
||||
│ └── runtime/
|
||||
│ ├── components/ # Vue components
|
||||
│ ├── composables/ # Composables
|
||||
│ ├── plugins/ # Nuxt plugins
|
||||
│ └── server/ # Server handlers
|
||||
├── playground/ # Development app
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
## Module Definition
|
||||
|
||||
### Basic Module with Type-safe Options
|
||||
|
||||
```ts
|
||||
// src/module.ts
|
||||
import { defineNuxtModule, createResolver, addPlugin, addComponent, addImports } from '@nuxt/kit'
|
||||
|
||||
export interface ModuleOptions {
|
||||
prefix?: string
|
||||
apiKey: string
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>({
|
||||
meta: {
|
||||
name: 'my-module',
|
||||
configKey: 'myModule',
|
||||
compatibility: {
|
||||
nuxt: '>=3.0.0',
|
||||
},
|
||||
},
|
||||
defaults: {
|
||||
prefix: 'My',
|
||||
enabled: true,
|
||||
},
|
||||
setup(options, nuxt) {
|
||||
if (!options.enabled) return
|
||||
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Module setup logic here
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Using `.with()` for Strict Type Inference
|
||||
|
||||
When you need TypeScript to infer that default values are always present:
|
||||
|
||||
```ts
|
||||
import { defineNuxtModule } from '@nuxt/kit'
|
||||
|
||||
interface ModuleOptions {
|
||||
apiKey: string
|
||||
baseURL: string
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export default defineNuxtModule<ModuleOptions>().with({
|
||||
meta: {
|
||||
name: '@nuxtjs/my-api',
|
||||
configKey: 'myApi',
|
||||
},
|
||||
defaults: {
|
||||
baseURL: 'https://api.example.com',
|
||||
timeout: 5000,
|
||||
},
|
||||
setup(resolvedOptions, nuxt) {
|
||||
// resolvedOptions.baseURL is guaranteed to be string (not undefined)
|
||||
// resolvedOptions.timeout is guaranteed to be number (not undefined)
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Adding Runtime Assets
|
||||
|
||||
### Components
|
||||
|
||||
```ts
|
||||
import { addComponent, addComponentsDir, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Single component
|
||||
addComponent({
|
||||
name: 'MyButton',
|
||||
filePath: resolve('./runtime/components/MyButton.vue'),
|
||||
})
|
||||
|
||||
// Component directory with prefix
|
||||
addComponentsDir({
|
||||
path: resolve('./runtime/components'),
|
||||
prefix: 'My',
|
||||
pathPrefix: false,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Composables and Auto-imports
|
||||
|
||||
```ts
|
||||
import { addImports, addImportsDir, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Single import
|
||||
addImports({
|
||||
name: 'useMyUtil',
|
||||
from: resolve('./runtime/composables/useMyUtil'),
|
||||
})
|
||||
|
||||
// Directory of composables
|
||||
addImportsDir(resolve('./runtime/composables'))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Plugins
|
||||
|
||||
```ts
|
||||
import { addPlugin, addPluginTemplate, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup(options) {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Static plugin file
|
||||
addPlugin({
|
||||
src: resolve('./runtime/plugins/myPlugin'),
|
||||
mode: 'client', // 'client', 'server', or 'all'
|
||||
})
|
||||
|
||||
// Dynamic plugin with generated code
|
||||
addPluginTemplate({
|
||||
filename: 'my-module-plugin.mjs',
|
||||
getContents: () => `
|
||||
import { defineNuxtPlugin } from '#app/nuxt'
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
name: 'my-module',
|
||||
setup() {
|
||||
const config = ${JSON.stringify(options)}
|
||||
// Plugin logic
|
||||
}
|
||||
})`,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Server Extensions
|
||||
|
||||
### Server Handlers
|
||||
|
||||
```ts
|
||||
import { addServerHandler, addServerScanDir, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Single handler
|
||||
addServerHandler({
|
||||
route: '/api/my-endpoint',
|
||||
handler: resolve('./runtime/server/api/my-endpoint'),
|
||||
})
|
||||
|
||||
// Scan entire server directory (api/, routes/, middleware/, utils/)
|
||||
addServerScanDir(resolve('./runtime/server'))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server Composables
|
||||
|
||||
```ts
|
||||
import { addServerImports, addServerImportsDir, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Single server import
|
||||
addServerImports({
|
||||
name: 'useServerUtil',
|
||||
from: resolve('./runtime/server/utils/useServerUtil'),
|
||||
})
|
||||
|
||||
// Server composables directory
|
||||
addServerImportsDir(resolve('./runtime/server/composables'))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Nitro Plugin
|
||||
|
||||
```ts
|
||||
import { addServerPlugin, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
addServerPlugin(resolve('./runtime/server/plugin'))
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
```ts
|
||||
// runtime/server/plugin.ts
|
||||
import { defineNitroPlugin } from 'nitropack/runtime'
|
||||
|
||||
export default defineNitroPlugin((nitroApp) => {
|
||||
nitroApp.hooks.hook('request', (event) => {
|
||||
console.log('Request:', event.path)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
## Templates and Virtual Files
|
||||
|
||||
### Generate Virtual Files
|
||||
|
||||
```ts
|
||||
import { addTemplate, addTypeTemplate, addServerTemplate, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup(options, nuxt) {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Client/build virtual file (accessible via #build/my-config.mjs)
|
||||
addTemplate({
|
||||
filename: 'my-config.mjs',
|
||||
getContents: () => `export default ${JSON.stringify(options)}`,
|
||||
})
|
||||
|
||||
// Type declarations
|
||||
addTypeTemplate({
|
||||
filename: 'types/my-module.d.ts',
|
||||
getContents: () => `
|
||||
declare module '#my-module' {
|
||||
export interface Config {
|
||||
apiKey: string
|
||||
}
|
||||
}`,
|
||||
})
|
||||
|
||||
// Nitro virtual file (accessible in server routes)
|
||||
addServerTemplate({
|
||||
filename: '#my-module/config.mjs',
|
||||
getContents: () => `export const config = ${JSON.stringify(options)}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Access Virtual Files
|
||||
|
||||
```ts
|
||||
// In runtime plugin
|
||||
// @ts-expect-error - virtual file
|
||||
import config from '#build/my-config.mjs'
|
||||
|
||||
// In server routes
|
||||
import { config } from '#my-module/config.js'
|
||||
```
|
||||
|
||||
## Extending Pages and Routes
|
||||
|
||||
```ts
|
||||
import { extendPages, extendRouteRules, addRouteMiddleware, createResolver } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
setup() {
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
// Add pages
|
||||
extendPages((pages) => {
|
||||
pages.push({
|
||||
name: 'my-page',
|
||||
path: '/my-route',
|
||||
file: resolve('./runtime/pages/MyPage.vue'),
|
||||
})
|
||||
})
|
||||
|
||||
// Add route rules (caching, redirects, etc.)
|
||||
extendRouteRules('/api/**', {
|
||||
cache: { maxAge: 60 },
|
||||
})
|
||||
|
||||
// Add middleware
|
||||
addRouteMiddleware({
|
||||
name: 'my-middleware',
|
||||
path: resolve('./runtime/middleware/myMiddleware'),
|
||||
global: true,
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Module Dependencies
|
||||
|
||||
Declare dependencies on other modules with version constraints:
|
||||
|
||||
```ts
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'my-module',
|
||||
},
|
||||
moduleDependencies: {
|
||||
'@nuxtjs/tailwindcss': {
|
||||
version: '>=6.0.0',
|
||||
// Set defaults (user can override)
|
||||
defaults: {
|
||||
exposeConfig: true,
|
||||
},
|
||||
// Force specific options
|
||||
overrides: {
|
||||
viewer: false,
|
||||
},
|
||||
},
|
||||
'@nuxtjs/i18n': {
|
||||
optional: true, // Won't fail if not installed
|
||||
defaults: {
|
||||
defaultLocale: 'en',
|
||||
},
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
// Dependencies are guaranteed to be set up before this runs
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Dynamic Dependencies
|
||||
|
||||
```ts
|
||||
moduleDependencies(nuxt) {
|
||||
const deps: Record<string, any> = {
|
||||
'@nuxtjs/tailwindcss': { version: '>=6.0.0' },
|
||||
}
|
||||
|
||||
if (nuxt.options.ssr) {
|
||||
deps['@nuxtjs/html-validator'] = { optional: true }
|
||||
}
|
||||
|
||||
return deps
|
||||
}
|
||||
```
|
||||
|
||||
## Lifecycle Hooks
|
||||
|
||||
Requires `meta.name` and `meta.version`:
|
||||
|
||||
```ts
|
||||
export default defineNuxtModule({
|
||||
meta: {
|
||||
name: 'my-module',
|
||||
version: '1.2.0',
|
||||
},
|
||||
onInstall(nuxt) {
|
||||
// First-time setup
|
||||
console.log('Module installed for the first time')
|
||||
},
|
||||
onUpgrade(nuxt, options, previousVersion) {
|
||||
// Version upgrade migrations
|
||||
console.log(`Upgrading from ${previousVersion}`)
|
||||
},
|
||||
setup(options, nuxt) {
|
||||
// Regular setup runs every build
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Extending Configuration
|
||||
|
||||
```ts
|
||||
export default defineNuxtModule({
|
||||
setup(options, nuxt) {
|
||||
// Add CSS
|
||||
nuxt.options.css.push('my-module/styles.css')
|
||||
|
||||
// Add runtime config
|
||||
nuxt.options.runtimeConfig.public.myModule = {
|
||||
apiUrl: options.apiUrl,
|
||||
}
|
||||
|
||||
// Extend Vite config
|
||||
nuxt.options.vite.optimizeDeps ||= {}
|
||||
nuxt.options.vite.optimizeDeps.include ||= []
|
||||
nuxt.options.vite.optimizeDeps.include.push('some-package')
|
||||
|
||||
// Add build transpile
|
||||
nuxt.options.build.transpile.push('my-package')
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Using Hooks
|
||||
|
||||
```ts
|
||||
export default defineNuxtModule({
|
||||
// Declarative hooks
|
||||
hooks: {
|
||||
'components:dirs': (dirs) => {
|
||||
dirs.push({ path: '~/extra' })
|
||||
},
|
||||
},
|
||||
|
||||
setup(options, nuxt) {
|
||||
// Programmatic hooks
|
||||
nuxt.hook('pages:extend', (pages) => {
|
||||
// Modify pages
|
||||
})
|
||||
|
||||
nuxt.hook('imports:extend', (imports) => {
|
||||
imports.push({ name: 'myHelper', from: 'my-package' })
|
||||
})
|
||||
|
||||
nuxt.hook('nitro:config', (config) => {
|
||||
// Modify Nitro config
|
||||
})
|
||||
|
||||
nuxt.hook('vite:extendConfig', (config) => {
|
||||
// Modify Vite config
|
||||
})
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Path Resolution
|
||||
|
||||
```ts
|
||||
import { createResolver, resolvePath, findPath } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtModule({
|
||||
async setup(options, nuxt) {
|
||||
// Resolver relative to module
|
||||
const { resolve } = createResolver(import.meta.url)
|
||||
|
||||
const pluginPath = resolve('./runtime/plugin')
|
||||
|
||||
// Resolve with extensions and aliases
|
||||
const entrypoint = await resolvePath('@some/package')
|
||||
|
||||
// Find first existing file
|
||||
const configPath = await findPath([
|
||||
resolve('./config.ts'),
|
||||
resolve('./config.js'),
|
||||
])
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Module Package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-nuxt-module",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/module.mjs",
|
||||
"require": "./dist/module.cjs"
|
||||
}
|
||||
},
|
||||
"main": "./dist/module.cjs",
|
||||
"module": "./dist/module.mjs",
|
||||
"types": "./dist/types.d.ts",
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"dev": "nuxi dev playground",
|
||||
"build": "nuxt-module-build build",
|
||||
"prepare": "nuxt-module-build build --stub"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxt/kit": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/module-builder": "latest",
|
||||
"nuxt": "^3.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Disabling Modules
|
||||
|
||||
Users can disable a module via config key:
|
||||
|
||||
```ts
|
||||
// nuxt.config.ts
|
||||
export default defineNuxtConfig({
|
||||
// Disable entirely
|
||||
myModule: false,
|
||||
|
||||
// Or with options
|
||||
myModule: {
|
||||
enabled: false,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. **Create module**: `npx nuxi init -t module my-module`
|
||||
2. **Develop**: `npm run dev` (runs playground)
|
||||
3. **Build**: `npm run build`
|
||||
4. **Test**: `npm run test`
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use `createResolver(import.meta.url)` for all path resolution
|
||||
- Prefix components to avoid naming conflicts
|
||||
- Make options type-safe with `ModuleOptions` interface
|
||||
- Use `moduleDependencies` instead of `installModule`
|
||||
- Provide sensible defaults for all options
|
||||
- Add compatibility requirements in `meta.compatibility`
|
||||
- Use virtual files for dynamic configuration
|
||||
- Separate client/server plugins appropriately
|
||||
|
||||
<!--
|
||||
Source references:
|
||||
- https://nuxt.com/docs/api/kit/modules
|
||||
- https://nuxt.com/docs/api/kit/components
|
||||
- https://nuxt.com/docs/api/kit/autoimports
|
||||
- https://nuxt.com/docs/api/kit/plugins
|
||||
- https://nuxt.com/docs/api/kit/templates
|
||||
- https://nuxt.com/docs/api/kit/nitro
|
||||
- https://nuxt.com/docs/api/kit/pages
|
||||
- https://nuxt.com/docs/api/kit/resolving
|
||||
-->
|
||||
Reference in New Issue
Block a user