Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
Merge branch 'main' into fix/#components-import
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed Oct 11, 2022
2 parents 6ea0049 + 2aa0973 commit 94c6f28
Show file tree
Hide file tree
Showing 31 changed files with 767 additions and 529 deletions.
22 changes: 22 additions & 0 deletions docs/content/1.getting-started/5.routing.md
Expand Up @@ -117,3 +117,25 @@ definePageMeta({
::

:ReadMore{link="/guide/directory-structure/middleware"}

## Route Validation

Nuxt offers route validation via the `validate` property in [`definePageMeta`](/api/utils/define-page-meta) in each page you wish to validate.

The `validate` property accepts the `route` as an argument. You can return a boolean value to determine whether or not this is a valid route to be rendered with this page. If you return false and another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).

If you have a more complex use case, then you can use anonymous route middleware instead.

:StabilityEdge

```vue [pages/post/[id].vue]
<script setup>
definePageMeta({
validate: async (route) => {
const nuxtApp = useNuxtApp()
// Check if the id is made up of digits
return /^\d+$/.test(params.id)
}
})
</script>
```
6 changes: 6 additions & 0 deletions docs/content/1.getting-started/6.data-fetching.md
Expand Up @@ -152,6 +152,12 @@ function next() {

The key to making this work is to call the `refresh()` method returned from the `useFetch()` composable when a query parameter has changed.

By default, `refresh()` will not make a new request if one is already pending. You can override any pending requests with the override option. Previous requests will not be cancelled, but their result will not update the data or pending state - and any previously awaited promises will not resolve until this new request resolves.

```js
refresh({ override: true })
```

### `refreshNuxtData`

Invalidate the cache of `useAsyncData`, `useLazyAsyncData`, `useFetch` and `useLazyFetch` and trigger the refetch.
Expand Down
18 changes: 17 additions & 1 deletion docs/content/2.guide/2.directory-structure/1.plugins.md
Expand Up @@ -40,6 +40,22 @@ export default defineNuxtPlugin(nuxtApp => {
})
```

## Plugin Registration Order

You can control the order in which plugins are registered by prefixing a number to the file names.

For example:

```bash
plugins/
| - 1.myPlugin.ts
| - 2.myOtherPlugin.ts
```

In this example, `2.myOtherPlugin.ts` will be able to access anything that was injected by `1.myPlugin.ts`.

This is useful in situations where you have a plugin that depends on another plugin.

## Using Composables Within Plugins

You can use [composables](/guide/directory-structure/composables) within Nuxt plugins:
Expand All @@ -54,7 +70,7 @@ However, keep in mind there are some limitations and differences:

**If a composable depends on another plugin registered later, it might not work.**

**Reason:** Plugins are called in order sequencially and before everything else. You might use a composable that dependants on another plugin which is not called yet.
**Reason:** Plugins are called in order sequentially and before everything else. You might use a composable that depends on another plugin which has not been called yet.

**If a composable depends on the Vue.js lifecycle, it won't work.**

Expand Down
2 changes: 1 addition & 1 deletion docs/content/3.api/1.composables/use-async-data.md
Expand Up @@ -30,7 +30,7 @@ type AsyncDataOptions<DataT> = {
}

interface RefreshOptions {
_initial?: boolean
override?: boolean
}

type AsyncData<DataT, ErrorT> = {
Expand Down
2 changes: 1 addition & 1 deletion docs/content/3.api/1.composables/use-fetch.md
Expand Up @@ -32,7 +32,7 @@ type UseFetchOptions = {
type AsyncData<DataT> = {
data: Ref<DataT>
pending: Ref<boolean>
refresh: () => Promise<void>
refresh: (opts?: { override?: boolean }) => Promise<void>
execute: () => Promise<void>
error: Ref<Error | boolean>
}
Expand Down
4 changes: 4 additions & 0 deletions docs/content/3.api/2.components/4.nuxt-loading-indicator.md
Expand Up @@ -19,6 +19,10 @@ Add `<NuxtLoadingIndicator/>` in your `app.vue` or layouts.

:button-link[Open on StackBlitz]{href="https://stackblitz.com/github/nuxt/framework/tree/main/examples/routing/pages?terminal=dev&file=/app.vue" blank}

## Slots

You can pass custom HTML or components through the loading indicator's default slot.

## Props

- **color**: The color of the loading bar.
Expand Down
13 changes: 4 additions & 9 deletions docs/content/3.api/3.utils/define-page-meta.md
Expand Up @@ -23,6 +23,7 @@ title: "definePageMeta"
definePageMeta(meta: PageMeta) => void

interface PageMeta {
validate?: (route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>
redirect?: RouteRecordRedirectOption
alias?: string | string[]
pageTransition?: boolean | TransitionProps
Expand Down Expand Up @@ -79,20 +80,14 @@ interface PageMeta {

Define anonymous or named middleware directly within `definePageMeta`. Learn more about [route middleware](/docs/directory-structure/middleware).

**`redirect`**
**`validate`**

- **Type**: [`RouteRecordRedirectOption`](https://router.vuejs.org/guide/essentials/redirect-and-alias.html#redirect-and-alias)
- **Type**: `(route: RouteLocationNormalized) => boolean | Promise<boolean> | Partial<NuxtError> | Promise<Partial<NuxtError>>`

Where to redirect if the route is directly matched. The redirection happens before any navigation guard and triggers a new navigation with the new target location.
Validate whether a given route can validly be rendered with this page. Return true if it is valid, or false if not. If another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).

:StabilityEdge

**`alias`**

- **Type**: `string | string[]`

Aliases for the record. Allows defining extra paths that will behave like a copy of the record. Allows having paths shorthands like `/users/:id` and `/u/:id`. All `alias` and `path` values must share the same params.

**`[key: string]`**

- **Type**: `any`
Expand Down
20 changes: 9 additions & 11 deletions docs/content/7.migration/7.component-options.md
Expand Up @@ -112,25 +112,23 @@ See [layout migration](/migration/pages-and-layouts).

## `validate`

There is no longer a validate hook in Nuxt 3. Instead, you can create a custom middleware function, or directly throw an error in the setup function of the page.
The validate hook in Nuxt 3 only accepts a single argument, the `route`. Just as in Nuxt 2, you can return a boolean value. If you return false and another match can't be found, this will mean a 404. You can also directly return an object with `statusCode`/`statusMessage` to respond immediately with an error (other matches will not be checked).

:StabilityEdge

```diff [pages/users/[id].vue]
- <script>
- export default {
- async validate({ params, query, store }) {
- return true // if valid
- async validate({ params }) {
- return /^\d+$/.test(params.id)
- }
- }
+ <script setup>
+ definePageMeta({
+ middleware: [
+ async function (to, from) {
+ const nuxtApp = useNuxtApp()
+ if (!valid) {
+ return abortNavigation('Page not found')
+ }
+ }
+ ]
+ validate: async (route) => {
+ const nuxtApp = useNuxtApp()
+ return /^\d+$/.test(params.id)
+ }
+ })
</script>
```
Expand Down
6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -43,7 +43,7 @@
"nuxi": "link:./packages/nuxi",
"nuxt": "link:./packages/nuxt",
"nuxt3": "link:./packages/nuxt",
"vite": "^3.1.6",
"vite": "^3.1.7",
"unbuild": "^0.8.11"
},
"devDependencies": {
Expand All @@ -64,7 +64,7 @@
"expect-type": "^0.14.2",
"globby": "^13.1.2",
"jiti": "^1.16.0",
"lerna": "^5.6.1",
"lerna": "^5.6.2",
"markdownlint-cli": "^0.32.2",
"ohmyfetch": "^0.4.19",
"pathe": "^0.3.9",
Expand All @@ -74,7 +74,7 @@
"typescript": "^4.8.4",
"ufo": "^0.8.5",
"unbuild": "^0.8.11",
"vite": "^3.1.6",
"vite": "^3.1.7",
"vitest": "~0.19.1",
"vue-tsc": "^1.0.3"
},
Expand Down
7 changes: 4 additions & 3 deletions packages/nuxt/package.json
Expand Up @@ -39,7 +39,7 @@
"@nuxt/devalue": "^2.0.0",
"@nuxt/kit": "3.0.0-rc.11",
"@nuxt/schema": "3.0.0-rc.11",
"@nuxt/telemetry": "^2.1.5",
"@nuxt/telemetry": "^2.1.6",
"@nuxt/ui-templates": "^0.4.0",
"@nuxt/vite-builder": "3.0.0-rc.11",
"@vue/reactivity": "^3.2.40",
Expand All @@ -56,7 +56,7 @@
"hash-sum": "^2.0.0",
"hookable": "^5.3.0",
"knitwork": "^0.1.2",
"magic-string": "^0.26.6",
"magic-string": "^0.26.7",
"mlly": "^0.5.16",
"nitropack": "^0.5.4",
"nuxi": "3.0.0-rc.11",
Expand All @@ -67,10 +67,11 @@
"scule": "^0.3.2",
"strip-literal": "^0.4.2",
"ufo": "^0.8.5",
"ultrahtml": "^0.1.1",
"unctx": "^2.0.2",
"unenv": "^0.6.2",
"unimport": "^0.6.8",
"unplugin": "^0.9.2",
"unplugin": "^0.9.6",
"untyped": "^0.5.0",
"vue": "^3.2.40",
"vue-bundle-renderer": "^0.4.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/nuxt/src/app/components/nuxt-loading-indicator.ts
Expand Up @@ -21,7 +21,7 @@ export default defineComponent({
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)'
}
},
setup (props) {
setup (props, { slots }) {
const indicator = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle
Expand Down Expand Up @@ -50,7 +50,7 @@ export default defineComponent({
transition: 'width 0.1s, height 0.4s, opacity 0.4s',
zIndex: 999999
}
})
}, slots)
}
})

Expand Down
24 changes: 21 additions & 3 deletions packages/nuxt/src/app/composables/asyncData.ts
Expand Up @@ -34,6 +34,12 @@ export interface AsyncDataOptions<

export interface AsyncDataExecuteOptions {
_initial?: boolean
/**
* 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
* previously awaited promises will not resolve until this new request resolves.
*/
override?: boolean
}

export interface _AsyncData<DataT, ErrorT> {
Expand Down Expand Up @@ -115,17 +121,20 @@ export function useAsyncData<
const asyncData = { ...nuxt._asyncData[key] } as AsyncData<DataT, DataE>

asyncData.refresh = asyncData.execute = (opts = {}) => {
// Avoid fetching same key more than once at a time
if (nuxt._asyncDataPromises[key]) {
return nuxt._asyncDataPromises[key]
if (!opts.override) {
// Avoid fetching same key more than once at a time
return nuxt._asyncDataPromises[key]
}
(nuxt._asyncDataPromises[key] as any).cancelled = true
}
// Avoid fetching same key that is already fetched
if (opts._initial && useInitialCache()) {
return nuxt.payload.data[key]
}
asyncData.pending.value = true
// TODO: Cancel previous promise
nuxt._asyncDataPromises[key] = new Promise<DataT>(
const promise = new Promise<DataT>(
(resolve, reject) => {
try {
resolve(handler(nuxt))
Expand All @@ -134,6 +143,9 @@ export function useAsyncData<
}
})
.then((result) => {
// If this request is cancelled, resolve to the latest request.
if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] }

if (options.transform) {
result = options.transform(result)
}
Expand All @@ -144,17 +156,23 @@ export function useAsyncData<
asyncData.error.value = null
})
.catch((error: any) => {
// If this request is cancelled, resolve to the latest request.
if ((promise as any).cancelled) { return nuxt._asyncDataPromises[key] }

asyncData.error.value = error
asyncData.data.value = unref(options.default?.() ?? null)
})
.finally(() => {
if ((promise as any).cancelled) { return }

asyncData.pending.value = false
nuxt.payload.data[key] = asyncData.data.value
if (asyncData.error.value) {
nuxt.payload._errors[key] = true
}
delete nuxt._asyncDataPromises[key]
})
nuxt._asyncDataPromises[key] = promise
return nuxt._asyncDataPromises[key]
}

Expand Down
6 changes: 5 additions & 1 deletion packages/nuxt/src/app/composables/fetch.ts
Expand Up @@ -86,8 +86,12 @@ export function useFetch<
]
}

let controller: AbortController

const asyncData = useAsyncData<_ResT, ErrorT, Transform, PickKeys>(key, () => {
return $fetch(_request.value, _fetchOptions) as Promise<_ResT>
controller?.abort?.()
controller = typeof AbortController !== 'undefined' ? new AbortController() : {} as AbortController
return $fetch(_request.value, { signal: controller.signal, ..._fetchOptions }) as Promise<_ResT>
}, _asyncDataOptions)

return asyncData
Expand Down
3 changes: 2 additions & 1 deletion packages/nuxt/src/app/plugins/router.ts
Expand Up @@ -234,7 +234,8 @@ export default defineNuxtPlugin<{ route: Route, router: Router }>((nuxtApp) => {
if (process.server) {
if (result === false || result instanceof Error) {
const error = result || createError({
statusMessage: `Route navigation aborted: ${initialURL}`
statusCode: 404,
statusMessage: `Page Not Found: ${initialURL}`
})
return callWithNuxt(nuxtApp, showError, [error])
}
Expand Down

0 comments on commit 94c6f28

Please sign in to comment.