feat(module): add support for vue using unplugin (#2416)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
Daniel Roe
2024-10-25 16:15:26 +01:00
committed by GitHub
parent 50c6bf0092
commit d4a943e631
97 changed files with 18948 additions and 117 deletions

25
src/plugins/app-config.ts Normal file
View File

@@ -0,0 +1,25 @@
import type { UnpluginOptions } from 'unplugin'
import type { NuxtUIOptions } from '../unplugin'
/**
* This plugin injects Nuxt UI configuration into the runtime build so Nuxt UI components can
* access it.
*/
export default function AppConfigPlugin(options: NuxtUIOptions & { theme: NonNullable<NuxtUIOptions['theme']> }, appConfig: Record<string, any>) {
return {
name: 'nuxt:ui:app-config',
enforce: 'pre',
resolveId(id) {
if (id === '#build/app.config') {
return 'virtual:nuxt-ui-app-config'
}
},
loadInclude: id => id === 'virtual:nuxt-ui-app-config',
load() {
return `
export default ${JSON.stringify(appConfig!)}
`
}
} satisfies UnpluginOptions
}

59
src/plugins/components.ts Normal file
View File

@@ -0,0 +1,59 @@
import { join, normalize } from 'pathe'
import type { UnpluginContextMeta, UnpluginOptions } from 'unplugin'
import { globSync } from 'tinyglobby'
import AutoImportComponents from 'unplugin-vue-components'
import { runtimeDir } from '../unplugin'
import type { NuxtUIOptions } from '../unplugin'
/**
* This plugin adds all the Nuxt UI components as auto-imports.
*/
export default function ComponentImportPlugin(framework: UnpluginContextMeta['framework'], options: NuxtUIOptions & { prefix: NonNullable<NuxtUIOptions['prefix']> }) {
const components = globSync('**/*.vue', { cwd: join(runtimeDir, 'components') })
const componentNames = new Set(components.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
const overrides = globSync('**/*.vue', { cwd: join(runtimeDir, 'vue/components') })
const overrideNames = new Set(overrides.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
return [
/**
* This plugin aims to ensure we override certain components with Vue-compatible versions:
* <UIcon> and <ULink> currently.
*/
{
name: 'nuxt:ui:components',
enforce: 'pre',
resolveId(id, importer) {
// only apply to runtime nuxt ui components
if (!importer || !normalize(importer).includes(runtimeDir)) {
return
}
// only apply to relative imports
if (!RELATIVE_IMPORT_RE.test(id)) {
return
}
const filename = id.match(/([^/]+)\.vue$/)?.[1]
if (filename && overrideNames.has(`${options.prefix}${filename}`)) {
return join(runtimeDir, 'vue/components', `${filename}.vue`)
}
}
},
AutoImportComponents[framework]({
dts: options.dts ?? true,
exclude: [/[\\/]node_modules[\\/](?!\.pnpm|@nuxt\/ui)/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/],
resolvers: [
(componentName) => {
if (overrideNames.has(componentName))
return { name: 'default', from: join(runtimeDir, 'vue/components', `${componentName.slice(options.prefix.length)}.vue`) }
if (componentNames.has(componentName))
return { name: 'default', from: join(runtimeDir, 'components', `${componentName.slice(options.prefix.length)}.vue`) }
}
]
})
] satisfies UnpluginOptions[]
}
const RELATIVE_IMPORT_RE = /^\.{1,2}\//

View File

@@ -0,0 +1,40 @@
import type { UnpluginOptions } from 'unplugin'
import { join, normalize } from 'pathe'
import { resolvePathSync } from 'mlly'
import MagicString from 'magic-string'
import { runtimeDir } from '../unplugin'
/**
* This plugin normalises Nuxt environment (#imports) and `import.meta.client` within the Nuxt UI components.
*/
export default function NuxtEnvironmentPlugin() {
const stubPath = resolvePathSync(join(runtimeDir, 'vue/stubs'), { extensions: ['.ts', '.mjs', '.js'] })
return {
name: 'nuxt:ui',
enforce: 'pre',
resolveId(id) {
// this is implemented here rather than in a vite `config` hook for cross-builder support
if (id === '#imports') {
return stubPath
}
},
transformInclude(id) {
return normalize(id).includes(runtimeDir)
},
transform(code) {
if (code.includes('import.meta.client')) {
const s = new MagicString(code)
s.replaceAll('import.meta.client', 'true')
if (s.hasChanged()) {
return {
code: s.toString(),
map: s.generateMap({ hires: true })
}
}
}
}
} satisfies UnpluginOptions
}

56
src/plugins/plugins.ts Normal file
View File

@@ -0,0 +1,56 @@
import { join } from 'pathe'
import { globSync } from 'tinyglobby'
import { genSafeVariableName } from 'knitwork'
import MagicString from 'magic-string'
import { resolvePathSync } from 'mlly'
import { runtimeDir, type NuxtUIOptions } from '../unplugin'
import type { UnpluginOptions } from 'unplugin'
/**
* This plugin provides the necessary transforms to allow loading the
* Nuxt UI _Nuxt_ plugins in `src/runtime/plugins/` in a pure Vue environment.
*/
export default function PluginsPlugin(options: NuxtUIOptions) {
const plugins = globSync(['**/*', '!*.d.ts'], { cwd: join(runtimeDir, 'plugins'), absolute: true })
plugins.unshift(resolvePathSync(join(runtimeDir, 'vue/plugins/head'), { extensions: ['.ts', '.mjs', '.js'] }))
if (options.colorMode) {
plugins.push(resolvePathSync(join(runtimeDir, 'vue/plugins/color-mode'), { extensions: ['.ts', '.mjs', '.js'] }))
}
return {
name: 'nuxt:ui:plugins',
enforce: 'pre',
resolveId(id) {
if (id === '@nuxt/ui/vue-plugin') {
return 'virtual:nuxt-ui-plugins'
}
},
transform(code, id) {
if (plugins.some(p => id.startsWith(p)) && code.includes('import.meta.client')) {
const s = new MagicString(code)
s.replaceAll('import.meta.client', 'true')
if (s.hasChanged()) {
return {
code: s.toString(),
map: s.generateMap({ hires: true })
}
}
}
},
loadInclude: id => id === 'virtual:nuxt-ui-plugins',
load() {
return `
${plugins.map(p => `import ${genSafeVariableName(p)} from "${p}"`).join('\n')}
export default {
install (app) {
${plugins.map(p => ` app.use(${genSafeVariableName(p)})`).join('\n')}
}
}
`
}
} satisfies UnpluginOptions
}

28
src/plugins/templates.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { NuxtUIOptions } from '../unplugin'
import { getTemplates } from '../templates'
import type { UnpluginOptions } from 'unplugin'
/**
* This plugin is responsible for getting the generated virtual templates and
* making them available to the Vue build.
*/
export default function TemplatePlugin(options: NuxtUIOptions, appConfig: Record<string, any>) {
const templates = getTemplates(options, appConfig.ui)
const templateKeys = new Set(templates.map(t => `#build/${t.filename}`))
return {
name: 'nuxt:ui:templates',
enforce: 'pre',
resolveId(id) {
if (templateKeys.has(id + '.ts')) {
return id.replace('#build/', 'virtual:nuxt-ui-templates/') + '.ts'
}
},
loadInclude: id => templateKeys.has(id.replace('virtual:nuxt-ui-templates/', '#build/')),
load(id) {
id = id.replace('virtual:nuxt-ui-templates/', '#build/')
return templates.find(t => `#build/${t.filename}` === id)!.getContents!({} as any)
}
} satisfies UnpluginOptions
}