2024-02-27 09:11:27 +00:00
import { existsSync } from 'node:fs'
2024-03-16 22:20:29 +00:00
import { addTemplate , addTypeTemplate , addVitePlugin , addWebpackPlugin , defineNuxtModule , isIgnored , logger , resolveAlias , tryResolveModule , updateTemplates , useNuxt } from '@nuxt/kit'
2023-04-07 16:02:47 +00:00
import { isAbsolute , join , normalize , relative , resolve } from 'pathe'
2022-12-11 21:44:52 +00:00
import type { Import , Unimport } from 'unimport'
2024-03-06 12:44:50 +00:00
import { createUnimport , scanDirExports , toExports } from 'unimport'
2024-03-14 20:26:40 +00:00
import type { ImportPresetWithDeprecation , ImportsOptions , ResolvedNuxtTemplate } from 'nuxt/schema'
2023-03-11 21:16:01 +00:00
2023-08-07 13:19:32 +00:00
import { lookupNodeModuleSubpath , parseNodeModulePath } from 'mlly'
2024-02-27 09:11:27 +00:00
import { isDirectory } from '../utils'
2021-08-24 07:49:03 +00:00
import { TransformPlugin } from './transform'
2022-03-11 08:09:11 +00:00
import { defaultPresets } from './presets'
2021-08-10 00:27:23 +00:00
2022-08-23 14:22:11 +00:00
export default defineNuxtModule < Partial < ImportsOptions > > ( {
2022-01-05 18:09:53 +00:00
meta : {
2022-08-23 14:22:11 +00:00
name : 'imports' ,
2024-04-05 18:08:32 +00:00
configKey : 'imports' ,
2022-01-05 18:09:53 +00:00
} ,
2021-10-18 13:39:53 +00:00
defaults : {
2022-08-24 08:44:38 +00:00
autoImport : true ,
2024-04-09 13:03:42 +00:00
scan : true ,
2022-03-11 08:09:11 +00:00
presets : defaultPresets ,
2021-10-20 09:47:18 +00:00
global : false ,
2022-03-11 08:09:11 +00:00
imports : [ ] ,
2021-12-21 14:28:45 +00:00
dirs : [ ] ,
transform : {
2022-06-10 13:33:16 +00:00
include : [ ] ,
2024-04-05 18:08:32 +00:00
exclude : undefined ,
2023-01-09 11:35:44 +00:00
} ,
2024-04-05 18:08:32 +00:00
virtualImports : [ '#imports' ] ,
2021-10-18 13:39:53 +00:00
} ,
async setup ( options , nuxt ) {
2022-11-10 13:52:04 +00:00
// TODO: fix sharing of defaults between invocations of modules
const presets = JSON . parse ( JSON . stringify ( options . presets ) ) as ImportPresetWithDeprecation [ ]
2021-10-18 13:39:53 +00:00
// Allow modules extending sources
2022-11-10 13:52:04 +00:00
await nuxt . callHook ( 'imports:sources' , presets )
2022-03-11 08:09:11 +00:00
2021-10-18 13:39:53 +00:00
// Filter disabled sources
2022-03-11 08:09:11 +00:00
// options.sources = options.sources.filter(source => source.disabled !== true)
2021-10-18 13:39:53 +00:00
2021-10-20 09:47:18 +00:00
// Create a context to share state between module internals
2022-03-11 08:09:11 +00:00
const ctx = createUnimport ( {
2023-01-09 11:35:44 +00:00
. . . options ,
2022-06-08 20:09:31 +00:00
addons : {
2023-01-09 11:35:44 +00:00
vueTemplate : options.autoImport ,
2024-04-05 18:08:32 +00:00
. . . options . addons ,
2023-01-09 11:35:44 +00:00
} ,
2024-04-05 18:08:32 +00:00
presets ,
2022-03-11 08:09:11 +00:00
} )
2021-10-20 09:47:18 +00:00
2023-01-09 11:35:44 +00:00
await nuxt . callHook ( 'imports:context' , ctx )
2022-03-16 20:36:30 +00:00
// composables/ dirs from all layers
2022-08-12 17:47:58 +00:00
let composablesDirs : string [ ] = [ ]
2024-04-09 13:03:42 +00:00
if ( options . scan ) {
for ( const layer of nuxt . options . _layers ) {
// Layer disabled scanning for itself
if ( layer . config ? . imports ? . scan === false ) {
2022-08-12 17:47:58 +00:00
continue
}
2024-04-09 13:03:42 +00:00
composablesDirs . push ( resolve ( layer . config . srcDir , 'composables' ) )
composablesDirs . push ( resolve ( layer . config . srcDir , 'utils' ) )
for ( const dir of ( layer . config . imports ? . dirs ? ? [ ] ) ) {
if ( ! dir ) {
continue
}
composablesDirs . push ( resolve ( layer . config . srcDir , dir ) )
}
2022-03-09 11:03:36 +00:00
}
2024-04-09 13:03:42 +00:00
await nuxt . callHook ( 'imports:dirs' , composablesDirs )
composablesDirs = composablesDirs . map ( dir = > normalize ( dir ) )
2021-10-12 12:24:43 +00:00
2024-04-09 13:03:42 +00:00
// Restart nuxt when composable directories are added/removed
nuxt . hook ( 'builder:watch' , ( event , relativePath ) = > {
if ( ! [ 'addDir' , 'unlinkDir' ] . includes ( event ) ) { return }
2023-03-09 11:46:08 +00:00
2024-04-09 13:03:42 +00:00
const path = resolve ( nuxt . options . srcDir , relativePath )
if ( composablesDirs . includes ( path ) ) {
logger . info ( ` Directory \` ${ relativePath } / \` ${ event === 'addDir' ? 'created' : 'removed' } ` )
return nuxt . callHook ( 'restart' )
}
} )
}
2023-03-09 11:46:08 +00:00
2021-11-29 11:20:57 +00:00
// Support for importing from '#imports'
addTemplate ( {
filename : 'imports.mjs' ,
2024-04-05 18:08:32 +00:00
getContents : async ( ) = > toExports ( await ctx . getImports ( ) ) + '\nif (import.meta.dev) { console.warn("[nuxt] `#imports` should be transformed with real imports. There seems to be something wrong with the imports plugin.") }' ,
2021-11-29 11:20:57 +00:00
} )
nuxt . options . alias [ '#imports' ] = join ( nuxt . options . buildDir , 'imports' )
2022-12-13 10:30:12 +00:00
// Transform to inject imports in production mode
2023-08-24 12:06:44 +00:00
addVitePlugin ( ( ) = > TransformPlugin . vite ( { ctx , options , sourcemap : ! ! nuxt . options . sourcemap . server || ! ! nuxt . options . sourcemap . client } ) )
addWebpackPlugin ( ( ) = > TransformPlugin . webpack ( { ctx , options , sourcemap : ! ! nuxt . options . sourcemap . server || ! ! nuxt . options . sourcemap . client } ) )
2021-08-10 00:27:23 +00:00
2023-01-14 01:14:24 +00:00
const priorities = nuxt . options . _layers . map ( ( layer , i ) = > [ layer . config . srcDir , - i ] as const ) . sort ( ( [ a ] , [ b ] ) = > b . length - a . length )
2024-03-14 20:26:40 +00:00
function isImportsTemplate ( template : ResolvedNuxtTemplate ) {
return [
'/types/imports.d.ts' ,
'/imports.d.ts' ,
2024-04-05 18:08:32 +00:00
'/imports.mjs' ,
2024-03-14 20:26:40 +00:00
] . some ( i = > template . filename . endsWith ( i ) )
}
2022-08-23 14:22:11 +00:00
const regenerateImports = async ( ) = > {
2022-03-11 08:09:11 +00:00
await ctx . modifyDynamicImports ( async ( imports ) = > {
2023-07-18 15:20:06 +00:00
// Clear old imports
imports . length = 0
2024-04-09 13:03:42 +00:00
// Scan for `composables/` and `utils/` directories
if ( options . scan ) {
const scannedImports = await scanDirExports ( composablesDirs , {
fileFilter : file = > ! isIgnored ( file ) ,
} )
for ( const i of scannedImports ) {
i . priority = i . priority || priorities . find ( ( [ dir ] ) = > i . from . startsWith ( dir ) ) ? . [ 1 ]
}
imports . push ( . . . scannedImports )
2023-01-14 01:14:24 +00:00
}
2024-04-09 13:03:42 +00:00
2022-04-26 15:52:39 +00:00
// Modules extending
2022-08-23 14:22:11 +00:00
await nuxt . callHook ( 'imports:extend' , imports )
2023-07-18 15:20:06 +00:00
return imports
2022-03-11 08:09:11 +00:00
} )
2024-03-14 20:26:40 +00:00
await updateTemplates ( {
2024-04-05 18:08:32 +00:00
filter : isImportsTemplate ,
2024-03-14 20:26:40 +00:00
} )
2021-10-05 20:47:19 +00:00
}
2022-02-08 19:09:24 +00:00
2022-08-23 14:22:11 +00:00
await regenerateImports ( )
2022-02-08 19:09:24 +00:00
// Generate types
2022-08-24 08:44:38 +00:00
addDeclarationTemplates ( ctx , options )
2021-08-11 20:28:38 +00:00
2021-10-20 09:47:18 +00:00
// Watch composables/ directory
2023-07-26 09:16:01 +00:00
nuxt . hook ( 'builder:watch' , async ( _ , relativePath ) = > {
const path = resolve ( nuxt . options . srcDir , relativePath )
2024-04-09 13:03:42 +00:00
if ( options . scan && composablesDirs . some ( dir = > dir === path || path . startsWith ( dir + '/' ) ) ) {
2024-03-14 20:26:40 +00:00
await regenerateImports ( )
2021-10-20 09:47:18 +00:00
}
2021-08-10 00:27:23 +00:00
} )
2022-03-07 10:39:54 +00:00
2024-03-14 20:26:40 +00:00
// Watch for template generation
nuxt . hook ( 'app:templatesGenerated' , async ( _app , templates ) = > {
// Only regenerate when non-imports templates are updated
if ( templates . some ( t = > ! isImportsTemplate ( t ) ) ) {
await regenerateImports ( )
}
2022-03-07 10:39:54 +00:00
} )
2024-04-05 18:08:32 +00:00
} ,
2021-08-10 00:27:23 +00:00
} )
2022-08-24 08:44:38 +00:00
function addDeclarationTemplates ( ctx : Unimport , options : Partial < ImportsOptions > ) {
2021-10-20 09:47:18 +00:00
const nuxt = useNuxt ( )
2023-08-07 13:19:32 +00:00
const resolvedImportPathMap = new Map < string , string > ( )
const r = ( { from } : Import ) = > resolvedImportPathMap . get ( from )
2023-11-23 21:01:26 +00:00
2024-03-12 17:25:28 +00:00
const SUPPORTED_EXTENSION_RE = new RegExp ( ` \\ .( ${ nuxt . options . extensions . map ( i = > i . replace ( '.' , '' ) ) . join ( '|' ) } ) $ ` )
2024-02-27 09:11:27 +00:00
async function cacheImportPaths ( imports : Import [ ] ) {
2023-11-23 21:01:26 +00:00
const importSource = Array . from ( new Set ( imports . map ( i = > i . from ) ) )
await Promise . all ( importSource . map ( async ( from ) = > {
if ( resolvedImportPathMap . has ( from ) ) {
return
}
let path = resolveAlias ( from )
2023-08-07 13:19:32 +00:00
if ( ! isAbsolute ( path ) ) {
2023-11-23 21:01:26 +00:00
path = await tryResolveModule ( from , nuxt . options . module sDir ) . then ( async ( r ) = > {
2023-08-07 13:19:32 +00:00
if ( ! r ) { return r }
const { dir , name } = parseNodeModulePath ( r )
if ( ! dir || ! name ) { return r }
const subpath = await lookupNodeModuleSubpath ( r )
return join ( dir , name , subpath || '' )
} ) ? ? path
}
2024-02-27 09:11:27 +00:00
if ( existsSync ( path ) && ! ( await isDirectory ( path ) ) ) {
2024-03-12 17:25:28 +00:00
path = path . replace ( SUPPORTED_EXTENSION_RE , '' )
2024-02-27 09:11:27 +00:00
}
2023-08-07 13:19:32 +00:00
if ( isAbsolute ( path ) ) {
path = relative ( join ( nuxt . options . buildDir , 'types' ) , path )
}
2022-03-11 08:09:11 +00:00
2023-11-23 21:01:26 +00:00
resolvedImportPathMap . set ( from , path )
} ) )
2021-10-18 13:39:53 +00:00
}
2021-10-20 09:47:18 +00:00
2024-03-16 22:20:29 +00:00
addTypeTemplate ( {
2022-02-15 09:50:11 +00:00
filename : 'imports.d.ts' ,
2024-04-05 18:08:32 +00:00
getContents : async ( { nuxt } ) = > toExports ( await ctx . getImports ( ) , nuxt . options . buildDir , true ) ,
2021-11-29 11:20:57 +00:00
} )
2024-03-16 22:20:29 +00:00
addTypeTemplate ( {
2022-08-23 14:22:11 +00:00
filename : 'types/imports.d.ts' ,
2023-08-07 13:19:32 +00:00
getContents : async ( ) = > {
2023-11-23 21:01:26 +00:00
const imports = await ctx . getImports ( )
2023-08-07 13:19:32 +00:00
await cacheImportPaths ( imports )
return '// Generated by auto imports\n' + (
options . autoImport
? await ctx . generateTypeDeclarations ( { resolvePath : r } )
: '// Implicit auto importing is disabled, you can use explicitly import from `#imports` instead.'
)
2024-04-05 18:08:32 +00:00
} ,
2021-10-20 09:47:18 +00:00
} )
2021-08-10 00:27:23 +00:00
}