vuetify-nuxt-module 1.0.0-beta.8 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -18
- package/dist/module.d.mts +6 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +227 -101
- package/dist/runtime/plugins/first-request-reload-guard.d.ts +13 -0
- package/dist/runtime/plugins/first-request-reload-guard.js +11 -0
- package/dist/runtime/plugins/vuetify-client-hints.client.js +11 -6
- package/package.json +30 -30
package/README.md
CHANGED
|
@@ -26,32 +26,29 @@
|
|
|
26
26
|
|
|
27
27
|
## 🚀 Features
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
> 📖 Full [**documentation & guides**](https://nuxt.vuetifyjs.com/)
|
|
30
|
+
|
|
30
31
|
- 👌 **Zero-Config**: sensible built-in default [Vuetify](https://vuetifyjs.com/) configuration for common use cases
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
- 📦 **Multiple Icon Sets**: register [multiple icon sets](https://vuetifyjs.com/en/features/icon-fonts/#multiple-icon-sets)
|
|
42
|
-
- 🌍 **I18n Ready**: install [@nuxtjs/i18n](https://i18n.nuxtjs.org/) Nuxt module, and you're ready to use Vuetify [internationalization](https://vuetifyjs.com/en/features/internationalization/) features
|
|
43
|
-
- 📆 **Date Components**: use Vuetify components [that require date functionality](https://vuetifyjs.com/en/features/dates/) installing and configuring one of the [@date-io](https://github.com/dmtrKovalenko/date-io#projects) adapters
|
|
44
|
-
- 💬 **Auto-Import Vuetify Locale Messages**: add [Vuetify Locale Messages](https://vuetifyjs.com/en/features/internationalization/#getting-started) adding just the locales you want to use, no more imports needed
|
|
45
|
-
- ⚙️ **Auto-Import Vuetify Composables**: you don't need to import Vuetify composables manually, they are automatically imported for you
|
|
46
|
-
- 🎨 **Vuetify Blueprints**: use [Vuetify Blueprints](https://vuetifyjs.com/en/features/blueprints/) to quickly scaffold components
|
|
47
|
-
- 👀 **Nuxt DevTools**: ready to inspect your Vuetify styles with the [Nuxt DevTools](https://github.com/nuxt/devtools) inspector
|
|
32
|
+
- ⚡ **Fully Tree Shakable**: by default, only the Vuetify components you use are imported
|
|
33
|
+
- 🪄 **Auto-Import**: Vuetify components and composables are auto-imported — no manual imports needed
|
|
34
|
+
- 🔌 **Extensible**: customize the Vuetify configuration via [Nuxt Runtime Hooks](https://nuxt.com/docs/guide/going-further/hooks#usage-with-plugins), [Nuxt Layers](https://nuxt.com/docs/getting-started/layers#layers), the `vuetify:registerModule` [module hook](https://nuxt.com/docs/guide/going-further/hooks#nuxt-hooks-build-time), or a dedicated `vuetify.config` file
|
|
35
|
+
- 💥 **SSR**: automatic SSR detection and configuration, including [HTTP Client Hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints)
|
|
36
|
+
- ✨ **Configurable Styles**: configure your variables using [Vuetify SASS Variables](https://vuetifyjs.com/en/features/sass-variables/)
|
|
37
|
+
- 🛠️ **Directives & Labs**: optional [directives](https://vuetifyjs.com/en/getting-started/installation/#manual-steps) and [labs components](https://vuetifyjs.com/en/labs/introduction/) registration
|
|
38
|
+
- 🎭 **Icons**: pure-CSS icons (UnoCSS), [icon fonts](https://vuetifyjs.com/en/features/icon-fonts/) (CDN or local), SVG packs ([@mdi/js](https://www.npmjs.com/package/@mdi/js), [FontAwesome](https://www.npmjs.com/package/@fortawesome/vue-fontawesome)), and [multiple icon sets](https://vuetifyjs.com/en/features/icon-fonts/#multiple-icon-sets)
|
|
39
|
+
- 🌍 **I18n**: integrate [@nuxtjs/i18n](https://i18n.nuxtjs.org/) for Vuetify [internationalization](https://vuetifyjs.com/en/features/internationalization/), with auto-imported Vuetify locale messages
|
|
40
|
+
- 📆 **Date Components**: use Vuetify [date components](https://vuetifyjs.com/en/features/dates/) via the [@date-io](https://github.com/dmtrKovalenko/date-io#projects) adapters
|
|
41
|
+
- 🎨 **Blueprints**: scaffold quickly with [Vuetify Blueprints](https://vuetifyjs.com/en/features/blueprints/)
|
|
48
42
|
- 🦾 **Type Strong**: written in [TypeScript](https://www.typescriptlang.org/)
|
|
49
43
|
|
|
50
44
|
## 📦 Install
|
|
51
45
|
|
|
52
46
|
> Requires Vite, will not work with Webpack
|
|
53
47
|
|
|
48
|
+
`vuetify` is a peer dependency (Vuetify 3 or 4) — install it alongside the module:
|
|
49
|
+
|
|
54
50
|
```bash
|
|
51
|
+
npm install -D vuetify
|
|
55
52
|
npx nuxt module add vuetify-nuxt-module
|
|
56
53
|
```
|
|
57
54
|
|
package/dist/module.d.mts
CHANGED
|
@@ -26,8 +26,13 @@ interface DateOptions {
|
|
|
26
26
|
adapter?: DateAdapter;
|
|
27
27
|
/**
|
|
28
28
|
* Formats.
|
|
29
|
+
*
|
|
30
|
+
* Only serializable `Intl.DateTimeFormatOptions` values are supported here:
|
|
31
|
+
* the date configuration is statically serialized to a virtual module, so
|
|
32
|
+
* function-valued formats cannot be expressed (see #313, #331). Use a custom
|
|
33
|
+
* date adapter if you need function formats.
|
|
29
34
|
*/
|
|
30
|
-
formats?: Record<string,
|
|
35
|
+
formats?: Record<string, Intl.DateTimeFormatOptions>;
|
|
31
36
|
/**
|
|
32
37
|
* Locales.
|
|
33
38
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
3
3
|
import { addPluginTemplate, resolvePath, addTemplate, extendWebpackConfig, isNuxtMajorVersion, addImports, addPlugin, addVitePlugin, useLogger, defineNuxtModule, getNuxtVersion, findPath, hasNuxtModule, createResolver } from '@nuxt/kit';
|
|
4
|
-
import { isAbsolute, resolve, dirname
|
|
4
|
+
import { isAbsolute, resolve, dirname } from 'pathe';
|
|
5
5
|
import semver from 'semver';
|
|
6
6
|
import { createFilter, version as version$1 } from 'vite';
|
|
7
7
|
import defu from 'defu';
|
|
8
|
-
import { transformAssetUrls } from '
|
|
8
|
+
import { transformAssetUrls, generateImports } from '@vuetify/loader-shared';
|
|
9
9
|
import Styles from '@vuetify/unplugin-styles/vite';
|
|
10
10
|
import { isPackageExists } from 'local-pkg';
|
|
11
|
-
import { generateImports } from '@vuetify/loader-shared';
|
|
12
|
-
import destr from 'destr';
|
|
13
11
|
import { parseQuery, parseURL } from 'ufo';
|
|
14
12
|
import { readFile } from 'node:fs/promises';
|
|
15
|
-
import { debounce } from 'perfect-debounce';
|
|
16
13
|
import process from 'node:process';
|
|
17
14
|
import { createConfigLoader } from 'unconfig';
|
|
18
15
|
|
|
19
|
-
const version = "1.0.0-
|
|
16
|
+
const version = "1.0.0-rc.1";
|
|
20
17
|
|
|
21
18
|
const VIRTUAL_VUETIFY_CONFIGURATION = "virtual:vuetify-configuration";
|
|
22
19
|
const RESOLVED_VIRTUAL_VUETIFY_CONFIGURATION = `\0${VIRTUAL_VUETIFY_CONFIGURATION}`;
|
|
@@ -356,6 +353,11 @@ function vuetifyConfigurationPlugin(ctx) {
|
|
|
356
353
|
},
|
|
357
354
|
async load(id) {
|
|
358
355
|
if (id === RESOLVED_VIRTUAL_VUETIFY_CONFIGURATION) {
|
|
356
|
+
if (ctx.isDev && ctx.canHmrConfig) {
|
|
357
|
+
for (const file of ctx.vuetifyFilesToWatch) {
|
|
358
|
+
this.addWatchFile(file);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
359
361
|
const {
|
|
360
362
|
directives: _directives,
|
|
361
363
|
date: _date,
|
|
@@ -377,7 +379,12 @@ function vuetifyConfigurationPlugin(ctx) {
|
|
|
377
379
|
}
|
|
378
380
|
const result = await buildConfiguration(ctx);
|
|
379
381
|
const deepCopy = result.messages.length > 0;
|
|
380
|
-
|
|
382
|
+
let configDepImports = "";
|
|
383
|
+
if (ctx.isDev && ctx.canHmrConfig && this.environment?.name === "ssr") {
|
|
384
|
+
configDepImports = ctx.vuetifyFilesToWatch.map((file) => `import ${JSON.stringify(file)}`).join("\n");
|
|
385
|
+
}
|
|
386
|
+
return `${configDepImports}
|
|
387
|
+
${result.imports}
|
|
381
388
|
|
|
382
389
|
export const isDev = ${ctx.isDev}
|
|
383
390
|
export function vuetifyConfiguration() {
|
|
@@ -596,6 +603,70 @@ ${useLocales.map((locale) => {
|
|
|
596
603
|
};
|
|
597
604
|
}
|
|
598
605
|
|
|
606
|
+
const VUETIFY_TO_DATE_FNS = {
|
|
607
|
+
en: "enUS",
|
|
608
|
+
fa: "faIR",
|
|
609
|
+
no: "nb",
|
|
610
|
+
srCyrl: "sr",
|
|
611
|
+
zhHans: "zhCN",
|
|
612
|
+
zhHant: "zhTW"
|
|
613
|
+
};
|
|
614
|
+
const DATE_FNS_SUPPORTED = /* @__PURE__ */ new Set([
|
|
615
|
+
"af",
|
|
616
|
+
"ar",
|
|
617
|
+
"az",
|
|
618
|
+
"bg",
|
|
619
|
+
"ca",
|
|
620
|
+
"ckb",
|
|
621
|
+
"cs",
|
|
622
|
+
"da",
|
|
623
|
+
"de",
|
|
624
|
+
"el",
|
|
625
|
+
"enUS",
|
|
626
|
+
"es",
|
|
627
|
+
"et",
|
|
628
|
+
"faIR",
|
|
629
|
+
"fi",
|
|
630
|
+
"fr",
|
|
631
|
+
"he",
|
|
632
|
+
"hr",
|
|
633
|
+
"hu",
|
|
634
|
+
"id",
|
|
635
|
+
"it",
|
|
636
|
+
"ja",
|
|
637
|
+
"km",
|
|
638
|
+
"ko",
|
|
639
|
+
"lt",
|
|
640
|
+
"lv",
|
|
641
|
+
"nb",
|
|
642
|
+
"nl",
|
|
643
|
+
"pl",
|
|
644
|
+
"pt",
|
|
645
|
+
"ro",
|
|
646
|
+
"ru",
|
|
647
|
+
"sk",
|
|
648
|
+
"sl",
|
|
649
|
+
"sr",
|
|
650
|
+
"srLatn",
|
|
651
|
+
"sv",
|
|
652
|
+
"th",
|
|
653
|
+
"tr",
|
|
654
|
+
"uk",
|
|
655
|
+
"vi",
|
|
656
|
+
"zhCN",
|
|
657
|
+
"zhTW"
|
|
658
|
+
]);
|
|
659
|
+
function resolveDateFnsLocaleName(code) {
|
|
660
|
+
if (!code) {
|
|
661
|
+
return { name: "enUS", fallback: true };
|
|
662
|
+
}
|
|
663
|
+
const candidate = VUETIFY_TO_DATE_FNS[code] ?? code;
|
|
664
|
+
if (DATE_FNS_SUPPORTED.has(candidate)) {
|
|
665
|
+
return { name: candidate, fallback: false };
|
|
666
|
+
}
|
|
667
|
+
return { name: "enUS", fallback: true };
|
|
668
|
+
}
|
|
669
|
+
|
|
599
670
|
function vuetifyDateConfigurationPlugin(ctx) {
|
|
600
671
|
return {
|
|
601
672
|
name: "vuetify:date-configuration:nuxt",
|
|
@@ -607,6 +678,11 @@ function vuetifyDateConfigurationPlugin(ctx) {
|
|
|
607
678
|
},
|
|
608
679
|
async load(id) {
|
|
609
680
|
if (id === RESOLVED_VIRTUAL_VUETIFY_DATE_CONFIGURATION) {
|
|
681
|
+
if (ctx.isDev && ctx.canHmrConfig) {
|
|
682
|
+
for (const file of ctx.vuetifyFilesToWatch) {
|
|
683
|
+
this.addWatchFile(file);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
610
686
|
if (!ctx.dateAdapter) {
|
|
611
687
|
return `
|
|
612
688
|
export const enabled = false
|
|
@@ -619,34 +695,41 @@ export function dateConfiguration() {
|
|
|
619
695
|
`;
|
|
620
696
|
}
|
|
621
697
|
const { adapter: _adapter, ...newDateOptions } = ctx.vuetifyOptions.date ?? {};
|
|
622
|
-
|
|
698
|
+
let dateFnsLocale;
|
|
699
|
+
if (ctx.dateAdapter === "date-fns") {
|
|
700
|
+
const resolved = resolveDateFnsLocaleName(ctx.vuetifyOptions.locale?.locale);
|
|
701
|
+
dateFnsLocale = resolved.name;
|
|
702
|
+
if (resolved.fallback) {
|
|
703
|
+
ctx.logger.warn(`[vuetify-nuxt-module] date-fns locale for "${ctx.vuetifyOptions.locale?.locale ?? "(unset)"}" not found, falling back to "enUS". Set "vuetifyOptions.locale.locale" to a supported locale.`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return `${buildImports(dateFnsLocale)}
|
|
623
707
|
export const enabled = true
|
|
624
708
|
export const isDev = ${ctx.isDev}
|
|
625
709
|
export const i18n = ${ctx.i18n}
|
|
626
710
|
export const adapter = '${ctx.dateAdapter}'
|
|
627
711
|
export function dateConfiguration() {
|
|
628
712
|
const options = JSON.parse('${JSON.stringify(newDateOptions)}')
|
|
629
|
-
${buildAdapter()}
|
|
713
|
+
${buildAdapter(dateFnsLocale)}
|
|
630
714
|
return options
|
|
631
715
|
}
|
|
632
716
|
`;
|
|
633
717
|
}
|
|
634
718
|
}
|
|
635
719
|
};
|
|
636
|
-
function buildAdapter() {
|
|
720
|
+
function buildAdapter(dateFnsLocale) {
|
|
637
721
|
if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
|
|
638
722
|
return "";
|
|
639
723
|
}
|
|
640
724
|
if (ctx.dateAdapter === "vuetify") {
|
|
641
725
|
return "options.adapter = VuetifyDateAdapter";
|
|
642
726
|
}
|
|
643
|
-
const locale = ctx.vuetifyOptions.locale?.locale ?? "en";
|
|
644
727
|
if (ctx.dateAdapter === "date-fns") {
|
|
645
|
-
return `options.adapter = new Adapter({ locale: ${
|
|
728
|
+
return `options.adapter = new Adapter({ locale: ${dateFnsLocale} })`;
|
|
646
729
|
}
|
|
647
730
|
return "options.adapter = Adapter";
|
|
648
731
|
}
|
|
649
|
-
function buildImports() {
|
|
732
|
+
function buildImports(dateFnsLocale) {
|
|
650
733
|
if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
|
|
651
734
|
return "";
|
|
652
735
|
}
|
|
@@ -655,7 +738,7 @@ export function dateConfiguration() {
|
|
|
655
738
|
}
|
|
656
739
|
const imports = [`import Adapter from '@date-io/${ctx.dateAdapter}'`];
|
|
657
740
|
if (ctx.dateAdapter === "date-fns") {
|
|
658
|
-
imports.push(`import { ${
|
|
741
|
+
imports.push(`import { ${dateFnsLocale} } from 'date-fns/locale'`);
|
|
659
742
|
}
|
|
660
743
|
return imports.join("\n");
|
|
661
744
|
}
|
|
@@ -672,6 +755,11 @@ function vuetifyIconsPlugin(ctx) {
|
|
|
672
755
|
},
|
|
673
756
|
async load(id) {
|
|
674
757
|
if (id === RESOLVED_VIRTUAL_VUETIFY_ICONS_CONFIGURATION) {
|
|
758
|
+
if (ctx.isDev && ctx.canHmrConfig) {
|
|
759
|
+
for (const file of ctx.vuetifyFilesToWatch) {
|
|
760
|
+
this.addWatchFile(file);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
675
763
|
const {
|
|
676
764
|
enabled,
|
|
677
765
|
unocss,
|
|
@@ -841,10 +929,24 @@ function parseId2(id) {
|
|
|
841
929
|
id = id.replace(/^(virtual:nuxt:|virtual:)/, "");
|
|
842
930
|
return parseURL(decodeURIComponent(isAbsolute(id) ? pathToFileURL(id).href : id));
|
|
843
931
|
}
|
|
932
|
+
function reviver(key, value) {
|
|
933
|
+
if (key === "__proto__" || key === "constructor") {
|
|
934
|
+
return void 0;
|
|
935
|
+
}
|
|
936
|
+
return value;
|
|
937
|
+
}
|
|
938
|
+
function parseProps(value) {
|
|
939
|
+
try {
|
|
940
|
+
const parsed = JSON.parse(value, reviver);
|
|
941
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
942
|
+
} catch {
|
|
943
|
+
return void 0;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
844
946
|
function parseId(id) {
|
|
845
947
|
const { search, pathname } = parseId2(id);
|
|
846
948
|
const query = parseQuery(search);
|
|
847
|
-
const urlProps = query.props ?
|
|
949
|
+
const urlProps = query.props ? parseProps(query.props) : void 0;
|
|
848
950
|
return {
|
|
849
951
|
query: urlProps,
|
|
850
952
|
path: pathname ?? id
|
|
@@ -1266,6 +1368,29 @@ async function loadVuetifyConfiguration(cwd = process.cwd(), configOrPath = cwd,
|
|
|
1266
1368
|
return result;
|
|
1267
1369
|
}
|
|
1268
1370
|
|
|
1371
|
+
const MODULE_DEFAULTS = {
|
|
1372
|
+
moduleOptions: {
|
|
1373
|
+
importComposables: true,
|
|
1374
|
+
includeTransformAssetsUrls: true,
|
|
1375
|
+
styles: true,
|
|
1376
|
+
rulesConfiguration: {
|
|
1377
|
+
fromLabs: true
|
|
1378
|
+
}
|
|
1379
|
+
},
|
|
1380
|
+
vuetifyOptions: {
|
|
1381
|
+
labComponents: false,
|
|
1382
|
+
directives: false
|
|
1383
|
+
}
|
|
1384
|
+
};
|
|
1385
|
+
function finalizeConfiguration(moduleOptions) {
|
|
1386
|
+
if (moduleOptions.length > 1) {
|
|
1387
|
+
const [app, ...rest] = moduleOptions;
|
|
1388
|
+
const configuration = defu(app, ...rest, MODULE_DEFAULTS);
|
|
1389
|
+
dedupeIcons(configuration, moduleOptions.toReversed());
|
|
1390
|
+
return configuration;
|
|
1391
|
+
}
|
|
1392
|
+
return defu(moduleOptions[0] ?? {}, MODULE_DEFAULTS);
|
|
1393
|
+
}
|
|
1269
1394
|
async function mergeVuetifyModules(options, nuxt) {
|
|
1270
1395
|
const moduleOptions = [];
|
|
1271
1396
|
const vuetifyConfigurationFilesToWatch = /* @__PURE__ */ new Set();
|
|
@@ -1293,37 +1418,18 @@ async function mergeVuetifyModules(options, nuxt) {
|
|
|
1293
1418
|
options.vuetifyOptions
|
|
1294
1419
|
);
|
|
1295
1420
|
if (nuxt.options.dev && resolvedOptions.sources.length > 0) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
nuxt.options.watch.push(s.replace(/\\/g, "/"));
|
|
1299
|
-
}
|
|
1300
|
-
} else {
|
|
1301
|
-
for (const s of resolvedOptions.sources) {
|
|
1302
|
-
vuetifyConfigurationFilesToWatch.add(s.replace(/\\/g, "/"));
|
|
1303
|
-
}
|
|
1421
|
+
for (const s of resolvedOptions.sources) {
|
|
1422
|
+
vuetifyConfigurationFilesToWatch.add(s.replace(/\\/g, "/"));
|
|
1304
1423
|
}
|
|
1305
1424
|
}
|
|
1306
1425
|
moduleOptions.unshift({
|
|
1307
1426
|
moduleOptions: options.moduleOptions,
|
|
1308
1427
|
vuetifyOptions: resolvedOptions.config
|
|
1309
1428
|
});
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return {
|
|
1315
|
-
configuration,
|
|
1316
|
-
vuetifyConfigurationFilesToWatch
|
|
1317
|
-
};
|
|
1318
|
-
} else {
|
|
1319
|
-
return {
|
|
1320
|
-
configuration: {
|
|
1321
|
-
moduleOptions: options.moduleOptions,
|
|
1322
|
-
vuetifyOptions: resolvedOptions.config
|
|
1323
|
-
},
|
|
1324
|
-
vuetifyConfigurationFilesToWatch
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1429
|
+
return {
|
|
1430
|
+
configuration: finalizeConfiguration(moduleOptions),
|
|
1431
|
+
vuetifyConfigurationFilesToWatch
|
|
1432
|
+
};
|
|
1327
1433
|
}
|
|
1328
1434
|
function dedupeIcons(configuration, moduleOptions) {
|
|
1329
1435
|
const vuetifyOptions = configuration.vuetifyOptions;
|
|
@@ -1368,6 +1474,16 @@ function resolveColorSchemeCookie(options, logger) {
|
|
|
1368
1474
|
cookieSameSite
|
|
1369
1475
|
};
|
|
1370
1476
|
}
|
|
1477
|
+
function resolveDefaultTheme(defaultTheme, themes, lightThemeName, logger) {
|
|
1478
|
+
if (defaultTheme === "system") {
|
|
1479
|
+
logger.warn(`Vuetify "system" theme cannot be resolved during SSR; using "${lightThemeName}" as the server-side fallback. The browser preference is applied on the client via prefersColorScheme client hints. To avoid a flash of the wrong theme, set explicit dark/light themes and enable moduleOptions.ssrClientHints.prefersColorSchemeOptions.useBrowserThemeOnly.`);
|
|
1480
|
+
return lightThemeName;
|
|
1481
|
+
}
|
|
1482
|
+
if (!themes[defaultTheme]) {
|
|
1483
|
+
throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
|
|
1484
|
+
}
|
|
1485
|
+
return defaultTheme;
|
|
1486
|
+
}
|
|
1371
1487
|
function prepareSSRClientHints(baseUrl, ctx) {
|
|
1372
1488
|
if (!ctx.isSSR || ctx.isNuxtGenerate) {
|
|
1373
1489
|
return disabledClientHints;
|
|
@@ -1394,9 +1510,6 @@ function prepareSSRClientHints(baseUrl, ctx) {
|
|
|
1394
1510
|
if (!defaultTheme) {
|
|
1395
1511
|
throw new Error("Vuetify default theme is missing in theme!");
|
|
1396
1512
|
}
|
|
1397
|
-
if (!themes[defaultTheme]) {
|
|
1398
|
-
throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
|
|
1399
|
-
}
|
|
1400
1513
|
const darkThemeName = ssrClientHintsConfiguration.prefersColorSchemeOptions?.darkThemeName ?? "dark";
|
|
1401
1514
|
if (!themes[darkThemeName]) {
|
|
1402
1515
|
throw new Error(`Missing theme ${darkThemeName} in the Vuetify themes!`);
|
|
@@ -1409,9 +1522,10 @@ function prepareSSRClientHints(baseUrl, ctx) {
|
|
|
1409
1522
|
throw new Error("Vuetify dark theme and light theme are the same, change darkThemeName or lightThemeName!");
|
|
1410
1523
|
}
|
|
1411
1524
|
const pcsOptions = ssrClientHintsConfiguration.prefersColorSchemeOptions;
|
|
1525
|
+
const effectiveDefaultTheme = resolveDefaultTheme(defaultTheme, themes, lightThemeName, ctx.logger);
|
|
1412
1526
|
clientHints.prefersColorSchemeOptions = {
|
|
1413
1527
|
baseUrl,
|
|
1414
|
-
defaultTheme,
|
|
1528
|
+
defaultTheme: effectiveDefaultTheme,
|
|
1415
1529
|
themeNames: Array.from(Object.keys(themes)),
|
|
1416
1530
|
...resolveColorSchemeCookie(pcsOptions, ctx.logger),
|
|
1417
1531
|
darkThemeName,
|
|
@@ -1422,7 +1536,7 @@ function prepareSSRClientHints(baseUrl, ctx) {
|
|
|
1422
1536
|
return clientHints;
|
|
1423
1537
|
}
|
|
1424
1538
|
|
|
1425
|
-
async function load(options, nuxt, ctx) {
|
|
1539
|
+
async function load(options, nuxt, ctx, reload = false) {
|
|
1426
1540
|
const {
|
|
1427
1541
|
configuration,
|
|
1428
1542
|
vuetifyConfigurationFilesToWatch
|
|
@@ -1467,9 +1581,11 @@ async function load(options, nuxt, ctx) {
|
|
|
1467
1581
|
ctx.dateAdapter = date[0];
|
|
1468
1582
|
}
|
|
1469
1583
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1584
|
+
if (!reload) {
|
|
1585
|
+
const oldIcons = ctx.icons;
|
|
1586
|
+
if (oldIcons && oldIcons.cdn?.length && nuxt.options.app.head.link) {
|
|
1587
|
+
nuxt.options.app.head.link = nuxt.options.app.head.link.filter((link) => !link.key || !oldIcons.cdn.some(([key]) => link.key === key));
|
|
1588
|
+
}
|
|
1473
1589
|
}
|
|
1474
1590
|
ctx.moduleOptions = configuration.moduleOptions;
|
|
1475
1591
|
ctx.vuetifyOptions = configuration.vuetifyOptions;
|
|
@@ -1478,7 +1594,10 @@ async function load(options, nuxt, ctx) {
|
|
|
1478
1594
|
ctx.vuetifyFilesToWatch = Array.from(vuetifyConfigurationFilesToWatch);
|
|
1479
1595
|
ctx.icons = prepareIcons(ctx.unocss, ctx.logger, vuetifyAppOptions, ctx.resolvePaths);
|
|
1480
1596
|
ctx.ssrClientHints = prepareSSRClientHints(nuxt.options.app.baseURL ?? "/", ctx);
|
|
1481
|
-
if (ctx.
|
|
1597
|
+
if (ctx.isSSR && !ctx.ssrClientHints.prefersColorScheme && ctx.vuetifyOptions.theme && typeof ctx.vuetifyOptions.theme === "object" && ctx.vuetifyOptions.theme.defaultTheme === "system") {
|
|
1598
|
+
ctx.logger.warn('`theme.defaultTheme: "system"` cannot be resolved during SSR/SSG: the server has no access to the OS color-scheme preference, so the first paint defaults to light and may flash on dark systems. Set explicit dark/light themes and enable `moduleOptions.ssrClientHints.prefersColorScheme` (optionally `prefersColorSchemeOptions.useBrowserThemeOnly`). See the SSR guide.');
|
|
1599
|
+
}
|
|
1600
|
+
if (!reload && ctx.icons.enabled) {
|
|
1482
1601
|
if (ctx.icons.local) {
|
|
1483
1602
|
for (const css of ctx.icons.local) {
|
|
1484
1603
|
nuxt.options.css.push(css);
|
|
@@ -1498,43 +1617,67 @@ async function load(options, nuxt, ctx) {
|
|
|
1498
1617
|
}
|
|
1499
1618
|
}
|
|
1500
1619
|
}
|
|
1501
|
-
function
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return nuxt.callHook("restart");
|
|
1620
|
+
function bindInvalidator(graph) {
|
|
1621
|
+
return () => {
|
|
1622
|
+
for (const id of RESOLVED_VIRTUAL_MODULES) {
|
|
1623
|
+
const mod = graph.getModuleById(id);
|
|
1624
|
+
if (mod) {
|
|
1625
|
+
graph.invalidateModule(mod);
|
|
1508
1626
|
}
|
|
1509
|
-
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1627
|
+
}
|
|
1628
|
+
};
|
|
1629
|
+
}
|
|
1630
|
+
function registerWatcher(options, nuxt, ctx) {
|
|
1631
|
+
if (!nuxt.options.dev) {
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (!ctx.canHmrConfig) {
|
|
1635
|
+
for (const file of ctx.vuetifyFilesToWatch) {
|
|
1636
|
+
nuxt.options.watch.push(file);
|
|
1637
|
+
}
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
let clientServer;
|
|
1641
|
+
let invalidateSsrModules;
|
|
1642
|
+
nuxt.hook("vite:serverCreated", (server, { isClient }) => {
|
|
1643
|
+
if (!isClient) {
|
|
1644
|
+
invalidateSsrModules = bindInvalidator(server.moduleGraph);
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
if (!server.ws) {
|
|
1648
|
+
return;
|
|
1649
|
+
}
|
|
1650
|
+
clientServer = server;
|
|
1651
|
+
const ssrEnv = server.environments?.ssr;
|
|
1652
|
+
if (ssrEnv) {
|
|
1653
|
+
invalidateSsrModules ??= bindInvalidator(ssrEnv.moduleGraph);
|
|
1654
|
+
}
|
|
1655
|
+
server.watcher.add(ctx.vuetifyFilesToWatch);
|
|
1656
|
+
});
|
|
1657
|
+
async function reloadConfig() {
|
|
1658
|
+
await load(options, nuxt, ctx, true);
|
|
1659
|
+
invalidateSsrModules?.();
|
|
1660
|
+
clientServer?.ws.send({ type: "full-reload" });
|
|
1661
|
+
}
|
|
1662
|
+
addVitePlugin({
|
|
1663
|
+
name: "vuetify:configuration:watch",
|
|
1664
|
+
enforce: "pre",
|
|
1665
|
+
async handleHotUpdate({ file }) {
|
|
1666
|
+
if (clientServer && ctx.vuetifyFilesToWatch.includes(file)) {
|
|
1667
|
+
await reloadConfig();
|
|
1668
|
+
return [];
|
|
1535
1669
|
}
|
|
1536
|
-
}
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
const MIN_NUXT_VERSION_FOR_SSR_CONFIG_HMR = "3.18.0";
|
|
1675
|
+
function supportsSsrConfigHmr(nuxtVersion) {
|
|
1676
|
+
const parsed = semver.parse(nuxtVersion) ?? semver.coerce(nuxtVersion);
|
|
1677
|
+
if (!parsed) {
|
|
1678
|
+
return false;
|
|
1537
1679
|
}
|
|
1680
|
+
return semver.gte(parsed.version, MIN_NUXT_VERSION_FOR_SSR_CONFIG_HMR);
|
|
1538
1681
|
}
|
|
1539
1682
|
|
|
1540
1683
|
const CONFIG_KEY = "vuetify";
|
|
@@ -1548,24 +1691,6 @@ const module$1 = defineNuxtModule({
|
|
|
1548
1691
|
},
|
|
1549
1692
|
version
|
|
1550
1693
|
},
|
|
1551
|
-
/**
|
|
1552
|
-
* Default configuration options of the Nuxt module
|
|
1553
|
-
*/
|
|
1554
|
-
defaults: () => ({
|
|
1555
|
-
vuetifyOptions: {
|
|
1556
|
-
labComponents: false,
|
|
1557
|
-
directives: false
|
|
1558
|
-
},
|
|
1559
|
-
moduleOptions: {
|
|
1560
|
-
importComposables: true,
|
|
1561
|
-
includeTransformAssetsUrls: true,
|
|
1562
|
-
styles: true,
|
|
1563
|
-
disableVuetifyStyles: false,
|
|
1564
|
-
rulesConfiguration: {
|
|
1565
|
-
fromLabs: true
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
}),
|
|
1569
1694
|
/**
|
|
1570
1695
|
* Sets up the Vuetify Nuxt module.
|
|
1571
1696
|
*
|
|
@@ -1601,6 +1726,7 @@ const module$1 = defineNuxtModule({
|
|
|
1601
1726
|
moduleOptions: void 0,
|
|
1602
1727
|
vuetifyOptions: void 0,
|
|
1603
1728
|
vuetifyFilesToWatch: [],
|
|
1729
|
+
canHmrConfig: !nuxt.options.ssr || supportsSsrConfigHmr(getNuxtVersion(nuxt)),
|
|
1604
1730
|
isSSR: nuxt.options.ssr,
|
|
1605
1731
|
isDev: nuxt.options.dev,
|
|
1606
1732
|
isNuxtGenerate: !!nuxt.options.nitro.static,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session cookie marking that the one-time `reloadOnFirstRequest` reload has
|
|
3
|
+
* already happened this browser session. Prevents an infinite reload loop when
|
|
4
|
+
* a browser requests client hints but never delivers them (e.g. Brave Shields
|
|
5
|
+
* strip `Sec-CH-*`), since `firstRequest` would otherwise stay `true` forever (#334).
|
|
6
|
+
*/
|
|
7
|
+
export declare const RELOAD_GUARD_COOKIE = "vuetify-nuxt-client-hints-reloaded";
|
|
8
|
+
/** True when the guard cookie is present in a `document.cookie` string. */
|
|
9
|
+
export declare function hasReloadGuardCookie(cookie: string): boolean;
|
|
10
|
+
/** Build a session guard cookie (no expiry → cleared on browser close). */
|
|
11
|
+
export declare function buildReloadGuardCookie(path: string): string;
|
|
12
|
+
/** Whether to perform the first-request reload: only once per session. */
|
|
13
|
+
export declare function shouldReloadOnFirstRequest(firstRequest: boolean, reloadOnFirstRequest: boolean, alreadyReloaded: boolean): boolean;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const RELOAD_GUARD_COOKIE = "vuetify-nuxt-client-hints-reloaded";
|
|
2
|
+
export function hasReloadGuardCookie(cookie) {
|
|
3
|
+
const prefix = `${RELOAD_GUARD_COOKIE}=`;
|
|
4
|
+
return cookie.split(";").some((c) => c.trim().startsWith(prefix));
|
|
5
|
+
}
|
|
6
|
+
export function buildReloadGuardCookie(path) {
|
|
7
|
+
return `${RELOAD_GUARD_COOKIE}=1; Path=${path}; SameSite=Lax`;
|
|
8
|
+
}
|
|
9
|
+
export function shouldReloadOnFirstRequest(firstRequest, reloadOnFirstRequest, alreadyReloaded) {
|
|
10
|
+
return firstRequest && reloadOnFirstRequest && !alreadyReloaded;
|
|
11
|
+
}
|
|
@@ -2,6 +2,7 @@ import { defineNuxtPlugin, useNuxtApp, useState } from "#imports";
|
|
|
2
2
|
import { ssrClientHintsConfiguration } from "virtual:vuetify-ssr-client-hints-configuration";
|
|
3
3
|
import { reactive, ref, watch } from "vue";
|
|
4
4
|
import { VuetifyHTTPClientHints } from "./client-hints.js";
|
|
5
|
+
import { buildReloadGuardCookie, hasReloadGuardCookie, shouldReloadOnFirstRequest } from "./first-request-reload-guard.js";
|
|
5
6
|
const plugin = defineNuxtPlugin({
|
|
6
7
|
name: "vuetify:client-hints:client:plugin",
|
|
7
8
|
order: -25,
|
|
@@ -22,7 +23,11 @@ const plugin = defineNuxtPlugin({
|
|
|
22
23
|
prefersColorScheme,
|
|
23
24
|
prefersColorSchemeOptions
|
|
24
25
|
} = ssrClientHintsConfiguration;
|
|
25
|
-
if (firstRequest
|
|
26
|
+
if (shouldReloadOnFirstRequest(firstRequest, reloadOnFirstRequest, hasReloadGuardCookie(document.cookie))) {
|
|
27
|
+
const markAndReload = () => {
|
|
28
|
+
document.cookie = buildReloadGuardCookie(prefersColorSchemeOptions?.baseUrl ?? "/");
|
|
29
|
+
window.location.reload();
|
|
30
|
+
};
|
|
26
31
|
if (prefersColorScheme) {
|
|
27
32
|
const themeCookie = state.value.colorSchemeCookie;
|
|
28
33
|
if (prefersColorSchemeOptions && themeCookie) {
|
|
@@ -32,19 +37,19 @@ const plugin = defineNuxtPlugin({
|
|
|
32
37
|
const cookieEntry = `${parseCookieName}${state.value.colorSchemeFromCookie ?? prefersColorSchemeOptions.defaultTheme};`;
|
|
33
38
|
const newThemeName = prefersDark ? prefersColorSchemeOptions.darkThemeName : prefersColorSchemeOptions.lightThemeName;
|
|
34
39
|
document.cookie = themeCookie.replace(cookieEntry, `${cookieName}=${newThemeName};`);
|
|
35
|
-
|
|
40
|
+
markAndReload();
|
|
36
41
|
} else if (prefersColorSchemeAvailable) {
|
|
37
|
-
|
|
42
|
+
markAndReload();
|
|
38
43
|
}
|
|
39
44
|
}
|
|
40
45
|
if (prefersReducedMotion && prefersReducedMotionAvailable) {
|
|
41
|
-
|
|
46
|
+
markAndReload();
|
|
42
47
|
}
|
|
43
48
|
if (viewportSize && viewportHeightAvailable) {
|
|
44
|
-
|
|
49
|
+
markAndReload();
|
|
45
50
|
}
|
|
46
51
|
if (viewportSize && viewportWidthAvailable) {
|
|
47
|
-
|
|
52
|
+
markAndReload();
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
nuxtApp.hook("vuetify:before-create", ({ vuetifyOptions }) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vuetify-nuxt-module",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-
|
|
4
|
+
"version": "1.0.0-rc.1",
|
|
5
5
|
"description": "Zero-Config Nuxt Module for Vuetify",
|
|
6
6
|
"author": "userquin <userquin@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -51,50 +51,52 @@
|
|
|
51
51
|
"*.mjs"
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@nuxt/kit": "^4.
|
|
54
|
+
"@nuxt/kit": "^4.4.8",
|
|
55
|
+
"@vuetify/loader-shared": "^2.1.2",
|
|
55
56
|
"@vuetify/unplugin-styles": "^1.0.0-beta.11",
|
|
56
|
-
"defu": "^6.1.
|
|
57
|
-
"
|
|
58
|
-
"local-pkg": "^1.1.2",
|
|
57
|
+
"defu": "^6.1.7",
|
|
58
|
+
"local-pkg": "^1.2.1",
|
|
59
59
|
"pathe": "^2.0.3",
|
|
60
60
|
"perfect-debounce": "^2.1.0",
|
|
61
|
-
"semver": "^7.
|
|
62
|
-
"ufo": "^1.6.
|
|
63
|
-
"unconfig": "^7.5.0"
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
"semver": "^7.8.4",
|
|
62
|
+
"ufo": "^1.6.4",
|
|
63
|
+
"unconfig": "^7.5.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"vuetify": "^3.4.0 || ^4.0.0"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
|
-
"@antfu/eslint-config": "^7.
|
|
69
|
-
"@antfu/ni": "^28.
|
|
69
|
+
"@antfu/eslint-config": "^7.7.3",
|
|
70
|
+
"@antfu/ni": "^28.3.0",
|
|
70
71
|
"@date-io/luxon": "^3.2.0",
|
|
71
72
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
|
72
73
|
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
|
73
|
-
"@fortawesome/vue-fontawesome": "^3.
|
|
74
|
-
"@iconify-json/carbon": "^1.2.
|
|
74
|
+
"@fortawesome/vue-fontawesome": "^3.2.0",
|
|
75
|
+
"@iconify-json/carbon": "^1.2.23",
|
|
75
76
|
"@iconify-json/mdi": "^1.2.3",
|
|
76
77
|
"@mdi/js": "^7.4.47",
|
|
77
78
|
"@nuxt/devtools": "latest",
|
|
78
79
|
"@nuxt/module-builder": "^1.0.2",
|
|
79
|
-
"@nuxt/schema": "^4.
|
|
80
|
-
"@nuxt/test-utils": "^4.0.
|
|
81
|
-
"@nuxtjs/i18n": "^10.
|
|
80
|
+
"@nuxt/schema": "^4.4.8",
|
|
81
|
+
"@nuxt/test-utils": "^4.0.3",
|
|
82
|
+
"@nuxtjs/i18n": "^10.4.0",
|
|
82
83
|
"@parcel/watcher": "^2.5.6",
|
|
83
|
-
"@types/node": "^25.
|
|
84
|
+
"@types/node": "^25.9.3",
|
|
84
85
|
"@types/semver": "^7.7.1",
|
|
85
|
-
"@unocss/nuxt": "^66.
|
|
86
|
-
"bumpp": "^
|
|
87
|
-
"eslint": "^10.0
|
|
86
|
+
"@unocss/nuxt": "^66.7.2",
|
|
87
|
+
"bumpp": "^11.1.0",
|
|
88
|
+
"eslint": "^10.5.0",
|
|
88
89
|
"luxon": "^3.7.2",
|
|
89
|
-
"nuxt": "^4.
|
|
90
|
-
"playwright-core": "^1.
|
|
91
|
-
"publint": "^0.3.
|
|
90
|
+
"nuxt": "^4.4.8",
|
|
91
|
+
"playwright-core": "^1.61.0",
|
|
92
|
+
"publint": "^0.3.21",
|
|
92
93
|
"rimraf": "^6.1.3",
|
|
93
|
-
"sass": "^1.
|
|
94
|
+
"sass": "^1.101.0",
|
|
94
95
|
"typescript": "^5.9.3",
|
|
95
|
-
"vite": "7.3.
|
|
96
|
-
"vitest": "^4.
|
|
97
|
-
"vue-tsc": "^3.
|
|
96
|
+
"vite": "7.3.5",
|
|
97
|
+
"vitest": "^4.1.9",
|
|
98
|
+
"vue-tsc": "^3.3.5",
|
|
99
|
+
"vuetify": "^4.1.2"
|
|
98
100
|
},
|
|
99
101
|
"build": {
|
|
100
102
|
"externals": [
|
|
@@ -103,7 +105,6 @@
|
|
|
103
105
|
"node:child_process",
|
|
104
106
|
"node:fs",
|
|
105
107
|
"consola",
|
|
106
|
-
"destr",
|
|
107
108
|
"esbuild",
|
|
108
109
|
"local-pkg",
|
|
109
110
|
"pathe",
|
|
@@ -114,7 +115,6 @@
|
|
|
114
115
|
"ufo",
|
|
115
116
|
"unconfig",
|
|
116
117
|
"vite",
|
|
117
|
-
"vite-plugin-vuetify",
|
|
118
118
|
"vuetify"
|
|
119
119
|
]
|
|
120
120
|
},
|