mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-31 07:40:33 +00:00
Merge branch 'main' into patch-21
This commit is contained in:
commit
6e6832fc3e
@ -71,62 +71,7 @@ There are two ways to deploy a Nuxt application to any static hosting services:
|
||||
- Static site generation (SSG) with `ssr: true` pre-renders routes of your application at build time. (This is the default behavior when running `nuxi generate`.) It will also generate `/200.html` and `/404.html` single-page app fallback pages, which can render dynamic routes or 404 errors on the client (though you may need to configure this on your static host).
|
||||
- Alternatively, you can prerender your site with `ssr: false` (static single-page app). This will produce HTML pages with an empty `<div id="__nuxt"></div>` where your Vue app would normally be rendered. You will lose many SEO benefits of prerendering your site, so it is suggested instead to use [`<ClientOnly>`](/docs/api/components/client-only) to wrap the portions of your site that cannot be server rendered (if any).
|
||||
|
||||
### Crawl-based Pre-rendering
|
||||
|
||||
Use the [`nuxi generate` command](/docs/api/commands/generate) to build and pre-render your application using the [Nitro](/docs/guide/concepts/server-engine) crawler. This command is similar to `nuxt build` with the `nitro.static` option set to `true`, or running `nuxt build --prerender`.
|
||||
|
||||
```bash [Terminal]
|
||||
npx nuxi generate
|
||||
```
|
||||
|
||||
That's it! You can now deploy the `.output/public` directory to any static hosting service or preview it locally with `npx serve .output/public`.
|
||||
|
||||
Working of the Nitro crawler:
|
||||
|
||||
1. Load the HTML of your application's root route (`/`), any non-dynamic pages in your `~/pages` directory, and any other routes in the `nitro.prerender.routes` array.
|
||||
2. Save the HTML and `payload.json` to the `~/.output/public/` directory to be served statically.
|
||||
3. Find all anchor tags (`<a href="...">`) in the HTML to navigate to other routes.
|
||||
4. Repeat steps 1-3 for each anchor tag found until there are no more anchor tags to crawl.
|
||||
|
||||
This is important to understand since pages that are not linked to a discoverable page can't be pre-rendered automatically.
|
||||
|
||||
::read-more{to="/docs/api/commands/generate#nuxi-generate"}
|
||||
Read more about the `nuxi generate` command.
|
||||
::
|
||||
|
||||
### Selective Pre-rendering
|
||||
|
||||
You can manually specify routes that [Nitro](/docs/guide/concepts/server-engine) will fetch and pre-render during the build or ignore routes that you don't want to pre-render like `/dynamic` in the `nuxt.config` file:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
prerender: {
|
||||
routes: ['/user/1', '/user/2'],
|
||||
ignore: ['/dynamic']
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can combine this with the `crawlLinks` option to pre-render a set of routes that the crawler can't discover like your `/sitemap.xml` or `/robots.txt`:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
prerender: {
|
||||
crawlLinks: true,
|
||||
routes: ['/sitemap.xml', '/robots.txt']
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Setting `nitro.prerender` to `true` is similar to `nitro.prerender.crawlLinks` to `true`.
|
||||
|
||||
::read-more{to="https://nitro.unjs.io/config#prerender"}
|
||||
Read more about pre-rendering in the Nitro documentation.
|
||||
::
|
||||
:read-more{title="Nuxt prerendering" to="/docs/getting-started/prerendering"}
|
||||
|
||||
### Client-side Only Rendering
|
||||
|
||||
|
@ -23,7 +23,7 @@ To use the latest Nuxt build and test features before their release, read about
|
||||
|
||||
Nuxt 4 is planned to be released **on or before June 14** (though obviously this is dependent on having enough time after Nitro's major release to be properly tested in the community, so be aware that this is not an exact date).
|
||||
|
||||
Until then, it is possible to test many of Nuxt 4's breaking changes on the nightly release channel.
|
||||
Until then, it is possible to test many of Nuxt 4's breaking changes from Nuxt version 3.12 or via the nightly release channel.
|
||||
|
||||
::tip{icon="i-ph-video-duotone" to="https://www.youtube.com/watch?v=r4wFKlcJK6c" target="_blank"}
|
||||
Watch a video from Alexander Lichter showing how to opt in to Nuxt 4's breaking changes already.
|
||||
@ -223,6 +223,46 @@ export default defineNuxtConfig({
|
||||
|
||||
Please report an issue if you are doing this, as we do not plan to keep this as configurable.
|
||||
|
||||
#### Removal of deprecated `boolean` values for `dedupe` option when calling `refresh` in `useAsyncData` and `useFetch`
|
||||
|
||||
🚦 **Impact Level**: Minimal
|
||||
|
||||
##### What Changed
|
||||
|
||||
Previously it was possible to pass `dedupe: boolean` to `refresh`. These were aliases of `cancel` (`true`) and `defer` (`false`).
|
||||
|
||||
```ts twoslash [app.vue]
|
||||
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
|
||||
|
||||
async function refreshData () {
|
||||
await refresh({ dedupe: true })
|
||||
}
|
||||
```
|
||||
|
||||
##### Reasons for Change
|
||||
|
||||
These aliases were removed, for greater clarity.
|
||||
|
||||
The issue came up when adding `dedupe` as an option to `useAsyncData`, and we removed the boolean values as they ended up being _opposites_.
|
||||
|
||||
`refresh({ dedupe: false })` meant 'do not _cancel_ existing requests in favour of this new one'. But passing `dedupe: true` within the options of `useAsyncData` means 'do not make any new requests if there is an existing pending request.' (See [PR](https://github.com/nuxt/nuxt/pull/24564#pullrequestreview-1764584361).)
|
||||
|
||||
##### Migration Steps
|
||||
|
||||
The migration should be straightforward:
|
||||
|
||||
```diff
|
||||
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
|
||||
|
||||
async function refreshData () {
|
||||
- await refresh({ dedupe: true })
|
||||
+ await refresh({ dedupe: 'cancel' })
|
||||
|
||||
- await refresh({ dedupe: false })
|
||||
+ await refresh({ dedupe: 'defer' })
|
||||
}
|
||||
```
|
||||
|
||||
#### Respect defaults when clearing `data` in `useAsyncData` and `useFetch`
|
||||
|
||||
🚦 **Impact Level**: Minimal
|
||||
|
177
docs/1.getting-started/9.prerendering.md
Normal file
177
docs/1.getting-started/9.prerendering.md
Normal file
@ -0,0 +1,177 @@
|
||||
---
|
||||
title: "Prerendering"
|
||||
description: Nuxt allows pages to be statically rendered at build time to improve certain performance or SEO metrics
|
||||
navigation.icon: i-ph-code-block-duotone
|
||||
---
|
||||
|
||||
Nuxt allows for select pages from your application to be rendered at build time. Nuxt will serve the prebuilt pages when requested instead of generating them on the fly.
|
||||
|
||||
:read-more{title="Nuxt rendering modes" to="/docs/guide/concepts/rendering"}
|
||||
|
||||
## Crawl-based Pre-rendering
|
||||
|
||||
Use the [`nuxi generate` command](/docs/api/commands/generate) to build and pre-render your application using the [Nitro](/docs/guide/concepts/server-engine) crawler. This command is similar to `nuxt build` with the `nitro.static` option set to `true`, or running `nuxt build --prerender`.
|
||||
|
||||
This will build your site, stand up a nuxt instance, and, by default, prerender the root page `/` along with any of your site's pages it links to, any of your site's pages they link to, and so on.
|
||||
|
||||
```bash [Terminal]
|
||||
npx nuxi generate
|
||||
```
|
||||
|
||||
You can now deploy the `.output/public` directory to any static hosting service or preview it locally with `npx serve .output/public`.
|
||||
|
||||
Working of the Nitro crawler:
|
||||
|
||||
1. Load the HTML of your application's root route (`/`), any non-dynamic pages in your `~/pages` directory, and any other routes in the `nitro.prerender.routes` array.
|
||||
2. Save the HTML and `payload.json` to the `~/.output/public/` directory to be served statically.
|
||||
3. Find all anchor tags (`<a href="...">`) in the HTML to navigate to other routes.
|
||||
4. Repeat steps 1-3 for each anchor tag found until there are no more anchor tags to crawl.
|
||||
|
||||
This is important to understand since pages that are not linked to a discoverable page can't be pre-rendered automatically.
|
||||
|
||||
::read-more{to="/docs/api/commands/generate#nuxi-generate"}
|
||||
Read more about the `nuxi generate` command.
|
||||
::
|
||||
|
||||
### Selective Pre-rendering
|
||||
|
||||
You can manually specify routes that [Nitro](/docs/guide/concepts/server-engine) will fetch and pre-render during the build or ignore routes that you don't want to pre-render like `/dynamic` in the `nuxt.config` file:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
prerender: {
|
||||
routes: ["/user/1", "/user/2"],
|
||||
ignore: ["/dynamic"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
You can combine this with the `crawlLinks` option to pre-render a set of routes that the crawler can't discover like your `/sitemap.xml` or `/robots.txt`:
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
prerender: {
|
||||
crawlLinks: true,
|
||||
routes: ["/sitemap.xml", "/robots.txt"],
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Setting `nitro.prerender` to `true` is similar to `nitro.prerender.crawlLinks` to `true`.
|
||||
|
||||
::read-more{to="https://nitro.unjs.io/config#prerender"}
|
||||
Read more about pre-rendering in the Nitro documentation.
|
||||
::
|
||||
|
||||
Lastly, you can manually configure this using routeRules.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
// Set prerender to true to configure it to be prerendered
|
||||
"/rss.xml": { prerender: true },
|
||||
// Set it to false to configure it to be skipped for prerendering
|
||||
"/this-DOES-NOT-get-prerendered": { prerender: false },
|
||||
// Everything under /blog gets prerendered as long as it
|
||||
// is linked to from another page
|
||||
"/blog/**": { prerender: true },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
::read-more{to="https://nitro.unjs.io/config/#routerules"}
|
||||
Read more about Nitro's `routeRules` configuration.
|
||||
::
|
||||
|
||||
As a shorthand, you can also configure this in a page file using [`defineRouteRules`](/docs/api/utils/define-route-rules).
|
||||
|
||||
::read-more{to="/docs/guide/going-further/experimental-features#inlinerouterules" icon="i-ph-star-duotone"}
|
||||
This feature is experimental and in order to use it you must enable the `experimental.inlineRouteRules` option in your `nuxt.config`.
|
||||
::
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup>
|
||||
// Or set at the page level
|
||||
defineRouteRules({
|
||||
prerender: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>Homepage</h1>
|
||||
<p>Pre-rendered at build time</p>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
This will be translated to:
|
||||
|
||||
```ts [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
routeRules: {
|
||||
"/": { prerender: true },
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Runtime prerender configuration
|
||||
|
||||
### `prerenderRoutes`
|
||||
|
||||
You can use this at runtime within a [Nuxt context](/docs/guide/going-further/nuxt-app#the-nuxt-context) to add more routes for Nitro to prerender.
|
||||
|
||||
```vue [pages/index.vue]
|
||||
<script setup>
|
||||
prerenderRoutes(["/some/other/url"]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>This will register other routes for prerendering when prerendered</h1>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
:read-more{title="prerenderRoutes" to="/docs/api/utils/prerender-routes"}
|
||||
|
||||
### `prerender:routes` Nuxt hook
|
||||
|
||||
This is called before prerendering for additional routes to be registered.
|
||||
|
||||
```ts [nitro.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
hooks: {
|
||||
async "prerender:routes"(ctx) {
|
||||
const { pages } = await fetch("https://api.some-cms.com/pages").then(
|
||||
(res) => res.json(),
|
||||
);
|
||||
for (const page of pages) {
|
||||
ctx.routes.add(`/${page.name}`);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### `prerender:generate` Nitro hook
|
||||
|
||||
This is called for each route during prerendering. You can use this for fine grained handling of each route that gets prerendered.
|
||||
|
||||
```ts [nitro.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
nitro: {
|
||||
hooks: {
|
||||
"prerender:generate"(route) {
|
||||
if (route.route?.includes("private")) {
|
||||
route.skip = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
@ -81,8 +81,9 @@ Watch a video from Alexander Lichter about the Object Syntax for Nuxt plugins.
|
||||
::
|
||||
|
||||
::note
|
||||
If you are using the object-syntax, the properties may be statically analyzed in future to produce a more optimized build. So you should not define them at runtime. :br
|
||||
If you are using the object-syntax, the properties are statically analyzed to produce a more optimized build. So you should not define them at runtime. :br
|
||||
For example, setting `enforce: import.meta.server ? 'pre' : 'post'` would defeat any future optimization Nuxt is able to do for your plugins.
|
||||
Nuxt does statically pre-load any hook listeners when using object-syntax, allowing you to define hooks without needing to worry about order of plugin registration.
|
||||
::
|
||||
|
||||
## Registration Order
|
||||
|
@ -386,7 +386,7 @@ This option allows exposing some route metadata defined in `definePageMeta` at b
|
||||
|
||||
This only works with static or strings/arrays rather than variables or conditional assignment. See [original issue](https://github.com/nuxt/nuxt/issues/24770) for more information and context.
|
||||
|
||||
<!-- You can disable this feature if it causes issues in your project.
|
||||
You can disable this feature if it causes issues in your project.
|
||||
|
||||
```ts twoslash [nuxt.config.ts]
|
||||
export default defineNuxtConfig({
|
||||
@ -394,7 +394,7 @@ export default defineNuxtConfig({
|
||||
scanPageMeta: false
|
||||
}
|
||||
})
|
||||
``` -->
|
||||
```
|
||||
|
||||
## cookieStore
|
||||
|
||||
|
@ -40,7 +40,7 @@ There is also a `future` namespace for early opting-in to new features that will
|
||||
### compatibilityVersion
|
||||
|
||||
::important
|
||||
This configuration option is available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||
This configuration option is available in Nuxt v3.12+.
|
||||
::
|
||||
|
||||
This enables early access to Nuxt features or flags.
|
||||
|
@ -11,7 +11,7 @@ links:
|
||||
---
|
||||
|
||||
::important
|
||||
This component will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||
This component is available in Nuxt v3.12+.
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
@ -10,7 +10,7 @@ links:
|
||||
---
|
||||
|
||||
::important
|
||||
This composable will be available in Nuxt v3.12+ or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||
This composable is available in Nuxt v3.12+.
|
||||
::
|
||||
|
||||
`onPrehydrate` is a composable lifecycle hook that allows you to run a callback on the client immediately before
|
||||
|
@ -11,7 +11,7 @@ links:
|
||||
---
|
||||
|
||||
::important
|
||||
This composable will be available in Nuxt v3.12 or in [the nightly release channel](/docs/guide/going-further/nightly-release-channel).
|
||||
This composable is available in Nuxt v3.12+.
|
||||
::
|
||||
|
||||
## Description
|
||||
|
@ -73,10 +73,10 @@
|
||||
"globby": "14.0.1",
|
||||
"h3": "1.11.1",
|
||||
"happy-dom": "14.12.0",
|
||||
"jiti": "1.21.0",
|
||||
"jiti": "1.21.6",
|
||||
"markdownlint-cli": "0.41.0",
|
||||
"nitropack": "2.9.6",
|
||||
"nuxi": "3.11.1",
|
||||
"nuxi": "3.12.0",
|
||||
"nuxt": "workspace:*",
|
||||
"nuxt-content-twoslash": "0.0.10",
|
||||
"ofetch": "1.3.4",
|
||||
@ -90,10 +90,10 @@
|
||||
"vitest": "1.6.0",
|
||||
"vitest-environment-nuxt": "1.0.0",
|
||||
"vue": "3.4.27",
|
||||
"vue-router": "4.3.2",
|
||||
"vue-router": "4.3.3",
|
||||
"vue-tsc": "2.0.21"
|
||||
},
|
||||
"packageManager": "pnpm@9.2.0",
|
||||
"packageManager": "pnpm@9.3.0",
|
||||
"engines": {
|
||||
"node": "^16.10.0 || >=18.0.0"
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/kit",
|
||||
"version": "3.11.2",
|
||||
"version": "3.12.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
@ -34,7 +34,7 @@
|
||||
"globby": "^14.0.1",
|
||||
"hash-sum": "^2.0.0",
|
||||
"ignore": "^5.3.1",
|
||||
"jiti": "^1.21.0",
|
||||
"jiti": "^1.21.6",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.1.0",
|
||||
"mlly": "^1.7.1",
|
||||
|
@ -3,7 +3,7 @@ import { performance } from 'node:perf_hooks'
|
||||
import { defu } from 'defu'
|
||||
import { applyDefaults } from 'untyped'
|
||||
import { dirname } from 'pathe'
|
||||
import type { ModuleDefinition, ModuleOptions, ModuleSetupInstallResult, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedModuleOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
|
||||
import type { ModuleDefinition, ModuleOptions, ModuleSetupReturn, Nuxt, NuxtModule, NuxtOptions, ResolvedNuxtTemplate } from '@nuxt/schema'
|
||||
import { logger } from '../logger'
|
||||
import { nuxtCtx, tryUseNuxt, useNuxt } from '../context'
|
||||
import { checkNuxtCompatibility, isNuxt2 } from '../compatibility'
|
||||
@ -13,53 +13,28 @@ import { compileTemplate, templateUtils } from '../internal/template'
|
||||
* Define a Nuxt module, automatically merging defaults with user provided options, installing
|
||||
* any hooks that are provided, and calling an optional setup function for full control.
|
||||
*/
|
||||
export function defineNuxtModule<TOptions extends ModuleOptions> (definition: ModuleDefinition<TOptions> | NuxtModule<TOptions>): NuxtModule<TOptions>
|
||||
|
||||
export function defineNuxtModule<TOptions extends ModuleOptions> (): {
|
||||
with: <TOptionsDefaults extends Partial<TOptions>> (
|
||||
definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>
|
||||
) => NuxtModule<TOptions, TOptionsDefaults>
|
||||
}
|
||||
|
||||
export function defineNuxtModule<TOptions extends ModuleOptions> (definition?: ModuleDefinition<TOptions> | NuxtModule<TOptions>) {
|
||||
if (definition) {
|
||||
return _defineNuxtModule(definition)
|
||||
}
|
||||
|
||||
return {
|
||||
with: <TOptionsDefaults extends Partial<TOptions>>(
|
||||
definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>,
|
||||
) => _defineNuxtModule(definition),
|
||||
}
|
||||
}
|
||||
|
||||
function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults extends Partial<TOptions>> (definition: ModuleDefinition<TOptions, TOptionsDefaults> | NuxtModule<TOptions, TOptionsDefaults>): NuxtModule<TOptions, TOptionsDefaults> {
|
||||
if (typeof definition === 'function') { return _defineNuxtModule<TOptions, TOptionsDefaults>({ setup: definition }) }
|
||||
export function defineNuxtModule<OptionsT extends ModuleOptions> (definition: ModuleDefinition<OptionsT> | NuxtModule<OptionsT>): NuxtModule<OptionsT> {
|
||||
if (typeof definition === 'function') { return defineNuxtModule({ setup: definition }) }
|
||||
|
||||
// Normalize definition and meta
|
||||
const module: ModuleDefinition<TOptions, TOptionsDefaults> & Required<Pick<ModuleDefinition<TOptions, TOptionsDefaults>, 'meta'>> = defu(definition, { meta: {} })
|
||||
|
||||
module.meta.configKey ||= module.meta.name
|
||||
|
||||
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
||||
async function getOptions (inlineOptions?: Partial<TOptions>, nuxt: Nuxt = useNuxt()): Promise<ResolvedModuleOptions<TOptions, TOptionsDefaults>> {
|
||||
const nuxtConfigOptionsKey = module.meta.configKey || module.meta.name
|
||||
|
||||
const nuxtConfigOptions: Partial<TOptions> = nuxtConfigOptionsKey && nuxtConfigOptionsKey in nuxt.options ? nuxt.options[<keyof NuxtOptions> nuxtConfigOptionsKey] : {}
|
||||
|
||||
const optionsDefaults: TOptionsDefaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults ?? <TOptionsDefaults> {}
|
||||
|
||||
let options: ResolvedModuleOptions<TOptions, TOptionsDefaults> = defu(inlineOptions, nuxtConfigOptions, optionsDefaults)
|
||||
|
||||
if (module.schema) {
|
||||
options = await applyDefaults(module.schema, options) as any
|
||||
const module: ModuleDefinition<OptionsT> & Required<Pick<ModuleDefinition<OptionsT>, 'meta'>> = defu(definition, { meta: {} })
|
||||
if (module.meta.configKey === undefined) {
|
||||
module.meta.configKey = module.meta.name
|
||||
}
|
||||
|
||||
return Promise.resolve(options)
|
||||
// Resolves module options from inline options, [configKey] in nuxt.config, defaults and schema
|
||||
async function getOptions (inlineOptions?: OptionsT, nuxt: Nuxt = useNuxt()) {
|
||||
const configKey = module.meta.configKey || module.meta.name!
|
||||
const _defaults = module.defaults instanceof Function ? module.defaults(nuxt) : module.defaults
|
||||
let _options = defu(inlineOptions, nuxt.options[configKey as keyof NuxtOptions], _defaults) as OptionsT
|
||||
if (module.schema) {
|
||||
_options = await applyDefaults(module.schema, _options) as OptionsT
|
||||
}
|
||||
return Promise.resolve(_options)
|
||||
}
|
||||
|
||||
// Module format is always a simple function
|
||||
async function normalizedModule (this: any, inlineOptions: Partial<TOptions>, nuxt: Nuxt): Promise<ModuleSetupReturn> {
|
||||
async function normalizedModule (this: any, inlineOptions: OptionsT, nuxt: Nuxt) {
|
||||
if (!nuxt) {
|
||||
nuxt = tryUseNuxt() || this.nuxt /* invoked by nuxt 2 */
|
||||
}
|
||||
@ -112,7 +87,7 @@ function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults exte
|
||||
if (res === false) { return false }
|
||||
|
||||
// Return module install result
|
||||
return defu(res, <ModuleSetupInstallResult> {
|
||||
return defu(res, <ModuleSetupReturn> {
|
||||
timings: {
|
||||
setup: setupTime,
|
||||
},
|
||||
@ -123,7 +98,7 @@ function _defineNuxtModule<TOptions extends ModuleOptions, TOptionsDefaults exte
|
||||
normalizedModule.getMeta = () => Promise.resolve(module.meta)
|
||||
normalizedModule.getOptions = getOptions
|
||||
|
||||
return <NuxtModule<TOptions, TOptionsDefaults>> normalizedModule
|
||||
return normalizedModule as NuxtModule<OptionsT>
|
||||
}
|
||||
|
||||
// -- Nuxt 2 compatibility shims --
|
||||
|
@ -31,7 +31,7 @@ export async function installModule<
|
||||
isNuxt2()
|
||||
// @ts-expect-error Nuxt 2 `moduleContainer` is not typed
|
||||
? await nuxtModule.call(nuxt.moduleContainer, inlineOptions, nuxt)
|
||||
: await nuxtModule(inlineOptions || {}, nuxt)
|
||||
: await nuxtModule(inlineOptions, nuxt)
|
||||
) ?? {}
|
||||
if (res === false /* setup aborted */) {
|
||||
return
|
||||
@ -84,7 +84,7 @@ export async function loadNuxtModuleInstance (nuxtModule: string | NuxtModule, n
|
||||
let error: unknown
|
||||
for (const path of paths) {
|
||||
try {
|
||||
const src = await resolvePath(path)
|
||||
const src = await resolvePath(path, { fallbackToOriginal: true })
|
||||
// Prefer ESM resolution if possible
|
||||
nuxtModule = await importModule(src, nuxt.options.modulesDir).catch(() => null) ?? requireModule(src, { paths: nuxt.options.modulesDir })
|
||||
|
||||
|
@ -23,6 +23,13 @@ export interface ResolvePathOptions {
|
||||
* @default false
|
||||
*/
|
||||
virtual?: boolean
|
||||
|
||||
/**
|
||||
* Whether to fallback to the original path if the resolved path does not exist instead of returning the normalized input path.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
fallbackToOriginal?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,7 +106,7 @@ export async function resolvePath (path: string, opts: ResolvePathOptions = {}):
|
||||
}
|
||||
|
||||
// Return normalized input
|
||||
return path
|
||||
return opts.fallbackToOriginal ? _path : path
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,7 +135,7 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
/* Base options: */
|
||||
esModuleInterop: true,
|
||||
skipLibCheck: true,
|
||||
target: 'es2022',
|
||||
target: 'ESNext',
|
||||
allowJs: true,
|
||||
resolveJsonModule: true,
|
||||
moduleDetection: 'force',
|
||||
@ -147,11 +147,11 @@ export async function _generateTypes (nuxt: Nuxt) {
|
||||
forceConsistentCasingInFileNames: true,
|
||||
noImplicitOverride: true,
|
||||
/* If NOT transpiling with TypeScript: */
|
||||
module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'es2022',
|
||||
module: hasTypescriptVersionWithModulePreserve ? 'preserve' : 'ESNext',
|
||||
noEmit: true,
|
||||
/* If your code runs in the DOM: */
|
||||
lib: [
|
||||
'es2022',
|
||||
'ESNext',
|
||||
'dom',
|
||||
'dom.iterable',
|
||||
],
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nuxt",
|
||||
"version": "3.11.2",
|
||||
"version": "3.12.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
@ -84,13 +84,13 @@
|
||||
"h3": "^1.11.1",
|
||||
"hookable": "^5.5.3",
|
||||
"ignore": "^5.3.1",
|
||||
"jiti": "^1.21.0",
|
||||
"jiti": "^1.21.6",
|
||||
"klona": "^2.0.6",
|
||||
"knitwork": "^1.1.0",
|
||||
"magic-string": "^0.30.10",
|
||||
"mlly": "^1.7.1",
|
||||
"nitropack": "^2.9.6",
|
||||
"nuxi": "^3.11.1",
|
||||
"nuxi": "^3.12.0",
|
||||
"nypm": "^0.3.8",
|
||||
"ofetch": "^1.3.4",
|
||||
"ohash": "^1.1.3",
|
||||
@ -115,9 +115,10 @@
|
||||
"vue": "^3.4.27",
|
||||
"vue-bundle-renderer": "^2.1.0",
|
||||
"vue-devtools-stub": "^0.1.0",
|
||||
"vue-router": "^4.3.2"
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/scripts": "0.4.7",
|
||||
"@nuxt/ui-templates": "1.3.4",
|
||||
"@parcel/watcher": "2.4.1",
|
||||
"@types/estree": "1.0.5",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Component } from 'vue'
|
||||
import type { Component, PropType } from 'vue'
|
||||
import { Fragment, Teleport, computed, createStaticVNode, createVNode, defineComponent, getCurrentInstance, h, nextTick, onMounted, ref, toRaw, watch, withMemo } from 'vue'
|
||||
import { debounce } from 'perfect-debounce'
|
||||
import { hash } from 'ohash'
|
||||
@ -59,6 +59,10 @@ export default defineComponent({
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
scopeId: {
|
||||
type: String as PropType<string | undefined | null>,
|
||||
default: () => undefined,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
default: () => undefined,
|
||||
@ -131,6 +135,10 @@ export default defineComponent({
|
||||
const currentSlots = Object.keys(slots)
|
||||
let html = ssrHTML.value
|
||||
|
||||
if (props.scopeId) {
|
||||
html = html.replace(/^<[^> ]*/, full => full + ' ' + props.scopeId)
|
||||
}
|
||||
|
||||
if (import.meta.client && !canLoadClientComponent.value) {
|
||||
for (const [key, value] of Object.entries(payloads.components || {})) {
|
||||
html = html.replace(new RegExp(` data-island-uid="${uid.value}" data-island-component="${key}"[^>]*>`), (full) => {
|
||||
|
@ -11,7 +11,7 @@ import { onNuxtReady } from './ready'
|
||||
import { asyncDataDefaults, resetAsyncDataToUndefined } from '#build/nuxt.config.mjs'
|
||||
|
||||
// TODO: temporary module for backwards compatibility
|
||||
import type { DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||
import type { DedupeOption, DefaultAsyncDataErrorValue, DefaultAsyncDataValue } from '#app/defaults'
|
||||
|
||||
export type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
|
||||
|
||||
@ -99,7 +99,6 @@ export interface AsyncDataOptions<
|
||||
|
||||
export interface AsyncDataExecuteOptions {
|
||||
_initial?: boolean
|
||||
// TODO: remove boolean option in Nuxt 4
|
||||
/**
|
||||
* Force a refresh, even if there is already a pending request. Previous requests will
|
||||
* not be cancelled, but their result will not affect the data/pending state - and any
|
||||
@ -108,7 +107,7 @@ export interface AsyncDataExecuteOptions {
|
||||
* Instead of using `boolean` values, use `cancel` for `true` and `defer` for `false`.
|
||||
* Boolean values will be removed in a future release.
|
||||
*/
|
||||
dedupe?: boolean | 'cancel' | 'defer'
|
||||
dedupe?: DedupeOption
|
||||
}
|
||||
|
||||
export interface _AsyncData<DataT, ErrorT> {
|
||||
|
@ -7,8 +7,8 @@ export const onNuxtReady = (callback: () => any) => {
|
||||
|
||||
const nuxtApp = useNuxtApp()
|
||||
if (nuxtApp.isHydrating) {
|
||||
nuxtApp.hooks.hookOnce('app:suspense:resolve', () => { requestIdleCallback(callback) })
|
||||
nuxtApp.hooks.hookOnce('app:suspense:resolve', () => { requestIdleCallback(() => callback()) })
|
||||
} else {
|
||||
requestIdleCallback(callback)
|
||||
requestIdleCallback(() => callback())
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,6 @@
|
||||
export type DefaultAsyncDataErrorValue = null
|
||||
export type DefaultAsyncDataValue = null
|
||||
export type DefaultErrorValue = null
|
||||
export type DedupeOption = boolean | 'cancel' | 'defer'
|
||||
|
||||
export {}
|
||||
|
@ -387,11 +387,15 @@ export function createNuxtApp (options: CreateOptions) {
|
||||
return nuxtApp
|
||||
}
|
||||
|
||||
/** @since 3.0.0 */
|
||||
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
|
||||
/** @since 3.12.0 */
|
||||
export function registerPluginHooks (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
|
||||
if (plugin.hooks) {
|
||||
nuxtApp.hooks.addHooks(plugin.hooks)
|
||||
}
|
||||
}
|
||||
|
||||
/** @since 3.0.0 */
|
||||
export async function applyPlugin (nuxtApp: NuxtApp, plugin: Plugin & ObjectPlugin<any>) {
|
||||
if (typeof plugin === 'function') {
|
||||
const { provide } = await nuxtApp.runWithContext(() => plugin(nuxtApp)) || {}
|
||||
if (provide && typeof provide === 'object') {
|
||||
@ -438,6 +442,11 @@ export async function applyPlugins (nuxtApp: NuxtApp, plugins: Array<Plugin & Ob
|
||||
}
|
||||
}
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue }
|
||||
registerPluginHooks(nuxtApp, plugin)
|
||||
}
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (import.meta.server && nuxtApp.ssrContext?.islandContext && plugin.env?.islands === false) { continue }
|
||||
await executePlugin(plugin)
|
||||
@ -554,8 +563,8 @@ function wrappedConfig (runtimeConfig: Record<string, unknown>) {
|
||||
const keys = Object.keys(runtimeConfig).map(key => `\`${key}\``)
|
||||
const lastKey = keys.pop()
|
||||
return new Proxy(runtimeConfig, {
|
||||
get (target, p: string, receiver) {
|
||||
if (p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
|
||||
get (target, p, receiver) {
|
||||
if (typeof p === 'string' && p !== 'public' && !(p in target) && !p.startsWith('__v') /* vue check for reactivity, e.g. `__v_isRef` */) {
|
||||
console.warn(`[nuxt] Could not access \`${p}\`. The only available runtime config keys on the client side are ${keys.join(', ')} and ${lastKey}. See \`https://nuxt.com/docs/guide/going-further/runtime-config\` for more information.`)
|
||||
}
|
||||
return Reflect.get(target, p, receiver)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { defineComponent, h, ref } from 'vue'
|
||||
import { defineComponent, getCurrentInstance, h, ref } from 'vue'
|
||||
import NuxtIsland from '#app/components/nuxt-island'
|
||||
import { useRoute } from '#app/composables/router'
|
||||
import { isPrerendered } from '#app/composables/payload'
|
||||
@ -11,6 +11,7 @@ export const createServerComponent = (name: string) => {
|
||||
props: { lazy: Boolean },
|
||||
emits: ['error'],
|
||||
setup (props, { attrs, slots, expose, emit }) {
|
||||
const vm = getCurrentInstance()
|
||||
const islandRef = ref<null | typeof NuxtIsland>(null)
|
||||
|
||||
expose({
|
||||
@ -22,6 +23,7 @@ export const createServerComponent = (name: string) => {
|
||||
name,
|
||||
lazy: props.lazy,
|
||||
props: attrs,
|
||||
scopeId: vm?.vnode.scopeId,
|
||||
ref: islandRef,
|
||||
onError: (err) => {
|
||||
emit('error', err)
|
||||
|
@ -388,7 +388,10 @@ export async function initNitro (nuxt: Nuxt & { _nitro?: Nitro }) {
|
||||
}
|
||||
|
||||
// Init nitro
|
||||
const nitro = await createNitro(nitroConfig)
|
||||
const nitro = await createNitro(nitroConfig, {
|
||||
// @ts-expect-error this will be valid in a future version of Nitro
|
||||
compatibilityDate: nuxt.options.compatibilityDate,
|
||||
})
|
||||
|
||||
// Trigger Nitro reload when SPA loading template changes
|
||||
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt)
|
||||
|
@ -455,6 +455,9 @@ export default defineRenderHandler(async (event): Promise<Partial<RenderResponse
|
||||
type: resource.module ? 'module' : null,
|
||||
src: renderer.rendererContext.buildAssetsURL(resource.file),
|
||||
defer: resource.module ? null : true,
|
||||
// if we are rendering script tag payloads that import an async payload
|
||||
// we need to ensure this resolves before executing the Nuxt entry
|
||||
tagPosition: (_PAYLOAD_EXTRACTION && !process.env.NUXT_JSON_PAYLOADS) ? 'bodyClose' : 'head',
|
||||
crossorigin: '',
|
||||
})),
|
||||
}, headEntryOptions)
|
||||
|
@ -112,6 +112,8 @@ export const pluginsDeclaration: NuxtTemplate = {
|
||||
|
||||
const pluginsName = (await annotatePlugins(ctx.nuxt, ctx.app.plugins)).filter(p => p.name).map(p => `'${p.name}'`)
|
||||
|
||||
const isV4 = ctx.nuxt.options.future.compatibilityVersion === 4
|
||||
|
||||
return `// Generated by Nuxt'
|
||||
import type { Plugin } from '#app'
|
||||
|
||||
@ -131,9 +133,10 @@ declare module '#app' {
|
||||
}
|
||||
|
||||
declare module '#app/defaults' {
|
||||
type DefaultAsyncDataErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||
type DefaultAsyncDataValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||
type DefaultErrorValue = ${ctx.nuxt.options.future.compatibilityVersion === 4 ? 'undefined' : 'null'}
|
||||
type DefaultAsyncDataErrorValue = ${isV4 ? 'undefined' : 'null'}
|
||||
type DefaultAsyncDataValue = ${isV4 ? 'undefined' : 'null'}
|
||||
type DefaultErrorValue = ${isV4 ? 'undefined' : 'null'}
|
||||
type DedupeOption = ${isV4 ? '\'cancel\' | \'defer\'' : 'boolean | \'cancel\' | \'defer\''}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
|
@ -123,15 +123,16 @@ export const scriptsStubsPreset = {
|
||||
'useScriptFathomAnalytics',
|
||||
'useScriptMatomoAnalytics',
|
||||
'useScriptGoogleTagManager',
|
||||
'useScriptGoogleAdsense',
|
||||
'useScriptSegment',
|
||||
'useScriptFacebookPixel',
|
||||
'useScriptMetaPixel',
|
||||
'useScriptXPixel',
|
||||
'useScriptIntercom',
|
||||
'useScriptHotjar',
|
||||
'useScriptStripe',
|
||||
'useScriptLemonSqueezy',
|
||||
'useScriptVimeoPlayer',
|
||||
'useScriptYouTubeIframe',
|
||||
'useScriptYouTubePlayer',
|
||||
'useScriptGoogleMaps',
|
||||
'useScriptNpm',
|
||||
],
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { existsSync, readdirSync } from 'node:fs'
|
||||
import { mkdir, readFile } from 'node:fs/promises'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, updateTemplates, useNitro } from '@nuxt/kit'
|
||||
import { addBuildPlugin, addComponent, addPlugin, addTemplate, addTypeTemplate, addVitePlugin, addWebpackPlugin, defineNuxtModule, findPath, logger, resolvePath, updateTemplates, useNitro } from '@nuxt/kit'
|
||||
import { dirname, join, relative, resolve } from 'pathe'
|
||||
import { genImport, genObjectFromRawEntries, genString } from 'knitwork'
|
||||
import type { Nuxt, NuxtApp, NuxtPage } from 'nuxt/schema'
|
||||
@ -61,8 +61,12 @@ export default defineNuxtModule({
|
||||
}
|
||||
|
||||
const pages = await resolvePagesRoutes()
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
if (pages.length) { return true }
|
||||
if (pages.length) {
|
||||
if (nuxt.apps.default) {
|
||||
nuxt.apps.default.pages = pages
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@ -75,7 +79,6 @@ export default defineNuxtModule({
|
||||
|
||||
nuxt.hook('app:templates', async (app) => {
|
||||
app.pages = await resolvePagesRoutes()
|
||||
await nuxt.callHook('pages:extend', app.pages)
|
||||
|
||||
if (!nuxt.options.ssr && app.pages.some(p => p.mode === 'server')) {
|
||||
logger.warn('Using server pages with `ssr: false` is not supported with auto-detected component islands. Set `experimental.componentIslands` to `true`.')
|
||||
@ -153,10 +156,9 @@ export default defineNuxtModule({
|
||||
logs: nuxt.options.debug,
|
||||
async beforeWriteFiles (rootPage) {
|
||||
rootPage.children.forEach(child => child.delete())
|
||||
let pages = nuxt.apps.default?.pages
|
||||
if (!pages) {
|
||||
pages = await resolvePagesRoutes()
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
const pages = nuxt.apps.default?.pages || await resolvePagesRoutes()
|
||||
if (nuxt.apps.default) {
|
||||
nuxt.apps.default.pages = pages
|
||||
}
|
||||
function addPage (parent: EditableTreeNode, page: NuxtPage) {
|
||||
// @ts-expect-error TODO: either fix types upstream or figure out another
|
||||
@ -342,6 +344,7 @@ export default defineNuxtModule({
|
||||
}
|
||||
|
||||
if (nuxt.options.experimental.appManifest) {
|
||||
const componentStubPath = await resolvePath(resolve(runtimeDir, 'component-stub'))
|
||||
// Add all redirect paths as valid routes to router; we will handle these in a client-side middleware
|
||||
// when the app manifest is enabled.
|
||||
nuxt.hook('pages:extend', (routes) => {
|
||||
@ -356,7 +359,7 @@ export default defineNuxtModule({
|
||||
routes.push({
|
||||
_sync: true,
|
||||
path: path.replace(/\/[^/]*\*\*/, '/:pathMatch(.*)'),
|
||||
file: resolve(runtimeDir, 'component-stub'),
|
||||
file: componentStubPath,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ import { runInNewContext } from 'node:vm'
|
||||
import fs from 'node:fs'
|
||||
import { extname, normalize, relative, resolve } from 'pathe'
|
||||
import { encodePath, joinURL, withLeadingSlash } from 'ufo'
|
||||
import { logger, resolveFiles, useNuxt } from '@nuxt/kit'
|
||||
import { logger, resolveFiles, resolvePath, useNuxt } from '@nuxt/kit'
|
||||
import { genArrayFromRaw, genDynamicImport, genImport, genSafeVariableName } from 'knitwork'
|
||||
import escapeRE from 'escape-string-regexp'
|
||||
import { filename } from 'pathe/utils'
|
||||
@ -58,21 +58,30 @@ export async function resolvePagesRoutes (): Promise<NuxtPage[]> {
|
||||
scannedFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath, 'en-US'))
|
||||
|
||||
const allRoutes = await generateRoutesFromFiles(uniqueBy(scannedFiles, 'relativePath'), {
|
||||
shouldExtractBuildMeta: nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages,
|
||||
shouldUseServerComponents: !!nuxt.options.experimental.componentIslands,
|
||||
vfs: nuxt.vfs,
|
||||
})
|
||||
|
||||
return uniqueBy(allRoutes, 'path')
|
||||
const pages = uniqueBy(allRoutes, 'path')
|
||||
|
||||
const shouldAugment = nuxt.options.experimental.scanPageMeta || nuxt.options.experimental.typedPages
|
||||
|
||||
if (shouldAugment) {
|
||||
const augmentedPages = await augmentPages(pages, nuxt.vfs)
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
await augmentPages(pages, nuxt.vfs, augmentedPages)
|
||||
augmentedPages.clear()
|
||||
} else {
|
||||
await nuxt.callHook('pages:extend', pages)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
||||
type GenerateRoutesFromFilesOptions = {
|
||||
shouldExtractBuildMeta?: boolean
|
||||
shouldUseServerComponents?: boolean
|
||||
vfs?: Record<string, string>
|
||||
}
|
||||
|
||||
export async function generateRoutesFromFiles (files: ScannedFile[], options: GenerateRoutesFromFilesOptions = {}): Promise<NuxtPage[]> {
|
||||
export function generateRoutesFromFiles (files: ScannedFile[], options: GenerateRoutesFromFilesOptions = {}): NuxtPage[] {
|
||||
const routes: NuxtPage[] = []
|
||||
|
||||
for (const file of files) {
|
||||
@ -124,17 +133,27 @@ export async function generateRoutesFromFiles (files: ScannedFile[], options: Ge
|
||||
}
|
||||
}
|
||||
|
||||
if (options.shouldExtractBuildMeta && options.vfs) {
|
||||
const fileContent = file.absolutePath in options.vfs ? options.vfs[file.absolutePath] : fs.readFileSync(file.absolutePath, 'utf-8')
|
||||
Object.assign(route, await getRouteMeta(fileContent, file.absolutePath))
|
||||
}
|
||||
|
||||
parent.push(route)
|
||||
}
|
||||
|
||||
return prepareRoutes(routes)
|
||||
}
|
||||
|
||||
export async function augmentPages (routes: NuxtPage[], vfs: Record<string, string>, augmentedPages = new Set<string>()) {
|
||||
for (const route of routes) {
|
||||
if (route.file && !augmentedPages.has(route.file)) {
|
||||
const fileContent = route.file in vfs ? vfs[route.file] : fs.readFileSync(await resolvePath(route.file), 'utf-8')
|
||||
Object.assign(route, await getRouteMeta(fileContent, route.file))
|
||||
augmentedPages.add(route.file)
|
||||
}
|
||||
|
||||
if (route.children && route.children.length > 0) {
|
||||
await augmentPages(route.children, vfs)
|
||||
}
|
||||
}
|
||||
return augmentedPages
|
||||
}
|
||||
|
||||
const SFC_SCRIPT_RE = /<script[^>]*>([\s\S]*?)<\/script[^>]*>/i
|
||||
export function extractScriptContent (html: string) {
|
||||
const match = html.match(SFC_SCRIPT_RE)
|
||||
|
@ -6,8 +6,9 @@ import * as VueFunctions from 'vue'
|
||||
import type { Import } from 'unimport'
|
||||
import { createUnimport } from 'unimport'
|
||||
import type { Plugin } from 'vite'
|
||||
import { registry as scriptRegistry } from '@nuxt/scripts/registry'
|
||||
import { TransformPlugin } from '../src/imports/transform'
|
||||
import { defaultPresets } from '../src/imports/presets'
|
||||
import { defaultPresets, scriptsStubsPreset } from '../src/imports/presets'
|
||||
|
||||
describe('imports:transform', () => {
|
||||
const imports: Import[] = [
|
||||
@ -193,3 +194,21 @@ describe('imports:vue', () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('imports:nuxt/scripts', () => {
|
||||
const scripts = scriptRegistry().map(s => s.import?.name).filter(Boolean)
|
||||
const globalScripts = new Set([
|
||||
'useScript',
|
||||
'useAnalyticsPageEvent',
|
||||
'useElementScriptTrigger',
|
||||
'useConsentScriptTrigger',
|
||||
])
|
||||
it.each(scriptsStubsPreset.imports)(`should register %s from @nuxt/scripts`, (name) => {
|
||||
if (globalScripts.has(name)) { return }
|
||||
|
||||
expect(scripts).toContain(name)
|
||||
})
|
||||
it.each(scripts)(`should register %s from @nuxt/scripts`, (name) => {
|
||||
expect(scriptsStubsPreset.imports).toContain(name)
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { NuxtPage } from 'nuxt/schema'
|
||||
import { generateRoutesFromFiles, normalizeRoutes, pathToNitroGlob } from '../src/pages/utils'
|
||||
import { augmentPages, generateRoutesFromFiles, normalizeRoutes, pathToNitroGlob } from '../src/pages/utils'
|
||||
import { generateRouteKey } from '../src/pages/runtime/utils'
|
||||
|
||||
describe('pages:generateRoutesFromFiles', () => {
|
||||
@ -568,11 +568,12 @@ describe('pages:generateRoutesFromFiles', () => {
|
||||
) as Record<string, string>
|
||||
|
||||
try {
|
||||
result = await generateRoutesFromFiles(test.files.map(file => ({
|
||||
result = generateRoutesFromFiles(test.files.map(file => ({
|
||||
shouldUseServerComponents: true,
|
||||
absolutePath: file.path,
|
||||
relativePath: file.path.replace(/^(pages|layer\/pages)\//, ''),
|
||||
})), { shouldExtractBuildMeta: true, vfs })
|
||||
})))
|
||||
await augmentPages(result, vfs)
|
||||
} catch (error: any) {
|
||||
expect(error.message).toEqual(test.error)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/schema",
|
||||
"version": "3.11.2",
|
||||
"version": "3.12.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
@ -58,11 +58,12 @@
|
||||
"vue": "3.4.27",
|
||||
"vue-bundle-renderer": "2.1.0",
|
||||
"vue-loader": "17.4.2",
|
||||
"vue-router": "4.3.2",
|
||||
"vue-router": "4.3.3",
|
||||
"webpack": "5.91.0",
|
||||
"webpack-dev-middleware": "7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"compatx": "^0.1.3",
|
||||
"consola": "^3.2.3",
|
||||
"defu": "^6.1.4",
|
||||
"hookable": "^5.5.3",
|
||||
@ -71,8 +72,8 @@
|
||||
"scule": "^1.3.0",
|
||||
"std-env": "^3.7.0",
|
||||
"ufo": "^1.5.3",
|
||||
"unimport": "^3.7.2",
|
||||
"uncrypto": "^0.1.3",
|
||||
"unimport": "^3.7.2",
|
||||
"untyped": "^1.4.2"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -20,6 +20,18 @@ export default defineUntypedSchema({
|
||||
*/
|
||||
extends: null,
|
||||
|
||||
/**
|
||||
* Specify a compatibility date for your app.
|
||||
*
|
||||
* This is used to control the behavior of presets in Nitro, Nuxt Image
|
||||
* and other modules that may change behavior without a major version bump.
|
||||
*
|
||||
* We plan to improve the tooling around this feature in the future.
|
||||
*
|
||||
* @type {typeof import('compatx').CompatibilityDateSpec}
|
||||
*/
|
||||
compatibilityDate: undefined,
|
||||
|
||||
/**
|
||||
* Extend project from a local or remote source.
|
||||
*
|
||||
|
@ -385,7 +385,7 @@ export default defineUntypedSchema({
|
||||
*
|
||||
* https://github.com/nuxt/nuxt/issues/24770
|
||||
*/
|
||||
scanPageMeta: false,
|
||||
scanPageMeta: true,
|
||||
|
||||
/**
|
||||
* Automatically share payload _data_ between pages that are prerendered. This can result in a significant
|
||||
|
@ -1,4 +1,3 @@
|
||||
import type { Defu } from 'defu'
|
||||
import type { NuxtHooks } from './hooks'
|
||||
import type { Nuxt } from './nuxt'
|
||||
import type { NuxtCompatibility } from './compatibility'
|
||||
@ -27,7 +26,8 @@ export interface ModuleMeta {
|
||||
/** The options received. */
|
||||
export type ModuleOptions = Record<string, any>
|
||||
|
||||
export type ModuleSetupInstallResult = {
|
||||
/** Optional result for nuxt modules */
|
||||
export interface ModuleSetupReturn {
|
||||
/**
|
||||
* Timing information for the initial setup
|
||||
*/
|
||||
@ -39,37 +39,19 @@ export type ModuleSetupInstallResult = {
|
||||
}
|
||||
|
||||
type Awaitable<T> = T | Promise<T>
|
||||
type _ModuleSetupReturn = Awaitable<void | false | ModuleSetupReturn>
|
||||
|
||||
type Prettify<T> = {
|
||||
[K in keyof T]: T[K];
|
||||
} & {}
|
||||
|
||||
export type ModuleSetupReturn = Awaitable<false | void | ModuleSetupInstallResult>
|
||||
|
||||
export type ResolvedModuleOptions<TOptions extends ModuleOptions, TOptionsDefaults extends Partial<TOptions>> = Prettify<
|
||||
Defu<
|
||||
Partial<TOptions>,
|
||||
[Partial<TOptions>, TOptionsDefaults]
|
||||
>
|
||||
>
|
||||
|
||||
/** Module definition passed to 'defineNuxtModule(...)' or 'defineNuxtModule().with(...)'. */
|
||||
export interface ModuleDefinition<
|
||||
TOptions extends ModuleOptions,
|
||||
TOptionsDefaults extends Partial<TOptions> = Partial<TOptions>,
|
||||
> {
|
||||
/** Input module passed to defineNuxtModule. */
|
||||
export interface ModuleDefinition<T extends ModuleOptions = ModuleOptions> {
|
||||
meta?: ModuleMeta
|
||||
defaults?: TOptionsDefaults | ((nuxt: Nuxt) => TOptionsDefaults)
|
||||
schema?: TOptions
|
||||
defaults?: T | ((nuxt: Nuxt) => T)
|
||||
schema?: T
|
||||
hooks?: Partial<NuxtHooks>
|
||||
setup?: (this: void, resolvedOptions: ResolvedModuleOptions<TOptions, TOptionsDefaults>, nuxt: Nuxt) => ModuleSetupReturn
|
||||
setup?: (this: void, resolvedOptions: T, nuxt: Nuxt) => _ModuleSetupReturn
|
||||
}
|
||||
|
||||
export interface NuxtModule<
|
||||
TOptions extends ModuleOptions = ModuleOptions,
|
||||
TOptionsDefaults extends Partial<TOptions> = Partial<TOptions>,
|
||||
> {
|
||||
(this: void, resolvedOptions: ResolvedModuleOptions<TOptions, TOptionsDefaults>, nuxt: Nuxt): ModuleSetupReturn
|
||||
getOptions?: (inlineOptions?: Partial<TOptions>, nuxt?: Nuxt) => Promise<ResolvedModuleOptions<TOptions, TOptionsDefaults>>
|
||||
export interface NuxtModule<T extends ModuleOptions = ModuleOptions> {
|
||||
(this: void, inlineOptions: T, nuxt: Nuxt): _ModuleSetupReturn
|
||||
getOptions?: (inlineOptions?: T, nuxt?: Nuxt) => Promise<T>
|
||||
getMeta?: () => Promise<ModuleMeta>
|
||||
}
|
||||
|
@ -26,11 +26,11 @@
|
||||
"execa": "9.2.0",
|
||||
"globby": "14.0.1",
|
||||
"html-minifier": "4.0.0",
|
||||
"jiti": "1.21.0",
|
||||
"jiti": "1.21.6",
|
||||
"knitwork": "1.1.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"pathe": "1.1.2",
|
||||
"prettier": "3.3.1",
|
||||
"prettier": "3.3.2",
|
||||
"scule": "1.3.0",
|
||||
"unocss": "0.60.4",
|
||||
"vite": "5.2.13"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/vite-builder",
|
||||
"version": "3.11.2",
|
||||
"version": "3.12.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nuxt/webpack-builder",
|
||||
"version": "3.11.2",
|
||||
"version": "3.12.1",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nuxt/nuxt.git",
|
||||
|
623
pnpm-lock.yaml
623
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -31,7 +31,6 @@
|
||||
"main"
|
||||
],
|
||||
"ignoreDeps": [
|
||||
"jiti",
|
||||
"@vitejs/plugin-vue",
|
||||
"nuxt",
|
||||
"nuxt3",
|
||||
|
@ -16,7 +16,7 @@ for PKG in packages/* ; do
|
||||
if [[ $PKG == "packages/test-utils" ]] ; then
|
||||
continue
|
||||
fi
|
||||
if [[ $p == "packages/ui-templates" ]] ; then
|
||||
if [[ $PKG == "packages/ui-templates" ]] ; then
|
||||
continue
|
||||
fi
|
||||
pushd $PKG
|
||||
|
@ -32,7 +32,7 @@ describe.skipIf(process.env.SKIP_BUNDLE_SIZE === 'true' || process.env.ECOSYSTEM
|
||||
const serverDir = join(rootDir, '.output/server')
|
||||
|
||||
const serverStats = await analyzeSizes(['**/*.mjs', '!node_modules'], serverDir)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"208k"`)
|
||||
expect.soft(roundToKilobytes(serverStats.totalBytes)).toMatchInlineSnapshot(`"209k"`)
|
||||
|
||||
const modules = await analyzeSizes('node_modules/**/*', serverDir)
|
||||
expect.soft(roundToKilobytes(modules.totalBytes)).toMatchInlineSnapshot(`"1338k"`)
|
||||
|
2
test/fixtures/basic-types/package.json
vendored
2
test/fixtures/basic-types/package.json
vendored
@ -11,7 +11,7 @@
|
||||
"devDependencies": {
|
||||
"ofetch": "latest",
|
||||
"unplugin-vue-router": "^0.7.0",
|
||||
"vitest": "1.5.3",
|
||||
"vitest": "1.6.0",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest"
|
||||
}
|
||||
|
16
test/fixtures/basic-types/types.ts
vendored
16
test/fixtures/basic-types/types.ts
vendored
@ -4,8 +4,8 @@ import type { FetchError } from 'ofetch'
|
||||
import type { NavigationFailure, RouteLocationNormalized, RouteLocationRaw, Router, useRouter as vueUseRouter } from '#vue-router'
|
||||
|
||||
import type { AppConfig, RuntimeValue, UpperSnakeCase } from 'nuxt/schema'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
import { defineNuxtModule } from 'nuxt/kit'
|
||||
import { defineNuxtConfig } from 'nuxt/config'
|
||||
import { callWithNuxt, isVue3 } from '#app'
|
||||
import type { NuxtError } from '#app'
|
||||
import type { NavigateToOptions } from '#app/composables/router'
|
||||
@ -244,19 +244,13 @@ describe('modules', () => {
|
||||
defineNuxtConfig({ undeclaredKey: { other: false } })
|
||||
})
|
||||
|
||||
it('correctly typed resolved options in defineNuxtModule setup using `.with()`', () => {
|
||||
defineNuxtModule<{
|
||||
foo?: string
|
||||
baz: number
|
||||
}>().with({
|
||||
it('preserves options in defineNuxtModule setup without `.with()`', () => {
|
||||
defineNuxtModule<{ foo?: string, baz: number }>({
|
||||
defaults: {
|
||||
foo: 'bar',
|
||||
baz: 100,
|
||||
},
|
||||
setup: (resolvedOptions) => {
|
||||
expectTypeOf(resolvedOptions).toEqualTypeOf<{
|
||||
foo: string
|
||||
baz?: number | undefined
|
||||
}>()
|
||||
expectTypeOf(resolvedOptions).toEqualTypeOf<{ foo?: string, baz: number }>()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
@ -16,9 +16,15 @@ export default defineNuxtModule({
|
||||
}, {
|
||||
path: '/big-page-1',
|
||||
file: resolver.resolve('./pages/big-page.vue'),
|
||||
meta: {
|
||||
layout: false,
|
||||
},
|
||||
}, {
|
||||
path: '/big-page-2',
|
||||
file: resolver.resolve('./pages/big-page.vue'),
|
||||
meta: {
|
||||
layout: false,
|
||||
},
|
||||
})
|
||||
})
|
||||
},
|
||||
|
@ -15,7 +15,7 @@ if (count || data.value !== 1) {
|
||||
}
|
||||
|
||||
timeout = 100
|
||||
const p = refresh({ dedupe: true, _initial: false })
|
||||
const p = refresh({ dedupe: 'cancel', _initial: false })
|
||||
|
||||
if (import.meta.client && (count !== 0 || data.value !== 1)) {
|
||||
throw new Error('count should start at 0')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { beforeEach } from 'node:test'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { h, nextTick } from 'vue'
|
||||
import { defineComponent, h, nextTick, popScopeId, pushScopeId } from 'vue'
|
||||
import { mountSuspended } from '@nuxt/test-utils/runtime'
|
||||
import { createServerComponent } from '../../packages/nuxt/src/components/runtime/server-component'
|
||||
import { createSimpleRemoteIslandProvider } from '../fixtures/remote-provider'
|
||||
@ -133,6 +133,19 @@ describe('runtime server component', () => {
|
||||
expect(wrapper.emitted('error')).toHaveLength(1)
|
||||
vi.mocked(fetch).mockReset()
|
||||
})
|
||||
|
||||
it('expect NuxtIsland to have parent scopeId', async () => {
|
||||
const wrapper = await mountSuspended(defineComponent({
|
||||
render () {
|
||||
pushScopeId('data-v-654e2b21')
|
||||
const vnode = h(createServerComponent('dummyName'))
|
||||
popScopeId()
|
||||
return vnode
|
||||
},
|
||||
}))
|
||||
|
||||
expect(wrapper.find('*').attributes()).toHaveProperty('data-v-654e2b21')
|
||||
})
|
||||
})
|
||||
|
||||
describe('client components', () => {
|
||||
@ -186,7 +199,7 @@ describe('client components', () => {
|
||||
expect(fetch).toHaveBeenCalledOnce()
|
||||
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(`
|
||||
"<div data-island-uid="4">hello<div data-island-uid="4" data-island-component="Client-12345">
|
||||
"<div data-island-uid="5">hello<div data-island-uid="5" data-island-component="Client-12345">
|
||||
<div>client component</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -212,7 +225,7 @@ describe('client components', () => {
|
||||
await wrapper.vm.$.exposed!.refresh()
|
||||
await nextTick()
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(`
|
||||
"<div data-island-uid="4">hello<div>
|
||||
"<div data-island-uid="5">hello<div>
|
||||
<div>fallback</div>
|
||||
</div>
|
||||
</div>"
|
||||
@ -305,7 +318,7 @@ describe('client components', () => {
|
||||
})
|
||||
expect(fetch).toHaveBeenCalledOnce()
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(`
|
||||
"<div data-island-uid="6">hello<div data-island-uid="6" data-island-component="ClientWithSlot-12345">
|
||||
"<div data-island-uid="7">hello<div data-island-uid="7" data-island-component="ClientWithSlot-12345">
|
||||
<div class="client-component">
|
||||
<div style="display: contents" data-island-uid="" data-island-slot="default">
|
||||
<div>slot in client component</div>
|
||||
|
@ -283,3 +283,34 @@ describe('plugin dependsOn', () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('plugin hooks', () => {
|
||||
it('registers hooks before executing plugins', async () => {
|
||||
const nuxtApp = useNuxtApp()
|
||||
|
||||
const sequence: string[] = []
|
||||
const plugins = [
|
||||
defineNuxtPlugin({
|
||||
name: 'A',
|
||||
setup (nuxt) {
|
||||
sequence.push('start A')
|
||||
nuxt.callHook('a:setup')
|
||||
},
|
||||
}),
|
||||
defineNuxtPlugin({
|
||||
name: 'B',
|
||||
hooks: {
|
||||
'a:setup': () => {
|
||||
sequence.push('listen B')
|
||||
},
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
await applyPlugins(nuxtApp, plugins)
|
||||
expect(sequence).toMatchObject([
|
||||
'start A',
|
||||
'listen B',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user