vuetify-nuxt-module 1.0.0-beta.8 → 1.0.0-beta.9

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 CHANGED
@@ -51,7 +51,10 @@
51
51
 
52
52
  > Requires Vite, will not work with Webpack
53
53
 
54
+ `vuetify` is a peer dependency (Vuetify 3 or 4) — install it alongside the module:
55
+
54
56
  ```bash
57
+ npm install -D vuetify
55
58
  npx nuxt module add vuetify-nuxt-module
56
59
  ```
57
60
 
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, string>;
35
+ formats?: Record<string, Intl.DateTimeFormatOptions>;
31
36
  /**
32
37
  * Locales.
33
38
  *
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.15.0"
6
6
  },
7
- "version": "1.0.0-beta.8",
7
+ "version": "1.0.0-beta.9",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -5,10 +5,9 @@ import { isAbsolute, resolve, dirname, relative } 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 'vite-plugin-vuetify';
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
11
  import destr from 'destr';
13
12
  import { parseQuery, parseURL } from 'ufo';
14
13
  import { readFile } from 'node:fs/promises';
@@ -16,7 +15,7 @@ import { debounce } from 'perfect-debounce';
16
15
  import process from 'node:process';
17
16
  import { createConfigLoader } from 'unconfig';
18
17
 
19
- const version = "1.0.0-beta.8";
18
+ const version = "1.0.0-beta.9";
20
19
 
21
20
  const VIRTUAL_VUETIFY_CONFIGURATION = "virtual:vuetify-configuration";
22
21
  const RESOLVED_VIRTUAL_VUETIFY_CONFIGURATION = `\0${VIRTUAL_VUETIFY_CONFIGURATION}`;
@@ -596,6 +595,70 @@ ${useLocales.map((locale) => {
596
595
  };
597
596
  }
598
597
 
598
+ const VUETIFY_TO_DATE_FNS = {
599
+ en: "enUS",
600
+ fa: "faIR",
601
+ no: "nb",
602
+ srCyrl: "sr",
603
+ zhHans: "zhCN",
604
+ zhHant: "zhTW"
605
+ };
606
+ const DATE_FNS_SUPPORTED = /* @__PURE__ */ new Set([
607
+ "af",
608
+ "ar",
609
+ "az",
610
+ "bg",
611
+ "ca",
612
+ "ckb",
613
+ "cs",
614
+ "da",
615
+ "de",
616
+ "el",
617
+ "enUS",
618
+ "es",
619
+ "et",
620
+ "faIR",
621
+ "fi",
622
+ "fr",
623
+ "he",
624
+ "hr",
625
+ "hu",
626
+ "id",
627
+ "it",
628
+ "ja",
629
+ "km",
630
+ "ko",
631
+ "lt",
632
+ "lv",
633
+ "nb",
634
+ "nl",
635
+ "pl",
636
+ "pt",
637
+ "ro",
638
+ "ru",
639
+ "sk",
640
+ "sl",
641
+ "sr",
642
+ "srLatn",
643
+ "sv",
644
+ "th",
645
+ "tr",
646
+ "uk",
647
+ "vi",
648
+ "zhCN",
649
+ "zhTW"
650
+ ]);
651
+ function resolveDateFnsLocaleName(code) {
652
+ if (!code) {
653
+ return { name: "enUS", fallback: true };
654
+ }
655
+ const candidate = VUETIFY_TO_DATE_FNS[code] ?? code;
656
+ if (DATE_FNS_SUPPORTED.has(candidate)) {
657
+ return { name: candidate, fallback: false };
658
+ }
659
+ return { name: "enUS", fallback: true };
660
+ }
661
+
599
662
  function vuetifyDateConfigurationPlugin(ctx) {
600
663
  return {
601
664
  name: "vuetify:date-configuration:nuxt",
@@ -619,34 +682,41 @@ export function dateConfiguration() {
619
682
  `;
620
683
  }
621
684
  const { adapter: _adapter, ...newDateOptions } = ctx.vuetifyOptions.date ?? {};
622
- return `${buildImports()}
685
+ let dateFnsLocale;
686
+ if (ctx.dateAdapter === "date-fns") {
687
+ const resolved = resolveDateFnsLocaleName(ctx.vuetifyOptions.locale?.locale);
688
+ dateFnsLocale = resolved.name;
689
+ if (resolved.fallback) {
690
+ 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.`);
691
+ }
692
+ }
693
+ return `${buildImports(dateFnsLocale)}
623
694
  export const enabled = true
624
695
  export const isDev = ${ctx.isDev}
625
696
  export const i18n = ${ctx.i18n}
626
697
  export const adapter = '${ctx.dateAdapter}'
627
698
  export function dateConfiguration() {
628
699
  const options = JSON.parse('${JSON.stringify(newDateOptions)}')
629
- ${buildAdapter()}
700
+ ${buildAdapter(dateFnsLocale)}
630
701
  return options
631
702
  }
632
703
  `;
633
704
  }
634
705
  }
635
706
  };
636
- function buildAdapter() {
707
+ function buildAdapter(dateFnsLocale) {
637
708
  if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
638
709
  return "";
639
710
  }
640
711
  if (ctx.dateAdapter === "vuetify") {
641
712
  return "options.adapter = VuetifyDateAdapter";
642
713
  }
643
- const locale = ctx.vuetifyOptions.locale?.locale ?? "en";
644
714
  if (ctx.dateAdapter === "date-fns") {
645
- return `options.adapter = new Adapter({ locale: ${locale} })`;
715
+ return `options.adapter = new Adapter({ locale: ${dateFnsLocale} })`;
646
716
  }
647
717
  return "options.adapter = Adapter";
648
718
  }
649
- function buildImports() {
719
+ function buildImports(dateFnsLocale) {
650
720
  if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
651
721
  return "";
652
722
  }
@@ -655,7 +725,7 @@ export function dateConfiguration() {
655
725
  }
656
726
  const imports = [`import Adapter from '@date-io/${ctx.dateAdapter}'`];
657
727
  if (ctx.dateAdapter === "date-fns") {
658
- imports.push(`import { ${ctx.vuetifyOptions.locale?.locale ?? "en"} } from 'date-fns/locale'`);
728
+ imports.push(`import { ${dateFnsLocale} } from 'date-fns/locale'`);
659
729
  }
660
730
  return imports.join("\n");
661
731
  }
@@ -1266,6 +1336,29 @@ async function loadVuetifyConfiguration(cwd = process.cwd(), configOrPath = cwd,
1266
1336
  return result;
1267
1337
  }
1268
1338
 
1339
+ const MODULE_DEFAULTS = {
1340
+ moduleOptions: {
1341
+ importComposables: true,
1342
+ includeTransformAssetsUrls: true,
1343
+ styles: true,
1344
+ rulesConfiguration: {
1345
+ fromLabs: true
1346
+ }
1347
+ },
1348
+ vuetifyOptions: {
1349
+ labComponents: false,
1350
+ directives: false
1351
+ }
1352
+ };
1353
+ function finalizeConfiguration(moduleOptions) {
1354
+ if (moduleOptions.length > 1) {
1355
+ const [app, ...rest] = moduleOptions;
1356
+ const configuration = defu(app, ...rest, MODULE_DEFAULTS);
1357
+ dedupeIcons(configuration, moduleOptions.toReversed());
1358
+ return configuration;
1359
+ }
1360
+ return defu(moduleOptions[0] ?? {}, MODULE_DEFAULTS);
1361
+ }
1269
1362
  async function mergeVuetifyModules(options, nuxt) {
1270
1363
  const moduleOptions = [];
1271
1364
  const vuetifyConfigurationFilesToWatch = /* @__PURE__ */ new Set();
@@ -1307,23 +1400,10 @@ async function mergeVuetifyModules(options, nuxt) {
1307
1400
  moduleOptions: options.moduleOptions,
1308
1401
  vuetifyOptions: resolvedOptions.config
1309
1402
  });
1310
- if (moduleOptions.length > 1) {
1311
- const [app, ...rest] = moduleOptions;
1312
- const configuration = defu(app, ...rest);
1313
- dedupeIcons(configuration, moduleOptions.toReversed());
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
- }
1403
+ return {
1404
+ configuration: finalizeConfiguration(moduleOptions),
1405
+ vuetifyConfigurationFilesToWatch
1406
+ };
1327
1407
  }
1328
1408
  function dedupeIcons(configuration, moduleOptions) {
1329
1409
  const vuetifyOptions = configuration.vuetifyOptions;
@@ -1368,6 +1448,16 @@ function resolveColorSchemeCookie(options, logger) {
1368
1448
  cookieSameSite
1369
1449
  };
1370
1450
  }
1451
+ function resolveDefaultTheme(defaultTheme, themes, lightThemeName, logger) {
1452
+ if (defaultTheme === "system") {
1453
+ 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.`);
1454
+ return lightThemeName;
1455
+ }
1456
+ if (!themes[defaultTheme]) {
1457
+ throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
1458
+ }
1459
+ return defaultTheme;
1460
+ }
1371
1461
  function prepareSSRClientHints(baseUrl, ctx) {
1372
1462
  if (!ctx.isSSR || ctx.isNuxtGenerate) {
1373
1463
  return disabledClientHints;
@@ -1394,9 +1484,6 @@ function prepareSSRClientHints(baseUrl, ctx) {
1394
1484
  if (!defaultTheme) {
1395
1485
  throw new Error("Vuetify default theme is missing in theme!");
1396
1486
  }
1397
- if (!themes[defaultTheme]) {
1398
- throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
1399
- }
1400
1487
  const darkThemeName = ssrClientHintsConfiguration.prefersColorSchemeOptions?.darkThemeName ?? "dark";
1401
1488
  if (!themes[darkThemeName]) {
1402
1489
  throw new Error(`Missing theme ${darkThemeName} in the Vuetify themes!`);
@@ -1409,9 +1496,10 @@ function prepareSSRClientHints(baseUrl, ctx) {
1409
1496
  throw new Error("Vuetify dark theme and light theme are the same, change darkThemeName or lightThemeName!");
1410
1497
  }
1411
1498
  const pcsOptions = ssrClientHintsConfiguration.prefersColorSchemeOptions;
1499
+ const effectiveDefaultTheme = resolveDefaultTheme(defaultTheme, themes, lightThemeName, ctx.logger);
1412
1500
  clientHints.prefersColorSchemeOptions = {
1413
1501
  baseUrl,
1414
- defaultTheme,
1502
+ defaultTheme: effectiveDefaultTheme,
1415
1503
  themeNames: Array.from(Object.keys(themes)),
1416
1504
  ...resolveColorSchemeCookie(pcsOptions, ctx.logger),
1417
1505
  darkThemeName,
@@ -1478,6 +1566,9 @@ async function load(options, nuxt, ctx) {
1478
1566
  ctx.vuetifyFilesToWatch = Array.from(vuetifyConfigurationFilesToWatch);
1479
1567
  ctx.icons = prepareIcons(ctx.unocss, ctx.logger, vuetifyAppOptions, ctx.resolvePaths);
1480
1568
  ctx.ssrClientHints = prepareSSRClientHints(nuxt.options.app.baseURL ?? "/", ctx);
1569
+ if (ctx.isSSR && !ctx.ssrClientHints.prefersColorScheme && ctx.vuetifyOptions.theme && typeof ctx.vuetifyOptions.theme === "object" && ctx.vuetifyOptions.theme.defaultTheme === "system") {
1570
+ 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.');
1571
+ }
1481
1572
  if (ctx.icons.enabled) {
1482
1573
  if (ctx.icons.local) {
1483
1574
  for (const css of ctx.icons.local) {
@@ -1548,24 +1639,6 @@ const module$1 = defineNuxtModule({
1548
1639
  },
1549
1640
  version
1550
1641
  },
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
1642
  /**
1570
1643
  * Sets up the Vuetify Nuxt module.
1571
1644
  *
@@ -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 && reloadOnFirstRequest) {
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
- window.location.reload();
40
+ markAndReload();
36
41
  } else if (prefersColorSchemeAvailable) {
37
- window.location.reload();
42
+ markAndReload();
38
43
  }
39
44
  }
40
45
  if (prefersReducedMotion && prefersReducedMotionAvailable) {
41
- window.location.reload();
46
+ markAndReload();
42
47
  }
43
48
  if (viewportSize && viewportHeightAvailable) {
44
- window.location.reload();
49
+ markAndReload();
45
50
  }
46
51
  if (viewportSize && viewportWidthAvailable) {
47
- window.location.reload();
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-beta.8",
4
+ "version": "1.0.0-beta.9",
5
5
  "description": "Zero-Config Nuxt Module for Vuetify",
6
6
  "author": "userquin <userquin@gmail.com>",
7
7
  "license": "MIT",
@@ -52,6 +52,7 @@
52
52
  ],
53
53
  "dependencies": {
54
54
  "@nuxt/kit": "^4.3.1",
55
+ "@vuetify/loader-shared": "^2.1.2",
55
56
  "@vuetify/unplugin-styles": "^1.0.0-beta.11",
56
57
  "defu": "^6.1.4",
57
58
  "destr": "^2.0.5",
@@ -60,9 +61,10 @@
60
61
  "perfect-debounce": "^2.1.0",
61
62
  "semver": "^7.7.4",
62
63
  "ufo": "^1.6.3",
63
- "unconfig": "^7.5.0",
64
- "vite-plugin-vuetify": "^2.1.3",
65
- "vuetify": "^4.0.1"
64
+ "unconfig": "^7.5.0"
65
+ },
66
+ "peerDependencies": {
67
+ "vuetify": "^3.4.0 || ^4.0.0"
66
68
  },
67
69
  "devDependencies": {
68
70
  "@antfu/eslint-config": "^7.6.1",
@@ -94,7 +96,8 @@
94
96
  "typescript": "^5.9.3",
95
97
  "vite": "7.3.1",
96
98
  "vitest": "^4.0.18",
97
- "vue-tsc": "^3.2.5"
99
+ "vue-tsc": "^3.2.5",
100
+ "vuetify": "^4.0.1"
98
101
  },
99
102
  "build": {
100
103
  "externals": [
@@ -114,7 +117,6 @@
114
117
  "ufo",
115
118
  "unconfig",
116
119
  "vite",
117
- "vite-plugin-vuetify",
118
120
  "vuetify"
119
121
  ]
120
122
  },