shadcn-vue/packages/module/src/module.ts
2024-11-04 18:19:28 +08:00

107 lines
3.8 KiB
TypeScript

import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { addComponent, addTemplate, createResolver, defineNuxtModule, findPath, installModule, useLogger } from '@nuxt/kit'
import { parseSync } from '@oxc-parser/wasm'
import { UTILS } from '../../cli/src/utils/templates'
// TODO: add test to make sure all registry is being parse correctly
// Module options TypeScript interface definition
export interface ModuleOptions {
/**
* Prefix for all the imported component.
*/
prefix?: string
/**
* Directory that the component lives in.
* @default "./components/ui"
*/
componentDir?: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'shadcn',
configKey: 'shadcn',
},
defaults: {
prefix: '',
componentDir: './components/ui',
},
async setup({ prefix, componentDir }, nuxt) {
const COMPONENT_DIR_PATH = componentDir!
const ROOT_DIR_PATH = nuxt.options.rootDir
const UTILS_ALIAS = '@/lib/utils' // Use the same path from the cli for backward compatibility
const { resolve, resolvePath } = createResolver(ROOT_DIR_PATH)
const logger = useLogger('shadcn-nuxt')
// `lib/utils`
const utilsTemplate = addTemplate({
filename: 'shadcn-nuxt/utils.ts',
getContents: () => UTILS,
write: true,
})
nuxt.options.alias = { [UTILS_ALIAS]: utilsTemplate.dst, ...nuxt.options.alias } // We add our alias to the top to avoid conflicts with nuxt built in `@` alias
// Recommend to remove the root `lib/utils` if exists
const isRootUtilsExists = await findPath('./lib/utils.ts', { cwd: ROOT_DIR_PATH })
if (isRootUtilsExists)
logger.warn('[shadcn-nuxt] `lib/utils.ts` is auto generated by the module and can be safely removed.')
// Components Auto Imports
const componentsPath = resolve(COMPONENT_DIR_PATH)
// Tell Nuxt to not scan `componentsDir` for auto imports as we will do it manually
// See https://github.com/unovue/shadcn-vue/pull/528#discussion_r1590206268
nuxt.hook('components:dirs', (dirs) => {
dirs.unshift({
path: componentsPath,
extensions: [],
})
})
// Install the `@nuxtjs/tailwindcss` module.
await installModule('@nuxtjs/tailwindcss')
// Installs the `@nuxtjs/color-mode` module.
await installModule('@nuxtjs/color-mode')
// Manually scan `componentsDir` for components and register them for auto imports
try {
readdirSync(resolve(COMPONENT_DIR_PATH))
.forEach(async (dir) => {
try {
const filePath = await resolvePath(join(COMPONENT_DIR_PATH, dir, 'index'), { extensions: ['.ts', '.js'] })
const content = readFileSync(filePath, { encoding: 'utf8' })
const ast = parseSync(content, {
sourceType: 'module',
sourceFilename: filePath,
})
const exportedKeys: string[] = ast.program.body
.filter(node => node.type === 'ExportNamedDeclaration')
// @ts-expect-error parse return any
.flatMap(node => node.specifiers.map(specifier => specifier.exported.name))
.filter((key: string) => /^[A-Z]/.test(key))
exportedKeys.forEach((key) => {
addComponent({
name: `${prefix}${key}`, // name of the component to be used in vue templates
export: key, // (optional) if the component is a named (rather than default) export
filePath: resolve(filePath),
priority: 1,
})
})
}
catch (err) {
if (err instanceof Error)
console.warn('Module error: ', err.message)
}
})
}
catch (err) {
if (err instanceof Error)
console.warn(err.message)
}
},
})