Skip to content

Commit b34cf8a

Browse files
feat(unplugin): add router option to disable router (#5213)
Co-authored-by: Benjamin Canac <[email protected]>
1 parent b38f680 commit b34cf8a

File tree

15 files changed

+381
-174
lines changed

15 files changed

+381
-174
lines changed

docs/content/docs/1.getting-started/2.installation/2.vue.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export default defineConfig({
8383
}
8484
}),
8585
ui({
86-
inertia: true
86+
router: 'inertia'
8787
})
8888
]
8989
})
@@ -105,7 +105,7 @@ export default defineConfig({
105105
inertia(),
106106
vue(),
107107
ui({
108-
inertia: true
108+
router: 'inertia'
109109
})
110110
]
111111
})
@@ -702,9 +702,17 @@ This will automatically prefix all Tailwind utility classes and CSS variables in
702702
Learn more about using a prefix in the Tailwind CSS documentation.
703703
::
704704

705-
### `inertia`
705+
### `router` :badge{label="Soon" class="align-text-top"}
706706

707-
Use the `inertia` option to enable compatibility with [Inertia.js](https://inertiajs.com/).
707+
Use the `router` option to configure routing integration. This is useful for applications that don't use `vue-router`, such as Electron apps, MPAs, or frameworks like [Inertia.js](https://inertiajs.com/) or [Hybridly](https://hybridly.dev/).
708+
709+
- Default: `true`{lang="ts-type"}
710+
711+
| Value | Description |
712+
| ----- | ----------- |
713+
| `true`{lang="ts-type"} | Uses `vue-router` for navigation with `RouterLink` component. |
714+
| `false`{lang="ts-type"} | Disables routing integration, links render as plain `<a>` tags. |
715+
| `'inertia'`{lang="ts-type"} | Uses Inertia.js for navigation with its `Link` component. |
708716

709717
```ts [vite.config.ts] {9}
710718
import { defineConfig } from 'vite'
@@ -715,17 +723,17 @@ export default defineConfig({
715723
plugins: [
716724
vue(),
717725
ui({
718-
inertia: true
726+
router: false
719727
})
720728
]
721729
})
722730
```
723731

724732
::note
725-
When using this option, `vue-router` is not required as Inertia.js provides its own routing system. The components that would normally use `RouterLink` will automatically use Inertia's `InertiaLink` component instead.
733+
When set to `false` or `'inertia'`, `vue-router` is not required as a dependency.
726734
::
727735

728-
### `scanPackages`
736+
### `scanPackages` :badge{label="Soon" class="align-text-top"}
729737

730738
Use the `scanPackages` option to specify additional npm packages that should be scanned for components using Nuxt UI. This is useful when you have a shared component library that uses Nuxt UI components internally.
731739

src/plugins/components.ts

Lines changed: 59 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,65 @@ import AutoImportComponents from 'unplugin-vue-components'
66
import type { Options as ComponentsOptions } from 'unplugin-vue-components/types'
77
import type { NuxtUIOptions } from '../unplugin'
88
import { runtimeDir } from '../unplugin'
9+
import { resolveRouterMode } from '../utils/router'
10+
11+
interface ComponentSource {
12+
has: (name: string) => boolean
13+
resolve: (name: string) => { name: string, from: string } | undefined
14+
resolveFile: (filename: string) => string | undefined
15+
}
16+
17+
function createComponentSource(cwd: string, prefix: string, ignore: string[] = []): ComponentSource {
18+
const files = globSync('**/*.vue', { cwd, ignore: ignore.filter(Boolean) as string[] })
19+
const names = new Set(files.map(c => `${prefix}${c.split('/').pop()?.replace(/\.vue$/, '')}`))
20+
const paths = new Map(files.map((c) => {
21+
const componentName = `${prefix}${c.split('/').pop()?.replace(/\.vue$/, '')}`
22+
return [componentName, c] as const
23+
}))
24+
25+
return {
26+
has: name => names.has(name),
27+
resolve: (componentName) => {
28+
const relativePath = paths.get(componentName)
29+
if (!relativePath) return
30+
return { name: 'default', from: join(cwd, relativePath) }
31+
},
32+
resolveFile: (filename) => {
33+
const componentName = `${prefix}${filename}`
34+
const relativePath = paths.get(componentName)
35+
if (!relativePath) return
36+
return join(cwd, relativePath)
37+
}
38+
}
39+
}
940

1041
/**
1142
* This plugin adds all the Nuxt UI components as auto-imports.
1243
*/
1344
export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix: NonNullable<NuxtUIOptions['prefix']> }, meta: UnpluginContextMeta) {
14-
const components = globSync('**/*.vue', {
15-
cwd: join(runtimeDir, 'components'),
16-
ignore: [
17-
!options.colorMode && 'color-mode/**/*.vue',
18-
'content/*.vue',
19-
'prose/**/*.vue'
20-
].filter(Boolean) as string[]
21-
})
22-
const componentNames = new Set(components.map(c => `${options.prefix}${c.split('/').pop()?.replace(/\.vue$/, '')}`))
23-
const componentPaths = new Map(components.map((c) => {
24-
const name = c.replace(/\.vue$/, '')
25-
const componentName = `${options.prefix}${name.split('/').pop()}`
26-
return [componentName, c]
27-
}))
45+
const colorModeIgnore = !options.colorMode ? ['color-mode/**/*.vue'] : []
46+
const routerMode = resolveRouterMode(options)
2847

29-
const overrides = globSync('**/*.vue', {
30-
cwd: join(runtimeDir, 'vue/components'),
31-
ignore: [
32-
!options.colorMode && 'color-mode/**/*.vue'
33-
].filter(Boolean) as string[]
34-
})
35-
const overrideNames = new Set(overrides.map(c => `${options.prefix}${c.split('/').pop()?.replace(/\.vue$/, '')}`))
36-
const overridePaths = new Map(overrides.map((c) => {
37-
const name = c.replace(/\.vue$/, '')
38-
const componentName = `${options.prefix}${name.split('/').pop()}`
39-
return [componentName, c]
40-
}))
48+
// Component sources in priority order (first match wins)
49+
const routerOverrides: Record<string, ComponentSource> = {
50+
'vue-router': createComponentSource(join(runtimeDir, 'vue/overrides/vue-router'), options.prefix),
51+
'inertia': createComponentSource(join(runtimeDir, 'vue/overrides/inertia'), options.prefix),
52+
'none': createComponentSource(join(runtimeDir, 'vue/overrides/none'), options.prefix)
53+
}
4154

42-
const inertiaOverrides = globSync('**/*.vue', {
43-
cwd: join(runtimeDir, 'inertia/components')
44-
})
45-
const inertiaOverrideNames = new Set(inertiaOverrides.map(c => `${options.prefix}${c.replace(/\.vue$/, '')}`))
55+
const unpluginComponents = createComponentSource(
56+
join(runtimeDir, 'vue/components'),
57+
options.prefix,
58+
colorModeIgnore
59+
)
4660

61+
const defaultComponents = createComponentSource(
62+
join(runtimeDir, 'components'),
63+
options.prefix,
64+
[...colorModeIgnore, 'content/*.vue', 'prose/**/*.vue']
65+
)
66+
67+
const sources = [routerOverrides[routerMode], unpluginComponents, defaultComponents].filter((s): s is ComponentSource => !!s)
4768
const packagesToScan = [
4869
'@nuxt/ui',
4970
'@compodium/examples',
@@ -62,16 +83,9 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
6283
],
6384
resolvers: [
6485
(componentName) => {
65-
if (options.inertia && inertiaOverrideNames.has(componentName)) {
66-
return { name: 'default', from: join(runtimeDir, 'inertia/components', `${componentName.slice(options.prefix.length)}.vue`) }
67-
}
68-
if (overrideNames.has(componentName)) {
69-
const relativePath = overridePaths.get(componentName)
70-
return { name: 'default', from: join(runtimeDir, 'vue/components', relativePath as string) }
71-
}
72-
if (componentNames.has(componentName)) {
73-
const relativePath = componentPaths.get(componentName)
74-
return { name: 'default', from: join(runtimeDir, 'components', relativePath as string) }
86+
for (const source of sources) {
87+
const resolved = source.resolve(componentName)
88+
if (resolved) return resolved
7589
}
7690
}
7791
]
@@ -97,12 +111,11 @@ export default function ComponentImportPlugin(options: NuxtUIOptions & { prefix:
97111
}
98112

99113
const filename = id.match(/([^/]+)\.vue$/)?.[1]
100-
if (filename && options.inertia && inertiaOverrideNames.has(`${options.prefix}${filename}`)) {
101-
return join(runtimeDir, 'inertia/components', `${filename}.vue`)
102-
}
103-
if (filename && overrideNames.has(`${options.prefix}${filename}`)) {
104-
const relativePath = overridePaths.get(`${options.prefix}${filename}`)
105-
return join(runtimeDir, 'vue/components', relativePath as string)
114+
if (filename) {
115+
for (const source of sources) {
116+
const resolved = source.resolveFile(filename)
117+
if (resolved) return resolved
118+
}
106119
}
107120
}
108121
},

src/plugins/nuxt-environment.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import { resolvePathSync } from 'mlly'
44
import type { UnpluginOptions } from 'unplugin'
55
import type { NuxtUIOptions } from '../unplugin'
66
import { runtimeDir } from '../unplugin'
7+
import { resolveRouterMode } from '../utils/router'
78

89
/**
910
* This plugin normalises Nuxt environment (#imports) and `import.meta.client` within the Nuxt UI components.
1011
*/
1112
export default function NuxtEnvironmentPlugin(options: NuxtUIOptions) {
12-
const stubPath = resolvePathSync(options.inertia ? '../runtime/inertia/stubs' : '../runtime/vue/stubs', { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })
13+
const routerMode = resolveRouterMode(options)
14+
const stubsPath = `../runtime/vue/stubs/${routerMode}`
15+
16+
const stubPath = resolvePathSync(stubsPath, { extensions: ['.ts', '.mjs', '.js'], url: import.meta.url })
1317

1418
return {
1519
name: 'nuxt:ui',

src/runtime/inertia/stubs.ts

Lines changed: 0 additions & 101 deletions
This file was deleted.

src/runtime/inertia/components/Link.vue renamed to src/runtime/vue/overrides/inertia/Link.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import type { AppConfig } from '@nuxt/schema'
33
import type { InertiaLinkProps } from '@inertiajs/vue3'
44
import theme from '#build/ui/link'
5-
import type { ButtonHTMLAttributes, AnchorHTMLAttributes } from '../../types/html'
6-
import type { ComponentConfig } from '../../types/tv'
5+
import type { ButtonHTMLAttributes, AnchorHTMLAttributes } from '../../../types/html'
6+
import type { ComponentConfig } from '../../../types/tv'
77
88
type Link = ComponentConfig<typeof theme, AppConfig, 'link'>
99
@@ -75,9 +75,9 @@ import { reactiveOmit } from '@vueuse/core'
7575
import { usePage } from '@inertiajs/vue3'
7676
import { hasProtocol } from 'ufo'
7777
import { useAppConfig } from '#imports'
78-
import { tv } from '../../utils/tv'
79-
import { mergeClasses } from '../../utils'
80-
import ULinkBase from '../../components/LinkBase.vue'
78+
import { tv } from '../../../utils/tv'
79+
import { mergeClasses } from '../../../utils'
80+
import ULinkBase from './LinkBase.vue'
8181
8282
defineOptions({ inheritAttrs: false })
8383

src/runtime/inertia/components/LinkBase.vue renamed to src/runtime/vue/overrides/inertia/LinkBase.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import type { LinkProps } from '../../types'
2+
import type { LinkProps } from '../../../types'
33
44
export interface LinkBaseProps {
55
as?: string

0 commit comments

Comments
 (0)