This commit is contained in:
Joaquín Sánchez 2024-11-20 06:35:52 -05:00 committed by GitHub
commit 72ded1b5e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 174 additions and 8 deletions

View File

@ -0,0 +1,45 @@
---
title: "directives"
head.title: "directives/"
description: "The directives/ directory is where you put all your Vue directives."
navigation.icon: i-ph-folder
---
Nuxt automatically imports any directive in this directory (along with directives that are registered by any modules you may be using).
```bash [Directory Structure]
| directives/
--| awesome.ts
--| focus.ts
```
```html [app.vue]
<template>
<div v-awesome v-focus>
</div>
</template>
```
## Directives
You must use always named exports for your directives.
If you have SSR enabled, you must use object notation in your directives. Otherwise, Vue will throw an error about missing `getSSRProps`.
```ts
function mounted (el: HTMLElement, binding: TouchDirectiveBinding) {
// ...
}
// other lifecycle hooks
export const Focus = {
mounted
// ...
}
// DONT' USE export default here
export default Focus
```
:link-example{to="/docs/examples/features/auto-imports"}

View File

@ -0,0 +1,63 @@
import { createUnplugin } from 'unplugin'
import type { AddonsOptions, Import } from 'unimport'
import { createUnimport } from 'unimport'
import type { ImportPresetWithDeprecation } from 'nuxt/schema'
import MagicString from 'magic-string'
import { isVue } from '../core/utils'
export const DirectivesPlugin = ({
addons,
dirs,
imports,
presets,
}: {
addons: AddonsOptions
dirs: string[]
imports: Import[]
presets: ImportPresetWithDeprecation[]
}) => createUnplugin(() => {
const useImports: Import[] = []
function visit (i: Import) {
if (i.meta?.vueDirective === true) {
useImports.push(i)
}
}
imports?.forEach(visit)
presets?.forEach((preset) => {
if (preset && 'imports' in preset) {
const imports = preset.imports as Import[]
imports.forEach(visit)
}
})
const ctx = createUnimport({
dirs,
imports: useImports,
addons,
})
return {
name: 'nuxt:directives-transform',
enforce: 'post',
transformInclude (id) {
return isVue(id, { type: ['script', 'template'] })
},
async transform (code, id) {
const s = new MagicString(code)
await ctx.injectImports(s, id, {
autoImport: false,
})
if (!s.hasChanged()) { return }
return {
code: s.toString(),
map: s.generateMap(),
}
},
async buildStart () {
await ctx.init()
},
}
})

View File

@ -1,7 +1,7 @@
import { existsSync } from 'node:fs'
import { addBuildPlugin, addTemplate, addTypeTemplate, defineNuxtModule, isIgnored, logger, resolveAlias, tryResolveModule, updateTemplates, useNuxt } from '@nuxt/kit'
import { isAbsolute, join, normalize, relative, resolve } from 'pathe'
import type { Import, Unimport } from 'unimport'
import type { AddonVueDirectivesOptions, AddonsOptions, Import, Unimport } from 'unimport'
import { createUnimport, scanDirExports, toExports } from 'unimport'
import type { ImportPresetWithDeprecation, ImportsOptions, ResolvedNuxtTemplate } from 'nuxt/schema'
import escapeRE from 'escape-string-regexp'
@ -10,6 +10,7 @@ import { lookupNodeModuleSubpath, parseNodeModulePath } from 'mlly'
import { isDirectory } from '../utils'
import { TransformPlugin } from './transform'
import { defaultPresets } from './presets'
import { DirectivesPlugin } from './directives'
export default defineNuxtModule<Partial<ImportsOptions>>({
meta: {
@ -41,14 +42,65 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
// Filter disabled sources
// options.sources = options.sources.filter(source => source.disabled !== true)
let directivesDir: string[] = []
if (options.scan) {
for (const layer of nuxt.options._layers) {
// Layer disabled scanning for itself
if (layer.config?.imports?.scan === false) {
continue
}
directivesDir.push(resolve(layer.config.srcDir, 'directives'))
}
directivesDir = directivesDir.map(dir => normalize(dir))
// Restart nuxt when directives directories are added/removed
nuxt.hook('builder:watch', (event, relativePath) => {
if (!['addDir', 'unlinkDir'].includes(event)) { return }
const path = resolve(nuxt.options.srcDir, relativePath)
if (directivesDir.includes(path)) {
logger.info(`Directory \`${relativePath}/\` ${event === 'addDir' ? 'created' : 'removed'}`)
return nuxt.callHook('restart')
}
})
}
// We need to enable vueDirectives when:
// - vueDirectives is explicitly set to true or callback enabled
// - autoImport is enabled: allow use directives from presets
// We also need to resolve the directives from any layer
function isDirectiveFactory (): true | AddonVueDirectivesOptions | undefined {
if (!options.scan && !options.autoImport) {
return undefined
}
if (!options.scan) {
return true
}
return {
isDirective: (normalizeImportFrom: string) => {
return directivesDir.some(dir => normalizeImportFrom.startsWith(dir))
},
}
}
const { addons: inlineAddons, ...rest } = options
const addons: AddonsOptions = {
addons: inlineAddons && Array.isArray(inlineAddons)
? [...inlineAddons]
: [],
vueDirectives: isDirectiveFactory(),
vueTemplate: options.autoImport,
}
// Create a context to share state between module internals
const ctx = createUnimport({
injectAtEnd: true,
...options,
addons: {
vueTemplate: options.autoImport,
...options.addons,
},
...rest,
addons,
presets,
})
@ -66,6 +118,7 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
}
composablesDirs.push(resolve(layer.config.srcDir, 'composables'))
composablesDirs.push(resolve(layer.config.srcDir, 'utils'))
composablesDirs.push(resolve(layer.config.srcDir, 'directives'))
if (isNuxtV4) {
composablesDirs.push(resolve(layer.config.rootDir, 'shared', 'utils'))
@ -102,6 +155,8 @@ export default defineNuxtModule<Partial<ImportsOptions>>({
})
nuxt.options.alias['#imports'] = join(nuxt.options.buildDir, 'imports')
// Auto import directives
addBuildPlugin(DirectivesPlugin({ addons, dirs: directivesDir, imports: options.imports ?? [], presets }))
// Transform to inject imports in production mode
addBuildPlugin(TransformPlugin({ ctx, options, sourcemap: !!nuxt.options.sourcemap.server || !!nuxt.options.sourcemap.client }))

View File

@ -242,6 +242,8 @@ const vueTypesPreset = defineUnimportPreset({
'Component',
'ComponentPublicInstance',
'ComputedRef',
'DirectiveBinding',
'ExtractDefaultPropTypes',
'ExtractPropTypes',
'ExtractPublicPropTypes',
'InjectionKey',
@ -250,6 +252,7 @@ const vueTypesPreset = defineUnimportPreset({
'MaybeRef',
'MaybeRefOrGetter',
'VNode',
'WritableComputedRef',
],
})

View File

@ -31,7 +31,7 @@ export default defineUntypedSchema({
/**
* An array of custom directories that will be auto-imported.
* Note that this option will not override the default directories (~/composables, ~/utils).
* Note that this option will not override the default directories (~/composables, ~/utils, ~/directives).
* @example
* ```js
* imports: {

View File

@ -11,7 +11,7 @@ export interface ImportsOptions extends UnimportOptions {
/**
* Directories to scan for auto imports.
* @see https://nuxt.com/docs/guide/directory-structure/composables#how-files-are-scanned
* @default ['./composables', './utils']
* @default ['./composables', './utils', './directives']
*/
dirs?: string[]