nuxt-seo-utils 6.0.12 → 7.0.0
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/LICENSE.md +9 -9
- package/README.md +74 -74
- package/dist/module.d.mts +6 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.json +2 -2
- package/dist/module.mjs +51 -29
- package/dist/runtime/app/composables/useBreadcrumbItems.d.ts +7 -1
- package/dist/runtime/app/composables/useBreadcrumbItems.js +64 -20
- package/dist/runtime/app/logic/applyDefaults.js +9 -4
- package/dist/runtime/app/plugins/0.routeRules.server.js +2 -2
- package/dist/runtime/app/plugins/1.absoluteImageUrls.server.js +2 -1
- package/dist/runtime/app/plugins/inferSeoMetaPlugin.js +1 -1
- package/dist/runtime/app/plugins/siteConfig.js +4 -3
- package/dist/runtime/app/plugins/titles.js +1 -1
- package/dist/runtime/server/tsconfig.json +3 -3
- package/dist/runtime/types.d.ts +1 -1
- package/package.json +25 -19
package/LICENSE.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Harlan Wilton
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
9
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Harlan Wilton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
<h1>nuxt-seo-utils</h1>
|
|
2
|
-
|
|
3
|
-
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
-
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
-
[![License][license-src]][license-href]
|
|
6
|
-
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
-
|
|
8
|
-
Nuxt SEO Utils is a collection of defaults and utilities to improve your Nuxt site's discoverability and shareability.
|
|
9
|
-
|
|
10
|
-
While there are several features covering many aspects of SEO, it covers important defaults such as [automatic canonical URLs](https://nuxtseo.com/learn/controlling-crawlers/canonical-urls) and
|
|
11
|
-
[open graph tags](https://nuxtseo.com/learn/mastering-meta/open-graph).
|
|
12
|
-
|
|
13
|
-
<p align="center">
|
|
14
|
-
<table>
|
|
15
|
-
<tbody>
|
|
16
|
-
<td align="center">
|
|
17
|
-
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
|
|
18
|
-
</td>
|
|
19
|
-
</tbody>
|
|
20
|
-
</table>
|
|
21
|
-
</p>
|
|
22
|
-
|
|
23
|
-
## Features
|
|
24
|
-
|
|
25
|
-
- ▲ Next.js inspired [Metadata Files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata)
|
|
26
|
-
- ✨ Use SEO meta in your nuxt.config (`app.seoMeta`) and route rules
|
|
27
|
-
- 🤖 Adds meta tags (`og:title`, `og:description`, etc.) from page title and description
|
|
28
|
-
- 🧙 Correct tags that need to be absolute (i.e `og:image`)
|
|
29
|
-
- 🧩 Site config template params `useHead({ title: '%site.title' })`
|
|
30
|
-
- 🍞 Breadcrumb composable
|
|
31
|
-
- 🧙 Best practice defaults and enhanced titles
|
|
32
|
-
|
|
33
|
-
## Installation
|
|
34
|
-
|
|
35
|
-
Install `nuxt-seo-utils` dependency to your project:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
npx nuxi@latest module add nuxt-seo-utils
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
💡 Need a complete SEO solution for Nuxt? Check out [Nuxt SEO](https://nuxtseo.com).
|
|
42
|
-
|
|
43
|
-
## Documentation
|
|
44
|
-
|
|
45
|
-
[📖 Read the full documentation](https://nuxtseo.com/docs/utils) for more information.
|
|
46
|
-
|
|
47
|
-
## Demos
|
|
48
|
-
|
|
49
|
-
[Basic](https://stackblitz.com/edit/nuxt-starter-vbay3q?file=app.vue)
|
|
50
|
-
|
|
51
|
-
## Sponsors
|
|
52
|
-
|
|
53
|
-
<p align="center">
|
|
54
|
-
<a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
|
|
55
|
-
<img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
|
|
56
|
-
</a>
|
|
57
|
-
</p>
|
|
58
|
-
|
|
59
|
-
## License
|
|
60
|
-
|
|
61
|
-
Licensed under the [MIT license](https://github.com/harlan-zw/nuxt-seo-utils/blob/main/LICENSE.md).
|
|
62
|
-
|
|
63
|
-
<!-- Badges -->
|
|
64
|
-
[npm-version-src]: https://img.shields.io/npm/v/nuxt-seo-utils/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
65
|
-
[npm-version-href]: https://npmjs.com/package/nuxt-seo-utils
|
|
66
|
-
|
|
67
|
-
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-seo-utils.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
68
|
-
[npm-downloads-href]: https://npmjs.com/package/nuxt-seo-utils
|
|
69
|
-
|
|
70
|
-
[license-src]: https://img.shields.io/github/license/harlan-zw/nuxt-seo-utils.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
71
|
-
[license-href]: https://github.com/harlan-zw/nuxt-seo-utils/blob/main/LICENSE.md
|
|
72
|
-
|
|
73
|
-
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
|
74
|
-
[nuxt-href]: https://nuxt.com
|
|
1
|
+
<h1>nuxt-seo-utils</h1>
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
Nuxt SEO Utils is a collection of defaults and utilities to improve your Nuxt site's discoverability and shareability.
|
|
9
|
+
|
|
10
|
+
While there are several features covering many aspects of SEO, it covers important defaults such as [automatic canonical URLs](https://nuxtseo.com/learn/controlling-crawlers/canonical-urls) and
|
|
11
|
+
[open graph tags](https://nuxtseo.com/learn/mastering-meta/open-graph).
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<table>
|
|
15
|
+
<tbody>
|
|
16
|
+
<td align="center">
|
|
17
|
+
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
|
|
18
|
+
</td>
|
|
19
|
+
</tbody>
|
|
20
|
+
</table>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- ▲ Next.js inspired [Metadata Files](https://nextjs.org/docs/app/api-reference/file-conventions/metadata)
|
|
26
|
+
- ✨ Use SEO meta in your nuxt.config (`app.seoMeta`) and route rules
|
|
27
|
+
- 🤖 Adds meta tags (`og:title`, `og:description`, etc.) from page title and description
|
|
28
|
+
- 🧙 Correct tags that need to be absolute (i.e `og:image`)
|
|
29
|
+
- 🧩 Site config template params `useHead({ title: '%site.title' })`
|
|
30
|
+
- 🍞 Breadcrumb composable
|
|
31
|
+
- 🧙 Best practice defaults and enhanced titles
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
Install `nuxt-seo-utils` dependency to your project:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx nuxi@latest module add nuxt-seo-utils
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
💡 Need a complete SEO solution for Nuxt? Check out [Nuxt SEO](https://nuxtseo.com).
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
[📖 Read the full documentation](https://nuxtseo.com/docs/utils) for more information.
|
|
46
|
+
|
|
47
|
+
## Demos
|
|
48
|
+
|
|
49
|
+
[Basic](https://stackblitz.com/edit/nuxt-starter-vbay3q?file=app.vue)
|
|
50
|
+
|
|
51
|
+
## Sponsors
|
|
52
|
+
|
|
53
|
+
<p align="center">
|
|
54
|
+
<a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
|
|
55
|
+
<img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
|
|
56
|
+
</a>
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
Licensed under the [MIT license](https://github.com/harlan-zw/nuxt-seo-utils/blob/main/LICENSE.md).
|
|
62
|
+
|
|
63
|
+
<!-- Badges -->
|
|
64
|
+
[npm-version-src]: https://img.shields.io/npm/v/nuxt-seo-utils/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
65
|
+
[npm-version-href]: https://npmjs.com/package/nuxt-seo-utils
|
|
66
|
+
|
|
67
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-seo-utils.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
68
|
+
[npm-downloads-href]: https://npmjs.com/package/nuxt-seo-utils
|
|
69
|
+
|
|
70
|
+
[license-src]: https://img.shields.io/github/license/harlan-zw/nuxt-seo-utils.svg?style=flat&colorA=18181B&colorB=28CF8D
|
|
71
|
+
[license-href]: https://github.com/harlan-zw/nuxt-seo-utils/blob/main/LICENSE.md
|
|
72
|
+
|
|
73
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
|
|
74
|
+
[nuxt-href]: https://nuxt.com
|
package/dist/module.d.mts
CHANGED
|
@@ -81,6 +81,12 @@ interface ModuleOptions {
|
|
|
81
81
|
* When enabled, it will whitelist the query parameters that are allowed in the canonical URL.
|
|
82
82
|
*/
|
|
83
83
|
canonicalQueryWhitelist?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* When enabled, it will lowercase the canonical URL.
|
|
86
|
+
*
|
|
87
|
+
* @default true
|
|
88
|
+
*/
|
|
89
|
+
canonicalLowercase?: boolean;
|
|
84
90
|
/**
|
|
85
91
|
* When enabled, it will redirect any request to the canonical domain (site url) using a 301 redirect on non-dev environments.
|
|
86
92
|
*
|
package/dist/module.d.ts
CHANGED
|
@@ -81,6 +81,12 @@ interface ModuleOptions {
|
|
|
81
81
|
* When enabled, it will whitelist the query parameters that are allowed in the canonical URL.
|
|
82
82
|
*/
|
|
83
83
|
canonicalQueryWhitelist?: string[];
|
|
84
|
+
/**
|
|
85
|
+
* When enabled, it will lowercase the canonical URL.
|
|
86
|
+
*
|
|
87
|
+
* @default true
|
|
88
|
+
*/
|
|
89
|
+
canonicalLowercase?: boolean;
|
|
84
90
|
/**
|
|
85
91
|
* When enabled, it will redirect any request to the canonical domain (site url) using a 301 redirect on non-dev environments.
|
|
86
92
|
*
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useNuxt, createResolver, addTemplate, defineNuxtModule, useLogger, addServerHandler, hasNuxtModule, addPlugin, addImports, addVitePlugin } from '@nuxt/kit';
|
|
1
|
+
import { useNuxt, createResolver, addTemplate, defineNuxtModule, useLogger, resolvePath, addServerHandler, hasNuxtModule, addPlugin, addImports, addVitePlugin } from '@nuxt/kit';
|
|
2
2
|
import UnheadVite from '@unhead/addons/vite';
|
|
3
3
|
import { defu } from 'defu';
|
|
4
|
-
import { resolvePath } from 'mlly';
|
|
5
4
|
import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
|
|
6
|
-
import { relative, resolve, extname, dirname, basename } from 'pathe';
|
|
7
|
-
import {
|
|
5
|
+
import { relative, resolve, extname, dirname, basename, join } from 'pathe';
|
|
6
|
+
import { readPackageJSON } from 'pkg-types';
|
|
7
|
+
import { gte } from 'semver';
|
|
8
8
|
import fg from 'fast-glob';
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import { encodePath, joinURL } from 'ufo';
|
|
@@ -12,9 +12,9 @@ import escapeRE from 'escape-string-regexp';
|
|
|
12
12
|
import { readFile } from 'node:fs/promises';
|
|
13
13
|
import imageSize from 'image-size';
|
|
14
14
|
|
|
15
|
-
function extendNuxtConfigAppHeadSeoMeta(nuxt = useNuxt()) {
|
|
15
|
+
function extendNuxtConfigAppHeadSeoMeta(unpackMeta, nuxt = useNuxt()) {
|
|
16
16
|
const seoMeta = nuxt.options.app?.seoMeta || nuxt.options.app?.head?.seoMeta || false;
|
|
17
|
-
const configSeoMeta = nuxt.options.seo?.meta || false;
|
|
17
|
+
const configSeoMeta = nuxt.options.seo?.meta || nuxt.options.seo?.seoMeta || false;
|
|
18
18
|
if (!seoMeta && !configSeoMeta)
|
|
19
19
|
return;
|
|
20
20
|
nuxt.options.app.head = nuxt.options.app.head || {};
|
|
@@ -55,7 +55,7 @@ function extendNuxtConfigAppHeadTypes(nuxt = useNuxt()) {
|
|
|
55
55
|
const jsPaths = paths.filter((p) => p.endsWith(".js") || p.endsWith(".mjs"));
|
|
56
56
|
return `
|
|
57
57
|
declare module '#app' {
|
|
58
|
-
import { HeadEntry, HeadTag } from '@unhead/
|
|
58
|
+
import { HeadEntry, HeadTag } from '@unhead/vue/types'
|
|
59
59
|
|
|
60
60
|
interface RuntimeNuxtHooks {
|
|
61
61
|
'head:tags': (tag: HeadTag[]) => Promise<void> | void
|
|
@@ -188,6 +188,15 @@ function parseSegment(segment) {
|
|
|
188
188
|
return tokens;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
+
async function resolveUnpackMeta(unheadV2) {
|
|
192
|
+
try {
|
|
193
|
+
const unhead = await import(String(unheadV2 ? "@unhead/vue/util" : "@unhead/shared"));
|
|
194
|
+
return unhead.unpackMeta;
|
|
195
|
+
} catch {
|
|
196
|
+
const unhead = await import(String(!unheadV2 ? "@unhead/vue/util" : "@unhead/shared"));
|
|
197
|
+
return unhead.unpackMeta;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
191
200
|
function hasLinkRel(input, rel) {
|
|
192
201
|
return input.link?.some((link) => link.rel === rel);
|
|
193
202
|
}
|
|
@@ -287,7 +296,7 @@ async function generateTagsFromPageDirImages(nuxt = useNuxt()) {
|
|
|
287
296
|
});
|
|
288
297
|
}
|
|
289
298
|
|
|
290
|
-
async function generateTagsFromPublicFiles(nuxt = useNuxt()) {
|
|
299
|
+
async function generateTagsFromPublicFiles(unpackMeta, nuxt = useNuxt()) {
|
|
291
300
|
const publicDirPath = resolve(nuxt.options.rootDir, nuxt.options.dir.public);
|
|
292
301
|
const rootPublicFiles = (await fg(MetaTagFileGlobs, { cwd: publicDirPath, onlyFiles: true, deep: 1 })).map((file) => basename(file));
|
|
293
302
|
const headConfig = defu(nuxt.options.app.head, {
|
|
@@ -391,7 +400,7 @@ const module = defineNuxtModule({
|
|
|
391
400
|
name: "nuxt-seo-utils",
|
|
392
401
|
configKey: "seo",
|
|
393
402
|
compatibility: {
|
|
394
|
-
nuxt: ">=3.
|
|
403
|
+
nuxt: ">=3.16.0",
|
|
395
404
|
bridge: false
|
|
396
405
|
}
|
|
397
406
|
},
|
|
@@ -409,7 +418,8 @@ const module = defineNuxtModule({
|
|
|
409
418
|
treeShakeUseSeoMeta: true,
|
|
410
419
|
extendNuxtConfigAppHeadTypes: true,
|
|
411
420
|
setupNuxtConfigAppHeadWithMoreDefaults: true,
|
|
412
|
-
automaticOgAndTwitterTags: true
|
|
421
|
+
automaticOgAndTwitterTags: true,
|
|
422
|
+
canonicalLowercase: true
|
|
413
423
|
},
|
|
414
424
|
async setup(config, nuxt) {
|
|
415
425
|
const logger = useLogger("nuxt-seo-utils");
|
|
@@ -420,13 +430,23 @@ const module = defineNuxtModule({
|
|
|
420
430
|
}
|
|
421
431
|
const { resolve } = createResolver(import.meta.url);
|
|
422
432
|
await installNuxtSiteConfig();
|
|
433
|
+
let isUnheadV2 = false;
|
|
434
|
+
const unheadPath = await resolvePath("@unhead/vue").catch(() => void 0).then((p) => p?.endsWith("index.mjs") ? dirname(p) : p);
|
|
435
|
+
if (unheadPath) {
|
|
436
|
+
const { version: unheadVersion } = await readPackageJSON(join(unheadPath, "package.json"));
|
|
437
|
+
if (gte(unheadVersion, "2.0.0-rc.1")) {
|
|
438
|
+
isUnheadV2 = true;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const runtimeDir = resolve("./runtime");
|
|
442
|
+
const unpackMeta = await resolveUnpackMeta(isUnheadV2);
|
|
423
443
|
if (config.metaDataFiles) {
|
|
424
|
-
await generateTagsFromPublicFiles();
|
|
444
|
+
await generateTagsFromPublicFiles(unpackMeta);
|
|
425
445
|
await generateTagsFromPageDirImages();
|
|
426
446
|
if (nuxt.options.dev) {
|
|
427
447
|
addServerHandler({
|
|
428
448
|
middleware: true,
|
|
429
|
-
handler: resolve("
|
|
449
|
+
handler: resolve(runtimeDir, "server/middleware/resolveImagesInPagesDir")
|
|
430
450
|
});
|
|
431
451
|
}
|
|
432
452
|
}
|
|
@@ -434,27 +454,27 @@ const module = defineNuxtModule({
|
|
|
434
454
|
if (config.automaticDefaults) {
|
|
435
455
|
if (hasI18n) {
|
|
436
456
|
addPlugin({
|
|
437
|
-
src: resolve(`./
|
|
457
|
+
src: resolve(runtimeDir, `./app/plugins/defaultsWaitI18n`)
|
|
438
458
|
});
|
|
439
459
|
} else {
|
|
440
460
|
addPlugin({
|
|
441
|
-
src: resolve(`./
|
|
461
|
+
src: resolve(runtimeDir, `./app/plugins/defaults`)
|
|
442
462
|
});
|
|
443
463
|
}
|
|
444
464
|
}
|
|
445
465
|
if (config.fallbackTitle) {
|
|
446
466
|
addPlugin({
|
|
447
|
-
src: resolve("./
|
|
467
|
+
src: resolve(runtimeDir, "./app/plugins/titles")
|
|
448
468
|
});
|
|
449
469
|
}
|
|
450
470
|
if (!hasI18n) {
|
|
451
471
|
addImports({
|
|
452
|
-
from: resolve(`./
|
|
472
|
+
from: resolve(runtimeDir, `./app/composables/polyfills`),
|
|
453
473
|
name: "useI18n"
|
|
454
474
|
});
|
|
455
475
|
}
|
|
456
476
|
addImports({
|
|
457
|
-
from: resolve(`./
|
|
477
|
+
from: resolve(runtimeDir, `./app/composables/useBreadcrumbItems`),
|
|
458
478
|
name: "useBreadcrumbItems"
|
|
459
479
|
});
|
|
460
480
|
const polyfills = [
|
|
@@ -468,18 +488,18 @@ const module = defineNuxtModule({
|
|
|
468
488
|
];
|
|
469
489
|
polyfills.forEach((name) => {
|
|
470
490
|
addImports({
|
|
471
|
-
from: resolve("./
|
|
491
|
+
from: resolve(runtimeDir, "./app/composables/polyfills"),
|
|
472
492
|
name
|
|
473
493
|
});
|
|
474
494
|
});
|
|
475
495
|
nuxt.options.experimental.headNext = true;
|
|
476
496
|
if (!nuxt.options.dev && config.redirectToCanonicalSiteUrl) {
|
|
477
497
|
addServerHandler({
|
|
478
|
-
handler: resolve("./
|
|
498
|
+
handler: resolve(runtimeDir, "./server/middleware/redirectCanonical"),
|
|
479
499
|
middleware: true
|
|
480
500
|
});
|
|
481
501
|
}
|
|
482
|
-
nuxt.options.alias["#seo-utils"] =
|
|
502
|
+
nuxt.options.alias["#seo-utils"] = runtimeDir;
|
|
483
503
|
nuxt.options.runtimeConfig.public["seo-utils"] = defu(nuxt.options.runtimeConfig.public["seo-utils"] || {}, {
|
|
484
504
|
canonicalQueryWhitelist: config.canonicalQueryWhitelist || [
|
|
485
505
|
"page",
|
|
@@ -489,20 +509,22 @@ const module = defineNuxtModule({
|
|
|
489
509
|
"q",
|
|
490
510
|
"category",
|
|
491
511
|
"tag"
|
|
492
|
-
]
|
|
512
|
+
],
|
|
513
|
+
canonicalLowercase: config.canonicalLowercase
|
|
493
514
|
});
|
|
494
515
|
if (config.extendNuxtConfigAppHeadSeoMeta)
|
|
495
|
-
extendNuxtConfigAppHeadSeoMeta();
|
|
516
|
+
extendNuxtConfigAppHeadSeoMeta(unpackMeta);
|
|
496
517
|
if (config.setupNuxtConfigAppHeadWithMoreDefaults)
|
|
497
518
|
setupNuxtConfigAppHeadWithMoreDefaults(nuxt);
|
|
498
519
|
if (config.extendNuxtConfigAppHeadTypes)
|
|
499
520
|
extendNuxtConfigAppHeadTypes();
|
|
500
521
|
extendTypes("nuxt-seo-utils", async () => {
|
|
522
|
+
const pkg = isUnheadV2 ? "@unhead/vue/types" : "@unhead/schema";
|
|
501
523
|
let unheadSchemaPath;
|
|
502
524
|
try {
|
|
503
|
-
unheadSchemaPath = relative(resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"), await resolvePath(
|
|
525
|
+
unheadSchemaPath = relative(resolve(nuxt.options.rootDir, nuxt.options.buildDir, "module"), await resolvePath(pkg));
|
|
504
526
|
} catch {
|
|
505
|
-
unheadSchemaPath =
|
|
527
|
+
unheadSchemaPath = pkg;
|
|
506
528
|
}
|
|
507
529
|
return `
|
|
508
530
|
declare module 'nitropack' {
|
|
@@ -525,17 +547,17 @@ declare module 'nuxt/schema' {
|
|
|
525
547
|
}
|
|
526
548
|
`;
|
|
527
549
|
});
|
|
528
|
-
const
|
|
550
|
+
const appRuntimeDir = resolve(runtimeDir, "./app");
|
|
529
551
|
if (config.treeShakeUseSeoMeta)
|
|
530
552
|
addVitePlugin(UnheadVite());
|
|
531
553
|
if (config.automaticOgAndTwitterTags)
|
|
532
|
-
addPlugin({ src: resolve(
|
|
554
|
+
addPlugin({ src: resolve(appRuntimeDir, "plugins", "inferSeoMetaPlugin") });
|
|
533
555
|
if (config.mergeWithSiteConfig)
|
|
534
|
-
addPlugin({ src: resolve(
|
|
556
|
+
addPlugin({ src: resolve(appRuntimeDir, "plugins", "siteConfig") });
|
|
535
557
|
if (config.extendRouteRules)
|
|
536
|
-
addPlugin({ src: resolve(
|
|
558
|
+
addPlugin({ src: resolve(appRuntimeDir, "plugins", "0.routeRules.server"), mode: "server" });
|
|
537
559
|
if (config.fixRequiredAbsoluteMetaTagsLinks)
|
|
538
|
-
addPlugin({ src: resolve(
|
|
560
|
+
addPlugin({ src: resolve(appRuntimeDir, "plugins", "1.absoluteImageUrls.server"), mode: "server" });
|
|
539
561
|
}
|
|
540
562
|
});
|
|
541
563
|
|
|
@@ -61,6 +61,12 @@ export interface BreadcrumbProps {
|
|
|
61
61
|
* Should the root breadcrumb be shown.
|
|
62
62
|
*/
|
|
63
63
|
hideRoot?: MaybeRefOrGetter<boolean>;
|
|
64
|
+
/**
|
|
65
|
+
* The root segment of the breadcrumb list.
|
|
66
|
+
*
|
|
67
|
+
* By default, this will be `/`, unless you're using Nuxt I18n with a prefix strategy.
|
|
68
|
+
*/
|
|
69
|
+
rootSegment?: string;
|
|
64
70
|
}
|
|
65
71
|
export interface BreadcrumbItemProps extends NuxtUIBreadcrumbItem {
|
|
66
72
|
/** Whether the breadcrumb item represents the aria-current. */
|
|
@@ -91,5 +97,5 @@ export interface BreadcrumbItemProps extends NuxtUIBreadcrumbItem {
|
|
|
91
97
|
* @see https://github.com/harlan-zw/nuxt-seo/blob/main/src/runtime/nuxt/composables/useBreadcrumbItems.ts
|
|
92
98
|
* @docs https://nuxtseo.com/nuxt-seo/api/breadcrumbs
|
|
93
99
|
*/
|
|
94
|
-
export declare function useBreadcrumbItems(
|
|
100
|
+
export declare function useBreadcrumbItems(_options?: BreadcrumbProps): import("vue").ComputedRef<BreadcrumbItemProps[]>;
|
|
95
101
|
export {};
|
|
@@ -9,7 +9,7 @@ import { defu } from "defu";
|
|
|
9
9
|
import { fixSlashes } from "nuxt-site-config/urls";
|
|
10
10
|
import { useRouter } from "nuxt/app";
|
|
11
11
|
import { withoutTrailingSlash } from "ufo";
|
|
12
|
-
import { computed, toValue } from "vue";
|
|
12
|
+
import { computed, inject, onUnmounted, getCurrentInstance, provide, ref, toRaw, toValue } from "vue";
|
|
13
13
|
import { pathBreadcrumbSegments } from "../../shared/breadcrumbs.js";
|
|
14
14
|
function withoutQuery(path) {
|
|
15
15
|
return path.split("?")[0];
|
|
@@ -17,7 +17,33 @@ function withoutQuery(path) {
|
|
|
17
17
|
function titleCase(s) {
|
|
18
18
|
return s.replaceAll("-", " ").replace(/\w\S*/g, (w) => w.charAt(0).toUpperCase() + w.substr(1).toLowerCase());
|
|
19
19
|
}
|
|
20
|
-
|
|
20
|
+
const BreadcrumbCtx = Symbol("BreadcrumbCtx");
|
|
21
|
+
export function useBreadcrumbItems(_options = {}) {
|
|
22
|
+
const vm = getCurrentInstance();
|
|
23
|
+
let isCreatingState = false;
|
|
24
|
+
if (vm) {
|
|
25
|
+
let stateRef2 = inject(BreadcrumbCtx, null);
|
|
26
|
+
if (!stateRef2) {
|
|
27
|
+
stateRef2 = ref({});
|
|
28
|
+
provide(BreadcrumbCtx, stateRef2);
|
|
29
|
+
isCreatingState = false;
|
|
30
|
+
}
|
|
31
|
+
const id2 = "breadcrumb";
|
|
32
|
+
const state = stateRef2.value;
|
|
33
|
+
if (!state[id2]) {
|
|
34
|
+
state[id2] = [];
|
|
35
|
+
}
|
|
36
|
+
const idx = state[id2].push(_options) - 1;
|
|
37
|
+
stateRef2.value = state;
|
|
38
|
+
onUnmounted(() => {
|
|
39
|
+
stateRef2.value = Object.fromEntries(Object.entries(stateRef2.value).map(([k, v]) => {
|
|
40
|
+
if (k === id2) {
|
|
41
|
+
return v.filter((_, i) => i !== idx);
|
|
42
|
+
}
|
|
43
|
+
return v;
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
}
|
|
21
47
|
const router = useRouter();
|
|
22
48
|
const i18n = useI18n();
|
|
23
49
|
const siteResolver = createSitePathResolver({
|
|
@@ -26,32 +52,50 @@ export function useBreadcrumbItems(options = {}) {
|
|
|
26
52
|
});
|
|
27
53
|
const siteConfig = useSiteConfig();
|
|
28
54
|
const items = computed(() => {
|
|
29
|
-
|
|
55
|
+
const optionStack = stateRef.value?.[id];
|
|
56
|
+
const flatOptions = toRaw([...optionStack]).reduce((acc, cur) => {
|
|
57
|
+
acc.rootSegment = acc.rootSegment || cur.rootSegment;
|
|
58
|
+
acc.path = acc.path || cur.path;
|
|
59
|
+
return acc;
|
|
60
|
+
}, {});
|
|
61
|
+
let rootNode = flatOptions.rootSegment || "/";
|
|
30
62
|
if (i18n) {
|
|
31
63
|
if (i18n.strategy === "prefix" || i18n.strategy !== "no_prefix" && toValue(i18n.defaultLocale) !== toValue(i18n.locale))
|
|
32
|
-
rootNode =
|
|
64
|
+
rootNode = `${rootNode}${toValue(i18n.locale)}`;
|
|
33
65
|
}
|
|
34
|
-
const current = withoutQuery(withoutTrailingSlash(toValue(
|
|
35
|
-
const
|
|
66
|
+
const current = withoutQuery(withoutTrailingSlash(toValue(flatOptions.path || router.currentRoute.value?.path) || rootNode));
|
|
67
|
+
const allOverrides = toRaw([...optionStack])?.map((opts) => toValue(opts.overrides)).filter(Boolean);
|
|
68
|
+
const flatOverrides = allOverrides?.reduce((acc, i) => {
|
|
69
|
+
if (i) {
|
|
70
|
+
i.forEach((item, index) => {
|
|
71
|
+
if (item !== void 0) {
|
|
72
|
+
acc[index] = item;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return acc;
|
|
77
|
+
}, []) || {};
|
|
36
78
|
const segments = pathBreadcrumbSegments(current, rootNode).map((path, index) => {
|
|
37
79
|
let item = {
|
|
38
80
|
to: path
|
|
39
81
|
};
|
|
40
|
-
if (typeof
|
|
41
|
-
if (
|
|
82
|
+
if (typeof flatOverrides[index] !== "undefined") {
|
|
83
|
+
if (flatOverrides[index] === false)
|
|
42
84
|
return false;
|
|
43
|
-
item = defu(
|
|
85
|
+
item = defu(flatOverrides[index], item);
|
|
44
86
|
}
|
|
45
87
|
return item;
|
|
46
88
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
segments.
|
|
89
|
+
const allPrepends = toRaw([...optionStack]).flatMap((opts) => toValue(opts.prepend)).filter(Boolean);
|
|
90
|
+
const allAppends = toRaw([...optionStack]).flatMap((opts) => toValue(opts.append)).filter(Boolean);
|
|
91
|
+
if (allPrepends.length)
|
|
92
|
+
segments.unshift(...allPrepends);
|
|
93
|
+
if (allAppends.length)
|
|
94
|
+
segments.push(...allAppends);
|
|
51
95
|
return segments.filter(Boolean).map((item) => {
|
|
52
96
|
let fallbackLabel = titleCase(String((item.to || "").split("/").pop()));
|
|
53
97
|
let fallbackAriaLabel = "";
|
|
54
|
-
const route = router.resolve(item.to)?.matched?.[0];
|
|
98
|
+
const route = item.to ? router.resolve(item.to)?.matched?.[0] : null;
|
|
55
99
|
if (route) {
|
|
56
100
|
const routeMeta = route?.meta || {};
|
|
57
101
|
if (routeMeta.breadcrumb) {
|
|
@@ -65,29 +109,29 @@ export function useBreadcrumbItems(options = {}) {
|
|
|
65
109
|
fallbackLabel = "Home";
|
|
66
110
|
}
|
|
67
111
|
fallbackLabel = routeMeta.breadcrumbLabel || routeMeta.breadcrumbTitle || routeMeta.title || fallbackLabel;
|
|
68
|
-
fallbackLabel = i18n.t(`breadcrumb.items.${routeName}.label`, fallbackLabel, { missingWarn:
|
|
112
|
+
fallbackLabel = i18n.t(`breadcrumb.items.${routeName}.label`, fallbackLabel, { missingWarn: false });
|
|
69
113
|
fallbackAriaLabel = i18n.t(`breadcrumb.items.${routeName}.ariaLabel`, fallbackAriaLabel, { missingWarn: false });
|
|
70
114
|
}
|
|
71
115
|
item.label = item.label || fallbackLabel;
|
|
72
116
|
item.ariaLabel = item.ariaLabel || fallbackAriaLabel || item.label;
|
|
73
117
|
item.current = item.current || item.to === current;
|
|
74
|
-
if (toValue(
|
|
118
|
+
if (toValue(flatOptions.hideCurrent) && item.current)
|
|
75
119
|
return false;
|
|
76
120
|
return item;
|
|
77
121
|
}).map((m) => {
|
|
78
122
|
if (m && m.to) {
|
|
79
123
|
m.to = fixSlashes(siteConfig.trailingSlash, m.to);
|
|
80
|
-
if (m.to === rootNode && toValue(
|
|
124
|
+
if (m.to === rootNode && toValue(flatOptions.hideRoot))
|
|
81
125
|
return false;
|
|
82
126
|
}
|
|
83
127
|
return m;
|
|
84
128
|
}).filter(Boolean);
|
|
85
129
|
});
|
|
86
|
-
const schemaOrgEnabled = typeof
|
|
87
|
-
if ((import.meta.dev || import.meta.server) && schemaOrgEnabled) {
|
|
130
|
+
const schemaOrgEnabled = typeof _options.schemaOrg === "undefined" ? true : _options.schemaOrg;
|
|
131
|
+
if (isCreatingState && (import.meta.dev || import.meta.server || import.meta.env?.NODE_ENV === "test") && schemaOrgEnabled) {
|
|
88
132
|
useSchemaOrg([
|
|
89
133
|
defineBreadcrumb({
|
|
90
|
-
id: `#${
|
|
134
|
+
id: `#${_options.id || "breadcrumb"}`,
|
|
91
135
|
itemListElement: computed(() => items.value.map((item) => ({
|
|
92
136
|
name: item.label || item.ariaLabel,
|
|
93
137
|
item: item.to ? siteResolver(item.to) : void 0
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { useHead, useSeoMeta } from "#imports";
|
|
1
2
|
import { useSiteConfig } from "#site-config/app/composables/useSiteConfig";
|
|
2
3
|
import { createSitePathResolver } from "#site-config/app/composables/utils";
|
|
3
|
-
import { useHead, useSeoMeta } from "@unhead/vue";
|
|
4
4
|
import { useError, useRoute, useRuntimeConfig } from "nuxt/app";
|
|
5
5
|
import { stringifyQuery } from "ufo";
|
|
6
6
|
import { computed, ref, watch } from "vue";
|
|
7
7
|
export function applyDefaults(i18n) {
|
|
8
|
-
const { canonicalQueryWhitelist } = useRuntimeConfig().public["seo-utils"];
|
|
8
|
+
const { canonicalQueryWhitelist, canonicalLowercase } = useRuntimeConfig().public["seo-utils"];
|
|
9
9
|
const siteConfig = useSiteConfig();
|
|
10
10
|
const route = useRoute();
|
|
11
11
|
const resolveUrl = createSitePathResolver({ withBase: true, absolute: true });
|
|
@@ -15,9 +15,13 @@ export function applyDefaults(i18n) {
|
|
|
15
15
|
return null;
|
|
16
16
|
}
|
|
17
17
|
const { query } = route;
|
|
18
|
-
|
|
18
|
+
let url = resolveUrl(route.path || "/").value || route.path;
|
|
19
|
+
if (canonicalLowercase) {
|
|
20
|
+
url = url.toLocaleLowerCase(siteConfig.currentLocale);
|
|
21
|
+
}
|
|
19
22
|
const filteredQuery = Object.fromEntries(
|
|
20
|
-
Object.entries(query).filter(([key]) => canonicalQueryWhitelist.includes(key))
|
|
23
|
+
Object.entries(query).filter(([key]) => canonicalQueryWhitelist.includes(key)).sort(([a], [b]) => a.localeCompare(b))
|
|
24
|
+
// Sort params
|
|
21
25
|
);
|
|
22
26
|
return Object.keys(filteredQuery).length ? `${url}?${stringifyQuery(filteredQuery)}` : url;
|
|
23
27
|
});
|
|
@@ -29,6 +33,7 @@ export function applyDefaults(i18n) {
|
|
|
29
33
|
deep: true
|
|
30
34
|
});
|
|
31
35
|
}
|
|
36
|
+
useHead({ templateParams });
|
|
32
37
|
const minimalPriority = {
|
|
33
38
|
// give nuxt.config values higher priority
|
|
34
39
|
tagPriority: 101
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { injectHead } from "#imports";
|
|
2
|
+
import { unpackMeta } from "@unhead/vue/utils";
|
|
3
3
|
import { defineNuxtPlugin, useRequestEvent } from "nuxt/app";
|
|
4
4
|
export default defineNuxtPlugin({
|
|
5
5
|
enforce: "post",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { injectHead } from "#imports";
|
|
1
2
|
import { createSitePathResolver } from "#site-config/app/composables/utils";
|
|
2
|
-
import { injectHead } from "@unhead/vue";
|
|
3
3
|
import { defineNuxtPlugin } from "nuxt/app";
|
|
4
4
|
import { unref } from "vue";
|
|
5
5
|
export default defineNuxtPlugin({
|
|
@@ -14,6 +14,7 @@ export default defineNuxtPlugin({
|
|
|
14
14
|
canonical: true
|
|
15
15
|
});
|
|
16
16
|
head.use({
|
|
17
|
+
key: "absoluteImageUrls",
|
|
17
18
|
hooks: {
|
|
18
19
|
"tags:resolve": async ({ tags }) => {
|
|
19
20
|
for (const tag of tags) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { injectHead } from "#imports";
|
|
1
2
|
import { useSiteConfig } from "#site-config/app/composables/useSiteConfig";
|
|
2
|
-
import { injectHead } from "@unhead/vue";
|
|
3
3
|
import { defineNuxtPlugin } from "nuxt/app";
|
|
4
4
|
export default defineNuxtPlugin(() => {
|
|
5
5
|
const head = injectHead();
|
|
@@ -24,9 +24,10 @@ export default defineNuxtPlugin(() => {
|
|
|
24
24
|
input.meta.push(
|
|
25
25
|
{
|
|
26
26
|
name: "description",
|
|
27
|
-
content: "%site.description"
|
|
27
|
+
content: "%site.description",
|
|
28
|
+
tagPriority: "low"
|
|
28
29
|
}
|
|
29
30
|
);
|
|
30
31
|
}
|
|
31
|
-
head.push(input
|
|
32
|
+
head.push(input);
|
|
32
33
|
});
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "
|
|
3
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../../.nuxt/tsconfig.server.json"
|
|
3
|
+
}
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { Head, UseSeoMetaInput } from '@unhead/
|
|
1
|
+
export type { Head, UseSeoMetaInput } from '@unhead/vue';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-seo-utils",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "7.0.0",
|
|
5
5
|
"description": "SEO utilities to improve your Nuxt sites discoverability and shareability",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/harlan-zw",
|
|
@@ -29,44 +29,50 @@
|
|
|
29
29
|
"externals": [
|
|
30
30
|
"@unhead/shared",
|
|
31
31
|
"@unhead/schema",
|
|
32
|
-
"unhead"
|
|
32
|
+
"unhead",
|
|
33
|
+
"@unhead/vue/utils"
|
|
33
34
|
]
|
|
34
35
|
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@unhead/vue": "^1 || ^2"
|
|
38
|
+
},
|
|
35
39
|
"dependencies": {
|
|
36
|
-
"@nuxt/kit": "^3.
|
|
37
|
-
"@unhead/addons": "
|
|
40
|
+
"@nuxt/kit": "^3.16.0",
|
|
41
|
+
"@unhead/addons": "2.0.0-rc.9",
|
|
38
42
|
"defu": "^6.1.4",
|
|
39
43
|
"escape-string-regexp": "^5.0.0",
|
|
40
44
|
"fast-glob": "^3.3.3",
|
|
41
45
|
"image-size": "^1.2.0",
|
|
42
|
-
"
|
|
43
|
-
"nuxt-site-config": "^3.1.0",
|
|
46
|
+
"nuxt-site-config": "^3.1.4",
|
|
44
47
|
"pathe": "^2.0.3",
|
|
48
|
+
"pkg-types": "^2.1.0",
|
|
45
49
|
"scule": "^1.3.0",
|
|
50
|
+
"semver": "^7.7.1",
|
|
46
51
|
"ufo": "^1.5.4"
|
|
47
52
|
},
|
|
48
53
|
"devDependencies": {
|
|
49
|
-
"@antfu/eslint-config": "4.
|
|
54
|
+
"@antfu/eslint-config": "4.8.1",
|
|
50
55
|
"@nuxt/module-builder": "^0.8.4",
|
|
51
|
-
"@nuxt/test-utils": "^3.
|
|
52
|
-
"@nuxt/ui": "^2.21.
|
|
56
|
+
"@nuxt/test-utils": "^3.17.2",
|
|
57
|
+
"@nuxt/ui": "^2.21.1",
|
|
53
58
|
"@nuxtjs/eslint-config-typescript": "^12.1.0",
|
|
54
|
-
"@nuxtjs/i18n": "^9.
|
|
55
|
-
"@unhead/schema": "^1.11.
|
|
56
|
-
"@unhead/shared": "^1.11.
|
|
59
|
+
"@nuxtjs/i18n": "^9.3.0",
|
|
60
|
+
"@unhead/schema": "^1.11.20",
|
|
61
|
+
"@unhead/shared": "^1.11.20",
|
|
62
|
+
"@unhead/vue": "2.0.0-rc.9",
|
|
57
63
|
"@vue/test-utils": "^2.4.6",
|
|
58
64
|
"bumpp": "^10.0.3",
|
|
59
65
|
"case-police": "^1.0.0",
|
|
60
66
|
"cheerio": "1.0.0",
|
|
61
|
-
"eslint": "^9.
|
|
62
|
-
"happy-dom": "^17.
|
|
67
|
+
"eslint": "^9.22.0",
|
|
68
|
+
"happy-dom": "^17.4.2",
|
|
63
69
|
"markdownlint-cli": "^0.44.0",
|
|
64
|
-
"nuxt": "^3.
|
|
70
|
+
"nuxt": "^3.16.0",
|
|
65
71
|
"nuxt-content-twoslash": "^0.1.2",
|
|
66
|
-
"playwright-core": "^1.
|
|
67
|
-
"sass": "^1.85.
|
|
68
|
-
"typescript": "5.
|
|
69
|
-
"vitest": "^3.0.
|
|
72
|
+
"playwright-core": "^1.51.0",
|
|
73
|
+
"sass": "^1.85.1",
|
|
74
|
+
"typescript": "5.8.2",
|
|
75
|
+
"vitest": "^3.0.8"
|
|
70
76
|
},
|
|
71
77
|
"resolutions": {
|
|
72
78
|
"typescript": "5.6.3"
|