2023-08-24 12:42:15 +00:00
import { addTemplate , addVitePlugin , addWebpackPlugin , defineNuxtModule , isIgnored , 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'
2022-12-13 10:30:12 +00:00
import { createUnimport , scanDirExports } from 'unimport'
2023-04-07 16:02:47 +00:00
import type { ImportPresetWithDeprecation , ImportsOptions } from 'nuxt/schema'
2023-03-11 21:16:01 +00:00
2023-08-07 13:19:32 +00:00
import { lookupNodeModuleSubpath , parseNodeModulePath } from 'mlly'
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' ,
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 ,
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 : [ ] ,
2021-12-21 14:28:45 +00:00
exclude : undefined
2023-01-09 11:35:44 +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 ,
. . . options . addons
} ,
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 [ ] = [ ]
2022-03-16 20:36:30 +00:00
for ( const layer of nuxt . options . _layers ) {
2022-03-09 11:03:36 +00:00
composablesDirs . push ( resolve ( layer . config . srcDir , 'composables' ) )
2022-11-09 09:43:16 +00:00
composablesDirs . push ( resolve ( layer . config . srcDir , 'utils' ) )
2022-08-23 14:22:11 +00:00
for ( const dir of ( layer . config . imports ? . dirs ? ? [ ] ) ) {
2022-08-12 17:47:58 +00:00
if ( ! dir ) {
continue
}
2022-03-09 11:03:36 +00:00
composablesDirs . push ( resolve ( layer . config . srcDir , dir ) )
}
}
2022-08-23 14:22:11 +00:00
await nuxt . callHook ( 'imports:dirs' , composablesDirs )
2021-10-20 09:47:18 +00:00
composablesDirs = composablesDirs . map ( dir = > normalize ( dir ) )
2021-10-12 12:24:43 +00:00
2023-03-09 11:46:08 +00:00
// Restart nuxt when composable directories are added/removed
2023-07-26 09:16:01 +00:00
nuxt . hook ( 'builder:watch' , ( event , relativePath ) = > {
if ( ! [ 'addDir' , 'unlinkDir' ] . includes ( event ) ) { return }
2023-03-09 11:46:08 +00:00
2023-07-26 09:16:01 +00:00
const path = resolve ( nuxt . options . srcDir , relativePath )
if ( composablesDirs . includes ( path ) ) {
console . info ( ` Directory \` ${ relativePath } / \` ${ event === 'addDir' ? 'created' : 'removed' } ` )
2023-03-09 11:46:08 +00:00
return nuxt . callHook ( 'restart' )
}
} )
2021-11-29 11:20:57 +00:00
// Support for importing from '#imports'
addTemplate ( {
filename : 'imports.mjs' ,
2023-08-07 22:03:40 +00:00
getContents : async ( ) = > await ctx . toExports ( ) + '\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 )
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
// Scan `composables/`
2023-08-24 12:42:15 +00:00
const composableImports = await scanDirExports ( composablesDirs , {
fileFilter : file = > ! isIgnored ( file )
} )
2023-01-14 01:14:24 +00:00
for ( const i of composableImports ) {
i . priority = i . priority || priorities . find ( ( [ dir ] ) = > i . from . startsWith ( dir ) ) ? . [ 1 ]
}
imports . push ( . . . composableImports )
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
} )
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 12:53:13 +00:00
// Add generated types to `nuxt.d.ts`
nuxt . hook ( 'prepare:types' , ( { references } ) = > {
2022-08-23 14:22:11 +00:00
references . push ( { path : resolve ( nuxt . options . buildDir , 'types/imports.d.ts' ) } )
2022-02-15 09:50:11 +00:00
references . push ( { path : resolve ( nuxt . options . buildDir , 'imports.d.ts' ) } )
2021-10-20 12:53:13 +00:00
} )
2021-10-20 09:47:18 +00:00
// Watch composables/ directory
2022-10-24 08:53:02 +00:00
const templates = [
'types/imports.d.ts' ,
'imports.d.ts' ,
'imports.mjs'
]
2023-07-26 09:16:01 +00:00
nuxt . hook ( 'builder:watch' , async ( _ , relativePath ) = > {
const path = resolve ( nuxt . options . srcDir , relativePath )
if ( composablesDirs . some ( dir = > dir === path || path . startsWith ( dir + '/' ) ) ) {
2022-10-24 08:53:02 +00:00
await updateTemplates ( {
filter : template = > templates . includes ( template . filename )
} )
2021-10-20 09:47:18 +00:00
}
2021-08-10 00:27:23 +00:00
} )
2022-03-07 10:39:54 +00:00
2023-07-05 09:25:21 +00:00
nuxt . hook ( 'app:templatesGenerated' , async ( ) = > {
2022-08-23 14:22:11 +00:00
await regenerateImports ( )
2022-03-07 10:39:54 +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 ( )
2022-03-08 10:07:40 +00:00
// Remove file extension for benefit of TypeScript
const stripExtension = ( path : string ) = > path . replace ( /\.[a-z]+$/ , '' )
2023-08-07 13:19:32 +00:00
const resolvedImportPathMap = new Map < string , string > ( )
const r = ( { from } : Import ) = > resolvedImportPathMap . get ( from )
async function cacheImportPaths ( imports : Import [ ] ) {
for ( const i of imports ) {
if ( resolvedImportPathMap . has ( i . from ) ) { continue }
let path = resolveAlias ( i . from )
if ( ! isAbsolute ( path ) ) {
path = await tryResolveModule ( i . from , nuxt . options . module sDir ) . then ( async ( r ) = > {
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
}
if ( isAbsolute ( path ) ) {
path = relative ( join ( nuxt . options . buildDir , 'types' ) , path )
}
2022-03-11 08:09:11 +00:00
2023-08-07 13:19:32 +00:00
path = stripExtension ( path )
resolvedImportPathMap . set ( i . from , path )
}
2021-10-18 13:39:53 +00:00
}
2021-10-20 09:47:18 +00:00
2021-11-29 11:20:57 +00:00
addTemplate ( {
2022-02-15 09:50:11 +00:00
filename : 'imports.d.ts' ,
2023-06-26 15:53:29 +00:00
getContents : ( ) = > ctx . toExports ( nuxt . options . buildDir , true )
2021-11-29 11:20:57 +00:00
} )
2021-10-20 09:47:18 +00:00
addTemplate ( {
2022-08-23 14:22:11 +00:00
filename : 'types/imports.d.ts' ,
2023-08-07 13:19:32 +00:00
getContents : async ( ) = > {
const imports = await ctx . getImports ( ) . then ( r = > r . filter ( i = > ! i . type ) )
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.'
)
}
2021-10-20 09:47:18 +00:00
} )
2021-08-10 00:27:23 +00:00
}