Skip to content

Commit 7e4bc9b

Browse files
authored
fix(config): make module defaults overridable by vuetify:registerModule hooks & layers (#290) (#375)
* refactor(config): extract finalizeConfiguration with MODULE_DEFAULTS as lowest priority (#290) * fix(config): drop Nuxt defaults() so module defaults are overridable by hooks/layers (#290) * fix(config): drop dead disableVuetifyStyles default, add layer + deep-merge tests (#290)
1 parent d4851e4 commit 7e4bc9b

3 files changed

Lines changed: 117 additions & 37 deletions

File tree

packages/vuetify-nuxt-module/src/module.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -92,24 +92,6 @@ export default defineNuxtModule<ModuleOptions>({
9292
},
9393
version,
9494
},
95-
/**
96-
* Default configuration options of the Nuxt module
97-
*/
98-
defaults: () => ({
99-
vuetifyOptions: {
100-
labComponents: false,
101-
directives: false,
102-
},
103-
moduleOptions: {
104-
importComposables: true,
105-
includeTransformAssetsUrls: true,
106-
styles: true,
107-
disableVuetifyStyles: false,
108-
rulesConfiguration: {
109-
fromLabs: true,
110-
},
111-
},
112-
}),
11395
/**
11496
* Sets up the Vuetify Nuxt module.
11597
*

packages/vuetify-nuxt-module/src/utils/layers.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,42 @@ import type { FontIconSet, IconFontName, InlineModuleOptions, VuetifyModuleOptio
33
import defu from 'defu'
44
import { loadVuetifyConfiguration } from './config'
55

6+
/**
7+
* Module defaults — applied as the LOWEST-priority `defu` argument so that
8+
* `vuetify:registerModule` hooks and layers can override them, while the app's
9+
* explicit `nuxt.config` values (the defu base) still win (#290).
10+
*/
11+
export const MODULE_DEFAULTS: InlineModuleOptions = {
12+
moduleOptions: {
13+
importComposables: true,
14+
includeTransformAssetsUrls: true,
15+
styles: true,
16+
rulesConfiguration: {
17+
fromLabs: true,
18+
},
19+
},
20+
vuetifyOptions: {
21+
labComponents: false,
22+
directives: false,
23+
},
24+
}
25+
26+
/**
27+
* Merge collected configuration entries — `[app, ...hooks, ...layers]` — applying
28+
* MODULE_DEFAULTS last. `app` is the defu base (#231); ordering is not reversed (#218);
29+
* icons `sets` are deduped across entries (#214/#217).
30+
*/
31+
export function finalizeConfiguration (moduleOptions: InlineModuleOptions[]): InlineModuleOptions {
32+
if (moduleOptions.length > 1) {
33+
const [app, ...rest] = moduleOptions
34+
const configuration = <InlineModuleOptions>defu(app, ...rest, MODULE_DEFAULTS)
35+
// dedupe icons sets: fix #214 and #217 — reverse so the last (app) wins
36+
dedupeIcons(configuration, moduleOptions.toReversed())
37+
return configuration
38+
}
39+
return <InlineModuleOptions>defu(moduleOptions[0] ?? {}, MODULE_DEFAULTS)
40+
}
41+
642
/**
743
* Merges project layer with registered vuetify modules
844
*/
@@ -65,25 +101,9 @@ export async function mergeVuetifyModules (options: VuetifyModuleOptions, nuxt:
65101
// for example, adding a layer with inlined vuetify options:
66102
// nuxt will merge the conf for us (see issue #214 and #217)
67103
// - if the layer is configured using an external file, then we need to merge the configuration
68-
if (moduleOptions.length > 1) {
69-
const [app, ...rest] = moduleOptions
70-
// we don't need to reverse (defu second arg are the defaults): fix #218
71-
const configuration = <InlineModuleOptions>defu(app, ...rest)
72-
// dedupe icons sets: fix #214 and #217
73-
// we need to reverse the modules to override the icons from bottom to top layer
74-
dedupeIcons(configuration, moduleOptions.toReversed())
75-
return {
76-
configuration,
77-
vuetifyConfigurationFilesToWatch,
78-
}
79-
} else {
80-
return {
81-
configuration: {
82-
moduleOptions: options.moduleOptions,
83-
vuetifyOptions: resolvedOptions.config,
84-
} satisfies InlineModuleOptions,
85-
vuetifyConfigurationFilesToWatch,
86-
}
104+
return {
105+
configuration: finalizeConfiguration(moduleOptions),
106+
vuetifyConfigurationFilesToWatch,
87107
}
88108
}
89109

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import type { InlineModuleOptions } from '../src/types'
2+
import { describe, expect, it } from 'vitest'
3+
import { finalizeConfiguration, MODULE_DEFAULTS } from '../src/utils/layers'
4+
5+
const app = (o: Partial<InlineModuleOptions>): InlineModuleOptions => ({ moduleOptions: {}, vuetifyOptions: {}, ...o })
6+
7+
describe('finalizeConfiguration', () => {
8+
it('applies module defaults when nothing overrides them', () => {
9+
const c = finalizeConfiguration([app({})])
10+
expect(c.moduleOptions!.styles).toBe(true)
11+
expect(c.moduleOptions!.importComposables).toBe(true)
12+
expect(c.vuetifyOptions!.directives).toBe(false)
13+
expect(c.vuetifyOptions!.labComponents).toBe(false)
14+
})
15+
16+
it('lets a hook/layer override a defaulted moduleOptions field (#290)', () => {
17+
const c = finalizeConfiguration([
18+
app({ moduleOptions: {} }),
19+
{ moduleOptions: { styles: 'none' }, vuetifyOptions: {} },
20+
])
21+
expect(c.moduleOptions!.styles).toBe('none')
22+
})
23+
24+
it('keeps the app explicit value above hooks/layers (#231)', () => {
25+
const c = finalizeConfiguration([
26+
app({ moduleOptions: { styles: false } }),
27+
{ moduleOptions: { styles: 'none' }, vuetifyOptions: {} },
28+
])
29+
expect(c.moduleOptions!.styles).toBe(false)
30+
})
31+
32+
it('orders hooks above layers (existing push order)', () => {
33+
const c = finalizeConfiguration([
34+
app({ moduleOptions: {} }),
35+
{ moduleOptions: { styles: 'none' }, vuetifyOptions: {} },
36+
{ moduleOptions: { styles: false }, vuetifyOptions: {} },
37+
])
38+
expect(c.moduleOptions!.styles).toBe('none')
39+
})
40+
41+
it('lets a hook override a defaulted vuetifyOptions field', () => {
42+
const c = finalizeConfiguration([
43+
app({ vuetifyOptions: {} }),
44+
{ moduleOptions: {}, vuetifyOptions: { directives: true } },
45+
])
46+
expect(c.vuetifyOptions!.directives).toBe(true)
47+
})
48+
49+
it('dedupes icons sets across entries (#214/#217)', () => {
50+
const c = finalizeConfiguration([
51+
app({ vuetifyOptions: { icons: { defaultSet: 'mdi', sets: [{ name: 'mdi' }] } } as any }),
52+
{ moduleOptions: {}, vuetifyOptions: { icons: { sets: [{ name: 'fa' }] } } as any },
53+
])
54+
const names = (c.vuetifyOptions!.icons!.sets as Array<{ name: string }>).map(s => s.name).toSorted()
55+
expect(names).toEqual(['fa', 'mdi'])
56+
})
57+
58+
it('MODULE_DEFAULTS does not leak icons (dedupe only sees app+rest)', () => {
59+
expect((MODULE_DEFAULTS.vuetifyOptions as any)?.icons).toBeUndefined()
60+
})
61+
62+
it('lets a layer (not just a hook) override a defaulted moduleOptions field (#290)', () => {
63+
const c = finalizeConfiguration([
64+
app({ moduleOptions: {} }), // app — no explicit styles
65+
{ moduleOptions: {}, vuetifyOptions: {} }, // hook — no styles
66+
{ moduleOptions: { styles: 'none' }, vuetifyOptions: {} }, // layer — overrides default
67+
])
68+
expect(c.moduleOptions!.styles).toBe('none')
69+
})
70+
71+
it('deep-merges rulesConfiguration: a hook provides configFile, default fills fromLabs (#290)', () => {
72+
const c = finalizeConfiguration([
73+
app({ moduleOptions: {} }),
74+
{ moduleOptions: { rulesConfiguration: { configFile: 'my-rules.ts' } }, vuetifyOptions: {} },
75+
])
76+
expect(c.moduleOptions!.rulesConfiguration).toEqual({ configFile: 'my-rules.ts', fromLabs: true })
77+
})
78+
})

0 commit comments

Comments
 (0)