vuetify-nuxt-module 1.0.0-beta.1 → 1.0.0-beta.10
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 +4 -1
- package/configuration.d.ts +3 -0
- package/dist/module.d.mts +55 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +232 -331
- 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/i18n.js +6 -1
- package/dist/runtime/plugins/vuetify-client-hints.client.js +21 -7
- package/dist/runtime/plugins/vuetify-client-hints.server.js +14 -2
- package/package.json +14 -12
package/README.md
CHANGED
|
@@ -51,8 +51,11 @@
|
|
|
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
|
|
55
|
-
|
|
57
|
+
npm install -D vuetify
|
|
58
|
+
npx nuxt module add vuetify-nuxt-module
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
[](https://stackblitz.com/github/userquin/vuetify-nuxt-module)
|
package/configuration.d.ts
CHANGED
|
@@ -36,6 +36,9 @@ declare module 'virtual:vuetify-ssr-client-hints-configuration' {
|
|
|
36
36
|
defaultTheme: string
|
|
37
37
|
themeNames: string[]
|
|
38
38
|
cookieName: string
|
|
39
|
+
cookieDomain?: string
|
|
40
|
+
cookieSecure?: boolean
|
|
41
|
+
cookieSameSite: 'lax' | 'strict' | 'none'
|
|
39
42
|
darkThemeName: string
|
|
40
43
|
lightThemeName: string
|
|
41
44
|
useBrowserThemeOnly: boolean
|
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
|
*
|
|
@@ -251,6 +256,18 @@ interface MOptions {
|
|
|
251
256
|
* Path to the custom Vuetify SASS configuration file.
|
|
252
257
|
*/
|
|
253
258
|
configFile: string;
|
|
259
|
+
/**
|
|
260
|
+
* Caching options forwarded to `@vuetify/unplugin-styles`.
|
|
261
|
+
*
|
|
262
|
+
* @default true
|
|
263
|
+
*/
|
|
264
|
+
cache?: boolean | {
|
|
265
|
+
path?: string;
|
|
266
|
+
sassOptions?: Record<string, unknown>;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* @deprecated Use `styles.cache` instead.
|
|
270
|
+
*/
|
|
254
271
|
experimental?: {
|
|
255
272
|
cache?: boolean;
|
|
256
273
|
};
|
|
@@ -350,9 +367,43 @@ interface MOptions {
|
|
|
350
367
|
/**
|
|
351
368
|
* The name for the cookie.
|
|
352
369
|
*
|
|
370
|
+
* @deprecated Use `cookie.name` instead.
|
|
353
371
|
* @default 'color-scheme'
|
|
354
372
|
*/
|
|
355
373
|
cookieName?: string;
|
|
374
|
+
/**
|
|
375
|
+
* Cookie attributes for the color scheme cookie.
|
|
376
|
+
*/
|
|
377
|
+
cookie?: {
|
|
378
|
+
/**
|
|
379
|
+
* The name for the cookie.
|
|
380
|
+
*
|
|
381
|
+
* @default 'color-scheme'
|
|
382
|
+
*/
|
|
383
|
+
name?: string;
|
|
384
|
+
/**
|
|
385
|
+
* The domain for the color scheme cookie.
|
|
386
|
+
*
|
|
387
|
+
* Useful to share the cookie across subdomains, e.g. `.example.com`.
|
|
388
|
+
*
|
|
389
|
+
* @default undefined
|
|
390
|
+
*/
|
|
391
|
+
domain?: string;
|
|
392
|
+
/**
|
|
393
|
+
* Mark the cookie as `Secure`.
|
|
394
|
+
*
|
|
395
|
+
* Forced to `true` when `sameSite` is `'none'`.
|
|
396
|
+
*
|
|
397
|
+
* @default undefined
|
|
398
|
+
*/
|
|
399
|
+
secure?: boolean;
|
|
400
|
+
/**
|
|
401
|
+
* The `SameSite` attribute for the cookie.
|
|
402
|
+
*
|
|
403
|
+
* @default 'lax'
|
|
404
|
+
*/
|
|
405
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
406
|
+
};
|
|
356
407
|
/**
|
|
357
408
|
* The name for the dark theme.
|
|
358
409
|
*
|
|
@@ -447,6 +498,9 @@ interface SSRClientHintsConfiguration {
|
|
|
447
498
|
defaultTheme: string;
|
|
448
499
|
themeNames: string[];
|
|
449
500
|
cookieName: string;
|
|
501
|
+
cookieDomain?: string;
|
|
502
|
+
cookieSecure?: boolean;
|
|
503
|
+
cookieSameSite: 'lax' | 'strict' | 'none';
|
|
450
504
|
darkThemeName: string;
|
|
451
505
|
lightThemeName: string;
|
|
452
506
|
};
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
3
|
+
import { addPluginTemplate, resolvePath, addTemplate, extendWebpackConfig, isNuxtMajorVersion, addImports, addPlugin, addVitePlugin, useLogger, defineNuxtModule, getNuxtVersion, findPath, hasNuxtModule, createResolver } from '@nuxt/kit';
|
|
4
|
+
import { isAbsolute, resolve, dirname, relative } from 'pathe';
|
|
3
5
|
import semver from 'semver';
|
|
4
6
|
import { createFilter, version as version$1 } from 'vite';
|
|
5
|
-
import fs, { existsSync, statSync, readFileSync, readdirSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { isAbsolute, resolve, relative } from 'pathe';
|
|
7
7
|
import defu from 'defu';
|
|
8
|
-
import { transformAssetUrls } from '
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import destr from 'destr';
|
|
8
|
+
import { transformAssetUrls, generateImports } from '@vuetify/loader-shared';
|
|
9
|
+
import Styles from '@vuetify/unplugin-styles/vite';
|
|
10
|
+
import { isPackageExists } from 'local-pkg';
|
|
12
11
|
import { parseQuery, parseURL } from 'ufo';
|
|
13
|
-
import
|
|
14
|
-
import path from 'upath';
|
|
15
|
-
import { resolve as resolve$1, dirname, join, relative as relative$1 } from 'node:path';
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
16
13
|
import { debounce } from 'perfect-debounce';
|
|
17
14
|
import process from 'node:process';
|
|
18
15
|
import { createConfigLoader } from 'unconfig';
|
|
19
|
-
import { createHash } from 'node:crypto';
|
|
20
16
|
|
|
21
|
-
const version = "1.0.0-beta.
|
|
17
|
+
const version = "1.0.0-beta.10";
|
|
22
18
|
|
|
23
19
|
const VIRTUAL_VUETIFY_CONFIGURATION = "virtual:vuetify-configuration";
|
|
24
20
|
const RESOLVED_VIRTUAL_VUETIFY_CONFIGURATION = `\0${VIRTUAL_VUETIFY_CONFIGURATION}`;
|
|
@@ -128,6 +124,18 @@ function normalizeTransformAssetUrlsAttrs(attrs) {
|
|
|
128
124
|
return [...result];
|
|
129
125
|
}
|
|
130
126
|
|
|
127
|
+
function resolveVuetifyConfigFile(configFile, nuxt) {
|
|
128
|
+
if (typeof configFile === "string" && !isAbsolute(configFile)) {
|
|
129
|
+
for (const layer of nuxt.options._layers) {
|
|
130
|
+
const resolved = resolve(layer.config.srcDir, configFile);
|
|
131
|
+
if (existsSync(resolved)) {
|
|
132
|
+
return resolved;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return configFile;
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
function addVuetifyNuxtPlugins(nuxt, ctx) {
|
|
132
140
|
addVuetifyNuxtPlugin(nuxt, ctx, "client");
|
|
133
141
|
addVuetifyNuxtPlugin(nuxt, ctx, "server");
|
|
@@ -198,17 +206,6 @@ export default defineNuxtPlugin({
|
|
|
198
206
|
function getTemplate(source, settings) {
|
|
199
207
|
return [settings ? `@use '${settings}';` : "", `@use '${source}';`].filter(Boolean).join("\n");
|
|
200
208
|
}
|
|
201
|
-
function resolveVuetifyConfigFile(configFile, nuxt) {
|
|
202
|
-
if (typeof configFile === "string" && !isAbsolute(configFile)) {
|
|
203
|
-
for (const layer of nuxt.options._layers) {
|
|
204
|
-
const resolved = resolve(layer.config.rootDir, configFile);
|
|
205
|
-
if (existsSync(resolved)) {
|
|
206
|
-
return resolved;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return configFile;
|
|
211
|
-
}
|
|
212
209
|
async function configureNuxt(configKey, nuxt, ctx) {
|
|
213
210
|
const {
|
|
214
211
|
styles,
|
|
@@ -223,9 +220,14 @@ async function configureNuxt(configKey, nuxt, ctx) {
|
|
|
223
220
|
nuxt.options.css ??= [];
|
|
224
221
|
if (typeof styles === "object" && "configFile" in styles) {
|
|
225
222
|
const configFile = resolveVuetifyConfigFile(styles.configFile, nuxt);
|
|
223
|
+
ctx.stylesConfigFile = await resolvePath(configFile);
|
|
226
224
|
const a = addTemplate({
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
// Write to disk: without `write` Nuxt serves the template from its
|
|
226
|
+
// virtual FS, which 404s on Windows/SSR when the browser requests the
|
|
227
|
+
// real file (#363). Nest under `vuetify/` to match unplugin-styles.
|
|
228
|
+
write: true,
|
|
229
|
+
filename: "vuetify/vuetify.settings.scss",
|
|
230
|
+
getContents: async () => getTemplate("vuetify/styles", ctx.stylesConfigFile)
|
|
229
231
|
});
|
|
230
232
|
nuxt.options.css.push(a.dst);
|
|
231
233
|
} else if (ctx.vuetifyGte("4.0.0")) {
|
|
@@ -592,6 +594,70 @@ ${useLocales.map((locale) => {
|
|
|
592
594
|
};
|
|
593
595
|
}
|
|
594
596
|
|
|
597
|
+
const VUETIFY_TO_DATE_FNS = {
|
|
598
|
+
en: "enUS",
|
|
599
|
+
fa: "faIR",
|
|
600
|
+
no: "nb",
|
|
601
|
+
srCyrl: "sr",
|
|
602
|
+
zhHans: "zhCN",
|
|
603
|
+
zhHant: "zhTW"
|
|
604
|
+
};
|
|
605
|
+
const DATE_FNS_SUPPORTED = /* @__PURE__ */ new Set([
|
|
606
|
+
"af",
|
|
607
|
+
"ar",
|
|
608
|
+
"az",
|
|
609
|
+
"bg",
|
|
610
|
+
"ca",
|
|
611
|
+
"ckb",
|
|
612
|
+
"cs",
|
|
613
|
+
"da",
|
|
614
|
+
"de",
|
|
615
|
+
"el",
|
|
616
|
+
"enUS",
|
|
617
|
+
"es",
|
|
618
|
+
"et",
|
|
619
|
+
"faIR",
|
|
620
|
+
"fi",
|
|
621
|
+
"fr",
|
|
622
|
+
"he",
|
|
623
|
+
"hr",
|
|
624
|
+
"hu",
|
|
625
|
+
"id",
|
|
626
|
+
"it",
|
|
627
|
+
"ja",
|
|
628
|
+
"km",
|
|
629
|
+
"ko",
|
|
630
|
+
"lt",
|
|
631
|
+
"lv",
|
|
632
|
+
"nb",
|
|
633
|
+
"nl",
|
|
634
|
+
"pl",
|
|
635
|
+
"pt",
|
|
636
|
+
"ro",
|
|
637
|
+
"ru",
|
|
638
|
+
"sk",
|
|
639
|
+
"sl",
|
|
640
|
+
"sr",
|
|
641
|
+
"srLatn",
|
|
642
|
+
"sv",
|
|
643
|
+
"th",
|
|
644
|
+
"tr",
|
|
645
|
+
"uk",
|
|
646
|
+
"vi",
|
|
647
|
+
"zhCN",
|
|
648
|
+
"zhTW"
|
|
649
|
+
]);
|
|
650
|
+
function resolveDateFnsLocaleName(code) {
|
|
651
|
+
if (!code) {
|
|
652
|
+
return { name: "enUS", fallback: true };
|
|
653
|
+
}
|
|
654
|
+
const candidate = VUETIFY_TO_DATE_FNS[code] ?? code;
|
|
655
|
+
if (DATE_FNS_SUPPORTED.has(candidate)) {
|
|
656
|
+
return { name: candidate, fallback: false };
|
|
657
|
+
}
|
|
658
|
+
return { name: "enUS", fallback: true };
|
|
659
|
+
}
|
|
660
|
+
|
|
595
661
|
function vuetifyDateConfigurationPlugin(ctx) {
|
|
596
662
|
return {
|
|
597
663
|
name: "vuetify:date-configuration:nuxt",
|
|
@@ -615,34 +681,41 @@ export function dateConfiguration() {
|
|
|
615
681
|
`;
|
|
616
682
|
}
|
|
617
683
|
const { adapter: _adapter, ...newDateOptions } = ctx.vuetifyOptions.date ?? {};
|
|
618
|
-
|
|
684
|
+
let dateFnsLocale;
|
|
685
|
+
if (ctx.dateAdapter === "date-fns") {
|
|
686
|
+
const resolved = resolveDateFnsLocaleName(ctx.vuetifyOptions.locale?.locale);
|
|
687
|
+
dateFnsLocale = resolved.name;
|
|
688
|
+
if (resolved.fallback) {
|
|
689
|
+
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.`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return `${buildImports(dateFnsLocale)}
|
|
619
693
|
export const enabled = true
|
|
620
694
|
export const isDev = ${ctx.isDev}
|
|
621
695
|
export const i18n = ${ctx.i18n}
|
|
622
696
|
export const adapter = '${ctx.dateAdapter}'
|
|
623
697
|
export function dateConfiguration() {
|
|
624
698
|
const options = JSON.parse('${JSON.stringify(newDateOptions)}')
|
|
625
|
-
${buildAdapter()}
|
|
699
|
+
${buildAdapter(dateFnsLocale)}
|
|
626
700
|
return options
|
|
627
701
|
}
|
|
628
702
|
`;
|
|
629
703
|
}
|
|
630
704
|
}
|
|
631
705
|
};
|
|
632
|
-
function buildAdapter() {
|
|
706
|
+
function buildAdapter(dateFnsLocale) {
|
|
633
707
|
if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
|
|
634
708
|
return "";
|
|
635
709
|
}
|
|
636
710
|
if (ctx.dateAdapter === "vuetify") {
|
|
637
711
|
return "options.adapter = VuetifyDateAdapter";
|
|
638
712
|
}
|
|
639
|
-
const locale = ctx.vuetifyOptions.locale?.locale ?? "en";
|
|
640
713
|
if (ctx.dateAdapter === "date-fns") {
|
|
641
|
-
return `options.adapter = new Adapter({ locale: ${
|
|
714
|
+
return `options.adapter = new Adapter({ locale: ${dateFnsLocale} })`;
|
|
642
715
|
}
|
|
643
716
|
return "options.adapter = Adapter";
|
|
644
717
|
}
|
|
645
|
-
function buildImports() {
|
|
718
|
+
function buildImports(dateFnsLocale) {
|
|
646
719
|
if (ctx.dateAdapter === "custom" || ctx.dateAdapter === "vuetify" && ctx.vuetifyGte("3.4.0")) {
|
|
647
720
|
return "";
|
|
648
721
|
}
|
|
@@ -651,7 +724,7 @@ export function dateConfiguration() {
|
|
|
651
724
|
}
|
|
652
725
|
const imports = [`import Adapter from '@date-io/${ctx.dateAdapter}'`];
|
|
653
726
|
if (ctx.dateAdapter === "date-fns") {
|
|
654
|
-
imports.push(`import { ${
|
|
727
|
+
imports.push(`import { ${dateFnsLocale} } from 'date-fns/locale'`);
|
|
655
728
|
}
|
|
656
729
|
return imports.join("\n");
|
|
657
730
|
}
|
|
@@ -837,10 +910,24 @@ function parseId2(id) {
|
|
|
837
910
|
id = id.replace(/^(virtual:nuxt:|virtual:)/, "");
|
|
838
911
|
return parseURL(decodeURIComponent(isAbsolute(id) ? pathToFileURL(id).href : id));
|
|
839
912
|
}
|
|
913
|
+
function reviver(key, value) {
|
|
914
|
+
if (key === "__proto__" || key === "constructor") {
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
917
|
+
return value;
|
|
918
|
+
}
|
|
919
|
+
function parseProps(value) {
|
|
920
|
+
try {
|
|
921
|
+
const parsed = JSON.parse(value, reviver);
|
|
922
|
+
return parsed && typeof parsed === "object" ? parsed : void 0;
|
|
923
|
+
} catch {
|
|
924
|
+
return void 0;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
840
927
|
function parseId(id) {
|
|
841
928
|
const { search, pathname } = parseId2(id);
|
|
842
929
|
const query = parseQuery(search);
|
|
843
|
-
const urlProps = query.props ?
|
|
930
|
+
const urlProps = query.props ? parseProps(query.props) : void 0;
|
|
844
931
|
return {
|
|
845
932
|
query: urlProps,
|
|
846
933
|
path: pathname ?? id
|
|
@@ -901,122 +988,7 @@ function vuetifySSRClientHintsPlugin(ctx) {
|
|
|
901
988
|
};
|
|
902
989
|
}
|
|
903
990
|
|
|
904
|
-
function
|
|
905
|
-
let configFile;
|
|
906
|
-
const options = { styles: ctx.moduleOptions.styles };
|
|
907
|
-
const vuetifyBase = resolveVuetifyBase();
|
|
908
|
-
const noneFiles = /* @__PURE__ */ new Set();
|
|
909
|
-
let isNone = false;
|
|
910
|
-
let sassVariables = false;
|
|
911
|
-
let fileImport = false;
|
|
912
|
-
const PREFIX = "vuetify-styles/";
|
|
913
|
-
const SSR_PREFIX = `/@${PREFIX}`;
|
|
914
|
-
const resolveCss = resolveCssFactory();
|
|
915
|
-
const toPath = (file) => fileImport ? pathToFileURL(file).href : normalizePath(file);
|
|
916
|
-
return {
|
|
917
|
-
name: "vuetify:styles:nuxt",
|
|
918
|
-
enforce: "pre",
|
|
919
|
-
async configResolved(config) {
|
|
920
|
-
if (config.plugins.some((plugin) => plugin.name === "vuetify:styles")) {
|
|
921
|
-
throw new Error("Remove vite-plugin-vuetify from your Nuxt config file, this module registers a modified version.");
|
|
922
|
-
}
|
|
923
|
-
if (isObject(options.styles) && "configFile" in options.styles) {
|
|
924
|
-
sassVariables = true;
|
|
925
|
-
fileImport = semver.gt(ctx.viteVersion, "5.4.2");
|
|
926
|
-
configFile = await resolvePath(options.styles.configFile);
|
|
927
|
-
} else {
|
|
928
|
-
isNone = options.styles === "none";
|
|
929
|
-
}
|
|
930
|
-
},
|
|
931
|
-
async resolveId(source, importer, { custom, ssr }) {
|
|
932
|
-
if (!sassVariables) {
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
if (source.startsWith(PREFIX) || source.startsWith(SSR_PREFIX)) {
|
|
936
|
-
if (/\.s[ca]ss$/.test(source)) {
|
|
937
|
-
return source;
|
|
938
|
-
}
|
|
939
|
-
const idx = source.indexOf("?");
|
|
940
|
-
return idx === -1 ? source : source.slice(0, idx);
|
|
941
|
-
}
|
|
942
|
-
if (importer && source.endsWith(".css") && isSubdir(vuetifyBase, path.isAbsolute(source) ? source : importer)) {
|
|
943
|
-
let resolutionId;
|
|
944
|
-
if (source.startsWith(".")) {
|
|
945
|
-
resolutionId = path.resolve(path.dirname(importer), source);
|
|
946
|
-
} else if (path.isAbsolute(source)) {
|
|
947
|
-
resolutionId = source;
|
|
948
|
-
} else {
|
|
949
|
-
const resolution = await this.resolve(source, importer, { skipSelf: true, custom });
|
|
950
|
-
if (resolution) {
|
|
951
|
-
resolutionId = resolution.id;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
if (!resolutionId) {
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
const target = await resolveCss(resolutionId);
|
|
958
|
-
if (isNone) {
|
|
959
|
-
noneFiles.add(target);
|
|
960
|
-
return target;
|
|
961
|
-
}
|
|
962
|
-
if (ctx.stylesCachePath) {
|
|
963
|
-
const relative = path.relative(vuetifyBase, target);
|
|
964
|
-
const cacheFile = path.resolve(ctx.stylesCachePath, relative.replace(/\.s[ac]ss$/, ".css"));
|
|
965
|
-
if (fs.existsSync(cacheFile)) {
|
|
966
|
-
return cacheFile;
|
|
967
|
-
}
|
|
968
|
-
}
|
|
969
|
-
return `${ssr ? SSR_PREFIX : PREFIX}${path.relative(vuetifyBase, target)}`;
|
|
970
|
-
}
|
|
971
|
-
return void 0;
|
|
972
|
-
},
|
|
973
|
-
load(id) {
|
|
974
|
-
if (sassVariables) {
|
|
975
|
-
const target = id.startsWith(PREFIX) ? path.resolve(vuetifyBase, id.slice(PREFIX.length)) : id.startsWith(SSR_PREFIX) ? path.resolve(vuetifyBase, id.slice(SSR_PREFIX.length)) : void 0;
|
|
976
|
-
if (target) {
|
|
977
|
-
const suffix = /\.scss/.test(target) ? ";\n" : "\n";
|
|
978
|
-
return {
|
|
979
|
-
code: `@use "${toPath(configFile)}"${suffix}@use "${toPath(target)}"${suffix}`,
|
|
980
|
-
map: {
|
|
981
|
-
mappings: ""
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
return isNone && noneFiles.has(id) ? "" : void 0;
|
|
987
|
-
}
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
function resolveCssFactory() {
|
|
991
|
-
const mappings = /* @__PURE__ */ new Map();
|
|
992
|
-
return async (source) => {
|
|
993
|
-
let mapping = mappings.get(source);
|
|
994
|
-
if (!mapping) {
|
|
995
|
-
try {
|
|
996
|
-
mapping = source.replace(/\.css$/, ".sass");
|
|
997
|
-
await fsp.access(mapping, fs.constants.R_OK);
|
|
998
|
-
} catch (error) {
|
|
999
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
|
|
1000
|
-
throw error;
|
|
1001
|
-
}
|
|
1002
|
-
try {
|
|
1003
|
-
mapping = source.replace(/\.css$/, ".scss");
|
|
1004
|
-
await fsp.access(mapping, fs.constants.R_OK);
|
|
1005
|
-
} catch {
|
|
1006
|
-
mapping = source;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
mappings.set(source, mapping);
|
|
1010
|
-
}
|
|
1011
|
-
return mapping;
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
function isSubdir(root, test) {
|
|
1015
|
-
const relative$1 = relative(root, test);
|
|
1016
|
-
return relative$1 && !relative$1.startsWith("..") && !isAbsolute(relative$1);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
function detectDate() {
|
|
991
|
+
function detectDate(paths) {
|
|
1020
992
|
const result = [];
|
|
1021
993
|
for (const adapter of [
|
|
1022
994
|
"date-fns",
|
|
@@ -1028,12 +1000,15 @@ function detectDate() {
|
|
|
1028
1000
|
"jalaali",
|
|
1029
1001
|
"hijri"
|
|
1030
1002
|
]) {
|
|
1031
|
-
if (isPackageExists(`@date-io/${adapter}
|
|
1003
|
+
if (isPackageExists(`@date-io/${adapter}`, { paths })) {
|
|
1032
1004
|
result.push(adapter);
|
|
1033
1005
|
}
|
|
1034
1006
|
}
|
|
1035
1007
|
return result;
|
|
1036
1008
|
}
|
|
1009
|
+
function multipleDateAdaptersError(adapters) {
|
|
1010
|
+
return `Multiple date adapters found: ${adapters.map((adapter) => `@date-io/${adapter}`).join(", ")}, please specify the adapter to use in the "vuetifyOptions.date.adapter" option.`;
|
|
1011
|
+
}
|
|
1037
1012
|
function cleanupBlueprint(vuetifyOptions) {
|
|
1038
1013
|
const blueprint = vuetifyOptions.blueprint;
|
|
1039
1014
|
if (blueprint) {
|
|
@@ -1056,12 +1031,10 @@ function checkVuetifyPlugins(config) {
|
|
|
1056
1031
|
throw new Error("Remove vite-plugin-vuetify plugin from Vite Plugins entry in Nuxt config file!");
|
|
1057
1032
|
}
|
|
1058
1033
|
}
|
|
1059
|
-
function resolveVuetifyComponents(resolver) {
|
|
1060
|
-
const vuetifyBase = resolveVuetifyBase();
|
|
1034
|
+
function resolveVuetifyComponents(resolver, vuetifyBase) {
|
|
1061
1035
|
const componentsPromise = importMapResolver();
|
|
1062
1036
|
const labComponentsPromise = importMapLabResolver();
|
|
1063
1037
|
return {
|
|
1064
|
-
vuetifyBase,
|
|
1065
1038
|
componentsPromise,
|
|
1066
1039
|
labComponentsPromise
|
|
1067
1040
|
};
|
|
@@ -1073,6 +1046,9 @@ function resolveVuetifyComponents(resolver) {
|
|
|
1073
1046
|
}
|
|
1074
1047
|
}
|
|
1075
1048
|
|
|
1049
|
+
function resolveStylesCache(stylesOption) {
|
|
1050
|
+
return stylesOption.cache ?? stylesOption.experimental?.cache;
|
|
1051
|
+
}
|
|
1076
1052
|
function configureVite(configKey, nuxt, ctx) {
|
|
1077
1053
|
nuxt.hook("vite:extend", ({ config }) => checkVuetifyPlugins(config));
|
|
1078
1054
|
nuxt.hook("vite:extendConfig", (viteInlineConfig) => {
|
|
@@ -1081,7 +1057,7 @@ function configureVite(configKey, nuxt, ctx) {
|
|
|
1081
1057
|
if (!ctx.moduleOptions.disableModernSassCompiler) {
|
|
1082
1058
|
const enableModernSassCompiler = semver.gte(ctx.viteVersion, "5.4.0") && semver.lt(ctx.viteVersion, "7.0.0-0");
|
|
1083
1059
|
if (enableModernSassCompiler) {
|
|
1084
|
-
const sassEmbedded = isPackageExists("sass-embedded");
|
|
1060
|
+
const sassEmbedded = isPackageExists("sass-embedded", { paths: ctx.resolvePaths });
|
|
1085
1061
|
if (sassEmbedded) {
|
|
1086
1062
|
viteInlineConfig.css ??= {};
|
|
1087
1063
|
viteInlineConfig.css.preprocessorOptions ??= {};
|
|
@@ -1125,8 +1101,18 @@ function configureVite(configKey, nuxt, ctx) {
|
|
|
1125
1101
|
autoImport.ignore = Array.isArray(ignoreDirectives) ? ignoreDirectives : [ignoreDirectives];
|
|
1126
1102
|
}
|
|
1127
1103
|
viteInlineConfig.plugins.push(vuetifyImportPlugin({ autoImport }));
|
|
1128
|
-
|
|
1129
|
-
|
|
1104
|
+
const stylesOption = ctx.moduleOptions.styles;
|
|
1105
|
+
if (stylesOption === "none") {
|
|
1106
|
+
viteInlineConfig.plugins.push(Styles({ styles: "none" }));
|
|
1107
|
+
} else if (typeof stylesOption === "object" && stylesOption !== null && "configFile" in stylesOption) {
|
|
1108
|
+
if (!ctx.stylesConfigFile) {
|
|
1109
|
+
throw new Error("vuetify-nuxt-module: styles.configFile could not be resolved");
|
|
1110
|
+
}
|
|
1111
|
+
const cache = resolveStylesCache(stylesOption);
|
|
1112
|
+
viteInlineConfig.plugins.push(Styles({
|
|
1113
|
+
settings: ctx.stylesConfigFile,
|
|
1114
|
+
...cache === void 0 ? {} : { cache }
|
|
1115
|
+
}));
|
|
1130
1116
|
}
|
|
1131
1117
|
viteInlineConfig.plugins.push(vuetifyConfigurationPlugin(ctx), vuetifyIconsPlugin(ctx), vuetifyDateConfigurationPlugin(ctx));
|
|
1132
1118
|
if (ctx.ssrClientHints.enabled) {
|
|
@@ -1165,7 +1151,8 @@ const disabledResolvedIcons = Object.freeze({
|
|
|
1165
1151
|
local: [],
|
|
1166
1152
|
svg: {}
|
|
1167
1153
|
});
|
|
1168
|
-
function prepareIcons(unocssPresent, logger, vuetifyOptions) {
|
|
1154
|
+
function prepareIcons(unocssPresent, logger, vuetifyOptions, paths) {
|
|
1155
|
+
const iconPackageExists = (name) => isPackageExists(name, { paths });
|
|
1169
1156
|
if (vuetifyOptions.icons === false) {
|
|
1170
1157
|
return disabledResolvedIcons;
|
|
1171
1158
|
}
|
|
@@ -1209,7 +1196,7 @@ function prepareIcons(unocssPresent, logger, vuetifyOptions) {
|
|
|
1209
1196
|
}
|
|
1210
1197
|
resolvedIcons.imports.push(`import {${name === defaultSet ? "aliases," : ""}${name}} from 'vuetify/iconsets/${name}'`);
|
|
1211
1198
|
resolvedIcons.sets.push(name);
|
|
1212
|
-
if (
|
|
1199
|
+
if (iconPackageExists(iconsPackageNames[name].name)) {
|
|
1213
1200
|
resolvedIcons.local.push(iconsPackageNames[name].css);
|
|
1214
1201
|
} else {
|
|
1215
1202
|
resolvedIcons.cdn.push([name, cdn ?? iconsCDN[name]]);
|
|
@@ -1228,18 +1215,18 @@ function prepareIcons(unocssPresent, logger, vuetifyOptions) {
|
|
|
1228
1215
|
if (!faSvg) {
|
|
1229
1216
|
faSvg = {};
|
|
1230
1217
|
}
|
|
1231
|
-
let faSvgExists =
|
|
1218
|
+
let faSvgExists = iconPackageExists("@fortawesome/fontawesome-svg-core");
|
|
1232
1219
|
if (!faSvgExists) {
|
|
1233
1220
|
logger.warn("Missing @fortawesome/fontawesome-svg-core dependency, install it!");
|
|
1234
1221
|
}
|
|
1235
|
-
faSvgExists =
|
|
1222
|
+
faSvgExists = iconPackageExists("@fortawesome/vue-fontawesome");
|
|
1236
1223
|
if (faSvgExists) {
|
|
1237
1224
|
if (!faSvg.libraries?.length) {
|
|
1238
1225
|
faSvg.libraries = [[false, "fas", "@fortawesome/free-solid-svg-icons"]];
|
|
1239
1226
|
}
|
|
1240
1227
|
for (const p in faSvg.libraries) {
|
|
1241
1228
|
const [_defaultExport, _name, library] = faSvg.libraries[p];
|
|
1242
|
-
if (!
|
|
1229
|
+
if (!iconPackageExists(library)) {
|
|
1243
1230
|
faSvgExists = false;
|
|
1244
1231
|
logger.warn(`Missing library ${library} dependency, install it!`);
|
|
1245
1232
|
}
|
|
@@ -1266,7 +1253,7 @@ function prepareIcons(unocssPresent, logger, vuetifyOptions) {
|
|
|
1266
1253
|
if (!mdiSvg) {
|
|
1267
1254
|
mdiSvg = {};
|
|
1268
1255
|
}
|
|
1269
|
-
const mdiSvgExists =
|
|
1256
|
+
const mdiSvgExists = iconPackageExists("@mdi/js");
|
|
1270
1257
|
if (mdiSvgExists) {
|
|
1271
1258
|
resolvedIcons.svg.mdi = true;
|
|
1272
1259
|
resolvedIcons.aliasesImportPresent ||= defaultSet === "mdi-svg";
|
|
@@ -1314,7 +1301,7 @@ async function loadVuetifyConfiguration(cwd = process.cwd(), configOrPath = cwd,
|
|
|
1314
1301
|
inlineConfig = configOrPath;
|
|
1315
1302
|
configOrPath = process.cwd();
|
|
1316
1303
|
}
|
|
1317
|
-
const resolved = resolve
|
|
1304
|
+
const resolved = resolve(cwd, configOrPath);
|
|
1318
1305
|
let isFile = false;
|
|
1319
1306
|
if (existsSync(resolved) && statSync(resolved).isFile()) {
|
|
1320
1307
|
isFile = true;
|
|
@@ -1362,6 +1349,29 @@ async function loadVuetifyConfiguration(cwd = process.cwd(), configOrPath = cwd,
|
|
|
1362
1349
|
return result;
|
|
1363
1350
|
}
|
|
1364
1351
|
|
|
1352
|
+
const MODULE_DEFAULTS = {
|
|
1353
|
+
moduleOptions: {
|
|
1354
|
+
importComposables: true,
|
|
1355
|
+
includeTransformAssetsUrls: true,
|
|
1356
|
+
styles: true,
|
|
1357
|
+
rulesConfiguration: {
|
|
1358
|
+
fromLabs: true
|
|
1359
|
+
}
|
|
1360
|
+
},
|
|
1361
|
+
vuetifyOptions: {
|
|
1362
|
+
labComponents: false,
|
|
1363
|
+
directives: false
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
function finalizeConfiguration(moduleOptions) {
|
|
1367
|
+
if (moduleOptions.length > 1) {
|
|
1368
|
+
const [app, ...rest] = moduleOptions;
|
|
1369
|
+
const configuration = defu(app, ...rest, MODULE_DEFAULTS);
|
|
1370
|
+
dedupeIcons(configuration, moduleOptions.toReversed());
|
|
1371
|
+
return configuration;
|
|
1372
|
+
}
|
|
1373
|
+
return defu(moduleOptions[0] ?? {}, MODULE_DEFAULTS);
|
|
1374
|
+
}
|
|
1365
1375
|
async function mergeVuetifyModules(options, nuxt) {
|
|
1366
1376
|
const moduleOptions = [];
|
|
1367
1377
|
const vuetifyConfigurationFilesToWatch = /* @__PURE__ */ new Set();
|
|
@@ -1403,23 +1413,10 @@ async function mergeVuetifyModules(options, nuxt) {
|
|
|
1403
1413
|
moduleOptions: options.moduleOptions,
|
|
1404
1414
|
vuetifyOptions: resolvedOptions.config
|
|
1405
1415
|
});
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
return {
|
|
1411
|
-
configuration,
|
|
1412
|
-
vuetifyConfigurationFilesToWatch
|
|
1413
|
-
};
|
|
1414
|
-
} else {
|
|
1415
|
-
return {
|
|
1416
|
-
configuration: {
|
|
1417
|
-
moduleOptions: options.moduleOptions,
|
|
1418
|
-
vuetifyOptions: resolvedOptions.config
|
|
1419
|
-
},
|
|
1420
|
-
vuetifyConfigurationFilesToWatch
|
|
1421
|
-
};
|
|
1422
|
-
}
|
|
1416
|
+
return {
|
|
1417
|
+
configuration: finalizeConfiguration(moduleOptions),
|
|
1418
|
+
vuetifyConfigurationFilesToWatch
|
|
1419
|
+
};
|
|
1423
1420
|
}
|
|
1424
1421
|
function dedupeIcons(configuration, moduleOptions) {
|
|
1425
1422
|
const vuetifyOptions = configuration.vuetifyOptions;
|
|
@@ -1452,6 +1449,28 @@ const disabledClientHints = Object.freeze({
|
|
|
1452
1449
|
prefersColorScheme: false,
|
|
1453
1450
|
prefersReducedMotion: false
|
|
1454
1451
|
});
|
|
1452
|
+
function resolveColorSchemeCookie(options, logger) {
|
|
1453
|
+
if (options.cookieName !== void 0) {
|
|
1454
|
+
logger.warn("[vuetify-nuxt-module] `prefersColorSchemeOptions.cookieName` is deprecated, use `prefersColorSchemeOptions.cookie.name` instead.");
|
|
1455
|
+
}
|
|
1456
|
+
const cookieSameSite = options.cookie?.sameSite ?? "lax";
|
|
1457
|
+
return {
|
|
1458
|
+
cookieName: options.cookie?.name ?? options.cookieName ?? "color-scheme",
|
|
1459
|
+
cookieDomain: options.cookie?.domain,
|
|
1460
|
+
cookieSecure: cookieSameSite === "none" ? true : options.cookie?.secure,
|
|
1461
|
+
cookieSameSite
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
function resolveDefaultTheme(defaultTheme, themes, lightThemeName, logger) {
|
|
1465
|
+
if (defaultTheme === "system") {
|
|
1466
|
+
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.`);
|
|
1467
|
+
return lightThemeName;
|
|
1468
|
+
}
|
|
1469
|
+
if (!themes[defaultTheme]) {
|
|
1470
|
+
throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
|
|
1471
|
+
}
|
|
1472
|
+
return defaultTheme;
|
|
1473
|
+
}
|
|
1455
1474
|
function prepareSSRClientHints(baseUrl, ctx) {
|
|
1456
1475
|
if (!ctx.isSSR || ctx.isNuxtGenerate) {
|
|
1457
1476
|
return disabledClientHints;
|
|
@@ -1478,9 +1497,6 @@ function prepareSSRClientHints(baseUrl, ctx) {
|
|
|
1478
1497
|
if (!defaultTheme) {
|
|
1479
1498
|
throw new Error("Vuetify default theme is missing in theme!");
|
|
1480
1499
|
}
|
|
1481
|
-
if (!themes[defaultTheme]) {
|
|
1482
|
-
throw new Error(`Missing default theme ${defaultTheme} in the Vuetify themes!`);
|
|
1483
|
-
}
|
|
1484
1500
|
const darkThemeName = ssrClientHintsConfiguration.prefersColorSchemeOptions?.darkThemeName ?? "dark";
|
|
1485
1501
|
if (!themes[darkThemeName]) {
|
|
1486
1502
|
throw new Error(`Missing theme ${darkThemeName} in the Vuetify themes!`);
|
|
@@ -1492,137 +1508,21 @@ function prepareSSRClientHints(baseUrl, ctx) {
|
|
|
1492
1508
|
if (darkThemeName === lightThemeName) {
|
|
1493
1509
|
throw new Error("Vuetify dark theme and light theme are the same, change darkThemeName or lightThemeName!");
|
|
1494
1510
|
}
|
|
1511
|
+
const pcsOptions = ssrClientHintsConfiguration.prefersColorSchemeOptions;
|
|
1512
|
+
const effectiveDefaultTheme = resolveDefaultTheme(defaultTheme, themes, lightThemeName, ctx.logger);
|
|
1495
1513
|
clientHints.prefersColorSchemeOptions = {
|
|
1496
1514
|
baseUrl,
|
|
1497
|
-
defaultTheme,
|
|
1515
|
+
defaultTheme: effectiveDefaultTheme,
|
|
1498
1516
|
themeNames: Array.from(Object.keys(themes)),
|
|
1499
|
-
|
|
1517
|
+
...resolveColorSchemeCookie(pcsOptions, ctx.logger),
|
|
1500
1518
|
darkThemeName,
|
|
1501
1519
|
lightThemeName,
|
|
1502
|
-
useBrowserThemeOnly:
|
|
1520
|
+
useBrowserThemeOnly: pcsOptions?.useBrowserThemeOnly ?? false
|
|
1503
1521
|
};
|
|
1504
1522
|
}
|
|
1505
1523
|
return clientHints;
|
|
1506
1524
|
}
|
|
1507
1525
|
|
|
1508
|
-
async function prepareVuetifyStyles(nuxt, ctx) {
|
|
1509
|
-
const stylesConfig = ctx.moduleOptions.styles;
|
|
1510
|
-
if (!isObject(stylesConfig) || !("configFile" in stylesConfig)) {
|
|
1511
|
-
return;
|
|
1512
|
-
}
|
|
1513
|
-
if (stylesConfig.experimental?.cache === false) {
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
const vuetifyBase = resolveVuetifyBase();
|
|
1517
|
-
let configFile;
|
|
1518
|
-
let configContent = "";
|
|
1519
|
-
if (stylesConfig.configFile) {
|
|
1520
|
-
configFile = await resolvePath(stylesConfig.configFile);
|
|
1521
|
-
if (existsSync(configFile)) {
|
|
1522
|
-
configContent = readFileSync(configFile, "utf8");
|
|
1523
|
-
if (!ctx.vuetifyFilesToWatch.includes(configFile)) {
|
|
1524
|
-
ctx.vuetifyFilesToWatch.push(configFile);
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
if (!configFile) {
|
|
1529
|
-
return;
|
|
1530
|
-
}
|
|
1531
|
-
const hash = createHash("sha256").update(ctx.vuetifyVersion).update(ctx.viteVersion).update(configContent).update(configFile).digest("hex").slice(0, 8);
|
|
1532
|
-
const stylesDir = resolve$1(nuxt.options.rootDir, "node_modules/.cache/vuetify-nuxt-module/styles");
|
|
1533
|
-
const cacheDir = join(stylesDir, hash);
|
|
1534
|
-
ctx.stylesCachePath = cacheDir;
|
|
1535
|
-
if (existsSync(stylesDir)) {
|
|
1536
|
-
const dirents = readdirSync(stylesDir, { withFileTypes: true });
|
|
1537
|
-
for (const dirent of dirents) {
|
|
1538
|
-
if (dirent.isDirectory() && dirent.name !== hash) {
|
|
1539
|
-
rmSync(join(stylesDir, dirent.name), { recursive: true, force: true });
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
if (existsSync(cacheDir)) {
|
|
1544
|
-
return;
|
|
1545
|
-
}
|
|
1546
|
-
ctx.logger.info("Compiling Vuetify styles...");
|
|
1547
|
-
let sass;
|
|
1548
|
-
try {
|
|
1549
|
-
sass = await import('sass');
|
|
1550
|
-
} catch {
|
|
1551
|
-
try {
|
|
1552
|
-
sass = await import('sass-embedded');
|
|
1553
|
-
} catch {
|
|
1554
|
-
ctx.logger.warn('Could not load "sass" or "sass-embedded". Skipping styles pre-compilation.');
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
const files = [];
|
|
1559
|
-
findCssFiles(join(vuetifyBase, "lib/components"), files);
|
|
1560
|
-
findCssFiles(join(vuetifyBase, "lib/styles"), files);
|
|
1561
|
-
for (const file of files) {
|
|
1562
|
-
const relativePath = relative$1(vuetifyBase, file);
|
|
1563
|
-
const cacheFile = join(cacheDir, relativePath);
|
|
1564
|
-
const sassFile = file.replace(/\.css$/, ".sass");
|
|
1565
|
-
const scssFile = file.replace(/\.css$/, ".scss");
|
|
1566
|
-
let targetFile;
|
|
1567
|
-
if (existsSync(sassFile)) {
|
|
1568
|
-
targetFile = sassFile;
|
|
1569
|
-
} else if (existsSync(scssFile)) {
|
|
1570
|
-
targetFile = scssFile;
|
|
1571
|
-
}
|
|
1572
|
-
if (targetFile) {
|
|
1573
|
-
const dir = dirname(cacheFile);
|
|
1574
|
-
if (!existsSync(dir)) {
|
|
1575
|
-
mkdirSync(dir, { recursive: true });
|
|
1576
|
-
}
|
|
1577
|
-
const content = `@use "${normalizePath(configFile)}";
|
|
1578
|
-
@use "${normalizePath(targetFile)}";
|
|
1579
|
-
`;
|
|
1580
|
-
try {
|
|
1581
|
-
const result = sass.compileString(content, {
|
|
1582
|
-
loadPaths: [
|
|
1583
|
-
dirname(configFile),
|
|
1584
|
-
dirname(targetFile),
|
|
1585
|
-
resolve$1(vuetifyBase, ".."),
|
|
1586
|
-
resolve$1(vuetifyBase, "../.."),
|
|
1587
|
-
// In case of monorepo/hoisting issues, but standard is enough
|
|
1588
|
-
vuetifyBase
|
|
1589
|
-
],
|
|
1590
|
-
url: new URL(pathToFileURL(cacheFile).href)
|
|
1591
|
-
});
|
|
1592
|
-
writeFileSync(cacheFile, result.css, "utf8");
|
|
1593
|
-
} catch (error) {
|
|
1594
|
-
ctx.logger.error(`Failed to compile ${targetFile}:`, error);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
const metadata = {
|
|
1599
|
-
hash,
|
|
1600
|
-
vuetifyVersion: ctx.vuetifyVersion,
|
|
1601
|
-
viteVersion: ctx.viteVersion,
|
|
1602
|
-
configFile,
|
|
1603
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1604
|
-
};
|
|
1605
|
-
writeFileSync(join(cacheDir, "metadata.json"), JSON.stringify(metadata, null, 2), "utf8");
|
|
1606
|
-
}
|
|
1607
|
-
function findCssFiles(dir, fileList = []) {
|
|
1608
|
-
if (!existsSync(dir)) {
|
|
1609
|
-
return fileList;
|
|
1610
|
-
}
|
|
1611
|
-
const files = readdirSync(dir);
|
|
1612
|
-
for (const file of files) {
|
|
1613
|
-
const filePath = join(dir, file);
|
|
1614
|
-
const stat = statSync(filePath);
|
|
1615
|
-
if (stat.isDirectory()) {
|
|
1616
|
-
findCssFiles(filePath, fileList);
|
|
1617
|
-
} else {
|
|
1618
|
-
if (file.endsWith(".css")) {
|
|
1619
|
-
fileList.push(filePath);
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
return fileList;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
1526
|
async function load(options, nuxt, ctx) {
|
|
1627
1527
|
const {
|
|
1628
1528
|
configuration,
|
|
@@ -1632,7 +1532,7 @@ async function load(options, nuxt, ctx) {
|
|
|
1632
1532
|
const {
|
|
1633
1533
|
componentsPromise,
|
|
1634
1534
|
labComponentsPromise
|
|
1635
|
-
} = resolveVuetifyComponents(ctx.resolver);
|
|
1535
|
+
} = resolveVuetifyComponents(ctx.resolver, ctx.vuetifyBase);
|
|
1636
1536
|
ctx.componentsPromise = componentsPromise;
|
|
1637
1537
|
ctx.labComponentsPromise = labComponentsPromise;
|
|
1638
1538
|
}
|
|
@@ -1648,9 +1548,9 @@ async function load(options, nuxt, ctx) {
|
|
|
1648
1548
|
const dateOptions = vuetifyOptions.date;
|
|
1649
1549
|
if (dateOptions) {
|
|
1650
1550
|
const adapter = dateOptions.adapter;
|
|
1651
|
-
const date = detectDate();
|
|
1551
|
+
const date = detectDate(ctx.resolvePaths);
|
|
1652
1552
|
if (!adapter && date.length > 1) {
|
|
1653
|
-
throw new Error(
|
|
1553
|
+
throw new Error(multipleDateAdaptersError(date));
|
|
1654
1554
|
}
|
|
1655
1555
|
if (adapter) {
|
|
1656
1556
|
if (adapter === "vuetify" || adapter === "custom") {
|
|
@@ -1677,8 +1577,11 @@ async function load(options, nuxt, ctx) {
|
|
|
1677
1577
|
ctx.enableRules = ctx.moduleOptions.enableRules;
|
|
1678
1578
|
ctx.rulesConfiguration = ctx.moduleOptions.rulesConfiguration;
|
|
1679
1579
|
ctx.vuetifyFilesToWatch = Array.from(vuetifyConfigurationFilesToWatch);
|
|
1680
|
-
ctx.icons = prepareIcons(ctx.unocss, ctx.logger, vuetifyAppOptions);
|
|
1580
|
+
ctx.icons = prepareIcons(ctx.unocss, ctx.logger, vuetifyAppOptions, ctx.resolvePaths);
|
|
1681
1581
|
ctx.ssrClientHints = prepareSSRClientHints(nuxt.options.app.baseURL ?? "/", ctx);
|
|
1582
|
+
if (ctx.isSSR && !ctx.ssrClientHints.prefersColorScheme && ctx.vuetifyOptions.theme && typeof ctx.vuetifyOptions.theme === "object" && ctx.vuetifyOptions.theme.defaultTheme === "system") {
|
|
1583
|
+
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.');
|
|
1584
|
+
}
|
|
1682
1585
|
if (ctx.icons.enabled) {
|
|
1683
1586
|
if (ctx.icons.local) {
|
|
1684
1587
|
for (const css of ctx.icons.local) {
|
|
@@ -1698,13 +1601,12 @@ async function load(options, nuxt, ctx) {
|
|
|
1698
1601
|
}
|
|
1699
1602
|
}
|
|
1700
1603
|
}
|
|
1701
|
-
await prepareVuetifyStyles(nuxt, ctx);
|
|
1702
1604
|
}
|
|
1703
1605
|
function registerWatcher(options, nuxt, ctx) {
|
|
1704
1606
|
if (nuxt.options.dev) {
|
|
1705
1607
|
let pageReload;
|
|
1706
1608
|
nuxt.hooks.hook("builder:watch", (_event, path) => {
|
|
1707
|
-
path = relative
|
|
1609
|
+
path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path));
|
|
1708
1610
|
if (!pageReload && ctx.vuetifyFilesToWatch.includes(path)) {
|
|
1709
1611
|
return nuxt.callHook("restart");
|
|
1710
1612
|
}
|
|
@@ -1750,24 +1652,6 @@ const module$1 = defineNuxtModule({
|
|
|
1750
1652
|
},
|
|
1751
1653
|
version
|
|
1752
1654
|
},
|
|
1753
|
-
/**
|
|
1754
|
-
* Default configuration options of the Nuxt module
|
|
1755
|
-
*/
|
|
1756
|
-
defaults: () => ({
|
|
1757
|
-
vuetifyOptions: {
|
|
1758
|
-
labComponents: false,
|
|
1759
|
-
directives: false
|
|
1760
|
-
},
|
|
1761
|
-
moduleOptions: {
|
|
1762
|
-
importComposables: true,
|
|
1763
|
-
includeTransformAssetsUrls: true,
|
|
1764
|
-
styles: true,
|
|
1765
|
-
disableVuetifyStyles: false,
|
|
1766
|
-
rulesConfiguration: {
|
|
1767
|
-
fromLabs: true
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
1770
|
-
}),
|
|
1771
1655
|
/**
|
|
1772
1656
|
* Sets up the Vuetify Nuxt module.
|
|
1773
1657
|
*
|
|
@@ -1778,8 +1662,23 @@ const module$1 = defineNuxtModule({
|
|
|
1778
1662
|
if (isNuxtMajorVersion(2, nuxt)) {
|
|
1779
1663
|
logger.error(`Cannot support nuxt version: ${getNuxtVersion(nuxt)}`);
|
|
1780
1664
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1665
|
+
let vuetifyPkgPath = await findPath("vuetify/package.json");
|
|
1666
|
+
if (!vuetifyPkgPath) {
|
|
1667
|
+
const metaResolve = import.meta.resolve;
|
|
1668
|
+
if (metaResolve) {
|
|
1669
|
+
try {
|
|
1670
|
+
vuetifyPkgPath = fileURLToPath(metaResolve("vuetify/package.json"));
|
|
1671
|
+
} catch {
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
if (!vuetifyPkgPath) {
|
|
1676
|
+
throw new Error(
|
|
1677
|
+
'[vuetify-nuxt-module] Could not resolve "vuetify". Please add it as a dependency of your project (e.g. `npm add vuetify`).'
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
const vuetifyBase = dirname(vuetifyPkgPath);
|
|
1681
|
+
const currentVersion = JSON.parse(readFileSync(vuetifyPkgPath, "utf8")).version;
|
|
1783
1682
|
const vuetifyGte = (version2) => !!currentVersion && semver.gte(currentVersion, version2);
|
|
1784
1683
|
const viteVersion = version$1;
|
|
1785
1684
|
const ctx = {
|
|
@@ -1799,6 +1698,8 @@ const module$1 = defineNuxtModule({
|
|
|
1799
1698
|
labComponentsPromise: void 0,
|
|
1800
1699
|
vuetifyGte,
|
|
1801
1700
|
vuetifyVersion: currentVersion || "0.0.0",
|
|
1701
|
+
vuetifyBase,
|
|
1702
|
+
resolvePaths: [nuxt.options.rootDir, ...nuxt.options.modulesDir],
|
|
1802
1703
|
viteVersion
|
|
1803
1704
|
};
|
|
1804
1705
|
await load(options, nuxt, ctx);
|
|
@@ -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
|
+
}
|
|
@@ -4,6 +4,9 @@ import { useI18n } from "vue-i18n";
|
|
|
4
4
|
function inferDecimalSeparator(n) {
|
|
5
5
|
return n(0.1).includes(",") ? "," : ".";
|
|
6
6
|
}
|
|
7
|
+
function inferNumericGroupSeparator(n) {
|
|
8
|
+
return n(1e4, { useGrouping: true }).replace(/\p{Nd}/gu, "") || " ";
|
|
9
|
+
}
|
|
7
10
|
export function createAdapter(vuetifyOptions) {
|
|
8
11
|
vuetifyOptions.locale = {};
|
|
9
12
|
const nuxtApp = useNuxtApp();
|
|
@@ -32,7 +35,8 @@ export function createAdapter(vuetifyOptions) {
|
|
|
32
35
|
t: (key, ...params) => i18n.t(key, params),
|
|
33
36
|
n: i18n.n,
|
|
34
37
|
provide: createProvideFunction({ current: currentLocale, fallback, messages }),
|
|
35
|
-
decimalSeparator: toRef(() => inferDecimalSeparator(i18n.n))
|
|
38
|
+
decimalSeparator: toRef(() => inferDecimalSeparator(i18n.n)),
|
|
39
|
+
numericGroupSeparator: toRef(() => inferNumericGroupSeparator(i18n.n))
|
|
36
40
|
};
|
|
37
41
|
}
|
|
38
42
|
function createProvideFunction(data) {
|
|
@@ -61,6 +65,7 @@ function createProvideFunction(data) {
|
|
|
61
65
|
t,
|
|
62
66
|
n,
|
|
63
67
|
decimalSeparator: toRef(() => props.decimalSeparator ?? inferDecimalSeparator(n)),
|
|
68
|
+
numericGroupSeparator: toRef(() => inferNumericGroupSeparator(n)),
|
|
64
69
|
provide: createProvideFunction({ current: currentLocale, fallback: data.fallback, messages: data.messages })
|
|
65
70
|
};
|
|
66
71
|
};
|
|
@@ -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 }) => {
|
|
@@ -119,12 +124,21 @@ function useSSRClientHints() {
|
|
|
119
124
|
const {
|
|
120
125
|
baseUrl,
|
|
121
126
|
cookieName,
|
|
127
|
+
cookieDomain,
|
|
128
|
+
cookieSecure,
|
|
129
|
+
cookieSameSite,
|
|
122
130
|
defaultTheme
|
|
123
131
|
} = ssrClientHintsConfiguration.prefersColorSchemeOptions;
|
|
124
132
|
const cookieNamePrefix = `${cookieName}=`;
|
|
125
133
|
initial.value.colorSchemeFromCookie = document.cookie?.split(";")?.find((c) => c.trim().startsWith(cookieNamePrefix))?.split("=")[1] ?? defaultTheme;
|
|
126
134
|
const date = /* @__PURE__ */ new Date();
|
|
127
135
|
const expires = new Date(date.setDate(date.getDate() + 365));
|
|
128
|
-
initial.value.colorSchemeCookie = `${cookieName}=${initial.value.colorSchemeFromCookie}; Path=${baseUrl}; Expires=${expires.toUTCString()}; SameSite
|
|
136
|
+
initial.value.colorSchemeCookie = `${cookieName}=${initial.value.colorSchemeFromCookie}; Path=${baseUrl}; Expires=${expires.toUTCString()}; SameSite=${cookieSameSite[0].toUpperCase()}${cookieSameSite.slice(1)}`;
|
|
137
|
+
if (cookieDomain) {
|
|
138
|
+
initial.value.colorSchemeCookie += `; Domain=${cookieDomain}`;
|
|
139
|
+
}
|
|
140
|
+
if (cookieSecure) {
|
|
141
|
+
initial.value.colorSchemeCookie += "; Secure";
|
|
142
|
+
}
|
|
129
143
|
return initial;
|
|
130
144
|
}
|
|
@@ -273,15 +273,27 @@ function writeThemeCookie(clientHintsRequest, ssrClientHintsConfiguration2) {
|
|
|
273
273
|
const cookieName = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieName;
|
|
274
274
|
const themeName = clientHintsRequest.colorSchemeFromCookie ?? ssrClientHintsConfiguration2.prefersColorSchemeOptions.defaultTheme;
|
|
275
275
|
const path = ssrClientHintsConfiguration2.prefersColorSchemeOptions.baseUrl;
|
|
276
|
+
const domain = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieDomain;
|
|
277
|
+
const secure = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieSecure;
|
|
278
|
+
const sameSite = ssrClientHintsConfiguration2.prefersColorSchemeOptions.cookieSameSite;
|
|
276
279
|
const date = /* @__PURE__ */ new Date();
|
|
277
280
|
const expires = new Date(date.setDate(date.getDate() + 365));
|
|
278
281
|
if (!clientHintsRequest.firstRequest || !ssrClientHintsConfiguration2.reloadOnFirstRequest) {
|
|
279
282
|
useCookie(cookieName, {
|
|
280
283
|
path,
|
|
284
|
+
domain,
|
|
281
285
|
expires,
|
|
282
|
-
sameSite
|
|
286
|
+
sameSite,
|
|
287
|
+
secure
|
|
283
288
|
}).value = themeName;
|
|
284
289
|
}
|
|
285
|
-
|
|
290
|
+
let cookie = `${cookieName}=${themeName}; Path=${path}; Expires=${expires.toUTCString()}; SameSite=${sameSite[0].toUpperCase()}${sameSite.slice(1)}`;
|
|
291
|
+
if (domain) {
|
|
292
|
+
cookie += `; Domain=${domain}`;
|
|
293
|
+
}
|
|
294
|
+
if (secure) {
|
|
295
|
+
cookie += "; Secure";
|
|
296
|
+
}
|
|
297
|
+
return cookie;
|
|
286
298
|
}
|
|
287
299
|
export default plugin;
|
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.
|
|
4
|
+
"version": "1.0.0-beta.10",
|
|
5
5
|
"description": "Zero-Config Nuxt Module for Vuetify",
|
|
6
6
|
"author": "userquin <userquin@gmail.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -52,17 +52,18 @@
|
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@nuxt/kit": "^4.3.1",
|
|
55
|
+
"@vuetify/loader-shared": "^2.1.2",
|
|
56
|
+
"@vuetify/unplugin-styles": "^1.0.0-beta.11",
|
|
55
57
|
"defu": "^6.1.4",
|
|
56
|
-
"destr": "^2.0.5",
|
|
57
58
|
"local-pkg": "^1.1.2",
|
|
58
59
|
"pathe": "^2.0.3",
|
|
59
60
|
"perfect-debounce": "^2.1.0",
|
|
60
61
|
"semver": "^7.7.4",
|
|
61
62
|
"ufo": "^1.6.3",
|
|
62
|
-
"unconfig": "^7.5.0"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"vuetify": "^4.0.
|
|
63
|
+
"unconfig": "^7.5.0"
|
|
64
|
+
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"vuetify": "^3.4.0 || ^4.0.0"
|
|
66
67
|
},
|
|
67
68
|
"devDependencies": {
|
|
68
69
|
"@antfu/eslint-config": "^7.6.1",
|
|
@@ -87,21 +88,23 @@
|
|
|
87
88
|
"eslint": "^10.0.2",
|
|
88
89
|
"luxon": "^3.7.2",
|
|
89
90
|
"nuxt": "^4.3.1",
|
|
91
|
+
"playwright-core": "^1.58.0",
|
|
90
92
|
"publint": "^0.3.18",
|
|
91
93
|
"rimraf": "^6.1.3",
|
|
92
94
|
"sass": "^1.97.3",
|
|
93
95
|
"typescript": "^5.9.3",
|
|
94
96
|
"vite": "7.3.1",
|
|
95
97
|
"vitest": "^4.0.18",
|
|
96
|
-
"vue-tsc": "^3.2.5"
|
|
98
|
+
"vue-tsc": "^3.2.5",
|
|
99
|
+
"vuetify": "^4.0.1"
|
|
97
100
|
},
|
|
98
101
|
"build": {
|
|
99
102
|
"externals": [
|
|
100
103
|
"@vuetify/loader-shared",
|
|
104
|
+
"@vuetify/unplugin-styles",
|
|
101
105
|
"node:child_process",
|
|
102
106
|
"node:fs",
|
|
103
107
|
"consola",
|
|
104
|
-
"destr",
|
|
105
108
|
"esbuild",
|
|
106
109
|
"local-pkg",
|
|
107
110
|
"pathe",
|
|
@@ -109,11 +112,9 @@
|
|
|
109
112
|
"rollup",
|
|
110
113
|
"sass",
|
|
111
114
|
"sass-embedded",
|
|
112
|
-
"upath",
|
|
113
115
|
"ufo",
|
|
114
116
|
"unconfig",
|
|
115
117
|
"vite",
|
|
116
|
-
"vite-plugin-vuetify",
|
|
117
118
|
"vuetify"
|
|
118
119
|
]
|
|
119
120
|
},
|
|
@@ -139,8 +140,9 @@
|
|
|
139
140
|
"lint": "eslint .",
|
|
140
141
|
"lint:fix": "nr lint --fix",
|
|
141
142
|
"publint": "publint",
|
|
142
|
-
"test": "vitest run",
|
|
143
|
-
"test:watch": "vitest watch",
|
|
143
|
+
"test": "vitest run --exclude 'test/e2e/**'",
|
|
144
|
+
"test:watch": "vitest watch --exclude 'test/e2e/**'",
|
|
145
|
+
"test:e2e": "vitest run test/e2e",
|
|
144
146
|
"release": "bumpp"
|
|
145
147
|
}
|
|
146
148
|
}
|